// Copyright (C) Stichting Deltares 2019. 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.Geometry; using Deltares.DamEngine.Data.Standard; using Deltares.DamEngine.Data.Standard.Language; using Deltares.DamEngine.Data.Standard.Validation; namespace Deltares.DamEngine.Data.Geotechnics { /// /// Interface for providing the 1D Soil Profile /// public interface ISoilProfileProvider { /// /// Gets the soil profile. /// /// /// The soil profile. /// SoilProfile1D SoilProfile { get; } } /// /// 1D Soil Profile Object /// public class SoilProfile1D : SoilProfile { private const double defaultBottomLayerHeight = 20.0; private readonly DelegatedList layers = new DelegatedList(); private double bottomLevel = double.NaN; private SoilLayer1D infiltrationLayer = null; /// /// Default constructor /// public SoilProfile1D() { Name = LocalizationManager.GetTranslatedText(this, "DefaultNameSoilProfile1D"); layers.AddMethod = AddLayer; } /// /// Constructor to construct simple soilprofile /// /// /// /// public SoilProfile1D(double topLevel, double bottomLevel, Soil soil) : this() { this.bottomLevel = bottomLevel; layers.Add(new SoilLayer1D(soil, topLevel)); } public void Assign(SoilProfile1D profile) { this.Assign((SoilProfile)profile); this.Layers.Clear(); foreach (SoilLayer1D layer in (IEnumerable)profile.Layers) this.Layers.Add((SoilLayer1D)layer.Clone()); this.BottomLevel = profile.BottomLevel; } /// /// Gets the soil layer collection for this profile /// public IList Layers { get { return layers; } } /// /// Gets the number of layers. /// /// /// The layer count. /// public int LayerCount { get { return layers.Count; } } /// /// Gets or sets the bottom level. /// /// /// The bottom level. /// public double BottomLevel { get { if (double.IsNaN(bottomLevel) && layers.Count > 0) { bottomLevel = Layers.Last().TopLevel - defaultBottomLayerHeight; } return bottomLevel; } set { if (double.IsNaN(bottomLevel) || (Math.Abs(value - bottomLevel) > GeometryConstants.Accuracy)) { bottomLevel = value; } } } /// /// Gets or sets the top level. /// /// /// The top level. /// public double TopLevel { get { return layers.Any() ? layers.First().TopLevel : BottomLevel; } } /// /// Gets the infiltration layer. /// /// /// The infiltration layer. /// public SoilLayer1D InfiltrationLayer { get { return infiltrationLayer; } } /// /// Gets layer with the specified name. /// /// The name. /// public SoilLayer1D GetLayerWithName(string name) { return this.layers.FirstOrDefault((Func)(layer => { if (layer.Name != null) return layer.Name.Equals(name); return false; })); } /// /// This method makes sure all names are unique /// public void EnsureUniqueLayerNames() { if (AreAllLayerNamesUnique()) { return; } foreach (var layer in Layers) { layer.Name = null; } foreach (var layer in Layers) { layer.Name = GetNewUniqueLayerName(); } } /// /// Checks weather all layer names are unique. /// /// /// true if all layer names are unique; otherwise, false. /// public bool AreAllLayerNamesUnique() { var layersSize = Layers.Count; for (int i = 0; i < layersSize; ++i) { var id1 = layers[i].Name; if (id1 == null) { return false; } for (int j = i + 1; j < layersSize; ++j) { if (id1.Equals(Layers[j].Name)) { return false; } } } return true; } /// /// Gets the first unused unique layer name. /// /// new unique layer name public string GetNewUniqueLayerName() { string newName; int i = 0; SoilLayer1D soilLayer; do { newName = "L" + i++; soilLayer = GetLayerWithName(newName); } while (soilLayer != null); return newName; } /// /// The highest aquifer layer, can be null if not aquifer is present /// [Validate] public SoilLayer1D TopAquiferLayer { get { IList sortedLayers = Layers.OrderByDescending(l => l.TopLevel).ToList(); SoilLayer1D aquiferLayer = null; // Search the highest aquifer layer for (int layerIndex = 0; layerIndex < sortedLayers.Count; layerIndex++) { var layer = sortedLayers[layerIndex]; if (IsAquiferLayer(layer)) { aquiferLayer = layer; break; } } return aquiferLayer; } } /// /// Gets the toppest aquifer layer of the deepest cluster of aquifers /// /// /// The toppest aquifer layer in the deepest cluster of aquifers /// public SoilLayer1D BottomAquiferLayer { get { IList sortedLayers = Layers.OrderBy(l => l.TopLevel).ToList(); SoilLayer1D aquiferLayer = null; int aquiferIndex = -1; // Search deepest aquifer layer for (int layerIndex = 0; layerIndex < sortedLayers.Count; layerIndex++) { SoilLayer1D layer = sortedLayers[layerIndex]; if (IsAquiferLayer(layer)) { aquiferIndex = layerIndex; aquiferLayer = layer; break; } } // aquifer may consists of more then 1 connected (aquifer) layers // Search all layers above the first found aquifer to find top aquifer layer if (aquiferIndex >= 0) { for (int layerIndex = aquiferIndex + 1; layerIndex < sortedLayers.Count; layerIndex++) { var layer = sortedLayers[layerIndex]; if (IsAquiferLayer(layer)) { aquiferLayer = layer; } else { break; } } } return aquiferLayer; } } /// /// Gets the highest aquifer in the highest cluster of in-between aquifers. /// The top layer of a cluster of in-between aquifer can't be the highest layer of the soil profile. /// /// /// The highest aquifer in the highest in-between cluster of aquifers /// public SoilLayer1D InBetweenAquiferLayer { get { IList sortedLayers = Layers.OrderByDescending(l => l.TopLevel).ToList(); SoilLayer1D aquiferLayer = null; // Search the highest aquifer layer with an aquitard above for (int layerIndex = 1; layerIndex < sortedLayers.Count; layerIndex++) { var previousLayer = sortedLayers[layerIndex - 1]; var layer = sortedLayers[layerIndex]; if (IsAquiferLayer(layer) && !IsAquiferLayer(previousLayer)) { aquiferLayer = layer; break; } } // If highest aquifer layer is bottom aquifer layer, there is no in between aquiferlayer if (aquiferLayer == BottomAquiferLayer) { aquiferLayer = null; } return aquiferLayer; } } /// /// Gets the lowest layer in the highest cluster of in-between aquifers /// /// /// The lowest layer in the highest cluster of in-between aquifers /// public SoilLayer1D BottomLayerOfInBetweenAquiferCluster { get { IList sortedLayers = Layers.OrderByDescending(l => l.TopLevel).ToList(); SoilLayer1D highestAquiferLayer = null; SoilLayer1D aquiferLayer = null; int indexHighestAquifer = -1; // Search the index of the highest aquifer layer for (int layerIndex = 1; layerIndex < sortedLayers.Count; layerIndex++) { var previousLayer = sortedLayers[layerIndex - 1]; var layer = sortedLayers[layerIndex]; if (IsAquiferLayer(layer) && !IsAquiferLayer(previousLayer)) { highestAquiferLayer = layer; indexHighestAquifer = layerIndex; break; } } // If highest aquifer layer is bottom aquifer layer, there is no in between aquiferlayer if (highestAquiferLayer == BottomAquiferLayer) { return null; } // in-between aquifers cluster may consists of more then 1 connected (aquifer) layers. // Search all layers below the found highest aquifer to find bottom aquifer layer. if (indexHighestAquifer >= 0) { for (int layerIndex = indexHighestAquifer; layerIndex < sortedLayers.Count; layerIndex++) { var layer = sortedLayers[layerIndex]; if (IsAquiferLayer(layer)) { aquiferLayer = layer; } else { break; } } } return aquiferLayer; } } /// /// Gets (calculates) the height for a given layer in the profile /// /// The layer to process /// The height public double GetLayerHeight(SoilLayer1D soilLayer) { var layerIndex = layers.IndexOf(soilLayer); var soilLayerBelow = (layerIndex < layers.Count - 1) ? layers[layerIndex + 1] : null; var levelBelow = (soilLayerBelow != null) ? soilLayerBelow.TopLevel : BottomLevel; return soilLayer.TopLevel - levelBelow; } /// /// Make sure the last layer has a height /// public void EnsureLastLayerHasHeight() { var bottomLayer = Layers.Last(); if (bottomLayer.Height.IsZero()) { BottomLevel -= defaultBottomLayerHeight; } } /// /// Determines the infiltration layer. /// /// Length of the penetration. public void DetermineInfiltrationLayer(double penetrationLength) { infiltrationLayer = null; SoilLayer1D bottomAquiferLayer = BottomAquiferLayer; if (penetrationLength > 0 && bottomAquiferLayer != null) { SoilLayer1D inBetweenAquiferLayer = InBetweenAquiferLayer; double aquiferBottom = inBetweenAquiferLayer == null ? Double.PositiveInfinity : inBetweenAquiferLayer.BottomLevel; IList infiltrationLayers = layers.Where(l => l.TopLevel <= aquiferBottom && l.TopLevel > bottomAquiferLayer.TopLevel) .ToList(); if (infiltrationLayers.Count > 0) { double separationLevel = bottomAquiferLayer.TopLevel + penetrationLength; if (separationLevel <= infiltrationLayers.First().TopLevel) { infiltrationLayer = layers.Last(l => l.TopLevel >= separationLevel); } } } } /// /// Validates this instance (using validator mechanism). /// /// [Validate] public ValidationResult[] Validate() { SoilLayer1D erroneousLayer; if (LayerCount == 0) { var error = String.Format(LocalizationManager.GetTranslatedText(this, "SoilProfileWithoutLayers"), Name); return new[] { new ValidationResult(ValidationResultType.Error, error, "", this) }; } if (!IsStrictlyDescending(out erroneousLayer)) { var error = String.Format(LocalizationManager.GetTranslatedText(this, "SoilProfileLayersNotDescending"), Name, erroneousLayer.Name); return new[] { new ValidationResult(ValidationResultType.Error, error, "", this) }; } if (HasInvalidThicknessLayers(out erroneousLayer)) { var error = String.Format(LocalizationManager.GetTranslatedText(this, "SoilProfileInvalidLayerThickness"), Name, erroneousLayer.Name); return new[] { new ValidationResult(ValidationResultType.Error, error, "", this) }; } if (HasLayersWithoutSoil(out erroneousLayer)) { var error = String.Format(LocalizationManager.GetTranslatedText(this, "SoilProfileLayerWithoutSoil"), Name, erroneousLayer.Name); return new[] { new ValidationResult(ValidationResultType.Error, error, "", this) }; } return new ValidationResult[0]; } /// /// Gets the aquifer layers. /// /// list of Aquifer layers public IList GetAquiferLayers() { return Layers.Where(IsAquiferLayer).OrderBy(l => l.TopLevel).ToList(); } /// /// Gets the bottom level. /// /// The soil layer. /// Bottom level public double GetBottomLevel(SoilLayer1D soilLayer) { var layerBelow = GetLayerBelow(soilLayer); return layerBelow != null ? layerBelow.TopLevel : BottomLevel; } /// /// Ares the layers ordered descending. /// /// /// true if all layers are ordered descending; otherwise, false. /// private bool AreLayersOrderedByDescending() { // check for empty list if (Layers.Count <= 1) { return true; } var current = Layers[0]; for (int i = 1; i < Layers.Count; ++i) { var previous = current; current = Layers[i]; if (current.TopLevel > previous.TopLevel) { return false; } } return true; } /// /// Determines whether the layers are strictly ordered in descending order. /// If erroneous layer is found, false is returned and the layer /// is stored in the parameter /// /// The erroneous layer. /// true if the layers are strictly ordered in descending order; otherwise, false. private bool IsStrictlyDescending(out SoilLayer1D erroneousLayer) { for (int i = 1; i < layers.Count; i++) { if (layers[i].TopLevel > layers[i - 1].TopLevel) { erroneousLayer = layers[i]; return false; } } erroneousLayer = null; return true; } /// /// Determines whether there are layers with invalid thickness. /// /// The erroneous layer. /// True when a layer with invalid thickness exists private bool HasInvalidThicknessLayers(out SoilLayer1D erroneousLayer) { for (int i = 1; i < layers.Count; i++) { if (layers[i].TopLevel >= layers[i - 1].TopLevel) { erroneousLayer = layers[i]; return true; } } // check bottom layer using bottom of profile if (layers.Count > 0 && layers[layers.Count - 1].TopLevel <= BottomLevel) { erroneousLayer = layers[layers.Count - 1]; return true; } erroneousLayer = null; return false; } /// /// Determines whether there are layers without soil. /// /// The erroneous layer. /// true when a layer without soil is found private bool HasLayersWithoutSoil(out SoilLayer1D erroneousLayer) { for (int i = 0; i < layers.Count; i++) { if (layers[i].Soil == null) { erroneousLayer = layers[i]; return true; } } erroneousLayer = null; return false; } /// /// Gets the layer by inset offset, seen from the given layer plus the given offset. /// /// The layer. /// The offset. /// the found layer, if no layer found, null private SoilLayer1D GetLayerByInsetOffset(SoilLayer1D layer, int offset) { // only works if list is sorted if (!AreLayersOrderedByDescending()) { layers.Sort(); } int index = GetLayerIndexAt(layer); // was the layer found? if (index < 0 || index >= layers.Count) { return null; } // is there a layer at the specified offset? int requestedIndex = index + offset; if (requestedIndex < 0 || requestedIndex >= layers.Count) { return null; } // return the valid layer return layers[requestedIndex]; } /// /// Gets the layer below the given layer. /// /// The given layer. /// The found layer public SoilLayer1D GetLayerBelow(SoilLayer1D layer) { return GetLayerByInsetOffset(layer, 1); } /// /// Gets the layer index of the given layer. /// /// The layer. /// The index, -1 if not found public int GetLayerIndexAt(SoilLayer1D layer) { if (layer == null) { return -1; } for (int i = 0; i < layers.Count; ++i) { if (layers[i] == layer) { return i; } } return -1; } /// /// Gets the top level of highest aquifer. /// /// public double GetTopLevelOfHighestAquifer() { double topLevel; if (InBetweenAquiferLayer != null) { topLevel = InBetweenAquiferLayer.TopLevel; } else { topLevel = BottomAquiferLayer.TopLevel; } return topLevel; } /// /// Gets the total soil pressure, including the eventual free water on surface /// /// The level for wich the total pressure is calculated /// The phreatic level /// Unit weight of the water /// The soil pressure public double GetTotalPressure(double z, double phreaticLevel, double unitWeightWater) { double pressure = 0; // see if free water is at surface double surfaceLevel = Layers[0].TopLevel; if (z > surfaceLevel) { return Math.Max(0, (phreaticLevel - z)*unitWeightWater); } if (phreaticLevel > surfaceLevel) { if (z < phreaticLevel) { pressure += (phreaticLevel - Math.Max(Layers[0].TopLevel, z))*unitWeightWater; } } foreach (var layer in Layers) { if (layer.Height > 0 && layer.Soil != null) { if (z >= layer.TopLevel) { continue; } if (z <= layer.BottomLevel) { if (phreaticLevel <= layer.BottomLevel) { pressure += layer.Height*layer.Soil.AbovePhreaticLevel; } else if (phreaticLevel >= layer.TopLevel) { pressure += layer.Height*layer.Soil.BelowPhreaticLevel; } else { pressure += (phreaticLevel - layer.BottomLevel)*layer.Soil.BelowPhreaticLevel; pressure += (layer.TopLevel - phreaticLevel)*layer.Soil.AbovePhreaticLevel; } } else { if (phreaticLevel <= z) { pressure += (layer.TopLevel - z)*layer.Soil.AbovePhreaticLevel; } else if (phreaticLevel >= layer.TopLevel) { pressure += (layer.TopLevel - z)*layer.Soil.BelowPhreaticLevel; } else { pressure += (layer.TopLevel - phreaticLevel)*layer.Soil.AbovePhreaticLevel; pressure += (phreaticLevel - z)*layer.Soil.BelowPhreaticLevel; } } } } return pressure; } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() { return Name; } private void AddLayer(SoilLayer1D layer) { layer.SoilProfile = this; } /// /// Determines whether the specified layer is an aquifer layer. /// /// The layer. /// true if layer is aquifer layer; otherwise, false. private static bool IsAquiferLayer(SoilLayer1D layer) { return (layer.IsAquifer); } } }