// Copyright (C) Stichting Deltares 2018. 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.Calculators.PlLinesCreator; using Deltares.DamEngine.Data.General; using Deltares.DamEngine.Data.General.Gauges; using Deltares.DamEngine.Data.General.PlLines; using Deltares.DamEngine.Data.General.TimeSeries; using Deltares.DamEngine.Data.Standard; using Deltares.DamEngine.Data.Standard.Calculation; using Deltares.DamEngine.Data.Standard.Logging; using SendMessageDelegate = Deltares.DamEngine.Data.Standard.Calculation.SendMessageDelegate; namespace Deltares.DamEngine.Calculators.Dikes_Operational { public class TimeSerieStabilityCalculatorException : ApplicationException { public TimeSerieStabilityCalculatorException(string message) : base(message) { } } public class TimeSerieStabilityCalculator { private readonly LogHelper logger = LogHelper.Create(); private readonly Dictionary> stabTimeSerieEntry = new Dictionary>(); private SendMessageDelegate sendMessageDelegate = null; public const string LocationMappingExtension = "_locationmapping.txt"; public string StabilityWorkingPath { get; set; } public string MStabExePath { get; set; } public bool IsMStabCalculationOff { get; set; } public string StatesInputPath { get; set; } public string StatesOutputPath { get; set; } /// /// This boolean is used when the batch is too large and too memory leaks occur, causing the calculation to fail /// Only set this to false when above is a problem, because the calculation time will increase very much /// public bool IsCalculateAllStabilityProjectsAtOnce { get; set; } public SendMessageDelegate SendMessageDelegate { get { return sendMessageDelegate; } set { sendMessageDelegate = value; } } /// /// Constructor /// public TimeSerieStabilityCalculator() { StabilityWorkingPath = ""; IsMStabCalculationOff = false; IsCalculateAllStabilityProjectsAtOnce = true; } /// /// Create SafetyFactor TimeSerie for StabilityInside /// /// /// /// /// /// /// /// /// public TimeSerie CreateStabilityInsideSafetyFactorTimeSerie(TimeSerie timeSerieIn, Dike dike, Location location, int locationCounter, string dataDirectory, MStabParameters mstabParameters, IEnumerable gaugeTimeSerieList) { return CreateStabilitySafetyFactorTimeSerie(FailureMechanismSystemType.StabilityInside, timeSerieIn, dike, location, locationCounter, dataDirectory, mstabParameters, gaugeTimeSerieList); } public TimeSerie CreateStabilityInsideSafetyFactorTimeSerie(TimeSerie timeSerieIn, Dike dike, Location location, int locationCounter, MStabParameters mstabParameters, IEnumerable gaugeTimeSerieList) { return CreateStabilitySafetyFactorTimeSerie(FailureMechanismSystemType.StabilityInside, timeSerieIn, dike, location, locationCounter, null, mstabParameters, gaugeTimeSerieList); } /// /// Create SafetyFactor TimeSerie for StabilityOutside /// public TimeSerie CreateStabilityOutsideSafetyFactorTimeSerie(TimeSerie timeSerieIn, Dike dike, Location location, int locationCounter, string dataDirectory, MStabParameters mstabParameters, IEnumerable gaugeTimeSerieList) { return CreateStabilitySafetyFactorTimeSerie(FailureMechanismSystemType.StabilityOutside, timeSerieIn, dike, location, locationCounter, dataDirectory, mstabParameters, gaugeTimeSerieList); } public TimeSerie CreateStabilityOutsideSafetyFactorTimeSerie(TimeSerie timeSerieIn, Dike dike, Location location, int locationCounter, MStabParameters mstabParameters, IEnumerable gaugeTimeSerieList) { return CreateStabilitySafetyFactorTimeSerie(FailureMechanismSystemType.StabilityOutside, timeSerieIn, dike, location, locationCounter, mstabParameters, gaugeTimeSerieList); } private TimeSerie CreateStabilitySafetyFactorTimeSerie(FailureMechanismSystemType failureMechanismType, TimeSerie timeSerieIn, Dike dike, Location location, int locationCounter, string dataDirectory, MStabParameters mstabParameters, IEnumerable gaugeTimeSerieList) { return CreateStabilitySafetyFactorTimeSerie(failureMechanismType, timeSerieIn, dike, location, locationCounter, mstabParameters, gaugeTimeSerieList); } /// /// Creates the stability safety factor time serie. /// /// Type of the failure mechanism. /// The time serie in. /// The dike. /// The location. /// The location counter. /// The mstab parameters. /// The gauge time serie list. /// private TimeSerie CreateStabilitySafetyFactorTimeSerie(FailureMechanismSystemType failureMechanismType, TimeSerie timeSerieIn, Dike dike, Location location, int locationCounter, MStabParameters mstabParameters, IEnumerable gaugeTimeSerieList) { if (failureMechanismType != FailureMechanismSystemType.StabilityInside && failureMechanismType != FailureMechanismSystemType.StabilityOutside) { throw new TimeSerieStabilityCalculatorException(String.Format("Failurmechanism '{0}' not supported in CreateStabilitySafetyFactorTimeSerie()", failureMechanismType.ToString())); } string soilDatabasePath = location.StabilityOptions.SoilDatabaseName; string serieName = TimeSerieParameters.StabilityInsideFactor.ToString(); TimeSerie serie = TimeSerie.CreateTimeSerie(timeSerieIn, serieName); int timeSerieEntryCounter = 0; SoilProfileType soilProfileType; string soilGeometry2DName; CalculationHelper.DetermineSoilGeometryType(location, out soilProfileType, out soilGeometry2DName); string projectFileNameDikeFlow = CalculationHelper.GetProjectFileName(dike.Name, location, null, null, StabilityWorkingPath, null); TimeSerie safetyFactorTimeSerie = null; //foreach (TimeSerieEntry sourceEntry in timeSerieIn.Entries) for (int timeSerieEntryIndex = 0; timeSerieEntryIndex < timeSerieIn.Entries.Count; timeSerieEntryIndex++) { TimeSerieEntry sourceEntry = timeSerieIn.Entries[timeSerieEntryIndex]; var entry = new TimeSerieEntry(); stabTimeSerieEntry.Add(entry, new List()); entry.Assign(sourceEntry); entry.Value = timeSerieIn.MissVal; // With DupuitDynamic the waterlevel will be interpolated between the available timestep waterlevel points // With DGeoStability calculation a waterlevel entry is mandatory for each timestep bool isWaterLevelAvailable = sourceEntry.Value.IsNearEqual(timeSerieIn.MissVal); if (isWaterLevelAvailable) { var message = "No phreatic water level (Phreatic level = missVal): " + location.Name + " ID: " + locationCounter + " for time serie entry: " + timeSerieEntryCounter; logger.LogError(message); if (SendMessageDelegate != null) SendMessageDelegate(new LogMessage(LogMessageType.Error, dike, message)); } else { try { if (location.UsesGauges) { ProcessGauges(location, sourceEntry, gaugeTimeSerieList); } location.GaugeMissVal = timeSerieIn.MissVal; PLLines plLines = null; plLines = CalculationHelper.CreateAllPLLines(sourceEntry.Value, location, soilGeometry2DName, soilProfileType); if (this.IsMStabCalculationOff) { Random random = new Random(); entry.Value = random.NextDouble(); } else { IList models; double? upliftFactor = CalculationHelper.GetLowestUpliftFactor(location.SurfaceLine, location.GetMostProbableProfile( FailureMechanismSystemType.StabilityInside), soilGeometry2DName, plLines, location); if (mstabParameters != null && !mstabParameters.IsCombinedBishopUpliftVanCalculation) models = new List { mstabParameters.Model }; else models = CalculationHelper.GetMStabModelsToCalculate(upliftFactor); foreach (MStabModelType model in models) { string projectFileName = CalculationHelper.GetProjectFileName(dike.Name, location, null, model, StabilityWorkingPath, entry.DateTime); // get all the parameters needed for the calculation var damCalculation = new DamFailureMechanismeCalculationSpecification(); CalculationHelper.BuildDamCalculation(failureMechanismType, location, soilGeometry2DName, soilProfileType, plLines, mstabParameters, model, damCalculation, soilDatabasePath, projectFileName); // Create the project file CalculationHelper.CreateMStabProjectFile(damCalculation.FailureMechanismParametersMStab, MStabExePath); stabTimeSerieEntry[entry].Add(projectFileName); //((TimeSerieEntry)entry).MStabProjectPaths.Add(projectFileName); // write line to locationmapping file var dbgMessage = "Location: " + location.Name + " ID: " + locationCounter + " for timeserie entry: " + timeSerieEntryCounter; logger.LogDebug(dbgMessage); } entry.Value = -1 * timeSerieIn.MissVal; } } catch (Exception e) { var message = "Could not create MStab project file for location: " + location.Name + " ID: " + locationCounter + " for time serie entry: " + timeSerieEntryCounter + e.Message; logger.LogError(message, e); if (SendMessageDelegate != null) SendMessageDelegate(new LogMessage(LogMessageType.Error, dike, message)); } } serie.Entries.Add(entry); timeSerieEntryCounter++; } return serie; } /// /// Process the gauge timeserie list and store in dike.gauge /// /// /// /// /// private void ProcessGauges(Location location, TimeSerieEntry sourceEntry, IEnumerable gaugeTimeSerieList) { foreach (TimeSerie gaugeTimeSerie in gaugeTimeSerieList) { Gauge gauge = null; string[] parts = gaugeTimeSerie.LocationId.Split('/'); if (parts.Count() >= 2) { string gaugeName = parts[1]; gauge = location.Gauges.FirstOrDefault(x => x.Name.Equals(gaugeName) && x.Location.Name.Equals(location.Name)); if (gauge == null) throw new TimeSerieStabilityCalculatorException(String.Format("Gauge {0}, as specified in time series, not found in dike at location {1}.", gaugeName, location.Name)); } else throw new TimeSerieStabilityCalculatorException(String.Format("One of the time series at location {0} lacks a gauge ID.", location.Name)); TimeSerieEntry gaugeEntryForTime = gaugeTimeSerie.Entries.FirstOrDefault(x => x.DateTime == sourceEntry.DateTime); if (gaugeEntryForTime == null) throw new TimeSerieStabilityCalculatorException(String.Format("Gauge water pressure time series is out of sync with water level time series at location/gauge {0}: Could not find value for this gauge at time {1}.", gaugeTimeSerie.LocationId, sourceEntry.DateTime.ToString())); gauge.Value = gaugeEntryForTime.Value; } } /// /// Create time serie with results for safety factors MStab /// /// /// /// public void CalculateSafetyFactorFromTimeSeries(string safetyFactor, Dike dike, TimeSerieCollection fewsOutputTimeSerieCollection) { if (string.IsNullOrWhiteSpace(this.StabilityWorkingPath)) throw new InvalidOperationException("Invalid path. The given stability working path is empty. Please specify the correct path"); try { if (IsCalculateAllStabilityProjectsAtOnce) { // string stabilityWorkingPath = string.IsNullOrWhiteSpace(this.StabilityWorkingPath) ? // Path.GetFullPath(".") : Path.GetFullPath(this.StabilityWorkingPath); string stabilityWorkingPath = Path.GetFullPath(this.StabilityWorkingPath); CalculationHelper.CalculateMStabProjects(stabilityWorkingPath, this.MStabExePath); } } catch (Exception e) { var message = "Error occured in MStab batch calculation; " + e.ToString(); logger.LogError(message, e); if (SendMessageDelegate != null) SendMessageDelegate(new LogMessage(LogMessageType.Error, dike, message)); } int locationCounter = 0; int timeSerieEntryCounter = 0; foreach (Location location in dike.Locations) { foreach (TimeSerie timeSerieOut in fewsOutputTimeSerieCollection.GetSeriesByLocationId(location.Name).Where(x => x.ParameterId.Equals(safetyFactor))) { foreach (TimeSerieEntry entry in timeSerieOut.Entries) { if (entry.Value != timeSerieOut.MissVal) { try { if (!IsCalculateAllStabilityProjectsAtOnce) { CalculationHelper.CalculateMStabProjects(stabTimeSerieEntry[entry], MStabExePath); } string basisFileName = ""; entry.Value = CalculationHelper.DetermineSafetyFactor(stabTimeSerieEntry[entry], ref basisFileName, MStabExePath); entry.BasisFileName = Path.GetFileNameWithoutExtension(basisFileName); if (StabilityWorkingPath.Contains(DamProjectData.OldProjectWorkingPath)) { // This code only works when the OldProjectWorkingPath is part of the StabilityWorkingPath // This is not the case in FewsDam.exe var index = DamProjectData.OldProjectWorkingPath.Length; entry.RelativeCalculationPathName = StabilityWorkingPath.Substring(index); } } catch (Exception e) { var message = "Could not determine safety factor for location: " + location.Name + " ID: " + locationCounter + " for timeserie entry: " + timeSerieEntryCounter + e.ToString(); logger.LogError(message, e); entry.Value = 0; if (SendMessageDelegate != null) SendMessageDelegate(new LogMessage(LogMessageType.Error, dike, message)); } } } } locationCounter++; } } } }