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