// Copyright (C) Stichting Deltares 2024. 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.Data;
using System.IO;
using System.Text.RegularExpressions;
using Deltares.DamEngine.Calculators.KernelWrappers.Common;
using Deltares.DamEngine.Calculators.KernelWrappers.Interfaces;
using Deltares.DamEngine.Calculators.KernelWrappers.MacroStabilityCommon.MacroStabilityIo;
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.Geometry;
using Deltares.DamEngine.Data.Geotechnics;
using Deltares.DamEngine.Data.Standard;
using Deltares.DamEngine.Data.Standard.Calculation;
using Deltares.DamEngine.Data.Standard.Language;
using Deltares.DamEngine.Data.Standard.Logging;
using Deltares.MacroStability.CSharpWrapper;
using Deltares.MacroStability.CSharpWrapper.Input;
using Deltares.MacroStability.CSharpWrapper.Output;
using Deltares.StixFileWriter;
using CharacteristicPointType = Deltares.DamEngine.Data.Geotechnics.CharacteristicPointType;
using MacroStabilityOutput = Deltares.DamEngine.Calculators.KernelWrappers.MacroStabilityInwards.MacroStabilityOutput;
using Soil = Deltares.DamEngine.Data.Geotechnics.Soil;
namespace Deltares.DamEngine.Calculators.KernelWrappers.MacroStabilityCommon;
///
/// Class with helper methods for the Macro Stability Wrappers.
///
public class MacroStabilityCommonHelper
{
///
/// Combines the surfaceline with the SoilProfile1D or the SoilProfile2D.
///
/// The sub soil scenario.
/// The surfaceline.
/// The dike embankment soil.
/// Thrown when subSoilScenario.SoilProfileType is ProfileTypeStiFile
///
public static SoilProfile2D CombineSoilProfileWithSurfaceLine(SoilGeometryProbability subSoilScenario, SurfaceLine2 surfaceLine2, Soil dikeEmbankmentSoil)
{
switch (subSoilScenario.SoilProfileType)
{
case SoilProfileType.ProfileType1D:
CombineSoilProfile1DWithSurfaceLine(subSoilScenario, surfaceLine2, dikeEmbankmentSoil);
break;
case SoilProfileType.ProfileType2D:
CombineSoilProfile2DWithSurfaceLine(subSoilScenario, surfaceLine2, dikeEmbankmentSoil);
break;
default:
throw new NotImplementedException();
}
return subSoilScenario.SoilProfile2D;
}
///
/// Fills the traffic load.
///
/// The dam kernel input.
///
public static TrafficLoad FillTrafficLoad(DamKernelInput damKernelInput)
{
TrafficLoad trafficLoad = null;
if (damKernelInput.Location.StabilityOptions != null && damKernelInput.Location.StabilityOptions.TrafficLoad.HasValue &&
!(Math.Abs(damKernelInput.Location.StabilityOptions.TrafficLoad.Value) < 1e-6))
{
trafficLoad = new TrafficLoad
{
Pressure = damKernelInput.Location.StabilityOptions.TrafficLoad.Value,
XEnd = damKernelInput.Location.SurfaceLine
.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.TrafficLoadInside).X,
XStart = damKernelInput.Location.SurfaceLine
.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.TrafficLoadOutside).X
};
}
return trafficLoad;
}
///
/// Fills the bishop calculation grid.
///
/// The dam kernel input.
///
public static BishopCalculationGrid FillBishopCalculationGrid(DamKernelInput damKernelInput)
{
SlipCircleDefinition slipCircleDefinition = damKernelInput.DamFailureMechanismeCalculationSpecification
.FailureMechanismParametersMStab.MStabParameters.SlipCircleDefinition;
MStabGridPosition gridPosition = damKernelInput.DamFailureMechanismeCalculationSpecification
.FailureMechanismParametersMStab.MStabParameters.GridPosition;
BishopCalculationGrid bishopCalculationGrid = BishopGridCreator.DetermineGridsFromSettings(gridPosition,
slipCircleDefinition, damKernelInput.Location.SurfaceLine);
double centerOfLeftGridXCoordinate = (bishopCalculationGrid.GridXLeft + bishopCalculationGrid.GridXRight) * 0.5;
if (bishopCalculationGrid.IsGridsAutomatic)
{
if (damKernelInput.DamFailureMechanismeCalculationSpecification.FailureMechanismSystemType ==
FailureMechanismSystemType.StabilityInside)
{
centerOfLeftGridXCoordinate = damKernelInput.Location.SurfaceLine.CharacteristicPoints.GetGeometryPoint(
CharacteristicPointType.DikeToeAtPolder).X;
}
else
{
centerOfLeftGridXCoordinate = damKernelInput.Location.SurfaceLine.CharacteristicPoints.GetGeometryPoint(
CharacteristicPointType.DikeToeAtRiver).X;
}
}
SoilProfile1D soilProfile1DAtCenterOfLeftGridXCoordinate =
damKernelInput.SubSoilScenario.DetermineSoilProfile1DAtX(centerOfLeftGridXCoordinate,
damKernelInput.Location.SurfaceLine, damKernelInput.Location.GetDikeEmbankmentSoil());
BishopGridCreator.DetermineTangentLines(bishopCalculationGrid, slipCircleDefinition,
soilProfile1DAtCenterOfLeftGridXCoordinate);
return bishopCalculationGrid;
}
///
/// Gets the name of the stability input file.
///
/// The dam kernel input.
/// Index of the iteration.
/// The model.
///
public static string GetStabilityInputFileName(DamKernelInput damKernelInput, int iterationIndex, MStabModelType model)
{
// Assume 2D sti-file, then check on type being 1D
string soilGeometryName = damKernelInput.SubSoilScenario.SoilProfile2DName;
string calculationName = DetermineCalculationFilename(damKernelInput.FilenamePrefix, soilGeometryName, iterationIndex);
const string filenameExtension = ".skx";
string fileName = calculationName + filenameExtension;
string stabilityDirectory = GetStabilityCalculationDirectory(model, damKernelInput.CalculationDir);
return Path.Combine(stabilityDirectory, fileName);
}
///
/// Throws the when macro stability kernel input is not assigned.
///
/// The dam macro stability input.
///
public static void ThrowWhenMacroStabilityKernelInputNull(MacroStabilityKernelDataInput macroStabilityKernelDataInput)
{
if (macroStabilityKernelDataInput == null)
{
throw new NoNullAllowedException(Resources.MacroStabilityKernelWrapper_NoMacroStabilityInputObjectDefined);
}
}
///
/// Throws the when macro stability kernel output is not assigned.
///
/// The dam macro stability output.
///
public static void ThrowWhenMacroStabilityKernelOutputNull(MacroStabilityOutput macroStabilityOutput)
{
if (macroStabilityOutput == null)
{
throw new NoNullAllowedException(Resources.MacroStabilityKernelWrapper_NoMacroStabilityOutputObjectDefined);
}
}
///
/// Throws the when macro stability dam kernel input is not assigned.
///
/// The dam kernel input.
///
public static void ThrowWhenMacroStabilityDamKernelInputNull(DamKernelInput damKernelInput)
{
if (damKernelInput == null)
{
throw new NoNullAllowedException(Resources.MacroStabilityKernelWrapper_NoDamInputObjectDefinedForMacroStability);
}
}
///
/// Creates new designresult.
///
/// The dam kernel input.
/// The design scenario.
///
public static DesignResult NewDesignResult(DamKernelInput damKernelInput, DesignScenario designScenario)
{
var soilProfile2DName = damKernelInput.SubSoilScenario.ToString();
var designResult = new DesignResult(damKernelInput.DamFailureMechanismeCalculationSpecification,
designScenario, damKernelInput.SubSoilScenario.SoilProfile1D, soilProfile2DName)
{
// initialize as failed
CalculationResult = CalculationResult.RunFailed,
StabilityDesignResults = new StabilityDesignResults()
};
var stabilityDesignResults = new StabilityDesignResults
{
RedesignedSurfaceLine = damKernelInput.Location.SurfaceLine
};
designResult.ProfileName = soilProfile2DName;
designResult.StabilityDesignResults = stabilityDesignResults;
designResult.CalculationSubDir = damKernelInput.CalculationDir;
return designResult;
}
///
/// Fills the design result.
///
/// The macro stability output item.
/// The design result.
public static void FillDesignResult(MacroStabilityOutputItem macroStabilityOutputItem, DesignResult designResult)
{
designResult.BaseFileName = Path.GetFileNameWithoutExtension(macroStabilityOutputItem.ProjectName);
designResult.CalculationSubDir = macroStabilityOutputItem.CalculationPath;
designResult.CalculationResult = macroStabilityOutputItem.CalculationResult;
designResult.StabilityDesignResults.StabilityModelType = macroStabilityOutputItem.StabilityModelType;
if (designResult.CalculationResult == CalculationResult.Succeeded)
{
designResult.StabilityDesignResults.SafetyFactor = macroStabilityOutputItem.SafetyFactor;
}
designResult.StabilityDesignResults.ActiveCenterPoint =
macroStabilityOutputItem.ActiveCenterPoint;
designResult.StabilityDesignResults.ActiveCenterPointRadius =
macroStabilityOutputItem.ActiveCenterPointRadius;
designResult.StabilityDesignResults.ResultSlices = macroStabilityOutputItem.ResultSlices;
if (macroStabilityOutputItem.StabilityModelType == MStabModelType.UpliftVan ||
macroStabilityOutputItem.StabilityModelType == MStabModelType.BishopUpliftVan)
{
designResult.StabilityDesignResults.PassiveCenterPoint =
macroStabilityOutputItem.PassiveCenterPoint;
designResult.StabilityDesignResults.PassiveCenterPointRadius =
macroStabilityOutputItem.PassiveCenterPointRadius;
}
}
///
/// Prepares the kernel.
///
/// The calculator.
/// Name of the file.
///
public static PrepareResult PrepareKernel(Calculator calculator, string fileName)
{
try
{
// For now a simple check to see if any data has been past at all.
string inputAsXml = calculator.KernelInputXml;
File.WriteAllText(fileName, inputAsXml);
if (inputAsXml.Length > 10)
{
return PrepareResult.Successful;
}
return PrepareResult.Failed;
}
catch
{
return PrepareResult.Failed;
}
}
///
/// Validates the specified kernel data input.
///
/// The kernel data input.
/// The kernel data output.
/// The return messages.
///
/// Zero when there are no errors, one when there are errors that prevent a calculation
///
public static int Validate(IKernelDataInput kernelDataInput, IKernelDataOutput kernelDataOutput, out List messages)
{
var macroStabilityKernelDataInput = (MacroStabilityKernelDataInput) kernelDataInput;
messages = new List();
try
{
ValidationOutput result = new Validator(macroStabilityKernelDataInput.Input).Validate();
if (result.IsValid)
{
return 0;
}
if (kernelDataOutput != null)
{
((MacroStabilityOutput) kernelDataOutput).CalculationResult = CalculationResult.InvalidInputData;
}
foreach (Message resultMessage in result.Messages)
{
var message = new LogMessage
{
Message = resultMessage.Content
};
switch (resultMessage.MessageType)
{
case MessageType.Error:
{
message.MessageType = LogMessageType.Error;
break;
}
case MessageType.Info:
{
message.MessageType = LogMessageType.Info;
break;
}
case MessageType.Warning:
{
message.MessageType = LogMessageType.Warning;
break;
}
}
messages.Add(message);
}
return 1;
}
catch (Exception e)
{
var message = new LogMessage
{
MessageType = LogMessageType.FatalError,
Message = e.Message
};
messages.Add(message);
if (kernelDataOutput != null)
{
((MacroStabilityOutput) kernelDataOutput).CalculationResult = CalculationResult.InvalidInputData;
}
return 1;
}
}
///
/// Performs the stability calculation.
///
/// The input.
/// The macro stability output.
/// Name of the file.
/// The calculator.
/// The messages.
public void PerformStabilityCalculation(MacroStabilityInput input, MacroStabilityOutput macroStabilityOutput,
string fileName, Calculator calculator, out List messages)
{
macroStabilityOutput.CalculationResult = CalculationResult.NoRun;
macroStabilityOutput.StabilityOutputItems = new List();
messages = new List();
try
{
MacroStability.CSharpWrapper.Output.MacroStabilityOutput macroStabilityOutputKernel = calculator.Calculate();
string outputAsXml = calculator.KernelOutputXml;
string outputFileName = fileName.Replace(".skx", ".out.xml");
File.WriteAllText(outputFileName, outputAsXml);
FillEngineFromMacroStabilityWrapperOutput.FillEngineDataWithResults(macroStabilityOutputKernel, macroStabilityOutput,
out messages);
}
catch (Exception exceptionDuringCalculation)
{
macroStabilityOutput.CalculationResult = CalculationResult.UnexpectedError;
messages.Add(new LogMessage(LogMessageType.Error, null, exceptionDuringCalculation.Message));
}
try
{
foreach (MacroStabilityOutputItem stabilityOutputItem in macroStabilityOutput.StabilityOutputItems)
{
switch (stabilityOutputItem.StabilityModelType)
{
case MStabModelType.Bishop:
input.StabilityModel.BishopCalculationCircle = stabilityOutputItem.BishopCalculationCircle;
break;
case MStabModelType.UpliftVan:
input.StabilityModel.UpliftVanCalculationGrid = stabilityOutputItem.UpliftVanCalculationGrid;
break;
}
WriteStixFileBasedOnInputAndResultsSearchGrid(fileName, input);
}
WriteStixFileBasedOnInputAndResultsSlipPlane(fileName, input, macroStabilityOutput);
}
catch (Exception exceptionDuringStixFileWriter)
{
string message = LocalizationManager.GetTranslatedText(this, "UnexpectedErrorStixFileCreation") + exceptionDuringStixFileWriter.Message;
messages.Add(new LogMessage(LogMessageType.Warning, null, message));
}
}
///
/// Combines the surfaceline with the SoilProfile2D.
///
/// The sub soil scenario.
/// The surfaceline.
/// The dike embankment soil.
/// Thrown when no SoilProfile2D is defined
private static void CombineSoilProfile2DWithSurfaceLine(SoilGeometryProbability subSoilScenario, SurfaceLine2 surfaceLine2,
Soil dikeEmbankmentSoil)
{
ValidateForCombineSoilProfile2DWithSurfaceLine(subSoilScenario, surfaceLine2, dikeEmbankmentSoil);
subSoilScenario.SoilProfile2D = SoilProfile2DSurfaceLineHelper.CombineSurfaceLineWithSoilProfile2D(surfaceLine2.Geometry, subSoilScenario.SoilProfile2D, dikeEmbankmentSoil, 0);
}
private static void ValidateForCombineSoilProfile2DWithSurfaceLine(SoilGeometryProbability subSoilScenario, SurfaceLine2 surfaceLine2, Soil dikeEmbankmentSoil)
{
if (subSoilScenario.SoilProfile2D == null)
{
throw new ArgumentNullException(nameof(subSoilScenario), @"SoilProfile2D cannot be null.");
}
if (surfaceLine2 == null)
{
throw new ArgumentNullException(nameof(surfaceLine2), @"SurfaceLine cannot be null.");
}
if (dikeEmbankmentSoil == null)
{
throw new ArgumentNullException(nameof(dikeEmbankmentSoil), @"DikeEmbankmentSoil cannot be null.");
}
if (!SoilProfile2DSurfaceLineHelper.IsSurfaceLineAboveBottomSoilProfile2D(surfaceLine2, subSoilScenario.SoilProfile2D))
{
throw new InvalidOperationException(@"SurfaceLine is (partly) below the bottom of the SoilProfile2D.");
}
if (!SurfaceLine2Validator.AreAllCharacteristicPointsXCoordinatesAscending(surfaceLine2))
{
throw new InvalidOperationException(@"SurfaceLine points are not strictly ascending");
}
}
///
/// Combines the surfaceline with the SoilProfile1D.
///
/// The sub soil scenario.
/// The surfaceline.
/// The dike embankment soil.
/// Thrown when no SoilProfile1D is defined
private static void CombineSoilProfile1DWithSurfaceLine(SoilGeometryProbability subSoilScenario, SurfaceLine2 surfaceLine2, Soil dikeEmbankmentSoil)
{
if (subSoilScenario.SoilProfile1D == null)
{
throw new ArgumentNullException(nameof(subSoilScenario), @"SoilProfile1D cannot be null.");
}
SoilProfile2D soilProfile2D = subSoilScenario.SoilProfile2D;
if (soilProfile2D == null)
{
var soilSurfaceProfile = new SoilSurfaceProfile
{
SoilProfile = subSoilScenario.SoilProfile1D,
SurfaceLine2 = surfaceLine2,
Name = subSoilScenario.SoilProfile1D.Name,
DikeEmbankmentMaterial = dikeEmbankmentSoil
};
// Convert the soilSurfaceProfile to a SoilProfile2D to be able to edit it properly.
SoilProfile2D soilProfile2DNew = soilSurfaceProfile.ConvertToSoilProfile2D();
subSoilScenario.SoilProfile2D = soilProfile2DNew;
subSoilScenario.SoilProfile2DName = soilProfile2DNew.Name;
subSoilScenario.SoilProfileType = SoilProfileType.ProfileType2D;
subSoilScenario.SoilProfile1D = null;
}
}
///
/// Determines the calculation filename.
///
/// The filename prefix.
/// Name of the soil geometry.
/// Index of the iteration.
///
private static string DetermineCalculationFilename(string filenamePrefix, string soilGeometryName, int iterationIndex)
{
string calculationName;
if (iterationIndex <= 0)
{
calculationName = $"{filenamePrefix}_Pro({soilGeometryName})";
}
else
{
calculationName = $"{filenamePrefix}_Pro({soilGeometryName})_Ite({iterationIndex})";
}
return Regex.Replace(calculationName, @"[\\\/:\*\?""'<>|.]", "_");
}
///
/// Gets the stability calculation directory.
///
/// The model.
/// The project working path.
///
private static string GetStabilityCalculationDirectory(MStabModelType model, string projectWorkingPath)
{
string calculationBaseDirectory = projectWorkingPath;
string stabilitySubDir = GetCalculationSubDir(model);
string stabilityDirectory = Path.Combine(calculationBaseDirectory, stabilitySubDir);
if (!Directory.Exists(stabilityDirectory))
{
Directory.CreateDirectory(stabilityDirectory);
}
return stabilityDirectory;
}
///
/// Gets the calculation sub dir.
///
/// The model.
///
private static string GetCalculationSubDir(MStabModelType model)
{
const string stabilitySubDir = @"Stability\";
var modelSubDirectory = model.ToString();
string dir = Path.Combine(stabilitySubDir, modelSubDirectory);
return dir;
}
///
/// Writes the stix file based on input and results search grid.
/// For Uplift-Van with Grid, the stix file is not written because this combination is not possible in D-Stability.
///
/// Name of the file.
/// The input.
private static void WriteStixFileBasedOnInputAndResultsSearchGrid(string fileName, MacroStabilityInput input)
{
if (input.StabilityModel.ModelOption == StabilityModelOptionType.UpliftVan &&
input.StabilityModel.SearchAlgorithm == SearchAlgorithm.Grid)
{
return;
}
string fileNameForCalculationAsStix = DetermineStixFilename(fileName, "input");
var inputStixFile = new StixWriter();
inputStixFile.FillInfo("DAM Engine", Path.GetDirectoryName(fileName),
Path.GetFileName(fileName), true);
StixFileWrite(inputStixFile, fileNameForCalculationAsStix, input);
}
///
/// Writes the stix file based on input and results slip plane.
///
/// Name of the file.
/// The input.
/// The macro stability output.
private static void WriteStixFileBasedOnInputAndResultsSlipPlane(string fileName, MacroStabilityInput input,
MacroStabilityOutput macroStabilityOutput)
{
if (macroStabilityOutput.CalculationResult == CalculationResult.Succeeded)
{
foreach (MacroStabilityOutputItem macroStabilityOutputItem in macroStabilityOutput.StabilityOutputItems)
{
macroStabilityOutputItem.CalculationPath = Path.GetDirectoryName(fileName);
macroStabilityOutputItem.ProjectName = Path.GetFileName(fileName);
string fileNameForCalculationAsStix = DetermineStixFilename(fileName, "result");
var resultStixWrite = new StixWriter();
resultStixWrite.FillInfo("DAM Engine", macroStabilityOutputItem.CalculationPath,
macroStabilityOutputItem.ProjectName, true);
switch (input.StabilityModel.ModelOption)
{
case StabilityModelOptionType.UpliftVan:
resultStixWrite.FillCalculatedDualCircle(macroStabilityOutputItem.ActiveCenterPoint.X,
macroStabilityOutputItem.ActiveCenterPoint.Z,
macroStabilityOutputItem.ActiveCenterPointRadius,
macroStabilityOutputItem.PassiveCenterPoint.X,
macroStabilityOutputItem.PassiveCenterPoint.Z);
break;
case StabilityModelOptionType.Bishop:
resultStixWrite.FillCalculatedCircle(macroStabilityOutputItem.ActiveCenterPoint.X,
macroStabilityOutputItem.ActiveCenterPoint.Z,
macroStabilityOutputItem.ActiveCenterPointRadius);
break;
}
StixFileWrite(resultStixWrite, fileNameForCalculationAsStix, input);
}
}
}
private static void StixFileWrite(StixWriter stixWriter, string fileName, MacroStabilityInput input)
{
if (File.Exists(fileName))
{
File.Delete(fileName);
}
stixWriter.WriteStixFile(fileName, input);
}
private static string DetermineStixFilename(string fileNameForCalc, string suffix, string extension = ".stix")
{
return Path.Combine(Path.GetDirectoryName(fileNameForCalc) ?? string.Empty,
$"{Path.GetFileNameWithoutExtension(fileNameForCalc)}_{suffix}{extension}");
}
}