// Copyright (C) Stichting Deltares 2023. All rights reserved. // // This file is part of the application DAM - Live. // // DAM - Live 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.Xml.Linq; using Deltares.Dam.Data; using Deltares.Dam.Data.Assemblers; using Deltares.Dam.Data.DamEngineIo; using Deltares.DamEngine.Interface; using Deltares.DamEngine.Io; using Deltares.DamEngine.Io.XmlInput; using Deltares.DamEngine.Io.XmlOutput; using Deltares.DamLive.Application.Properties; using Deltares.DamLive.Io; using Deltares.Standard.Application; using Deltares.Standard.Logging; using Location = Deltares.Dam.Data.Location; namespace Deltares.DamLive.Application; public class DamEngineRunner { internal const string NoDamxFile = "No .damx file set to load project data from."; internal const string NoFewsInputFileAvailable = "No FEWS input file available to read the input time series from."; internal const string NoFewsOutputFileAvailable = "No FEWS output file available to write the result to."; internal const string ErrorExtractingWorkingFolder = "An error occured while trying to extract the path from the FEWS input file. This error can be solved by setting a working folder"; internal LogHelper Logger = LogHelper.Create(); /// /// Initializes a new instance of the class. /// public DamEngineRunner() { // WorkingPath will be extracted from file name if available StabilityWorkingPath = Settings.Default.StabilityWorkingPath; PipingWorkingPath = Settings.Default.PipingWorkingPath; WaterLevelOffset = Settings.Default.WaterLevelOffset; } /// /// Gets or sets the dam project data. /// /// /// The dam project data. /// public DamProjectData DamProjectData { get; set; } /// /// Gets or sets the output time series collection. /// /// /// The output time series collection. /// public TimeSerieCollection OutputTimeSeriesCollection { get; set; } /// /// Gets or sets the input time series collection. /// /// /// The input time series collection. /// public TimeSerieCollection InputTimeSeriesCollection { get; set; } /// /// Gets or sets the stability working path. /// /// /// The stability working path. /// public string StabilityWorkingPath { get; set; } /// /// Gets or sets the piping working path. /// /// /// The piping working path. /// public string PipingWorkingPath { get; set; } /// /// Gets or sets the water level offset. /// /// /// The water level offset. /// public double WaterLevelOffset { get; set; } /// /// Gets or sets the working path. /// /// /// The working path. /// public string WorkingPath { get; set; } /// /// Gets or sets the maximum calculation cores. /// /// /// The maximum calculation cores. /// public int MaxCalculationCores { get; set; } = 1; /// /// Gets or sets the filter. /// /// /// The filter. /// public string Filter { get; set; } public string TestFileName { get; set; } /// /// Gets a value indicating whether this instance has errors. /// /// /// true if this instance has errors; otherwise, false. /// public bool HasErrors { get { return Logger.HasLoggedExceptions; } } /// /// Initializes this instance. /// /// /// public void Initialize() { LoadDataFromFiles(); AdaptDamProjectData(); } // Wat te doen: debug input xml file (InputFile.xml in debug dir DamLive) oppakken en gebruiken in DamEngine via de aanstuurtruc van Tom en dan kijken wat daar gebeurt. /// /// Runs this instance. /// public void Run() { Initialize(); OutputTimeSeriesCollection.Series.Clear(); RunSelectedModels(); WriteResultsToFile(FewsOutputFile.FullName); } /// /// Reads the user settings of the slip circle definition. /// /// The slip circle definition. public void ReadUserSettingsSlipCircleDefinition(SlipCircleDefinition slipCircleDefinition) { if (slipCircleDefinition == null) { slipCircleDefinition = new SlipCircleDefinition(); } slipCircleDefinition.UpliftVanGridSizeDetermination = Settings.Default.SlipCircleUpliftVanGridSizeDetermination; slipCircleDefinition.UpliftVanTangentLinesDefinition = Settings.Default.SlipCircleUpliftVanTangentLinesDefinition; slipCircleDefinition.UpliftVanTangentLinesDistance = Settings.Default.SlipCircleUpliftVanTangentLinesDistance; slipCircleDefinition.UpliftVanLeftGridVerticalPointCount = Settings.Default.SlipCircleUpliftVanLeftGridVerticalPointCount; slipCircleDefinition.UpliftVanLeftGridVerticalPointDistance = Settings.Default.SlipCircleUpliftVanLeftGridVerticalPointDistance; slipCircleDefinition.UpliftVanLeftGridHorizontalPointCount = Settings.Default.SlipCircleUpliftVanLeftGridHorizontalPointCount; slipCircleDefinition.UpliftVanLeftGridHorizontalPointDistance = Settings.Default.SlipCircleUpliftVanLeftGridHorizontalPointDistance; slipCircleDefinition.UpliftVanRightGridVerticalPointCount = Settings.Default.SlipCircleUpliftVanRightGridVerticalPointCount; slipCircleDefinition.UpliftVanRightGridVerticalPointDistance = Settings.Default.SlipCircleUpliftVanRightGridVerticalPointDistance; slipCircleDefinition.UpliftVanRightGridHorizontalPointCount = Settings.Default.SlipCircleUpliftVanRightGridHorizontalPointCount; slipCircleDefinition.UpliftVanRightGridHorizontalPointDistance = Settings.Default.SlipCircleUpliftVanRightGridHorizontalPointDistance; slipCircleDefinition.BishopSearchAreaDetermination = Settings.Default.SlipCircleBishopSearchAreaDetermination; slipCircleDefinition.BishopTangentLinesDistance = Settings.Default.SlipCircleBishopTangentLinesDistance; slipCircleDefinition.BishopGridVerticalPointCount = Settings.Default.SlipCircleBishopGridVerticalPointCount; slipCircleDefinition.BishopGridVerticalPointDistance = Settings.Default.SlipCircleBishopGridVerticalPointDistance; slipCircleDefinition.BishopGridHorizontalPointCount = Settings.Default.SlipCircleBishopGridHorizontalPointCount; slipCircleDefinition.BishopGridHorizontalPointDistance = Settings.Default.SlipCircleBishopGridHorizontalPointDistance; } protected internal FileInfo DamXFile { get; set; } protected internal FileInfo FewsInputFile { get; set; } protected internal FileInfo FewsOutputFile { get; set; } protected internal FileInfo ParametersFile { get; set; } protected void WriteResultsToFile(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) { throw new ArgumentNullException("fileName"); } var timeSerieAssembler = new TimeSeriesAssembler(); XDocument doc = timeSerieAssembler.CreateDataTransferDocument(OutputTimeSeriesCollection); try { doc.Save(fileName); FileWriterUtil.RemoveThreeBytesFromXml(fileName); } catch (Exception e) { Logger.LogError("Could not export Fews xml file", e); } } private void AdaptDamProjectData() { // Prepare DamProjetData for serializing to Dam Engine DamProjectData.SelectedLocationJobs.Clear(); foreach (Location location in DamProjectData.Locations) { var locationJob = new LocationJob { Location = location, Run = true }; DamProjectData.LocationJobs.Add(locationJob); } DamProjectData.FillOverallSensorData(); // Add time series to DamProjectData DamProjectData.InputTimeSerieCollection = InputTimeSeriesCollection; // Set correct calculation settings DamProjectData.DamProjectType = DamProjectType.DamLiveConfiguration; } private void LoadDataFromFiles() { if (DamProjectData == null) { if (DamXFile == null) { throw new InvalidOperationException(NoDamxFile); } DamProjectData = DamProject.LoadData(DamXFile.FullName); WorkingPath = Path.ChangeExtension(DamXFile.FullName, ".Calc"); } if (InputTimeSeriesCollection == null) { if (FewsInputFile == null) { throw new InvalidOperationException(NoFewsInputFileAvailable); } InputTimeSeriesCollection = TimeSerieCollection.LoadFromFile(FewsInputFile); } if (OutputTimeSeriesCollection == null) { if (FewsOutputFile == null) { throw new InvalidOperationException(NoFewsOutputFileAvailable); } OutputTimeSeriesCollection = InputTimeSeriesCollection.GetShallowCopy(); } //perhaps is some sort of test needed before reading parameters file like //DamProjectData.DamProjectCalculationSpecification.CurrentSpecification.CalculationModel == null && //but that depends on what should prevail if both files are present if (ParametersFile != null) { // Read calculation parameters, if available XmlCalculationParameters xmlCalculationParameters = CalculationParametersXmlSerialization.LoadFromXmlFile(ParametersFile.FullName); DamProjectData.DamProjectCalculationSpecification.CurrentSpecification = FillDomainFromXmlCalculationParameters.CreateCalculationParameters(xmlCalculationParameters); } } private void RunSelectedModels() { if (DamProjectData.DamProjectCalculationSpecification.CurrentSpecification == null) { Logger.LogError("No calculation models selected"); return; } if (DamProjectData.DamProjectCalculationSpecification.CurrentSpecification.FailureMechanismSystemType == FailureMechanismSystemType.StabilityInside) { RunStability(FailureMechanismSystemType.StabilityInside); } if (DamProjectData.DamProjectCalculationSpecification.CurrentSpecification.FailureMechanismSystemType == FailureMechanismSystemType.StabilityOutside) { RunStability(FailureMechanismSystemType.StabilityOutside); } if (DamProjectData.DamProjectCalculationSpecification.CurrentSpecification.FailureMechanismSystemType == FailureMechanismSystemType.Piping && DamProjectData.DamProjectCalculationSpecification.CurrentSpecification.PipingModelType == PipingModelType.Bligh) { Logger.LogError("Calculation module PipingBligh not implemented yet"); } if (DamProjectData.DamProjectCalculationSpecification.CurrentSpecification.FailureMechanismSystemType == FailureMechanismSystemType.Piping && DamProjectData.DamProjectCalculationSpecification.CurrentSpecification.PipingModelType == PipingModelType.Wti2017) { RunPiping(FailureMechanismSystemType.Piping); } } private void RunStability(FailureMechanismSystemType failureMechanismSystemType) { ReadUserSettingsSlipCircleDefinition(DamProjectData.DamProjectCalculationSpecification.CurrentSpecification.SlipCircleDefinition); DamProjectData.DamProjectCalculationSpecification.CurrentSpecification.FailureMechanismSystemType = failureMechanismSystemType; CallDamEngine(); if (DamProjectData.OutputTimeSerieCollection != null && DamProjectData.OutputTimeSerieCollection.Series.Count > 0) { OutputTimeSeriesCollection.Series.AddRange(DamProjectData.OutputTimeSerieCollection.Series); } } private void RunPiping(FailureMechanismSystemType failureMechanismSystemType) { DamProjectData.DamProjectCalculationSpecification.CurrentSpecification.FailureMechanismSystemType = failureMechanismSystemType; CallDamEngine(); if (DamProjectData.OutputTimeSerieCollection != null && DamProjectData.OutputTimeSerieCollection.Series.Count > 0) { OutputTimeSeriesCollection.Series.AddRange(DamProjectData.OutputTimeSerieCollection.Series); } } private void CallDamEngine() { DamProjectData.MaxCalculationCores = MaxCalculationCores; try { Input input = FillXmlInputFromDamUi.CreateInput(DamProjectData); #if DEBUG string inputFilename = TestFileName + "Input.xml"; DamXmlSerialization.SaveInputAsXmlFile(inputFilename, input); #endif string inputXml = DamXmlSerialization.SaveInputAsXmlString(input); var damEngineInterface = new EngineInterface(inputXml); string validationMessages = damEngineInterface.Validate(); // now the validation messages should be deserialized. If any, they should be passed on to the Validator // and checked for errors. If errors are found, then no calculation. When no messages or only warnings then // do calculate. For now, just check length if (string.IsNullOrEmpty(validationMessages)) { string outputXml = damEngineInterface.Run(); Output output = DamXmlSerialization.LoadOutputFromXmlString(outputXml); FillDamUiFromXmlOutput.AddOutputToDamProjectData(DamProjectData, output); #if DEBUG string outputFilename = TestFileName + "Output.xml"; DamXmlSerialization.SaveOutputAsXmlFile(outputFilename, output); #endif foreach (Message calculationMessage in output.Results.CalculationMessages) { switch (calculationMessage.MessageType) { case MessageMessageType.Info: Logger.LogInfo(calculationMessage.Message1); break; case MessageMessageType.Warning: Logger.LogWarning(calculationMessage.Message1); break; case MessageMessageType.Error: Logger.LogError(calculationMessage.Message1); break; } } } // Todo: handle validation messages } catch (Exception e) { LogManager.Add(new LogMessage(LogMessageType.FatalError, typeof(EngineInterface), $"{e.Message}")); } } }