// Copyright (C) Stichting Deltares 2017. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.Linq; using Core.Common.Base.Data; using Ringtoets.Common.Data.Exceptions; using Ringtoets.Common.Data.Probabilistics; using Ringtoets.Piping.Data.SoilProfile; using Ringtoets.Piping.InputParameterCalculation; using Ringtoets.Piping.Primitives; namespace Ringtoets.Piping.Data { /// /// Class responsible for calculating the derived piping input. /// public static class DerivedPipingInput { /// /// Gets the piezometric head at the exit point. /// [m] /// /// The input to calculate the derived piping input for. /// The assessment level at stake. /// Thrown when is null. /// Returns the corresponding derived input value. public static RoundedDouble GetPiezometricHeadExit(PipingInput input, RoundedDouble assessmentLevel) { if (input == null) { throw new ArgumentNullException(nameof(input)); } RoundedDouble dampingFactorExit = PipingSemiProbabilisticDesignVariableFactory.GetDampingFactorExit(input).GetDesignValue(); RoundedDouble phreaticLevelExit = PipingSemiProbabilisticDesignVariableFactory.GetPhreaticLevelExit(input).GetDesignValue(); return new RoundedDouble(2, InputParameterCalculationService.CalculatePiezometricHeadAtExit(assessmentLevel, dampingFactorExit, phreaticLevelExit)); } /// /// Gets the horizontal distance between entry and exit point. /// [m] /// /// The input to calculate the derived piping input for. /// Thrown when is null. /// Returns the corresponding derived input value. public static VariationCoefficientLogNormalDistribution GetSeepageLength(PipingInput input) { if (input == null) { throw new ArgumentNullException(nameof(input)); } double seepageLengthMean = input.ExitPointL - input.EntryPointL; return new VariationCoefficientLogNormalDistribution(2) { Mean = (RoundedDouble) seepageLengthMean, CoefficientOfVariation = (RoundedDouble) 0.1 }; } /// /// Gets the total thickness of the coverage layers at the exit point. /// [m] /// /// The input to calculate the derived piping input for. /// Thrown when is null. /// Returns the corresponding derived input value. public static LogNormalDistribution GetThicknessCoverageLayer(PipingInput input) { if (input == null) { throw new ArgumentNullException(nameof(input)); } var thicknessCoverageLayer = new LogNormalDistribution(2) { Mean = RoundedDouble.NaN, StandardDeviation = (RoundedDouble) 0.5 }; UpdateThicknessCoverageLayerMean(input, thicknessCoverageLayer); return thicknessCoverageLayer; } /// /// Gets the effective thickness of the coverage layers at the exit point. /// [m] /// /// The input to calculate the derived piping input for. /// Thrown when is null. /// Returns the corresponding derived input value. public static LogNormalDistribution GetEffectiveThicknessCoverageLayer(PipingInput input) { if (input == null) { throw new ArgumentNullException(nameof(input)); } var thicknessCoverageLayer = new LogNormalDistribution(2) { Mean = RoundedDouble.NaN, StandardDeviation = (RoundedDouble) 0.5 }; UpdateEffectiveThicknessCoverageLayerMean(input, thicknessCoverageLayer); return thicknessCoverageLayer; } /// /// Gets the total thickness of the aquifer layers at the exit point. /// [m] /// /// The input to calculate the derived piping input for. /// Thrown when is null. /// Returns the corresponding derived input value. public static LogNormalDistribution GetThicknessAquiferLayer(PipingInput input) { if (input == null) { throw new ArgumentNullException(nameof(input)); } var thicknessAquiferLayer = new LogNormalDistribution(2) { Mean = RoundedDouble.NaN, StandardDeviation = (RoundedDouble) 0.5 }; UpdateThicknessAquiferLayerMean(input, thicknessAquiferLayer); return thicknessAquiferLayer; } /// /// Gets the sieve size through which 70% of the grains of the top part of the aquifer pass. /// [m] /// /// The input to calculate the derived piping input for. /// Thrown when is null. /// Returns the corresponding derived input value. public static VariationCoefficientLogNormalDistribution GetDiameterD70(PipingInput input) { if (input == null) { throw new ArgumentNullException(nameof(input)); } PipingSoilLayer topMostAquiferLayer = GetConsecutiveAquiferLayers(input).FirstOrDefault(); return topMostAquiferLayer != null ? topMostAquiferLayer.DiameterD70 : new VariationCoefficientLogNormalDistribution(6) { Mean = RoundedDouble.NaN, CoefficientOfVariation = RoundedDouble.NaN }; } /// /// Gets the Darcy-speed with which water flows through the aquifer layer. /// [m/s] /// /// The input to calculate the derived piping input for. /// Thrown when is null. /// Returns the corresponding derived input value. public static VariationCoefficientLogNormalDistribution GetDarcyPermeability(PipingInput input) { if (input == null) { throw new ArgumentNullException(nameof(input)); } var distribution = new VariationCoefficientLogNormalDistribution(6) { Mean = RoundedDouble.NaN, CoefficientOfVariation = RoundedDouble.NaN }; UpdateDarcyPermeabilityParameters(input, distribution); return distribution; } /// /// Gets the volumic weight of the saturated coverage layer. /// /// The input to calculate the derived piping input for. /// Thrown when is null. /// Returns the corresponding derived input value. public static LogNormalDistribution GetSaturatedVolumicWeightOfCoverageLayer(PipingInput input) { if (input == null) { throw new ArgumentNullException(nameof(input)); } var distribution = new LogNormalDistribution(2) { Mean = RoundedDouble.NaN, StandardDeviation = RoundedDouble.NaN, Shift = RoundedDouble.NaN }; UpdateSaturatedVolumicWeightOfCoverageLayerParameters(input, distribution); return distribution; } private static void UpdateThicknessAquiferLayerMean(PipingInput input, LogNormalDistribution thicknessAquiferLayer) { PipingStochasticSoilProfile stochasticSoilProfile = input.StochasticSoilProfile; PipingSurfaceLine surfaceLine = input.SurfaceLine; RoundedDouble exitPointL = input.ExitPointL; if (stochasticSoilProfile?.SoilProfile != null && surfaceLine != null && !double.IsNaN(exitPointL)) { var thicknessTopAquiferLayer = new RoundedDouble(GetNumberOfDecimals(thicknessAquiferLayer), GetThicknessTopAquiferLayer(stochasticSoilProfile.SoilProfile, surfaceLine, exitPointL)); if (thicknessTopAquiferLayer > 0) { thicknessAquiferLayer.Mean = thicknessTopAquiferLayer; } } } private static void UpdateThicknessCoverageLayerMean(PipingInput input, LogNormalDistribution thicknessCoverageLayerDistribution) { PipingStochasticSoilProfile stochasticSoilProfile = input.StochasticSoilProfile; PipingSurfaceLine surfaceLine = input.SurfaceLine; RoundedDouble exitPointL = input.ExitPointL; if (stochasticSoilProfile?.SoilProfile != null && surfaceLine != null && !double.IsNaN(exitPointL)) { var weightedMean = new RoundedDouble(GetNumberOfDecimals(thicknessCoverageLayerDistribution), GetThicknessCoverageLayers(stochasticSoilProfile.SoilProfile, surfaceLine, exitPointL)); if (weightedMean > 0) { thicknessCoverageLayerDistribution.Mean = weightedMean; } } } private static void UpdateEffectiveThicknessCoverageLayerMean(PipingInput input, LogNormalDistribution effectiveThicknessCoverageLayerDistribution) { if (input.SurfaceLine != null && input.StochasticSoilProfile?.SoilProfile != null && !double.IsNaN(input.ExitPointL)) { var weightedMean = new RoundedDouble(GetNumberOfDecimals(effectiveThicknessCoverageLayerDistribution), InputParameterCalculationService.CalculateEffectiveThicknessCoverageLayer( input.WaterVolumetricWeight, PipingSemiProbabilisticDesignVariableFactory.GetPhreaticLevelExit(input).GetDesignValue(), input.ExitPointL, input.SurfaceLine, input.StochasticSoilProfile.SoilProfile)); if (weightedMean > 0) { effectiveThicknessCoverageLayerDistribution.Mean = weightedMean; } } } private static void UpdateDarcyPermeabilityParameters(PipingInput input, VariationCoefficientLogNormalDistribution darcyPermeabilityDistribution) { IEnumerable aquiferLayers = GetConsecutiveAquiferLayers(input); int numberOfDecimals = GetNumberOfDecimals(darcyPermeabilityDistribution); if (HasCorrectDarcyPermeabilityWeightDistributionParameterDefinition(aquiferLayers)) { PipingSoilLayer topMostAquiferLayer = aquiferLayers.First(); var weightedMean = new RoundedDouble(numberOfDecimals, GetWeightedMeanForDarcyPermeabilityOfAquiferLayer(aquiferLayers, input.StochasticSoilProfile.SoilProfile, input.SurfaceLine.GetZAtL(input.ExitPointL))); darcyPermeabilityDistribution.Mean = weightedMean; darcyPermeabilityDistribution.CoefficientOfVariation = topMostAquiferLayer.Permeability.CoefficientOfVariation; } } private static void UpdateSaturatedVolumicWeightOfCoverageLayerParameters(PipingInput input, LogNormalDistribution volumicWeightDistribution) { IEnumerable coverageLayers = GetConsecutiveCoverageLayers(input); int numberOfDecimals = GetNumberOfDecimals(volumicWeightDistribution); if (HasCorrectSaturatedWeightDistributionParameterDefinition(coverageLayers)) { PipingSoilLayer topMostAquitardLayer = coverageLayers.First(); volumicWeightDistribution.Shift = topMostAquitardLayer.BelowPhreaticLevel.Shift; volumicWeightDistribution.StandardDeviation = topMostAquitardLayer.BelowPhreaticLevel.StandardDeviation; var weightedMean = new RoundedDouble(numberOfDecimals, GetWeightedMeanForVolumicWeightOfCoverageLayer( coverageLayers, input.StochasticSoilProfile.SoilProfile, input.SurfaceLine.GetZAtL(input.ExitPointL))); if (weightedMean > 0) { volumicWeightDistribution.Mean = weightedMean; } } } private static int GetNumberOfDecimals(IDistribution distribution) { return distribution.Mean.NumberOfDecimalPlaces; } private static int GetNumberOfDecimals(IVariationCoefficientDistribution distribution) { return distribution.Mean.NumberOfDecimalPlaces; } private static bool HasCorrectSaturatedWeightDistributionParameterDefinition(IEnumerable consecutiveAquitardLayers) { if (!consecutiveAquitardLayers.Any()) { return false; } IEnumerable distributions = consecutiveAquitardLayers.Select(layer => layer.BelowPhreaticLevel); if (distributions.Count() == 1) { return true; } return distributions.All(currentLayerDistribution => AreShiftAndDeviationEqual( currentLayerDistribution, distributions.First())); } private static bool HasCorrectDarcyPermeabilityWeightDistributionParameterDefinition(IEnumerable consecutiveAquitardLayers) { if (!consecutiveAquitardLayers.Any()) { return false; } IEnumerable distributions = consecutiveAquitardLayers.Select(layer => layer.Permeability); if (distributions.Count() == 1) { return true; } return distributions.All(currentLayerDistribution => AreCoefficientEqual( currentLayerDistribution, distributions.First())); } private static bool AreShiftAndDeviationEqual(LogNormalDistribution currentLayerDistribution, LogNormalDistribution baseLayerDistribution) { return currentLayerDistribution.StandardDeviation == baseLayerDistribution.StandardDeviation && currentLayerDistribution.Shift == baseLayerDistribution.Shift; } private static bool AreCoefficientEqual(VariationCoefficientLogNormalDistribution currentLayerDistribution, VariationCoefficientLogNormalDistribution baseLayerDistribution) { return Math.Abs(baseLayerDistribution.CoefficientOfVariation - currentLayerDistribution.CoefficientOfVariation) < 1e-6; } private static double GetWeightedMeanForVolumicWeightOfCoverageLayer(IEnumerable aquitardLayers, PipingSoilProfile profile, double surfaceLevel) { var totalThickness = 0.0; var weighedTotal = 0.0; foreach (PipingSoilLayer layer in aquitardLayers) { double layerThickness = profile.GetLayerThickness(layer); double bottom = layer.Top - layerThickness; double thicknessUnderSurface = Math.Min(layer.Top, surfaceLevel) - bottom; totalThickness += thicknessUnderSurface; weighedTotal += layer.BelowPhreaticLevel.Mean * thicknessUnderSurface; } return weighedTotal / totalThickness; } private static double GetWeightedMeanForDarcyPermeabilityOfAquiferLayer(IEnumerable aquitardLayers, PipingSoilProfile profile, double surfaceLevel) { var totalThickness = 0.0; var weighedTotal = 0.0; foreach (PipingSoilLayer layer in aquitardLayers) { double layerThickness = profile.GetLayerThickness(layer); double bottom = layer.Top - layerThickness; double thicknessUnderSurface = Math.Min(layer.Top, surfaceLevel) - bottom; totalThickness += thicknessUnderSurface; weighedTotal += layer.Permeability.Mean * thicknessUnderSurface; } return weighedTotal / totalThickness; } private static IEnumerable GetConsecutiveAquiferLayers(PipingInput input) { PipingSurfaceLine surfaceLine = input.SurfaceLine; PipingSoilProfile soilProfile = input.StochasticSoilProfile?.SoilProfile; RoundedDouble exitPointL = input.ExitPointL; if (surfaceLine != null && soilProfile != null && !double.IsNaN(exitPointL)) { return soilProfile.GetConsecutiveAquiferLayersBelowLevel(surfaceLine.GetZAtL(exitPointL)).ToArray(); } return new PipingSoilLayer[0]; } private static IEnumerable GetConsecutiveCoverageLayers(PipingInput input) { PipingSurfaceLine surfaceLine = input.SurfaceLine; PipingSoilProfile soilProfile = input.StochasticSoilProfile?.SoilProfile; RoundedDouble exitPointL = input.ExitPointL; if (surfaceLine != null && soilProfile != null && !double.IsNaN(exitPointL)) { PipingSoilLayer[] consecutiveAquitardLayersBelowLevel = soilProfile .GetConsecutiveCoverageLayersBelowLevel(surfaceLine.GetZAtL(exitPointL)) .ToArray(); return consecutiveAquitardLayersBelowLevel; } return new PipingSoilLayer[0]; } private static double GetThicknessTopAquiferLayer(PipingSoilProfile soilProfile, PipingSurfaceLine surfaceLine, RoundedDouble exitPointL) { try { double zAtL = surfaceLine.GetZAtL(exitPointL); return soilProfile.GetTopmostConsecutiveAquiferLayerThicknessBelowLevel(zAtL); } catch (Exception e) { if (e is MechanismSurfaceLineException || e is InvalidOperationException || e is ArgumentException) { return double.NaN; } throw; } } private static double GetThicknessCoverageLayers(PipingSoilProfile soilProfile, PipingSurfaceLine surfaceLine, RoundedDouble exitPointL) { try { double zAtL = surfaceLine.GetZAtL(exitPointL); return soilProfile.GetConsecutiveCoverageLayerThicknessBelowLevel(zAtL); } catch (Exception e) { if (e is MechanismSurfaceLineException || e is InvalidOperationException || e is ArgumentException) { return double.NaN; } throw; } } } }