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