Index: DamEngine/tags/19.2.1/release/Deltares.DamEngine.Interface.dll =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/release/nl-NL/Deltares.DamEngine.Interface.resources.dll =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/release/Deltares.DamEngine.Interface.pdb =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/src/Deltares.DamEngine.Version/GlobalAssembly.cs.svn =================================================================== diff -u --- DamEngine/tags/19.2.1/src/Deltares.DamEngine.Version/GlobalAssembly.cs.svn (revision 0) +++ DamEngine/tags/19.2.1/src/Deltares.DamEngine.Version/GlobalAssembly.cs.svn (revision 3351) @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Deltares")] +[assembly: AssemblyCopyright("Copyright © Deltares 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: AssemblyVersion("19.2.1.SVNREV")] +[assembly: AssemblyFileVersion("19.2.1.SVNREV")] \ No newline at end of file Index: DamEngine/tags/19.2.1/src/Deltares.DamEngine.Calculators/Uplift/UpliftLocationDeterminator.cs =================================================================== diff -u --- DamEngine/tags/19.2.1/src/Deltares.DamEngine.Calculators/Uplift/UpliftLocationDeterminator.cs (revision 0) +++ DamEngine/tags/19.2.1/src/Deltares.DamEngine.Calculators/Uplift/UpliftLocationDeterminator.cs (revision 3351) @@ -0,0 +1,296 @@ +// Copyright (C) Stichting Deltares 2019. 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.IO; +using System.Linq; +using Deltares.DamEngine.Calculators.General; +using Deltares.DamEngine.Data.General; +using Deltares.DamEngine.Data.General.PlLines; +using Deltares.DamEngine.Data.Geometry; +using Deltares.DamEngine.Data.Geotechnics; + +namespace Deltares.DamEngine.Calculators.Uplift +{ + /// + /// Determie uplift location and uplift factor + /// + public class UpliftLocationDeterminator + { + public bool IsUseOvenDryUnitWeight { get; set; } + public PlLines PlLines { get; set; } + public SurfaceLine2 SurfaceLine { get; set; } + public SoilProfile1D SoilProfile { get; set; } + public SoilProfile2D SoilProfile2D { get; set; } + public string SoilGeometry2DName { get; set; } + + /// + /// Gets or sets the path for the sti file (SoilGeometry2DName). + /// + /// + /// The path for the sti file . + /// + public string PathForStiFile { get; set; } + + public Soil DikeEmbankmentMaterial { get; set; } + // public SoilbaseDB SoilBaseDB { get; set; } + public SoilList SoilList { get; set; } + public double XSoilGeometry2DOrigin { get; set; } + + /// + /// Constructor + /// + public UpliftLocationDeterminator() + { + IsUseOvenDryUnitWeight = false; + } + + /// + /// Get location nearest dike with and upliftfactor lower than required + /// + /// location and upliftfactor + public UpliftLocationAndResult GetLocationAndResult(double upliftCriterion) + { + ThrowIfNoPlLinesDefined(); + ThrowIfNoSurfaceLinDefined(); + ThrowIfNoSoilProfileDefined(); + + return GetLocationInPolderNearestDikeWithUpliftFactorLowerThanRequired(upliftCriterion); + } + + /// + /// Get location nearest dike with and upliftfactor lower than required + /// + /// location and upliftfactor + public UpliftLocationAndResult GetLocationInPolderNearestDikeWithUpliftFactorLowerThanRequired(double upliftCriterion) + { + ThrowIfNoPlLinesDefined(); + ThrowIfNoSurfaceLinDefined(); + ThrowIfNoSoilProfileDefined(); + + GeometryPoint startSurfacePoint = SurfaceLine.GetDikeToeInward(); + + IEnumerable relevantSurfacePointsList = from GeometryPoint point in SurfaceLine.Geometry.Points + where point.X >= startSurfacePoint.X + orderby point.X ascending + select point; + + bool foundUpliftFactor = false; + UpliftLocationAndResult upliftLocationAndResult = null; + foreach (GeometryPoint surfacePoint in relevantSurfacePointsList) + { + upliftLocationAndResult = GetUpliftFactorAtPoint(surfacePoint); + if ((upliftLocationAndResult != null) && (upliftLocationAndResult.UpliftFactor < upliftCriterion)) + { + foundUpliftFactor = true; + upliftLocationAndResult.X = surfacePoint.X; + upliftLocationAndResult.Z = surfacePoint.Z; + break; + } + } + return (foundUpliftFactor == true) ? upliftLocationAndResult : null; + } + + /// + /// Create upliftcalculator at given point + /// + /// GeometryPoint for which to calculate upliftfactor + /// Top of layer where uplift occurs + /// location and upliftfactor + private UpliftCalculator CreateUpliftCalculator(GeometryPoint point, double topOfLayer, SoilProfile1D soilProfile) + { + PlLine phreaticLine = PlLines.Lines[PlLineType.Pl1]; + return new UpliftCalculator + { + + PhreaticLevel = phreaticLine.ZFromX(point.X), + SoilProfile = soilProfile, + TopOfLayerToBeEvaluated = topOfLayer, + SurfaceLevel = point.Z, + UnitWeightSoilEmbankment = (this.DikeEmbankmentMaterial == null) ? (double?) null : this.DikeEmbankmentMaterial.AbovePhreaticLevel, + IsUseOvenDryUnitWeight = this.IsUseOvenDryUnitWeight + }; + } + + /// + /// Calculate upliftfactor for given point + /// + /// + /// location and upliftfactor + public UpliftLocationAndResult GetUpliftFactorAtPoint(GeometryPoint point) + { + SoilProfile1D soilProfileInCurrentPoint = GetSoilProfileBelowPoint(point.X); + double upliftFactorForInBetweenSandLayer = double.MaxValue; + if (soilProfileInCurrentPoint.InBetweenAquiferLayer != null) + { + // Check if inbetween sandlayer below surface + double topInBetweenSandLayer = soilProfileInCurrentPoint.InBetweenAquiferLayer.TopLevel; + if (topInBetweenSandLayer < point.Z) + { + // There is an aquitard above the aquifer, for which we can determine the uplift factor + UpliftCalculator upliftCalculatorForInBetweenSandLayer = CreateUpliftCalculator(point, topInBetweenSandLayer, soilProfileInCurrentPoint); + if ( (PlLines.Lines[PlLineType.Pl4] != null) && (PlLines.Lines[PlLineType.Pl4].Points.Count > 0 )) + upliftFactorForInBetweenSandLayer = upliftCalculatorForInBetweenSandLayer.CalculateUpliftFactor(PlLines.Lines[PlLineType.Pl4].ZFromX(point.X)); + } + else + { + if (soilProfileInCurrentPoint.GetBottomLevel(soilProfileInCurrentPoint.InBetweenAquiferLayer) < point.Z) + { + // The surface cuts into the aquifer so the level to be evaluated is at surfacelevel + UpliftCalculator upliftCalculatorForInBetweenSandLayer = CreateUpliftCalculator(point, point.Z, soilProfileInCurrentPoint); + if ((PlLines.Lines[PlLineType.Pl4] != null) && (PlLines.Lines[PlLineType.Pl4].Points.Count > 0)) + upliftFactorForInBetweenSandLayer = upliftCalculatorForInBetweenSandLayer.CalculateUpliftFactor(PlLines.Lines[PlLineType.Pl4].ZFromX(point.X)); + + } + } + } + + double upliftFactorForBottomSandLayer = double.MaxValue; + if (soilProfileInCurrentPoint.BottomAquiferLayer != null) + { + // Check if bottom sandlayer below surface + double topBottomSandLayer = soilProfileInCurrentPoint.BottomAquiferLayer.TopLevel; + if (topBottomSandLayer < point.Z) + { + UpliftCalculator upliftCalculatorForBottomSandLayer = CreateUpliftCalculator(point, soilProfileInCurrentPoint.BottomAquiferLayer.TopLevel, soilProfileInCurrentPoint); + if ((PlLines.Lines[PlLineType.Pl3] != null) && (PlLines.Lines[PlLineType.Pl3].Points.Count > 0)) + upliftFactorForBottomSandLayer = upliftCalculatorForBottomSandLayer.CalculateUpliftFactor(PlLines.Lines[PlLineType.Pl3].ZFromX(point.X)); + } + else + { + if (soilProfileInCurrentPoint.GetBottomLevel(soilProfileInCurrentPoint.BottomAquiferLayer) < point.Z) + { + // The surface cuts into the aquifer so the level to be evaluated is at surfacelevel + UpliftCalculator upliftCalculatorForInBetweenSandLayer = CreateUpliftCalculator(point, point.Z, soilProfileInCurrentPoint); + if ((PlLines.Lines[PlLineType.Pl3] != null) && (PlLines.Lines[PlLineType.Pl3].Points.Count > 0)) + upliftFactorForBottomSandLayer = upliftCalculatorForInBetweenSandLayer.CalculateUpliftFactor(PlLines.Lines[PlLineType.Pl3].ZFromX(point.X)); + + } + } + + } + + if((upliftFactorForBottomSandLayer == double.MaxValue) && (upliftFactorForInBetweenSandLayer == double.MaxValue)) + return null; + + if (SoilProfile == null) + { + SoilProfile = soilProfileInCurrentPoint; + } + if (upliftFactorForBottomSandLayer < upliftFactorForInBetweenSandLayer) + { + return new UpliftLocationAndResult(point, upliftFactorForBottomSandLayer, soilProfileInCurrentPoint.BottomAquiferLayer.Name); + } + else + { + return new UpliftLocationAndResult(point, upliftFactorForInBetweenSandLayer, soilProfileInCurrentPoint.InBetweenAquiferLayer.Name); + } + } + + /// + /// Determine location with lowest upliftfactor + /// + /// location and upliftfactor + public UpliftLocationAndResult GetLocationAtWithLowestUpliftFactor() + { + double? lowestUpliftFactor = null; + + ThrowIfNoPlLinesDefined(); + ThrowIfNoSurfaceLinDefined(); + ThrowIfNoSoilProfileDefined(); + + GeometryPoint startSurfacePoint = SurfaceLine.GetDikeToeInward(); + IEnumerable relevantSurfacePointsList = from GeometryPoint point in SurfaceLine.Geometry.Points + where point.X >= startSurfacePoint.X + orderby point.X ascending + select point; + UpliftLocationAndResult upliftLocationAndResult = null; + UpliftLocationAndResult lowestUpliftLocationAndResult = null; + foreach (GeometryPoint surfacePoint in relevantSurfacePointsList) + { + upliftLocationAndResult = GetUpliftFactorAtPoint(surfacePoint); + if (upliftLocationAndResult != null) + { + if (!lowestUpliftFactor.HasValue || upliftLocationAndResult.UpliftFactor < lowestUpliftFactor) + { + lowestUpliftFactor = upliftLocationAndResult.UpliftFactor; + lowestUpliftLocationAndResult = upliftLocationAndResult; + } + } + } + return lowestUpliftLocationAndResult; + } + + /// + /// + /// + /// + /// + private SoilProfile1D GetSoilProfileBelowPoint(double xCoordinate) + { + if (this.SoilProfile != null) + { + return this.SoilProfile; + } + if (SoilProfile2D != null) + { + return SoilProfile2D.GetSoilProfile1D(xCoordinate); + } + + var soilGeometry2DName = SoilGeometry2DName; + if (!File.Exists(this.SoilGeometry2DName)) + { + soilGeometry2DName = PathForStiFile + SoilGeometry2DName; + } + var geometry2DTo1DConverter = new Geometry2DTo1DConverter(soilGeometry2DName, this.SurfaceLine, this.DikeEmbankmentMaterial, this.SoilList, -this.XSoilGeometry2DOrigin); + return geometry2DTo1DConverter.Convert(xCoordinate); + } + + /// + /// Check on precondition + /// + private void ThrowIfNoPlLinesDefined() + { + if (PlLines == null) + throw new UpliftLocationDeterminatorException("Required pllines not found"); + } + + /// + /// Check on precondition + /// + private void ThrowIfNoSurfaceLinDefined() + { + if (SurfaceLine == null) + throw new UpliftLocationDeterminatorException("Required surfaceLine line not found"); + } + + /// + /// Check on precondition + /// + private void ThrowIfNoSoilProfileDefined() + { + if (SoilProfile == null && (SoilGeometry2DName == null || SoilGeometry2DName == "") && SoilProfile2D == null) + throw new UpliftLocationDeterminatorException("Required soilProfile not found"); + } + + } +} Index: DamEngine/tags/19.2.1/release/Deltares.DamEngine.Calculators.dll =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/src/Deltares.DamEngine.IntegrationTests/IntegrationTests/OperationalWesterDijkPipingTests.cs =================================================================== diff -u --- DamEngine/tags/19.2.1/src/Deltares.DamEngine.IntegrationTests/IntegrationTests/OperationalWesterDijkPipingTests.cs (revision 0) +++ DamEngine/tags/19.2.1/src/Deltares.DamEngine.IntegrationTests/IntegrationTests/OperationalWesterDijkPipingTests.cs (revision 3351) @@ -0,0 +1,82 @@ +// Copyright (C) Stichting Deltares 2020. 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.IO; +using System.Text; +using Deltares.DamEngine.Interface; +using Deltares.DamEngine.Io; +using Deltares.DamEngine.TestHelpers; +using NUnit.Framework; + +namespace Deltares.DamEngine.IntegrationTests.IntegrationTests +{ + [TestFixture] + class OperationalWesterDijkPipingTests + { + private const double Tolerance = 0.0005; + + [Test] + public void Run_UsingTestFiles_HasExpectedResultsInOutputFileForDamPipingBligh() + { + const string calcDir = "TestOperationalWesterdijk"; + const string workingDir = @"TestFiles\"; + const string baseTestDirectory = @".\"; + if (Directory.Exists(calcDir)) + { + Directory.Delete(calcDir, true); // delete previous results + } + + Directory.CreateDirectory(calcDir); + // Switch to TestFiles directory to check if DamLive can also run from another directory + Directory.SetCurrentDirectory(workingDir); + + const string inputFileName = baseTestDirectory + @"WesterdijkPiping.xml"; + const string outputFileName = baseTestDirectory + @"WesterdijkPiping.output.xml"; + string inputString = File.ReadAllText(inputFileName); + inputString = XmlAdapter.ChangeValueInXml(inputString, "ProjectPath", ""); // Current directory will be used + inputString = XmlAdapter.ChangeValueInXml(inputString, "CalculationMap", calcDir); // Current directory will be used + inputString = XmlAdapter.ChangeValueInXml(inputString, "MapForSoilgeometries2D", baseTestDirectory + @"Operational\WesterDijkPiping\input.Geometries\"); + inputString = XmlAdapter.ChangeValueInXml(inputString, "SoilDatabaseName", baseTestDirectory + @"Operational\WesterDijkPiping\HHNK_Westerdijk0.soilmaterials.mdb"); + EngineInterface engineInterface = new EngineInterface(inputString); + Assert.IsNotNull(engineInterface.DamProjectData); + + string result = engineInterface.Validate(); + Assert.IsTrue(result == null, "Validation must succeed but does not, see validation output xml in debugger"); + string outputString = engineInterface.Run(); + File.WriteAllText(outputFileName, outputString, Encoding.Unicode); + Assert.IsNotNull(outputString); + var output = DamXmlSerialization.LoadOutputFromXmlString(outputString); + + // These are now just the values that were produced by Dam itself. But these are checked by hand as well! So they are correct. + Assert.AreEqual(90, output.Results.OperationalOutputTimeSeries[0].Entries.TimeSerieEntry[0].Value, Tolerance); + // Second location has no soilprofiles for current mechanism, so no result (NaN). See the warning in the messages. + Assert.AreEqual(Double.NaN, output.Results.OperationalOutputTimeSeries[1].Entries.TimeSerieEntry[0].Value, Tolerance); + Assert.AreEqual(3.294, output.Results.OperationalOutputTimeSeries[2].Entries.TimeSerieEntry[0].Value, Tolerance); + Assert.AreEqual(2.4925, output.Results.OperationalOutputTimeSeries[9].Entries.TimeSerieEntry[0].Value, Tolerance); + Assert.AreEqual(2, output.Results.CalculationMessages.Length); + Assert.AreEqual( + "Location 'Dijk_20_DWP_16+102_gedraineerd_zonderBB' has no soil profiles defined for the current failure mechanism Piping", + output.Results.CalculationMessages[1].Message1); + + } + } +} Index: DamEngine/tags/19.2.1/release/nl-NL/Deltares.DamEngine.Data.resources.dll =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/release/nl-NL/Deltares.DamEngine.Calculators.resources.dll =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/release/Deltares.DamEngine.Version.dll =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/release/Deltares.DamEngine.Io.dll =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/src/Deltares.DamEngine.Data/General/Dike.cs =================================================================== diff -u --- DamEngine/tags/19.2.1/src/Deltares.DamEngine.Data/General/Dike.cs (revision 0) +++ DamEngine/tags/19.2.1/src/Deltares.DamEngine.Data/General/Dike.cs (revision 3351) @@ -0,0 +1,438 @@ +// Copyright (C) Stichting Deltares 2019. 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.ComponentModel; +using System.Linq; +using Deltares.DamEngine.Data.Design; +using Deltares.DamEngine.Data.General.Gauges; +using Deltares.DamEngine.Data.General.PlLines; +using Deltares.DamEngine.Data.General.TimeSeries; +using Deltares.DamEngine.Data.Geotechnics; +using Deltares.DamEngine.Data.Standard; +using Deltares.DamEngine.Data.Standard.Language; +using Deltares.DamEngine.Data.Standard.Logging; + +namespace Deltares.DamEngine.Data.General +{ + /// + /// Class for the dike parameter names + /// + public class DikeParameterNames + { + /// + /// The map for soilgeometries2d + /// + public const string MapForSoilGeometries2D = "MapForSoilGeometries2D"; + } + + /// + /// Class holding all info for a Dike. + /// + public class Dike + { + private string description = ""; + public virtual string MapForSoilGeometries2D { get; set; } + private IList locations; + + private IList pl1Lines; + private bool removeStiFiles; + private MStabShearStrength shearmodel; + private SoilList soilList; + private IList gauges = new List(); + private IList gaugePlLines = new List(); + private IList soilProfiles; + private IList soilProfiles2D; + private List databaseSoils = new List(); + private TimeSerieCollection inputTimeSerieCollection = null; + + /// + /// Initializes a new instance of the class. + /// + public Dike() + { + Name = "Dijkring"; + MapForSoilGeometries2D = ""; + + locations = new List(); + soilProfiles = new List(); + soilProfiles2D = new List(); + // this.surfaceLines = new DelegatedList { AddMethod = ConvertAddedOldSurfaceLineToNewFormat }; + SurfaceLines2 = new List(); + pl1Lines = new List(); + soilList = new SoilList(); + removeStiFiles = true; + } + + public bool IsRemoveStiFiles { get { return removeStiFiles; } set { removeStiFiles = value; } } + public MStabShearStrength ShearStrengthModel { get { return shearmodel; } set { shearmodel = value; } } + + public virtual string Name { get; set; } + + /// + /// Gets the locations. + /// + /// + /// The locations. + /// + public virtual IList Locations + { + get { return this.locations; } + private set { this.locations = value; } + } + + /// + /// Sorts the locations. + /// + public void SortLocations() + { + this.locations = this.Locations.OrderBy(o => o.Name).ToList(); + } + + public IList SurfaceLines2 { get; set; } + + public virtual IList PL1Lines + { + get { return this.pl1Lines; } + set { this.pl1Lines = value; } + } + + public virtual IList SoilProfiles + { + get { return this.soilProfiles; } + set { this.soilProfiles = value; } + } + + /// + /// Gets or sets the soilprofiles2d. + /// + /// + /// The soilprofiles2d. + /// + public virtual IList SoilProfiles2D + { + get { return soilProfiles2D; } + set { soilProfiles2D = value; } + } + + public virtual SoilList SoilList + { + get { return this.soilList; } + set { this.soilList = value; } + } + + [Browsable(false)] + public virtual IList Gauges + { + get { return this.gauges; } + set { this.gauges = value; } + } + + [Browsable(false)] + public virtual IList GaugePlLines + { + get { return this.gaugePlLines; } + set { this.gaugePlLines = value; } + } + + public bool UsesGauges { get { return this.GaugePlLines != null && this.GaugePlLines.Count > 0 && this.Gauges != null; } } + + public virtual List Scenarios + { + get + { + var scenarios = new List(); + foreach (Location location in Locations) + { + scenarios.AddRange(location.Scenarios); + } + return scenarios; + } + } + + public string Description + { + get { return description; } + set { description = value; } + } + + /// + /// Gets or sets the input time serie collection. + /// + /// + /// Input time series for operational use + /// + public TimeSerieCollection InputTimeSerieCollection + { + get + { + return inputTimeSerieCollection; + } + set + { + inputTimeSerieCollection = value; + } + } + + public void Validate(bool isOperational) + { + if (Locations == null || Locations.Count < 1) + { + throw new DikeException("The dike ring has no locations defined"); + } + foreach (Location location in Locations) + { + if (location.SurfaceLine != null) + { + var validator = new SurfaceLine2Validator(); + var validationResults = validator.ValidateCharacteristicPointsAreOrdered(location.SurfaceLine) + .Concat(validator.ValidateGeometryPointsAreOrdered(location.SurfaceLine)).ToArray(); + if (validationResults.Length > 0) + { + throw new SurfaceLineException(validationResults[0].Text); + } + } + + if (isOperational) + { + ValidateOperationalProject(location); + } + } + } + + private static void ValidateOperationalProject(Location location) + { + if (location.Scenarios.Count < 1) + { + throw new DikeException("Location " + location.Name + + " has no scenarios, at least one scenario is required."); + } + + if (location.Scenarios.Count > 1) + { + throw new DikeException(string.Format( + "For Operational (DamLive), location {0} has {1} scenarios but only one is allowed!", + location.Name, location.Scenarios.Count)); + } + + if (location.SensorLocation != null) + { + foreach (var sensor in location.SensorLocation.SensorGroup.SensorArray) + { + if (sensor.RelativeLocation < 0) + { + throw new DikeException(string.Format( + "For Operational (DamLive), location {0} has a sensor ({1}) which can not not be relative '+ " + + "to the starting point (0) as its coordinate {2} is < 0 .", + location.Name, sensor.Name, sensor.RelativeLocation)); + } + } + } + } + + + /// + /// Adapt data so it is consistent + /// + public List MakeDataConsistent() + { + var errorSoils = TryToMakeSoilDataConsistent(); + var logMessages = new List(); + // Delete all locations without surfaceline + logMessages.AddRange(DeleteLocationsWithoutSurfaceLines()); + // Delete all locations that have profiles (in their segment) which hold soils + // that are not in the soil database and so have no parameters. + logMessages.AddRange(DeleteLocationsWithProfilesWithUnknownSoils(errorSoils)); + return logMessages; + } + + /// + /// Tries to make the soil data as read for 1D profiles consistent with the data in the soil database. + /// In the end we have a neat soil list with parameters for every soil as read from the database. + /// We might have a list with errors (soils that were not to be found in the database so soils for which + /// no parameters could be found). + /// + /// + private List TryToMakeSoilDataConsistent() + { + var errorSoils = new List(); + // Fill the list of errorSoils with soils that are in the current soillist (as result of importing + // 1D profiles) but that are not found in the soil database because that are errors + foreach (var soil in soilList.Soils) + { + var fs = databaseSoils.Find(t => String.Equals(t.Name, soil.Name, StringComparison.CurrentCultureIgnoreCase)); + if (fs == null) + { + errorSoils.Add(soil); + } + } + // Remove the error soils form the list + foreach (var errorSoil in errorSoils) + { + soilList.Soils.Remove(errorSoil); + } + // Get the parameters for every soil in the now proper soil list from the database. Add soils + // that are in the database but not yet in the soil list. + foreach (Soil soil in databaseSoils) + { + Soil existingSoil = this.soilList.GetSoilByName(soil.Name); + if (existingSoil == null) + { + this.soilList.Soils.Add(soil); + } + else + { + existingSoil.Assign(soil); + } + } + return errorSoils; + } + + /// + /// Removes all locations which have profiles that have invalid soils + /// + private List DeleteLocationsWithProfilesWithUnknownSoils(List invalidSoils) + { + var logMessages = new List(); + var invalidLocations = new List(); + string soilProf; + string invSoil; + foreach (var location in locations) + { + bool isInValid; + string message = ""; + if (location.Segment == null) + { + isInValid = true; + message = String.Format(LocalizationManager.GetTranslatedText(this.GetType(), "LocationWitNameHasNoSegment"), location.Name); + } + else + { + isInValid = DoesLocationHaveInvalidSoils(invalidSoils, location, out soilProf, out invSoil); + if (isInValid) + { + message = String.Format(LocalizationManager.GetTranslatedText(this.GetType(), "locationHasProfileWithInvalidSoils"), location.Name, soilProf, invSoil); + } + } + if (isInValid) + { + invalidLocations.Add(location); + logMessages.Add(new LogMessage(LogMessageType.Warning, this, message)); + } + } + foreach (var invalidLocation in invalidLocations) + { + + locations.Remove(invalidLocation); + } + return logMessages; + } + + /// + /// Checks wether a location (or rather the soilprofiles in the segement of the location) contains invalid soils. + /// A soil is hereby considered invalid if it is not found in the database. + /// + /// + /// + /// + /// + /// + private bool DoesLocationHaveInvalidSoils(List invalidSoils, Location location, out string soilProf, out string invSoil) + { + soilProf = " "; + invSoil = " "; + foreach (var spp in location.Segment.SoilProfileProbabilities) + { + foreach (var invalidSoil in invalidSoils) + { + var fl = spp.SoilProfile1D.Layers.Find(l => String.Equals(l.Soil.Name, invalidSoil.Name, StringComparison.CurrentCultureIgnoreCase)); + + if (fl != null) + { + soilProf = spp.SoilProfile1D.Name; + invSoil = invalidSoil.Name; + return true; + } + } + } + return false; + } + + /// + /// Delete all locations without surfacelines + /// + private List DeleteLocationsWithoutSurfaceLines() + { + var logMessages = new List(); + + //Add all locations with valid surfaceline + var newLocations = new List(); + newLocations.AddRange(this.Locations.Where(loc => loc.SurfaceLine != null)); + + // Report which locations are not added because no valid surfaceline is found + var deletedLocations = new List(); + deletedLocations.AddRange(this.Locations.Where(loc => loc.SurfaceLine == null)); + foreach (var deletedLocation in deletedLocations) + { + var locationHasNoSurfaceLine = LocalizationManager.GetTranslatedText(this.GetType(), "LocationHasNoSurfaceLine"); + logMessages.Add(new LogMessage(LogMessageType.Warning, this, + String.Format(locationHasNoSurfaceLine, deletedLocation.Name))); + } + + this.Locations = newLocations; + return logMessages; + } + + public override string ToString() + { + return this.Name; + } + + public Dictionary GetParametersAsNameValuePairs() + { + var nameValuePairs = new Dictionary(); + nameValuePairs.Add(DikeParameterNames.MapForSoilGeometries2D, MapForSoilGeometries2D); + return nameValuePairs; + } + + public void SetParameterFromNameValuePair(string parameterName, string parameterValue) + { + if (parameterName.Equals(DikeParameterNames.MapForSoilGeometries2D)) + this.MapForSoilGeometries2D = parameterValue; + } + + public void UpdateLocation(Location location) + { + location.SoilList = this.SoilList; + if (location.StabilityOptions != null) + { + location.StabilityOptions.SoilGeometries2DPath = this.MapForSoilGeometries2D; + } + + location.Gauges.Clear(); + location.Gauges.AddRange(Gauges); + + location.GaugePlLines.Clear(); + location.GaugePlLines.AddRange(GaugePlLines); + } + } +} \ No newline at end of file Index: DamEngine/tags/19.2.1/release/Deltares.DamEngine.Calculators.pdb =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/release/Deltares.DamEngine.Version.pdb =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/release/Deltares.DamEngine.Data.dll =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/src/Deltares.DamEngine.Calculators/DikesOperational/OperationalCalculator.cs =================================================================== diff -u --- DamEngine/tags/19.2.1/src/Deltares.DamEngine.Calculators/DikesOperational/OperationalCalculator.cs (revision 0) +++ DamEngine/tags/19.2.1/src/Deltares.DamEngine.Calculators/DikesOperational/OperationalCalculator.cs (revision 3351) @@ -0,0 +1,595 @@ +// Copyright (C) Stichting Deltares 2019. 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.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using Deltares.DamEngine.Calculators.General; +using Deltares.DamEngine.Calculators.KernelWrappers.Common; +using Deltares.DamEngine.Calculators.KernelWrappers.Interfaces; +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.General.Specifications.Extensions; +using Deltares.DamEngine.Data.General.Specifications; +using Deltares.DamEngine.Data.General.TimeSeries; +using Deltares.DamEngine.Data.General.Sensors; +using Deltares.DamEngine.Data.Standard; +using Deltares.DamEngine.Data.Standard.Calculation; +using Deltares.DamEngine.Data.Standard.Logging; + +namespace Deltares.DamEngine.Calculators.DikesOperational +{ + + /// + /// Class for the Operational Calculator + /// + public class OperationalCalculator + { + /// + /// Specifies a location with valid sensor data. The objects that are satisfied by the + /// the specification (predicate) will be used in this calculator. + /// See ReadSensorDataAndPrepareOutputSeries and Locations.GetBySpecification(p) + /// + private class LocationsWithSensorData : PredicateSpecification + { + public LocationsWithSensorData() + : base(l => l.HasSensorLocation && l.SensorLocation.SensorCount > 0) + { + } + } + + /// + /// Holds a reference to a dictionary of output date time entries + /// The keys represent the time step and the value part of the dictionary represents + /// the argument for the calculation + /// + /// TimeStep -> Location -> (Sensor, Value) + /// + /// Cardinality: + /// 1 -> N -> M + /// + private readonly IDictionary>> values = + new Dictionary>>(); + + private TimeSerieCollection inputTimeSerieCollection; + private TimeSerieCollection outputTimeSerieCollection; + private string outputParameter; + + /// + /// Executes operational calculation. + /// + /// The dam project data. + public void Execute(DamProjectData damProjectData) + { + if (damProjectData.CalculationMessages == null) + { + damProjectData.CalculationMessages = new List(); + } + else + { + damProjectData.CalculationMessages.Clear(); + } + outputParameter = DetermineOutputParameter(damProjectData.DamProjectCalculationSpecification.CurrentSpecification); + // counter to determine if locations are processed + int locationCount = 0; + + inputTimeSerieCollection = damProjectData.Dike.InputTimeSerieCollection; + outputTimeSerieCollection = new TimeSerieCollection(); + + var locations = damProjectData.Dike.Locations.GetBySpecification(new LocationsWithSensorData()); + foreach (var location in locations) + { + location.ModelParametersForPlLines.PlLineCreationMethod = PlLineCreationMethod.Sensors; + + PrepareSensorDataLookup(location); + InitializeOutputSeries(location); + locationCount++; + } + damProjectData.CalculationMessages.Add(new LogMessage(LogMessageType.Info, null, + string.Format("There are {0} locations with sensor data", locationCount))); + if (!locations.Any()) + { + damProjectData.CalculationMessages.Add(new LogMessage(LogMessageType.Error, null, "No location to process.")); + return; + } + + // Prepare the designCalculatorTasks + var operationalCalculatorTasks = new List(); + foreach (var series in outputTimeSerieCollection.Series) + { + int entryIndex = 0; + foreach (var entry in series.Entries) + { + var location = locations.First(l => series.LocationId == l.Name); + DesignScenario designScenario = null; + if (location.Scenarios.Count > 0) + { + // For Operational only ONE scenario is allowed (decided by Irene/Bernard on 30-07-2020). So that's the one that must be used. + designScenario = location.Scenarios[0]; + designScenario.Location = location; + } + var sensorValues = values[entry.DateTime][location]; + if (!ContainsMissingValues(sensorValues, series.MissVal)) + { + FailureMechanismSystemType soilProbabilityFailureMechanismSystemType = damProjectData.DamProjectCalculationSpecification.CurrentSpecification.FailureMechanismSystemType; + var soiProfileProbability = location.Segment.GetMostProbableSoilGeometryProbability( + ConversionHelper.ConvertToSegmentFailureMechanismType(soilProbabilityFailureMechanismSystemType)); + if (soiProfileProbability != null) + { + var projectPath = damProjectData.ProjectPath != "" + ? damProjectData.ProjectPath + : Directory.GetCurrentDirectory(); + var calculationMessages = new List(); + operationalCalculatorTasks.Add(new OperationalCalculatorTask() + { + Location = location, + SoiProfileProbability = soiProfileProbability, + DesignScenario = designScenario, + ProjectPath = projectPath, + CalculationMap = damProjectData.CalculationMap, + FailureMechanismeCalculationSpecification = damProjectData + .DamProjectCalculationSpecification.CurrentSpecification, + TimeSerieEntry = entry, + TimeStepIndex = entryIndex, + CalculationMessages = calculationMessages + }); + } + else + { + damProjectData.CalculationMessages.Add(new LogMessage(LogMessageType.Warning, null, + String.Format("Location '{0}' has no soil profiles defined for the current failure mechanism {1}", location.Name, + soilProbabilityFailureMechanismSystemType.ToString()))); + } + } + else + { + damProjectData.CalculationMessages.Add(new LogMessage(LogMessageType.Warning, null, + String.Format("In location '{0}' missing values are found in timestep {1}", location.Name, entry.DateTime))); + } + entryIndex++; + } + } + + if (operationalCalculatorTasks.Count > 0) + { + // Perform the calculation + Parallel.Run(operationalCalculatorTasks, RunOperationalCalculatorTask, null, + damProjectData.MaxCalculationCores); + foreach (var operationalCalculatorTask in operationalCalculatorTasks) + { + damProjectData.CalculationMessages.AddRange(operationalCalculatorTask.CalculationMessages); + } + + damProjectData.OutputTimeSerieCollection = outputTimeSerieCollection; + } + 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 RunOperationalCalculatorTask(object operationalCalculatorTask) + { + OperationalCalculatorTask task = (OperationalCalculatorTask) operationalCalculatorTask; + Debug.WriteLine("Start calculation Location '{0}', soilprofile '{1}'", task.Location, + task.SoiProfileProbability); + CalculateOneTimeEntry(task.Location, task.SoiProfileProbability, task.DesignScenario, task.ProjectPath, + task.CalculationMap, task.FailureMechanismeCalculationSpecification, + task.TimeSerieEntry, task.TimeStepIndex, + task.CalculationMessages, task.CalculationResult); + Debug.WriteLine("End calculation Location '{0}', soilprofile '{1}'", task.Location, task.SoiProfileProbability); + } + + private void CalculateOneTimeEntry(Location location, SoilGeometryProbability soiProfileProbability, + DesignScenario designScenario, + string projectPath, string calculationMap, + DamFailureMechanismeCalculationSpecification damFailureMechanismeCalculationSpecification, + TimeSerieEntry timeSerieEntry, int timeStepIndex, + List calculationMessages, CalculationResult calculationResult) + { + try + { + // Prepare input + var damKernelInput = new DamKernelInput(); + damKernelInput.ProjectDir = projectPath; + damKernelInput.CalculationDir = Path.Combine(projectPath, calculationMap); + damKernelInput.Location = location; + damKernelInput.SubSoilScenario = soiProfileProbability; + damKernelInput.TimeStepDateTime = timeSerieEntry.DateTime; + damKernelInput.DamFailureMechanismeCalculationSpecification = damFailureMechanismeCalculationSpecification; + damKernelInput.RiverLevelHigh = Double.NaN; + damKernelInput.RiverLevelLow = null; + damKernelInput.FilenamePrefix = String.Format("Dik(dike)_Loc({0})_Stp({1})_Mdl({2})_{3}", + location.Name, + timeStepIndex, + damFailureMechanismeCalculationSpecification.StabilityModelType, + DateToTimeStamp(timeSerieEntry.DateTime)); + SynchronizeLocationDataWithScenarioData(designScenario, location); + IKernelDataInput kernelDataInput; + IKernelDataOutput kernelDataOutput; + + // Create kernelwrapper + IKernelWrapper kernelWrapper = KernelWrapperHelper.CreateKernelWrapper(damFailureMechanismeCalculationSpecification); + if (kernelWrapper == null) + { + throw new NotImplementedException(Resources.DesignCalculatorKernelNotImplemented); + } + + PrepareResult prepareResult = kernelWrapper.Prepare(damKernelInput, 0, out kernelDataInput, out kernelDataOutput); + + // Sometimes the kernelDataInput is not created (p.e when soilprofileprobablility is meant for + // stability where Piping calc is wanted). In that case, do nothing but just skip. + if (prepareResult == PrepareResult.Successful) + { + PerformOperationalCalculation( + kernelWrapper, kernelDataInput, kernelDataOutput, + damKernelInput, timeStepIndex, timeSerieEntry, out calculationResult, + calculationMessages); + } + else + { + if (prepareResult == PrepareResult.NotRelevant) + { + // Do nothing. Calculation will be skipped. + } + if (prepareResult == PrepareResult.Failed) + { + calculationMessages.Add(new LogMessage(LogMessageType.Error, null, + string.Format(Resources.OperationalCalculatorPrepareError, + location.Name, + soiProfileProbability, + timeStepIndex, + DateToTimeStamp(timeSerieEntry.DateTime)))); + } + } + } + catch (Exception e) + { + calculationMessages.Add(new LogMessage(LogMessageType.Error, null, + string.Format(Resources.OperationalCalculatorGeneralException, + location.Name, + soiProfileProbability, + timeStepIndex, + DateToTimeStamp(timeSerieEntry.DateTime), + e.Message))); + } + + } + + private void PerformOperationalCalculation(IKernelWrapper kernelWrapper, IKernelDataInput kernelDataInput, + IKernelDataOutput kernelDataOutput, DamKernelInput damKernelInput, + int timeStepIndex, TimeSerieEntry timeSerieEntry, + out CalculationResult calculationResult, + List calculationMessages) + { + // Perform validation + var designResults = new List(); + List locationCalculationMessages = new List(); + List validationMessages; + calculationResult = CalculationResult.NoRun; + try + { + int errorCount = kernelWrapper.Validate(kernelDataInput, kernelDataOutput, out validationMessages); + if (errorCount > 0) + { + locationCalculationMessages.Add(new LogMessage(LogMessageType.Error, null, + string.Format(Resources.OperationalCalculatorValidationFailed, + damKernelInput.Location.Name, + damKernelInput.SubSoilScenario, + timeStepIndex, + DateToTimeStamp(timeSerieEntry.DateTime)))); + locationCalculationMessages.AddRange(validationMessages); + } + else + { + // Perform calculation + kernelWrapper.Execute(kernelDataInput, kernelDataOutput, out locationCalculationMessages); + } + // Process output + calculationMessages.AddRange(locationCalculationMessages); + StringBuilder sb = new StringBuilder(); + foreach (var message in locationCalculationMessages) + { + sb.Append(message.Message + Environment.NewLine); + } + string resultMessage = sb.ToString(); + + calculationResult = CalculationResult.Succeeded; + kernelWrapper.PostProcess(damKernelInput, kernelDataOutput, null, resultMessage, out designResults); + timeSerieEntry.Value = designResults[0].SafetyFactor.Value; + } + catch (Exception exception) + { + string resultMessage = exception.Message; + calculationResult = CalculationResult.RunFailed; + kernelWrapper.PostProcess(damKernelInput, kernelDataOutput, null, resultMessage, out designResults); + } + } + + /// + /// Dates to time stamp. + /// + /// The date time. + /// + public static string DateToTimeStamp(DateTime dateTime) + { + // Following 2 lines is an example how to handle customization of this format. + // TNO at the last moment decided they did not need this change so we change it back to + // the default format + // string customFormat = "yyyy-MM-dd_HH-mm-ss"; + // return dateTime.ToString(customFormat); + return dateTime.ToString("s", DateTimeFormatInfo.InvariantInfo); + } + + private string DetermineOutputParameter(DamFailureMechanismeCalculationSpecification currentSpecification) + { + string parameter = ""; + switch (currentSpecification.FailureMechanismSystemType) + { + case FailureMechanismSystemType.HorizontalBalance: + throw new NotImplementedException(); + case FailureMechanismSystemType.StabilityOutside: + if (currentSpecification.StabilityModelType == MStabModelType.Bishop) + { + parameter = TimeSerieParameters.StabilityOutsideFactor.ToString(); + break; + } + throw new NotImplementedException(); + case FailureMechanismSystemType.StabilityInside: + switch (currentSpecification.StabilityModelType) + { + case MStabModelType.Bishop: + case MStabModelType.UpliftVan: + case MStabModelType.BishopUpliftVan: + parameter = TimeSerieParameters.StabilityInsideFactor.ToString(); + break; + default: + throw new NotImplementedException(); + } + break; + case FailureMechanismSystemType.Piping: + switch (currentSpecification.PipingModelType) + { + case PipingModelType.Sellmeijer4Forces: + throw new NotImplementedException(); + case PipingModelType.Bligh: + case PipingModelType.SellmeijerVnk: + case PipingModelType.Wti2017: + parameter = TimeSerieParameters.Piping.ToString(); + break; + } + break; + + } + return parameter; + } + + /// + /// Initializes the output series. + /// + private void InitializeOutputSeries(Location location) + { + var firstTimeSeries = inputTimeSerieCollection.Series.First(); + var copyOfSeries = firstTimeSeries.GetShallowCopy(); + foreach (var entry in firstTimeSeries.Entries) + { + // get copy of the input entry + var copyOfEntry = entry.GetShallowCopy(); + copyOfEntry.Value = double.NaN; + // set parameter and location id's and add the projected entry to the output + copyOfSeries.LocationId = location.Name; + copyOfSeries.ParameterId = outputParameter; + copyOfSeries.Entries.Add(copyOfEntry); + } + + // add the output series to the output collection + outputTimeSerieCollection.Series.Add(copyOfSeries); + } + + /// + /// Prepares the output time series and the calculation arguments (sensor data). + /// + /// The location. + private void PrepareSensorDataLookup(Location location) + { + ThrowIfSensorsAreMissingInInputFile(location); + + // these variable are used to determine differences in time series entries + var firstSeriesEntries = new HashSet(); + bool hasFirstSeriesEntries = false; + + foreach (var sensor in location.SensorLocation.Sensors) + { + IEnumerable series = inputTimeSerieCollection.GetSeriesByLocationId(sensor.Name); + ThrowIfSensorNotExists(sensor, series.Any()); + + // Prepare the output time series and set sensor values + foreach (var timeSeries in series) + { + ThrowIfTimeEntryCountDontMatch(firstSeriesEntries, timeSeries, hasFirstSeriesEntries); + + foreach (var entry in timeSeries.Entries) + { + var key = entry.DateTime; + if (hasFirstSeriesEntries) + { + ThrowIfTimeEntriesKeysDontMatch(key, firstSeriesEntries); + } + + if (!hasFirstSeriesEntries) + { + firstSeriesEntries.Add(key); + } + + // everything ok set data into internal lookup + SetSensorValue(key, entry.Value, sensor, location); + } + } + hasFirstSeriesEntries = true; + } + location.SensorLocation.SensorValues = values; // Todo #The: only set sensorvalues to values for this location + } + + /// + /// Sets the sensor value. + /// + /// The time step. + /// The value. + /// The sensor. + /// The location. + private void SetSensorValue(DateTime timeStep, double value, Sensor sensor, Location location) + { + if (!values.ContainsKey(timeStep)) + values.Add(timeStep, new Dictionary>()); + + if (!values[timeStep].ContainsKey(location)) + values[timeStep].Add(location, new Dictionary()); + + if (values[timeStep][location].ContainsKey(sensor)) + { + var message = string.Format("Values for sensor with id {0} and name {1} and location {2} and time step {3} already exists. Check the time series data.", + sensor.ID, sensor.Name, location.Name, timeStep); + + throw new OperationalCalculatorException(message); + } + + values[timeStep][location].Add(sensor, value); + } + + /// + /// Throws when the sensor not exists. + /// + /// The sensor. + /// if set to true [has series by sensor ID]. + private static void ThrowIfSensorNotExists(Sensor sensor, bool hasSeriesBySensorID) + { + if (!hasSeriesBySensorID) + { + // TODO log info + string message = + string.Format("Sensor with name '{0}' and parameter id '{1}' not found in the input time series", + sensor.Name, TimeSerie.WaterPressureParameterId); + + throw new OperationalCalculatorException(message); + } + } + + /// + /// Throws if time entry count dont match. + /// + /// The first series entries. + /// The time series. + /// if set to true [has first series entries]. + private static void ThrowIfTimeEntryCountDontMatch(HashSet firstSeriesEntries, TimeSerie timeSeries, + bool hasFirstSeriesEntries) + { + if (hasFirstSeriesEntries) + { + // TODO log info + if (timeSeries.Entries.Count != firstSeriesEntries.Count) + throw new OperationalCalculatorException("Invalid data in time series entries. Number of entries differ on each sensor"); + } + } + + private void ThrowIfSensorsAreMissingInInputFile(Location location) + { + var sensorsNotFound = ( + from sensor in location.SensorLocation.Sensors + let series = inputTimeSerieCollection.GetSeriesByLocationId(sensor.Name) + where !series.Any() + select sensor.Name).ToList(); + + if (sensorsNotFound.Any()) + { + // TODO log info + string message = + string.Format("Sensor with name '{0}' and parameter id '{1}' not found in the input time series", + string.Join(", ", sensorsNotFound.ToArray()), TimeSerie.WaterPressureParameterId); + + throw new OperationalCalculatorException(message); + } + } + + private static void ThrowIfTimeEntriesKeysDontMatch(DateTime key, HashSet firstSeriesEntries) + { + // TODO log info + if (!firstSeriesEntries.Contains(key)) + throw new OperationalCalculatorException("Invalid data in time series entries. Time entries (date time values) don't match"); + } + + /// + /// Determines whether any of sensor values contains a missing value. + /// + /// The sensor values. + /// + /// + private bool ContainsMissingValues(IDictionary sensorValues, double missingValue) + { + foreach (var sensorValue in sensorValues) + { + if (sensorValues[sensorValue.Key].AlmostEquals(missingValue)) + { + return true; + } + } + return false; + } + + /// + /// Synchronizes the location data with scenario data. + /// Note that scenario data is leading when available. + /// + /// The scenario. + /// The location. + private void SynchronizeLocationDataWithScenarioData(DesignScenario designScenario, Location location) + { + if (designScenario != null) + { + location.ModelFactors.RequiredSafetyFactorStabilityInnerSlope = + designScenario.RequiredSafetyFactorStabilityInnerSlope; + location.ModelFactors.RequiredSafetyFactorStabilityOuterSlope = + designScenario.RequiredSafetyFactorStabilityOuterSlope; + location.ModelFactors.UpliftCriterionStability = designScenario.UpliftCriterionStability; + + location.ModelFactors.RequiredSafetyFactorPiping = designScenario.RequiredSafetyFactorPiping; + location.ModelFactors.UpliftCriterionPiping = designScenario.UpliftCriterionPiping; + + if (designScenario.DikeTableHeight.HasValue) + { + location.DikeTableHeight = designScenario.DikeTableHeight ?? 0; + } + } + } + } +} Index: DamEngine/tags/19.2.1/release/Deltares.DamEngine.Io.pdb =================================================================== diff -u Binary files differ Index: DamEngine/tags/19.2.1/release/Deltares.DamEngine.Data.pdb =================================================================== diff -u Binary files differ