// Copyright (C) Stichting Deltares 2017. All rights reserved. // // This file is part of the DAM Engine. // // The DAM Engine is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero 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 Deltares.DamEngine.Data.Geotechnics; using Deltares.DamEngine.Data.Standard; namespace Deltares.DamEngine.Calculators.Uplift { /// /// Exception class for SoilVolumicMassCalculator /// public class SoilVolumicMassCalculatorException : Exception { public SoilVolumicMassCalculatorException(string message) : base(message) {} } /// /// Calculation class to determine weight of the soil between TopOfLayerToBeEvaluated and SurfaceLevel /// public class SoilVolumicMassCalculator { private const double CTolerance = 0.00000001; /// /// Initializes a new instance of the class. /// public SoilVolumicMassCalculator() { VolumicWeightOfWater = 9.81; IsUseOvenDryUnitWeight = false; } public bool IsUseOvenDryUnitWeight { get; set; } public double VolumicWeightOfWater { get; set; } public double PhreaticLevel { get; set; } public double SurfaceLevel { get; set; } public double TopOfLayerToBeEvaluated { get; set; } public SoilProfile1D SoilProfile { get; set; } public double MinimumThicknessCoverLayer { get; set; } /// /// Calculates the total weight of the soil between SurfaceLevel and TopOfLayerToBeEvaluated, /// taking into account whether the soil is submerged or not /// /// total mass public double CalculateTotalMass() { ThrowWhenSoilProfileIsNull(); ThrowWhenSurfaceLevelNotInsideProfile(); ThrowWhenTopLayerToBeEvaluatedNotInsideProfile(); var weight = SoilProfile.Layers .Where(layer => layer.TopLevel > TopOfLayerToBeEvaluated) .Sum(layer => GetWeight(layer)); // Is there water above the soil layer? If so add the volumic weight // of water to the soil mass if (PhreaticLevel > SurfaceLevel) { var height = PhreaticLevel - SurfaceLevel; weight += height*VolumicWeightOfWater; } return weight; } /// /// Calculates the effective stress of the soil between TopOfLayerToBeEvaluated and SurfaceLevel /// /// public double CalculateEffectiveStress() { ThrowWhenSoilProfileIsNull(); ThrowWhenSoilProfileHasNoLayers(); ThrowWhenSurfaceLevelNotInsideProfile(); ThrowWhenTopLayerToBeEvaluatedNotInsideProfile(); var involvedLayers = SoilProfile.Layers.Where(layer => layer.TopLevel > TopOfLayerToBeEvaluated); var weight = involvedLayers.Sum(layer => GetEffectiveStress(layer)); // Check to see if thickness of all involved layers is is smaller than the given minimum thickness. If so, // then add the weight of the "missing" soil(s) by multipliying the difference by the weight of the toplevel. // Using the weight of the toplevel might be theoreticaly incorrect but this is a good and simple approach // which has been accorded by Erik Vastenburg. var thicknessInvolvedLayers = involvedLayers.Sum(layer => GetLayerHeight(layer)); if (thicknessInvolvedLayers < MinimumThicknessCoverLayer) { Soil soil = involvedLayers.First().Soil; double bottomLevel = Math.Min(involvedLayers.First().TopLevel, SurfaceLevel); double height = MinimumThicknessCoverLayer - thicknessInvolvedLayers; double topLevel = bottomLevel + height; double factorWet; double factorDry; DetermineHeightAndDryAndWetFraction(topLevel, bottomLevel, out factorWet, out factorDry); weight += GetSoilUnitWeightDry(soil).Value*factorDry*height + (soil.BelowPhreaticLevel - VolumicWeightOfWater)*factorWet*height; } return weight; } /// /// Validates the input /// /// a filled list when errors are found else an empty list public List Validate() { var errors = new List(); if (SoilProfile == null) { errors.Add("The soilprofile is not defined"); return errors; } if ((SurfaceLevel - SoilProfile.TopLevel) > CTolerance || (SurfaceLevel - SoilProfile.BottomLevel) < -CTolerance) { errors.Add("Surfacelevel is not inside soil profile"); } errors.AddRange(from soilLayer1D in SoilProfile.Layers where soilLayer1D.Soil == null select "The soil material in the layer could not be converted to local type"); if ((TopOfLayerToBeEvaluated - SoilProfile.TopLevel) > CTolerance || (TopOfLayerToBeEvaluated - SoilProfile.BottomLevel) < -CTolerance) { errors.Add("The top layer to be evaluated is not inside the soil profile"); } return errors; } /// /// Gets the contribution of the weight of a layer, taking in account the phreatic level /// /// The layer. /// private double GetWeight(SoilLayer1D layer) { var soil = layer.Soil as Soil; ThrowWhenSoilIsNull(soil); double topLevel, bottomLevel; double height = GetLayerHeight(layer, out topLevel, out bottomLevel); double factorWet; double factorDry; DetermineHeightAndDryAndWetFraction(topLevel, bottomLevel, out factorWet, out factorDry); return GetSoilUnitWeightDry(soil).Value*factorDry*height + soil.BelowPhreaticLevel*factorWet*height; } /// /// Gets the contribution to the effective stress of a layer, taking in account the phreatic level /// /// The layer. /// private double GetEffectiveStress(SoilLayer1D layer) { var soil = layer.Soil as Soil; ThrowWhenSoilIsNull(soil); double topLevel, bottomLevel; double height = GetLayerHeight(layer, out topLevel, out bottomLevel); double factorWet; double factorDry; DetermineHeightAndDryAndWetFraction(topLevel, bottomLevel, out factorWet, out factorDry); // If above phreatic line use the dry weight of the soil // if below phreatic line use the effective submerged weight (gamma_sat - gamma_water) return GetSoilUnitWeightDry(soil).Value*factorDry*height + (soil.BelowPhreaticLevel - VolumicWeightOfWater)*factorWet*height; } /// /// Determines the height and dry and wet fraction of the layer. /// /// The layer. /// The height. /// The factor wet. /// The factor dry. private void DetermineHeightAndDryAndWetFraction(double topLevel, double bottomLevel, out double factorWet, out double factorDry) { double height = topLevel - bottomLevel; factorWet = 0.0; factorDry = 0.0; if (topLevel < PhreaticLevel || topLevel.AlmostEquals(PhreaticLevel)) { factorWet = 1.0; } else if (bottomLevel > PhreaticLevel || bottomLevel.AlmostEquals(PhreaticLevel)) { factorDry = 1.0; } else { var pLevel = (topLevel - PhreaticLevel); factorDry = pLevel.AlmostEquals(0.0) ? 0.0 : pLevel/height; factorWet = 1.0 - factorDry; } } /// /// Calculates the height of a layer taking into account the surfacelevel and the TopOfLayerToBeEvaluated /// /// /// private double GetLayerHeight(SoilLayer1D layer) { var layerHeight = SoilProfile.GetLayerHeight(layer); double topLevel; if (layer.TopLevel > SurfaceLevel) { topLevel = SurfaceLevel; layerHeight = Math.Max(layerHeight - (layer.TopLevel - SurfaceLevel), 0); } else { topLevel = layer.TopLevel; } double bottomLevel = topLevel - layerHeight; bottomLevel = bottomLevel > TopOfLayerToBeEvaluated ? bottomLevel : TopOfLayerToBeEvaluated; return topLevel - bottomLevel; } /// /// Gets the soil dry unit weight. /// /// The soil. /// private double? GetSoilUnitWeightDry(Soil soil) { if (IsUseOvenDryUnitWeight) { return soil.DryUnitWeight; } else { return soil.AbovePhreaticLevel; } } /// /// Gets the height of the layer. /// /// The layer. /// The top level. /// The bottom level. /// private double GetLayerHeight(SoilLayer1D layer, out double topLevel, out double bottomLevel) { var layerHeight = SoilProfile.GetLayerHeight(layer); if (layer.TopLevel > SurfaceLevel) { topLevel = SurfaceLevel; layerHeight = Math.Max(layerHeight - (layer.TopLevel - SurfaceLevel), 0); } else { topLevel = layer.TopLevel; } bottomLevel = topLevel - layerHeight; bottomLevel = bottomLevel > TopOfLayerToBeEvaluated ? bottomLevel : TopOfLayerToBeEvaluated; return topLevel - bottomLevel; } /// /// Check precondition: Throws the when soil profile is null. /// /// The soilprofile is not defined private void ThrowWhenSoilProfileIsNull() { if (SoilProfile == null) { throw new SoilVolumicMassCalculatorException("The soilprofile is not defined"); } } /// /// Throws the when soil profile has no layers. /// /// The soilprofile is not defined private void ThrowWhenSoilProfileHasNoLayers() { if (SoilProfile.LayerCount == 0) { throw new SoilVolumicMassCalculatorException("The soilprofile has no layers"); } } /// /// Check precondition: Throws the when soil is null. /// /// The soil. /// The soil material in the layer could not be converted to local type private void ThrowWhenSoilIsNull(Soil soil) { if (soil == null) { throw new SoilVolumicMassCalculatorException("The soil material in the layer could not be converted to local type"); } } /// /// Check precondition: Throws the when surface level is not inside profile. /// /// Surfaceline is not inside soil profile private void ThrowWhenSurfaceLevelNotInsideProfile() { if ((SurfaceLevel - SoilProfile.TopLevel) > CTolerance || (SurfaceLevel - SoilProfile.BottomLevel) < -CTolerance) { throw new SoilVolumicMassCalculatorException("Surfacelevel is not inside soil profile"); } } /// /// Check precondition: Throws the when top layer to be evaluated is not inside profile. /// /// The top layer to be evaluated is not inside the soil profile private void ThrowWhenTopLayerToBeEvaluatedNotInsideProfile() { if ((TopOfLayerToBeEvaluated - SoilProfile.TopLevel) > CTolerance || (TopOfLayerToBeEvaluated - SoilProfile.BottomLevel) < -CTolerance) { throw new SoilVolumicMassCalculatorException("The top layer to be evaluated is not inside the soil profile"); } } } }