// 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);
}
}
}