// 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++;
}
}
}
}