// Copyright (C) Stichting Deltares 2023. 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.Diagnostics;
using System.IO;
using Deltares.DamEngine.Calculators.General;
using Deltares.DamEngine.Calculators.KernelWrappers.Common;
using Deltares.DamEngine.Calculators.KernelWrappers.Interfaces;
using Deltares.DamEngine.Calculators.KernelWrappers.MacroStabilityInwards;
using Deltares.DamEngine.Calculators.Properties;
using Deltares.DamEngine.Data.Design;
using Deltares.DamEngine.Data.General;
using Deltares.DamEngine.Data.General.Results;
using Deltares.DamEngine.Data.Geotechnics;
using Deltares.DamEngine.Data.Standard;
using Deltares.DamEngine.Data.Standard.Calculation;
using Deltares.DamEngine.Data.Standard.Logging;
namespace Deltares.DamEngine.Calculators.DikesDesign;
///
/// The Dam Engine design calculator
///
public class DesignCalculator
{
private ProgressDelegate progressDelegate;
///
/// Performs the design calculation
///
/// The dam project data.
///
public void Execute(DamProjectData damProjectData)
{
progressDelegate?.Invoke(0);
damProjectData.DesignCalculations = new List();
PrepareCalculationMessages(damProjectData);
// Prepare the designCalculatorTasks
var designCalculatorTasks = new List();
var relevantCombinationsCount = 0;
for (var locationIndex = 0; locationIndex < damProjectData.Dike.Locations.Count; locationIndex++)
{
Location location = damProjectData.Dike.Locations[locationIndex].Copy();
if (location.Segment.SoilProfileProbabilities.Count == 0)
{
var message = new LogMessage();
message.Message = string.Format(Resources.DesignCalculatorNoSoilProfilesAvailableForLocation, location.Name);
message.MessageType = LogMessageType.Warning;
damProjectData.CalculationMessages.Add(message);
}
else
{
relevantCombinationsCount = DetermineRelevantCombinations(damProjectData, location, designCalculatorTasks, relevantCombinationsCount);
}
}
HandleRelevantCombinations(damProjectData, relevantCombinationsCount, designCalculatorTasks);
}
///
/// Performs the design calculation
///
/// The kernel wrapper.
/// The kernel data input.
/// The kernel data output.
/// The dam kernel input.
/// The calculation messages.
/// The design calculations.
public static void PerformDesignCalculation(
IKernelWrapper kernelWrapper, IKernelDataInput kernelDataInput,
IKernelDataOutput kernelDataOutput, DamKernelInput damKernelInput,
List calculationMessages, List designCalculations)
{
// During the design calculation the location.SurfaceLine is adapted; so store a copy to restore it after the calculation
Location location = damKernelInput.Location;
SurfaceLine2 orgLocationSurfaceLine = location.SurfaceLine.FullDeepClone();
try
{
if (location.RedesignDikeShoulder)
{
// Redesign the surfaceline with shoulder and or slope adaption
switch (kernelWrapper.GetDesignStrategy(damKernelInput))
{
case DesignStrategy.ShoulderPerPoint:
DesignCalculatorShoulderPerPoint.PerformDesignCalculationShoulderPerPoint(
kernelWrapper, kernelDataInput, kernelDataOutput,
damKernelInput, calculationMessages, designCalculations);
break;
case DesignStrategy.NoDesignPossible:
throw new NotImplementedException("No design is possible for this failure mechanism");
case DesignStrategy.SlopeAdaptionBeforeShoulderAdaption:
DesignCalculatorFirstSlopeAdaptionThenShoulderAdaption.PerformDesignCalculationFirstSlopeAdaptionThenShoulderAdaption(
kernelWrapper, kernelDataInput, kernelDataOutput, damKernelInput, calculationMessages,
designCalculations);
break;
case DesignStrategy.OptimizedSlopeAndShoulderAdaption:
DesignCalculatorCombinedSlopeAndShoulderAdaption.PerformDesignCalculationCombinedSlopeAdaptionAndShoulderAdaption(
kernelWrapper, kernelDataInput, kernelDataOutput, damKernelInput, calculationMessages,
designCalculations);
break;
}
}
}
finally
{
// Restore the original surfaceline
location.SurfaceLine = orgLocationSurfaceLine;
}
}
///
/// Registers the progress.
///
/// The progress delegate.
///
public CalculationResult RegisterProgress(ProgressDelegate aProgressDelegate)
{
progressDelegate = aProgressDelegate;
return CalculationResult.Succeeded;
}
private int DetermineRelevantCombinations(DamProjectData damProjectData, Location location,
List designCalculatorTasks, int relevantCombinationsCount)
{
for (var subSoilScenarioIndex = 0;
subSoilScenarioIndex < location.Segment.SoilProfileProbabilities.Count;
subSoilScenarioIndex++)
{
// Check if the subSoilScenario is applicable for the failuremechanism that is being calculated
if (location.Segment.SoilProfileProbabilities[subSoilScenarioIndex].SegmentFailureMechanismType.HasValue &&
(location.Segment.SoilProfileProbabilities[subSoilScenarioIndex].SegmentFailureMechanismType.Value.In(
ConversionHelper.ConvertToSegmentFailureMechanismType(damProjectData.DamProjectCalculationSpecification
.CurrentSpecification.FailureMechanismSystemType),
SegmentFailureMechanismType.All)))
{
SoilGeometryProbability soiProfileProbability = location.Segment.SoilProfileProbabilities[subSoilScenarioIndex];
for (var designScenarioIndex = 0; designScenarioIndex < location.Scenarios.Count; designScenarioIndex++)
{
DesignScenario designScenario = location.Scenarios[designScenarioIndex];
designScenario.LocationName = location.Name;
string projectPath = damProjectData.ProjectPath != ""
? damProjectData.ProjectPath
: Directory.GetCurrentDirectory();
var designResults = new List();
var calculationMessages = new List();
designCalculatorTasks.Add(new DesignCalculatorTask
{
Location = location,
SoiProfileProbability = soiProfileProbability,
DesignScenario = designScenario,
ProjectPath = projectPath,
CalculationMap = damProjectData.CalculationMap,
FailureMechanismeCalculationSpecification =
damProjectData.DamProjectCalculationSpecification.CurrentSpecification.Copy(),
DesignResults = designResults,
CalculationMessages = calculationMessages
});
}
relevantCombinationsCount++;
}
}
return relevantCombinationsCount;
}
private void HandleRelevantCombinations(DamProjectData damProjectData, int relevantCombinationsCount,
List designCalculatorTasks)
{
if (relevantCombinationsCount > 0)
{
// Perform the calculation
Parallel.Run(designCalculatorTasks, RunDesignCalculatorTask, progressDelegate,
damProjectData.MaxCalculationCores);
foreach (DesignCalculatorTask designCalculatorTask in designCalculatorTasks)
{
damProjectData.CalculationMessages.AddRange(designCalculatorTask.CalculationMessages);
damProjectData.DesignCalculations.AddRange(designCalculatorTask.DesignResults);
}
}
else
{
var logMessage = new LogMessage
{
MessageType = LogMessageType.Error,
Subject = null,
Message = string.Format(Resources.DesignCalculatorNoSegmentsWithFailureMechanismTypePresent,
damProjectData.DamProjectCalculationSpecification.CurrentSpecification.FailureMechanismSystemType
.ToString())
};
damProjectData.CalculationMessages.Add(logMessage);
}
}
private void PrepareCalculationMessages(DamProjectData damProjectData)
{
if (damProjectData.CalculationMessages == null)
{
damProjectData.CalculationMessages = new List();
}
else
{
damProjectData.CalculationMessages.Clear();
}
}
private void RunDesignCalculatorTask(object designCalculatorTask)
{
var task = (DesignCalculatorTask) designCalculatorTask;
Debug.WriteLine("Start calculation Location '{0}', soilprofile '{1}'", task.Location, task.SoiProfileProbability);
CalculateOneScenario(task.Location, task.SoiProfileProbability, task.DesignScenario, task.ProjectPath,
task.CalculationMap, task.FailureMechanismeCalculationSpecification, task.DesignResults, task.CalculationMessages);
Debug.WriteLine("End calculation Location '{0}', soilprofile '{1}'", task.Location, task.SoiProfileProbability);
}
private void CalculateOneScenario(Location originalLocation, SoilGeometryProbability soiProfileProbability,
DesignScenario designScenario, string projectPath, string calculationMap,
DamFailureMechanismeCalculationSpecification damFailureMechanismeCalculationSpecification,
List designResults, List calculationMessages)
{
try
{
// Prepare input
Location location = originalLocation.Copy();
var damKernelInput = new DamKernelInput();
damKernelInput.ProjectDir = projectPath;
damKernelInput.CalculationDir = Path.Combine(projectPath, calculationMap);
damKernelInput.Location = location;
damKernelInput.SubSoilScenario = soiProfileProbability;
damKernelInput.DamFailureMechanismeCalculationSpecification = damFailureMechanismeCalculationSpecification;
damKernelInput.RiverLevelHigh = designScenario.RiverLevel;
damKernelInput.RiverLevelLow = designScenario.RiverLevelLow;
damKernelInput.FilenamePrefix = $"Loc({location.Name})_Sce({designScenario.LocationScenarioID})";
AnalysisType analysisType = DamProjectCalculationSpecification.SelectedAnalysisType;
SynchronizeScenarioDataWithLocationData(designScenario, location);
IKernelDataInput kernelDataInput;
IKernelDataOutput kernelDataOutput;
// Create kernelwrapper
IKernelWrapper kernelWrapper = KernelWrapperHelper.CreateKernelWrapper(damFailureMechanismeCalculationSpecification);
if (kernelWrapper == null)
{
throw new NotImplementedException(Resources.DesignCalculatorKernelNotImplemented);
}
// During the design calculation the location.SurfaceLine is adapted; so store a copy to restore it after the calculation
SurfaceLine2 orgLocationSurfaceLine = null;
try
{
orgLocationSurfaceLine = AdaptSurfaceLineWhenNeededForDesign(location, analysisType, damKernelInput);
PrepareResult prepareResult =
kernelWrapper.Prepare(damKernelInput, 0, out kernelDataInput, out kernelDataOutput);
// Sometimes the kernelDataInput is not created (e.g. when SoilProfileProbability is meant for
// stability where Piping calc is wanted). In that case, do nothing but just skip.
if (prepareResult == PrepareResult.Successful)
{
switch (analysisType)
{
case AnalysisType.AdaptGeometry:
PerformDesignCalculation(kernelWrapper, kernelDataInput, kernelDataOutput,
damKernelInput, calculationMessages, designResults);
break;
case AnalysisType.NoAdaption:
DesignCalculatorSingle.PerformSingleCalculation(kernelWrapper, kernelDataInput, kernelDataOutput,
damKernelInput, calculationMessages, designResults);
break;
}
}
else
{
if (prepareResult == PrepareResult.NotRelevant)
{
calculationMessages.Add(new LogMessage(LogMessageType.Info, null,
string.Format(Resources.DesignCalculatorPrepareNotRelevant,
location.Name,
soiProfileProbability,
designScenario.LocationScenarioID)));
}
if (prepareResult == PrepareResult.Failed)
{
calculationMessages.Add(new LogMessage(LogMessageType.Error, null,
string.Format(Resources.DesignCalculatorPrepareError,
location.Name,
soiProfileProbability,
designScenario.LocationScenarioID)));
var mo = (MacroStabilityOutput) kernelDataOutput;
if (mo != null)
{
calculationMessages.Add(mo.Message);
}
}
}
}
finally
{
if (orgLocationSurfaceLine != null)
{
location.SurfaceLine = orgLocationSurfaceLine;
}
}
}
catch (Exception e)
{
calculationMessages.Add(new LogMessage(LogMessageType.Error, null,
string.Format(Resources.DesignCalculatorGeneralException,
originalLocation.Name,
soiProfileProbability,
designScenario.LocationScenarioID,
e.Message)));
}
}
private SurfaceLine2 AdaptSurfaceLineWhenNeededForDesign(Location location, AnalysisType analysisType,
DamKernelInput damKernelInput)
{
SurfaceLine2 orgLocationSurfaceLine = null;
// Make sure that in case of AdaptGeometry (and RedesignDikeHeight), the surface line is adapted for the given DTH BEFORE
// the call to Prepare as otherwise the Prepare will be done on the original surface line which is wrong.
if (analysisType == AnalysisType.AdaptGeometry && damKernelInput.Location.RedesignDikeHeight)
{
double? dikeHeight = location.SurfaceLine.GetDikeHeight();
if (dikeHeight.HasValue && location.CurrentScenario.DikeTableHeight > dikeHeight.Value)
{
orgLocationSurfaceLine = location.SurfaceLine.FullDeepClone();
// Redesign the surface line to the desired Dike Table Height
var surfaceLineHeightAdapter =
new SurfaceLineHeightAdapter(location.SurfaceLine, location, location.CurrentScenario.PolderLevel);
SurfaceLine2 adaptedSurfaceLine =
surfaceLineHeightAdapter.ConstructNewSurfaceLine(
location.CurrentScenario.DikeTableHeight ??
location.SurfaceLine.GetDefaultDikeTableHeight() ?? 0);
location.CurrentScenario.SetRedesignedSurfaceLine(damKernelInput.SubSoilScenario.SoilProfile1D,
damKernelInput.SubSoilScenario.SoilProfile2D,
adaptedSurfaceLine);
damKernelInput.Location.SurfaceLine = adaptedSurfaceLine;
}
}
return orgLocationSurfaceLine;
}
///
/// Synchronizes the scenario data with location data.
/// Note that scenario data is leading when available.
///
/// The scenario.
/// The location.
private void SynchronizeScenarioDataWithLocationData(DesignScenario designScenario, Location location)
{
// Synchronize scenario data (Note: do not attempt to synchronize the ModelFactors as these are set by the direct
// properties in the scenario such as the Required Safeties. ModelFactors is just a place holder.
location.CurrentScenario = location.SynchronizeCurrentScenarioWithScenarioData(designScenario);
location.Scenarios.Clear();
location.PlLineOffsetBelowDikeToeAtPolder = designScenario.PlLineOffsetBelowDikeToeAtPolder;
location.PlLineOffsetBelowDikeTopAtPolder = designScenario.PlLineOffsetBelowDikeTopAtPolder;
location.PlLineOffsetBelowDikeTopAtRiver = designScenario.PlLineOffsetBelowDikeTopAtRiver;
location.PlLineOffsetBelowShoulderBaseInside = designScenario.PlLineOffsetBelowShoulderBaseInside;
if (designScenario.PlLineOffsetBelowDikeCrestMiddle.HasValue)
{
location.PlLineOffsetBelowDikeCrestMiddle = designScenario.PlLineOffsetBelowDikeCrestMiddle;
}
if (designScenario.PlLineOffsetFactorBelowShoulderCrest.HasValue)
{
location.PlLineOffsetFactorBelowShoulderCrest = designScenario.PlLineOffsetFactorBelowShoulderCrest;
}
if (designScenario.UsePlLineOffsetBelowDikeCrestMiddle.HasValue)
{
location.UsePlLineOffsetBelowDikeCrestMiddle = designScenario.UsePlLineOffsetBelowDikeCrestMiddle;
}
if (designScenario.UsePlLineOffsetFactorBelowShoulderCrest.HasValue)
{
location.UsePlLineOffsetFactorBelowShoulderCrest = designScenario.UsePlLineOffsetFactorBelowShoulderCrest;
}
// Synchronize piping design parameters
location.UpliftCriterionPiping = designScenario.UpliftCriterionPiping;
location.ModelFactors.RequiredSafetyFactorPiping = designScenario.RequiredSafetyFactorPiping;
// Synchronize stability design parameters
location.UpliftCriterionStability = designScenario.UpliftCriterionStability;
location.ModelFactors.RequiredSafetyFactorStabilityInnerSlope = designScenario.RequiredSafetyFactorStabilityInnerSlope;
location.ModelFactors.RequiredSafetyFactorStabilityOuterSlope = designScenario.RequiredSafetyFactorStabilityOuterSlope;
if (designScenario.DikeTableHeight.HasValue)
{
location.DikeTableHeight = designScenario.DikeTableHeight ?? location.SurfaceLine.GetDefaultDikeTableHeight() ?? 0;
}
}
}