// 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.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using Deltares.DamEngine.Data.Standard;
using Deltares.DamEngine.Io;
using Deltares.DamEngine.Io.XmlInput;
using Deltares.DamEngine.Io.XmlOutput;
using Deltares.DamEngine.TestHelpers;
using Deltares.MacroStability.Io.XmlInput;
using KellermanSoftware.CompareNetObjects;
using NUnit.Framework;
using Point2DType = Deltares.DamEngine.Io.XmlOutput.Point2DType;
namespace Deltares.DamEngine.IntegrationTests.IntegrationTests;
[TestFixture]
public class OperationalStabilityProfile1DTests
{
///
/// Constants for SensorType
///
private const uint sourceTypeIgnore = 0;
private const uint sourceTypeLocationData = 1;
private const uint sourceTypeSensor = 2;
public class SensorConfiguration
{
public uint SourceTypePl1OuterWaterLevel { get; set; } = sourceTypeSensor;
public double Pl1OuterWaterLevel { get; set; } = 0.0;
public uint SourceTypePl1PlLineOffsetBelowDikeTopAtRiver { get; set; } = sourceTypeIgnore;
public double Pl1PlLineOffsetBelowDikeTopAtRiver { get; set; } = 0.0;
public uint SourceTypePl1PlLineOffsetBelowDikeTopAtPolder { get; set; } = sourceTypeIgnore;
public double Pl1PlLineOffsetBelowDikeTopAtPolder { get; set; } = 0.0;
public uint SourceTypePl1PlLineOffsetBelowShoulderBaseInside { get; set; } = sourceTypeIgnore;
public double Pl1PlLineOffsetBelowShoulderBaseInside { get; set; } = 0.0;
public uint SourceTypePl1PlLineOffsetBelowDikeToeAtPolder { get; set; } = sourceTypeIgnore;
public double Pl1PlLineOffsetBelowDikeToeAtPolder { get; set; } = 0.0;
public uint SourceTypePl1PolderLevel { get; set; } = sourceTypeSensor;
public double PlPolderLevel { get; set; } = 0.0;
public uint SourceTypePl3 { get; set; } = sourceTypeLocationData;
public double HeadPl3 { get; set; } = 0.0;
public uint SourceTypePl4 { get; set; } = sourceTypeLocationData;
public double HeadPl4 { get; set; } = 0.0;
}
public class PlLinePoints
{
public Point2DType Pl1Left { get; set; } // Waterlevel
public Point2DType Pl1AtDikeTopAtPolder { get; set; }
public Point2DType Pl1Right { get; set; } // PolderLevel
}
public static IEnumerable AddTestCases
{
get
{
yield return new TestCaseData(
// This is the case that all values are taken from the location data
new SensorConfiguration()
{
SourceTypePl1OuterWaterLevel = sourceTypeLocationData,
Pl1OuterWaterLevel = -0.1,
SourceTypePl1PlLineOffsetBelowDikeTopAtRiver = sourceTypeLocationData,
Pl1PlLineOffsetBelowDikeTopAtRiver = 1.1,
SourceTypePl1PlLineOffsetBelowDikeTopAtPolder = sourceTypeLocationData,
Pl1PlLineOffsetBelowDikeTopAtPolder = 1.2,
SourceTypePl1PlLineOffsetBelowShoulderBaseInside = sourceTypeIgnore,
Pl1PlLineOffsetBelowShoulderBaseInside = 0.0,
SourceTypePl1PlLineOffsetBelowDikeToeAtPolder = sourceTypeLocationData,
Pl1PlLineOffsetBelowDikeToeAtPolder = 1.4,
SourceTypePl1PolderLevel = sourceTypeLocationData,
PlPolderLevel = -2.0,
SourceTypePl3 = sourceTypeLocationData,
HeadPl3 = -3.0,
SourceTypePl4 = sourceTypeLocationData,
HeadPl4 = 0.0
},
new PlLinePoints()
{
Pl1Left = new Point2DType()
{
X = 0.0, // Expected X-value is leftmost point.X
Z = -0.2 // Expected Z-value is value of sensor MPN-AS-27 at timestep 0
},
Pl1AtDikeTopAtPolder = new Point2DType()
{
X = 22.562, // Expected X-value is location of DikeTopAtPolder = 22.562
Z = -1.3 // Expected Z-value is WaterLevelOutside - PlLineOffsetBelowDikeTopAtPolder = -0.1 - 1.2 = -1.3
},
Pl1Right = new Point2DType()
{
X = 57.896, // Expected X-value is rightmost point.X
Z = -2.0 // Expected Z-value is location data Polderlevel of 'Purmer_PU0042+00_K': -3.3
}
});
}
}
// Following testcase based on the DamLive test Deltares.DamLive.Tests.StabilityInsideBishopGridTest
// "Deltares.DamLive.Tests\TestData\IntegrationTests\StabilityInsideBishopGrid\DAMLive.damx"
// with DamLive rev.4860
// This tests if the sensor location data is used in the calculation by changing the source type of Pl1 offset below diketop to
[Test, Category(Categories.WorkInProgress)]
[TestCaseSource(nameof(AddTestCases))]
public void GivenStabilityInsideProfile1DProject_WhenCalculatingWithVariatonsOnSpecifiedSensorLocationData_ThenExpectedResultIsGenerated(SensorConfiguration sensorConfiguration, PlLinePoints plLinePoints)
{
string projectPath = "SensorProfile1DTests";
const string calcDir = "DAMLive.Calc";
const string testFilesLocation = @".\TestFiles\Operational\SensorProfile1DTests\";
string inputFilename = testFilesLocation + "CalculateStabilityInsideBishopGrid1InputFile.xml";
string outputFilename = testFilesLocation + "CalculateStabilityInsideBishopGrid1OutputFile.xml";
if (Directory.Exists(calcDir))
{
Directory.Delete(calcDir, true); // delete previous results
}
Input input = DamXmlSerialization.LoadInputFromXmlFile(inputFilename);
input.ProjectPath = projectPath;
var location = input.Locations[0];
FillSensorLocation(input.SensorData.SensorLocations[0], location, sensorConfiguration);
string inputString = DamXmlSerialization.SaveInputAsXmlString(input);
DamXmlSerialization.SaveInputAsXmlFile(inputFilename, input);
Output output = GeneralHelper.RunAfterInputValidation(inputString, true, outputFilename);
Assert.That(output, Is.Not.Null);
// Read the kernel input data from the input file
string kernelInputFilename = $"{projectPath}\\DAMLive.Calc\\Stability\\Bishop\\Dik(dike)_Loc(Purmer_PU0042+00_K)_Stp(0)_Mdl(Bishop)_2016-03-02T03_10_00_Pro(Purmer_PU0042+00K).skx";
FullInputModelType expectedMacrostabilityInput = MacroStability.Io.MacroStabilityXmlSerialization.LoadInputFromXmlFile(kernelInputFilename);
const double tolerance = 0.0005;
List phreaticLine = expectedMacrostabilityInput.StabilityModel.ConstructionStages[0].Waternet.PhreaticLine.WaternetLine.Points.ToList();
var expectedPoint = new Point2DType()
{
X = plLinePoints.Pl1AtDikeTopAtPolder.X,
Z = plLinePoints.Pl1AtDikeTopAtPolder.Z
};
bool pointExists = phreaticLine.Any(p => p.X.IsNearEqual(expectedPoint.X, tolerance) && p.Z.IsNearEqual(expectedPoint.Z, tolerance));
Assert.That(pointExists, Is.True, "The expected point was not found in the phreatic line.");
// Output output = DamXmlSerialization.LoadOutputFromXmlFile(outputFilename);
}
private void FillSensorLocation(SensorLocation sensorDataSensorLocation, Location location, SensorConfiguration sensorConfiguration)
{
sensorDataSensorLocation.SourceTypePl1WaterLevelAtRiver = sensorConfiguration.SourceTypePl1OuterWaterLevel;
location.DesignScenarios[0].RiverLevel = sensorConfiguration.Pl1OuterWaterLevel;
sensorDataSensorLocation.SourceTypePl1PlLineOffsetBelowDikeTopAtRiver = sensorConfiguration.SourceTypePl1PlLineOffsetBelowDikeTopAtRiver;
location.DesignScenarios[0].PlLineOffsetBelowDikeTopAtRiver = sensorConfiguration.Pl1PlLineOffsetBelowDikeTopAtRiver;
sensorDataSensorLocation.SourceTypePl1PlLineOffsetBelowDikeTopAtPolder = sensorConfiguration.SourceTypePl1PlLineOffsetBelowDikeTopAtPolder;
location.DesignScenarios[0].PlLineOffsetBelowDikeTopAtPolder = sensorConfiguration.Pl1PlLineOffsetBelowDikeTopAtPolder;
sensorDataSensorLocation.SourceTypePl1PlLineOffsetBelowShoulderBaseInside = sensorConfiguration.SourceTypePl1PlLineOffsetBelowShoulderBaseInside;
location.DesignScenarios[0].PlLineOffsetBelowShoulderBaseInside = sensorConfiguration.Pl1PlLineOffsetBelowShoulderBaseInside;
sensorDataSensorLocation.SourceTypePl1PlLineOffsetBelowDikeToeAtPolder = sensorConfiguration.SourceTypePl1PlLineOffsetBelowDikeToeAtPolder;
location.DesignScenarios[0].PlLineOffsetBelowDikeToeAtPolder = sensorConfiguration.Pl1PlLineOffsetBelowDikeToeAtPolder;
sensorDataSensorLocation.SourceTypePl1WaterLevelAtPolder = sensorConfiguration.SourceTypePl1PolderLevel;
location.DesignScenarios[0].PolderLevel = sensorConfiguration.PlPolderLevel;
sensorDataSensorLocation.SourceTypePl3 = sensorConfiguration.SourceTypePl3;
location.DesignScenarios[0].HeadPl3 = sensorConfiguration.HeadPl3;
location.DesignScenarios[0].HeadPl3Specified = true;
sensorDataSensorLocation.SourceTypePl4 = sensorConfiguration.SourceTypePl4;
location.DesignScenarios[0].HeadPl4Specified = true;
location.DesignScenarios[0].HeadPl4 = sensorConfiguration.HeadPl4;
}
[Test, Category(Categories.Slow)]
// Following testcase based on the DamLive test Deltares.DamLive.Tests.StabilityInsideBishopGridTest
// "Deltares.DamLive.Tests\TestData\IntegrationTests\StabilityInsideBishopGrid\DAMLive.damx"
// with DamLive rev.4860
[TestCase("CalculateStabilityInsideBishopGrid1")]
// Following testcase based on the DamLive test Deltares.DamLive.Tests.StabilityInsideBishopGridTest
// "Deltares.DamLive.Tests\TestData\IntegrationTests\StabilityInsideBishopGrid\DAMLive.damx"
// with DamLive rev.4860
// This tests if the sensor location data is used in the calculation by changing the source type of Pl1 offset below diketop to
// use the specified location data (which will be set to 1.1).
// Expected X-value is location of DikeTopAtPolder = 22.562
// Expected Z-value is WaterLevelOutside - PlLineOffsetBelowDikeTopAtPolder = -0.2 - 1.1 = -1.3
[TestCase("CalculateStabilityInsideBishopGrid1", true, 22.562, -1.3)]
// Following testcase based on the DamLive test Deltares.DamLive.Tests.StabilityInsideUpliftVanBeeSwarmTest
// "Deltares.DamLive.Tests\TestData\IntegrationTests\StabilityInsideUpliftVanBeeSwarm\DAMLive.damx"
// with DamLive rev.4860
[TestCase("CalculateStabilityInsideUpliftVanBeeSwarm1")]
// Following testcase based on the DamLive test Deltares.DamLive.Tests.StabilityInsideUpliftVanGridTest
// "Deltares.DamLive.Tests\TestData\IntegrationTests\StabilityInsideUpliftVanGrid\DAMLive.damx"
// with DamLive rev.4860
[TestCase("CalculateStabilityInsideUpliftVanGrid1")]
public void GivenStabilityInsideProfile1DProject_WhenCalculatingWithSpecifiedModel_ThenExpectedResultIsGenerated(
string baseName, bool isUseSensorLocationData = false, double expectedPlLineXValue = 0.0, double expectedPlLineZValue = 0.0)
{
const string calcDir = "DAMLive.Calc";
const string testFilesLocation = @".\TestFiles\Operational\Profile1DTests\";
string baseOutputName = baseName;
if (isUseSensorLocationData)
{
baseOutputName += "_UseLocationData";
}
string projectPath = baseOutputName;
string inputFilename = baseName + "InputFile.xml";
string outputFilename = baseOutputName + "OutputFile.xml";
if (Directory.Exists(calcDir))
{
Directory.Delete(calcDir, true); // delete previous results
}
// Load expected output
// Note 1: The expected output files (e.g. "CalculateStabilityInsideBishopGrid1OutputFile.xml") is also saved to the
// working folder
// Note 2: The expected output files should be generated with the Release build of the solution.
// Unfortunately, the Debug build of the solution generates different output files.
string expectedOutputFileName = testFilesLocation + outputFilename;
Output expectedOutput = DamXmlSerialization.LoadOutputFromXmlFile(expectedOutputFileName);
// Given
// Load input
string inputString = File.ReadAllText(testFilesLocation + inputFilename);
inputString = XmlAdapter.ChangeValueInXml(inputString, "ProjectPath", projectPath); // Current directory will be used
inputString = XmlAdapter.ChangeValueInXml(inputString, "CalculationMap", calcDir); // Current directory will be used
if (isUseSensorLocationData)
{
// Change the source type of Pl1 offset below diketop location data (=1)
inputString = XmlAdapter.ChangeValueInXml(inputString, "SourceTypePl1PlLineOffsetBelowDikeTopAtPolder", "1");
inputString = XmlAdapter.ChangeValueInXml(inputString, "PlLineOffsetBelowDikeTopAtPolder", "1.1");
}
// When
// Run calculation
Output actualOutput = GeneralHelper.RunAfterInputValidation(inputString, true, outputFilename);
if (isUseSensorLocationData)
{
// Read the kernel input data from the input file
string kernelInputFilename = $"{baseName}_UseLocationData\\DAMLive.Calc\\Stability\\Bishop\\Dik(dike)_Loc(Purmer_PU0042+00_K)_Stp(0)_Mdl(Bishop)_2016-03-02T03_10_00_Pro(Purmer_PU0042+00K).skx";
FullInputModelType expectedMacrostabilityInput = MacroStability.Io.MacroStabilityXmlSerialization.LoadInputFromXmlFile(kernelInputFilename);
List phreaticLine = expectedMacrostabilityInput.StabilityModel.ConstructionStages[0].Waternet.PhreaticLine.WaternetLine.Points.ToList();
var expectedPoint = new Point2DType()
{
X = expectedPlLineXValue,
Z = expectedPlLineZValue
};
const double tolerance = 0.0005;
bool pointExists = phreaticLine.Any(p => p.X.IsNearEqual(expectedPoint.X, tolerance) && p.Z.IsNearEqual(expectedPoint.Z, tolerance));
Assert.That(pointExists, Is.True, "The expected point was not found in the phreatic line.");
}
// Then
// Compare output
Assert.Multiple(() =>
{
Assert.That(actualOutput, Is.Not.Null);
Assert.That(expectedOutput, Is.Not.Null);
}
);
CompareOutput(expectedOutput, actualOutput);
}
private static void CompareOutput(Output expected, Output actual)
{
var compare = new CompareLogic
{
Config =
{
MaxDifferences = 100
}
};
ComparisonResult result = compare.Compare(expected, actual);
Assert.That(result.Differences, Is.Empty, "Differences found read/write Output object");
}
}