// Copyright (C) Stichting Deltares 2025. 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.Calculators.KernelWrappers.Common; using Deltares.DamEngine.Calculators.KernelWrappers.MacroStabilityCommon; using Deltares.DamEngine.Calculators.KernelWrappers.MacroStabilityCommon.MacroStabilityIo; using Deltares.DamEngine.Data.General; using Deltares.DamEngine.Data.Geometry; using Deltares.DamEngine.Data.Geotechnics; using Deltares.DamEngine.Data.Standard.Logging; using Deltares.DamEngine.TestHelpers.Factories; using Deltares.MacroStability.CSharpWrapper; using Deltares.MacroStability.CSharpWrapper.Input; using Deltares.MacroStability.CSharpWrapper.Output; using KellermanSoftware.CompareNetObjects; using NUnit.Framework; using CharacteristicPointType = Deltares.DamEngine.Data.Geotechnics.CharacteristicPointType; using Point2D = Deltares.DamEngine.Data.Geometry.Point2D; using Slice = Deltares.MacroStability.CSharpWrapper.Output.Slice; using Soil = Deltares.DamEngine.Data.Geotechnics.Soil; using UpliftVanCalculationGrid = Deltares.DamEngine.Calculators.KernelWrappers.MacroStabilityCommon.UpliftVanCalculationGrid; namespace Deltares.DamEngine.Calculators.Tests.KernelWrappers.MacroStabilityCommon; [TestFixture] public class MacroStabilityIoTests { private static readonly List soilParametersToIgnore = [ "RRatio", "RheologicalCoefficient", "BondStressCurve", "UseSoilType", "UseDefaultShearStrengthModel" ]; [Test] public void GivenDamEngineDataModelWhenGoingToAndFromCSharpWrapperForInputThenDataIsEqual() { // Given DamEngine data (DamProjectData) DamProjectData expectedDamProjectData = FactoryForDamProjectData.CreateExampleDamProjectData(); expectedDamProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications[0] .StabilityModelType = StabilityModelType.UpliftVan; Waternet expectedWaternet = CreateExampleWaternet(); Location expectedLocation = expectedDamProjectData.Dike.Locations[0]; SoilList expectedSoilList = expectedDamProjectData.Dike.SoilList; SoilProfile2D expectedSoilProfile2D = expectedLocation.Segment.SoilProfileProbabilities[0].SoilProfile2D; SurfaceLine2 expectedSurfaceLine2D = expectedLocation.SurfaceLine; IList expectedConsolidationValues = expectedLocation.TrafficLoadDegreeOfConsolidations; FailureMechanismParametersMStab expectedParametersMStab = expectedDamProjectData.DamProjectCalculationSpecification.CurrentSpecification.FailureMechanismParametersMStab; var expectedTrafficLoad = new TrafficLoad { Pressure = 0.44, XStart = expectedSurfaceLine2D.CharacteristicPoints.GetPoint2D(CharacteristicPointType.TrafficLoadOutside).X, XEnd = expectedSurfaceLine2D.CharacteristicPoints.GetPoint2D(CharacteristicPointType.TrafficLoadInside).X }; var fillMacroStabilityWrapperInputFromEngine = new FillMacroStabilityWrapperInputFromEngine(); var damKernelInput = new DamKernelInput { SubSoilScenario = expectedLocation.Segment.SoilProfileProbabilities[0], Location = expectedLocation, DamFailureMechanismeCalculationSpecification = expectedDamProjectData.DamProjectCalculationSpecification.CurrentSpecification }; double xCoordinateLowestUpliftFactorPoint = (expectedSurfaceLine2D.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtPolder).X + expectedSurfaceLine2D.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtPolder).X) * 0.5; UpliftVanCalculationGrid expectedUpliftVanCalculationGrid = MacroStabilityCommonHelper.DetermineUpliftVanCalculationGrid(damKernelInput, xCoordinateLowestUpliftFactorPoint); // get the input for the CSharp wrapper MacroStabilityInput expectedMacrostabilityInput = fillMacroStabilityWrapperInputFromEngine.CreateMacroStabilityInput(damKernelInput, expectedParametersMStab.MStabParameters, expectedWaternet); // reverse that input to the engine data var fillEngineFromMacroStabilityWrapperInput = new FillEngineFromMacroStabilityWrapperInput(); fillEngineFromMacroStabilityWrapperInput.FillDamProjectDataFromKernelModel(expectedMacrostabilityInput); // Then the data models are equal CompareStabilityModel(expectedParametersMStab, fillEngineFromMacroStabilityWrapperInput.FailureMechanismParametersMStab); CompareSoilModel(expectedSoilList, fillEngineFromMacroStabilityWrapperInput.SoilList); CompareSoilProfile2D(expectedSoilProfile2D, fillEngineFromMacroStabilityWrapperInput.SoilProfile2D); CompareSurfaceLine(expectedSurfaceLine2D, fillEngineFromMacroStabilityWrapperInput.SurfaceLine2); CompareTrafficLoad(expectedTrafficLoad, fillEngineFromMacroStabilityWrapperInput.TrafficLoad); CompareTrafficLoadDegreeOfConsolidations(expectedConsolidationValues, fillEngineFromMacroStabilityWrapperInput.TrafficLoadDegreeOfConsolidations); SlipCircleDefinition expectedUpliftVanCalculationGridSettings = expectedParametersMStab.MStabParameters.SlipCircleDefinition; CompareUpliftVanCalculationGridSettings(expectedUpliftVanCalculationGridSettings, fillEngineFromMacroStabilityWrapperInput.SlipCircleDefinition); bool isAutoTangentLine = expectedUpliftVanCalculationGridSettings.UpliftVanTangentLinesDefinition == TangentLinesDefinition.Specified; CompareUpliftVanCalculationGrid(expectedUpliftVanCalculationGrid, fillEngineFromMacroStabilityWrapperInput.UpliftVanCalculationGrid, isAutoTangentLine); CompareWaternet(expectedWaternet, fillEngineFromMacroStabilityWrapperInput.Waternet); //Todo : add and or implement comparer per item as these are added to the code } private static Waternet CreateExampleWaternet() { var waterNet = new Waternet { IsGenerated = false, UnitWeight = 9.81, Name = "Test Waternet" }; var random = new Random(21); var phreaticLine = new PhreaticLine { Name = "Test Phreatic line" }; waterNet.PhreaticLine = phreaticLine; SetGeometry(phreaticLine, random.Next()); // Head line const int nrOfHeadLines = 10; for (var i = 0; i < nrOfHeadLines; i++) { var headLine = new HeadLine { Name = $"Test Head line{i}" }; SetGeometry(headLine, random.Next()); waterNet.HeadLineList.Add(headLine); } // Waternet line for (var i = 0; i < nrOfHeadLines; i++) { var waternetLine = new WaternetLine { Name = $"Test Waternet line {i}", HeadLine = waterNet.HeadLineList[i] }; SetGeometry(waternetLine, random.Next()); waterNet.WaternetLineList.Add(waternetLine); } return waterNet; } private static void SetGeometry(GeometryPointString geometry, int seed) { var random = new Random(seed); for (var i = 0; i < 10; i++) { geometry.Points.Add(new Point2D(random.NextDouble(), random.NextDouble())); } } private static void CompareTrafficLoad(TrafficLoad expectedTrafficLoad, TrafficLoad actualTrafficLoad) { var compare = new CompareLogic { Config = { MaxDifferences = 100 } }; compare.Config.MembersToInclude = new List { "XEnd", "XStart", "Pressure" }; ComparisonResult result = compare.Compare(expectedTrafficLoad, actualTrafficLoad); Assert.That(result.Differences, Is.Empty, "Differences found read/write kernel Traffic Load"); } private static void CompareTrafficLoadDegreeOfConsolidations(IList expectedDegreeOfConsolidations, IList actualDegreeOfConsolidation) { int expectedNrOfDegreeOfConsolidation = expectedDegreeOfConsolidations.Count; Assert.That(actualDegreeOfConsolidation, Has.Count.EqualTo(expectedNrOfDegreeOfConsolidation)); var compare = new CompareLogic { Config = { MaxDifferences = 100 } }; for (var i = 0; i < expectedNrOfDegreeOfConsolidation; i++) { compare.Config.MembersToInclude = new List { "Consolidator", "Consolidated", "Value" }; ComparisonResult result = compare.Compare(expectedDegreeOfConsolidations, actualDegreeOfConsolidation); Assert.That(result.Differences, Is.Empty, "Differences found read/write kernel Consolidation Values"); } } private static void CompareUpliftVanCalculationGridSettings(SlipCircleDefinition expectedSlipCircleDefinition, SlipCircleDefinition actualSlipCircleDefinition) { Assert.That(actualSlipCircleDefinition.UpliftVanGridSizeDetermination, Is.EqualTo(expectedSlipCircleDefinition.UpliftVanGridSizeDetermination)); Assert.That(actualSlipCircleDefinition.UpliftVanTangentLinesDefinition, Is.EqualTo(expectedSlipCircleDefinition.UpliftVanTangentLinesDefinition)); // Note: do not test UpliftVanTangentLinesDistance as there is no way to be sure of equal values as determination to and from involves rounding. //Assert.AreEqual(expectedSlipCircleDefinition.UpliftVanTangentLinesDistance, actualSlipCircleDefinition.UpliftVanTangentLinesDistance); } private static void CompareUpliftVanCalculationGrid(UpliftVanCalculationGrid expectedSlipPlaneUpliftVan, UpliftVanCalculationGrid actualSlipPlaneUpliftVan, bool isAutoTangentLine) { Assert.Multiple(() => { Assert.That(actualSlipPlaneUpliftVan.LeftGridXLeft, Is.EqualTo(expectedSlipPlaneUpliftVan.LeftGridXLeft)); Assert.That(actualSlipPlaneUpliftVan.LeftGridXRight, Is.EqualTo(expectedSlipPlaneUpliftVan.LeftGridXRight)); Assert.That(actualSlipPlaneUpliftVan.LeftGridZTop, Is.EqualTo(expectedSlipPlaneUpliftVan.LeftGridZTop)); Assert.That(actualSlipPlaneUpliftVan.LeftGridZBottom, Is.EqualTo(expectedSlipPlaneUpliftVan.LeftGridZBottom)); Assert.That(actualSlipPlaneUpliftVan.LeftGridXCount, Is.EqualTo(expectedSlipPlaneUpliftVan.LeftGridXCount)); Assert.That(actualSlipPlaneUpliftVan.LeftGridZCount, Is.EqualTo(expectedSlipPlaneUpliftVan.LeftGridZCount)); Assert.That(actualSlipPlaneUpliftVan.RightGridXLeft, Is.EqualTo(expectedSlipPlaneUpliftVan.RightGridXLeft)); Assert.That(actualSlipPlaneUpliftVan.RightGridXRight, Is.EqualTo(expectedSlipPlaneUpliftVan.RightGridXRight)); Assert.That(actualSlipPlaneUpliftVan.RightGridZTop, Is.EqualTo(expectedSlipPlaneUpliftVan.RightGridZTop)); Assert.That(actualSlipPlaneUpliftVan.RightGridZBottom, Is.EqualTo(expectedSlipPlaneUpliftVan.RightGridZBottom)); Assert.That(actualSlipPlaneUpliftVan.RightGridXCount, Is.EqualTo(expectedSlipPlaneUpliftVan.RightGridXCount)); Assert.That(actualSlipPlaneUpliftVan.RightGridZCount, Is.EqualTo(expectedSlipPlaneUpliftVan.RightGridZCount)); }); if (!isAutoTangentLine) { Assert.That(actualSlipPlaneUpliftVan.TangentLineLevels, Is.EqualTo(expectedSlipPlaneUpliftVan.TangentLineLevels).AsCollection); } } private static void CompareWaternet(Waternet expectedWaternet, Waternet actualWaternet) { Assert.That(actualWaternet.Name, Is.EqualTo(expectedWaternet.Name)); CompareLine(expectedWaternet.PhreaticLine, actualWaternet.PhreaticLine); CompareWaternetHeadLines(expectedWaternet.HeadLineList, actualWaternet.HeadLineList); CompareWaternetLines(actualWaternet.WaternetLineList, expectedWaternet.WaternetLineList); } private static void CompareWaternetLines(IEnumerable actualWaternetLines, IEnumerable expectedWaternetLines) { int expectedNrOfWaternetLines = actualWaternetLines.Count(); Assert.That(actualWaternetLines.Count(), Is.EqualTo(expectedNrOfWaternetLines)); for (var i = 0; i < expectedNrOfWaternetLines; i++) { WaternetLine expectedWaternetLine = expectedWaternetLines.ElementAt(i); WaternetLine actualWaternetLine = actualWaternetLines.ElementAt(i); CompareLine(expectedWaternetLine, actualWaternetLine); CompareLine(expectedWaternetLine.HeadLine, actualWaternetLine.HeadLine); } } private static void CompareWaternetHeadLines(IEnumerable expectedHeadLines, IEnumerable actualHeadLines) { int expectedNrOfHeadLines = expectedHeadLines.Count(); Assert.That(actualHeadLines.Count(), Is.EqualTo(expectedNrOfHeadLines)); for (var i = 0; i < expectedNrOfHeadLines; i++) { HeadLine expectedHeadLine = expectedHeadLines.ElementAt(i); HeadLine actualHeadLine = actualHeadLines.ElementAt(i); CompareLine(expectedHeadLine, actualHeadLine); } } private static void CompareLine(TLineType expectedHeadLine, TLineType actualHeadLine) where TLineType : GeometryPointString { Assert.That(actualHeadLine.Name, Is.EqualTo(expectedHeadLine.Name)); List expectedPoints = expectedHeadLine.Points; int expectedNrOfPoints = expectedPoints.Count; List actualPoints = actualHeadLine.Points; Assert.That(actualPoints, Has.Count.EqualTo(expectedNrOfPoints)); for (var i = 0; i < expectedNrOfPoints; i++) { Point2D expectedCoordinate = expectedPoints[i]; Point2D actualCoordinate = actualPoints[i]; Assert.That(expectedCoordinate.LocationEquals(actualCoordinate), Is.True); } } private static void CompareSurfaceLine(SurfaceLine2 expectedSurfaceLine2, SurfaceLine2 actualSurfaceLine2) { CharacteristicPointSet expectedCharacteristicPoints = expectedSurfaceLine2.CharacteristicPoints; CharacteristicPointSet actualCharacteristicPoints = actualSurfaceLine2.CharacteristicPoints; var compare = new CompareLogic { Config = { MaxDifferences = 100 } }; compare.Config.MembersToIgnore = new List { "Owner" }; ComparisonResult result = compare.Compare(expectedCharacteristicPoints, actualCharacteristicPoints); Assert.That(result.Differences, Is.Empty, "Differences found read/write kernel SurfaceLine"); } private static void CompareSoilProfile2D(SoilProfile2D expectedSoilProfile, SoilProfile2D actualSoilProfile) { var compare = new CompareLogic { Config = { MaxDifferences = 100 } }; compare.Config.MembersToIgnore = soilParametersToIgnore; compare.Config.MembersToIgnore.Add("Name"); ComparisonResult result = compare.Compare(expectedSoilProfile, actualSoilProfile); Assert.That(result.Differences, Is.Empty, "Differences found read/write kernel SoilProfile2D"); } private static void CompareSoilModel(SoilList expectedSoils, SoilList actualSoils) { Assert.That(actualSoils.Soils, Has.Count.EqualTo(expectedSoils.Soils.Count), "Soil Count does not match"); foreach (Soil expectedSoil in expectedSoils.Soils) { Soil actualSoil = actualSoils.Soils.SingleOrDefault(soil => soil.Name.Equals(expectedSoil.Name)); Assert.That(actualSoil, Is.Not.Null, $"Soil {expectedSoil.Name} not found"); var compare = new CompareLogic { Config = { MaxDifferences = 100 } }; compare.Config.MembersToIgnore = soilParametersToIgnore; ComparisonResult result = compare.Compare(expectedSoil, actualSoil); Assert.That(result.Differences, Is.Empty, "Differences found read/write kernel SoilModel"); } } private static void CompareStabilityModel(FailureMechanismParametersMStab expectedStabilityModel, FailureMechanismParametersMStab actualStabilityModel) { Assert.Multiple(() => { Assert.That(actualStabilityModel.MStabParameters.SearchMethod, Is.EqualTo(expectedStabilityModel.MStabParameters.SearchMethod)); Assert.That(actualStabilityModel.MStabParameters.Model, Is.EqualTo(expectedStabilityModel.MStabParameters.Model)); Assert.That(actualStabilityModel.MStabParameters.GridPosition, Is.EqualTo(expectedStabilityModel.MStabParameters.GridPosition)); }); } public class GivenDamKernelInput { private readonly DamKernelInput damKernelInput = CreateDamKernelInputWithForbiddenZone(); [Test] [TestCase(0.0, 5.0)] [TestCase(0.2, 6.0)] [TestCase(1.0, 10.0)] public void WithForbiddenZone_WhenTransferSlipPlaneConstraints_ThenSlipPlaneConstraintsAreSet(double forbiddenZoneFactor, double xEntryMax) { damKernelInput.Location.StabilityOptions.ForbiddenZoneFactor = forbiddenZoneFactor; var macroStabilityInput = new MacroStabilityInput(); // When FillMacroStabilityWrapperInputFromEngine.TransferSlipPlaneConstraints(damKernelInput.Location, macroStabilityInput.StabilityModel.SlipPlaneConstraints); Assert.Multiple(() => { // Then Assert.That(macroStabilityInput.StabilityModel.SlipPlaneConstraints.SlipPlaneMinDepth, Is.EqualTo(1.5)); Assert.That(macroStabilityInput.StabilityModel.SlipPlaneConstraints.XEntryMin, Is.EqualTo(1.0)); Assert.That(macroStabilityInput.StabilityModel.SlipPlaneConstraints.XEntryMax, Is.EqualTo(xEntryMax)); }); } [Test] public void WithNoZones_WhenTransferSlipPlaneConstraints_ThenXEntryIsNotSet() { damKernelInput.Location.StabilityOptions.StabilityZoneType = StabilityZoneType.NoZones; var macroStabilityInput = new MacroStabilityInput(); // When FillMacroStabilityWrapperInputFromEngine.TransferSlipPlaneConstraints(damKernelInput.Location, macroStabilityInput.StabilityModel.SlipPlaneConstraints); Assert.Multiple(() => { // Then Assert.That(macroStabilityInput.StabilityModel.SlipPlaneConstraints.SlipPlaneMinDepth, Is.EqualTo(1.5)); Assert.That(macroStabilityInput.StabilityModel.SlipPlaneConstraints.XEntryMin, Is.NaN); Assert.That(macroStabilityInput.StabilityModel.SlipPlaneConstraints.XEntryMax, Is.NaN); }); } private static DamKernelInput CreateDamKernelInputWithForbiddenZone() { var surfaceLine = new SurfaceLine2 { CharacteristicPoints = { GeometryMustContainPoint = true }, Geometry = new GeometryPointString() }; surfaceLine.EnsurePointOfType(1.0, 0, CharacteristicPointType.DikeTopAtRiver); surfaceLine.EnsurePointOfType(5.0, 0, CharacteristicPointType.DikeTopAtPolder); surfaceLine.EnsurePointOfType(10.0, 0, CharacteristicPointType.DikeToeAtPolder); var damKernelInput = new DamKernelInput { Location = new Location { SurfaceLine = surfaceLine, StabilityOptions = new StabilityOptions { MinimalCircleDepth = 1.5, StabilityZoneType = StabilityZoneType.ForbiddenZone, ForbiddenZoneFactor = 0.2 } } }; return damKernelInput; } } }