// Copyright (C) Stichting Deltares 2023. 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.IO; using Deltares.DamEngine.Calculators.General; using Deltares.DamEngine.Calculators.KernelWrappers.Common; using Deltares.DamEngine.Calculators.KernelWrappers.Interfaces; using Deltares.DamEngine.Calculators.KernelWrappers.MacroStabilityInwards; 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.Geotechnics; using Deltares.DamEngine.Data.Standard; using Deltares.DamEngine.Data.Standard.Calculation; using Deltares.DamEngine.Data.Standard.Logging; namespace Deltares.DamEngine.Calculators.DikesDesign; /// /// The Dam Engine design calculator /// public class DesignCalculator { private ProgressDelegate progressDelegate; /// /// Performs the design calculation /// /// The dam project data. /// public void Execute(DamProjectData damProjectData) { progressDelegate?.Invoke(0); damProjectData.DesignCalculations = new List(); PrepareCalculationMessages(damProjectData); // Prepare the designCalculatorTasks var designCalculatorTasks = new List(); var relevantCombinationsCount = 0; for (var locationIndex = 0; locationIndex < damProjectData.Dike.Locations.Count; locationIndex++) { Location location = damProjectData.Dike.Locations[locationIndex].Copy(); if (location.Segment.SoilProfileProbabilities.Count == 0) { var message = new LogMessage(); message.Message = string.Format(Resources.DesignCalculatorNoSoilProfilesAvailableForLocation, location.Name); message.MessageType = LogMessageType.Warning; damProjectData.CalculationMessages.Add(message); } else { relevantCombinationsCount = DetermineRelevantCombinations(damProjectData, location, designCalculatorTasks, relevantCombinationsCount); } } HandleRelevantCombinations(damProjectData, relevantCombinationsCount, designCalculatorTasks); } /// /// Performs the design calculation /// /// The kernel wrapper. /// The kernel data input. /// The kernel data output. /// The dam kernel input. /// The calculation messages. /// The design calculations. public static void PerformDesignCalculation( IKernelWrapper kernelWrapper, IKernelDataInput kernelDataInput, IKernelDataOutput kernelDataOutput, DamKernelInput damKernelInput, List calculationMessages, List designCalculations) { // During the design calculation the location.SurfaceLine is adapted; so store a copy to restore it after the calculation Location location = damKernelInput.Location; SurfaceLine2 orgLocationSurfaceLine = location.SurfaceLine.FullDeepClone(); try { if (location.RedesignDikeShoulder) { // Redesign the surfaceline with shoulder and or slope adaption switch (kernelWrapper.GetDesignStrategy(damKernelInput)) { case DesignStrategy.ShoulderPerPoint: DesignCalculatorShoulderPerPoint.PerformDesignCalculationShoulderPerPoint( kernelWrapper, kernelDataInput, kernelDataOutput, damKernelInput, calculationMessages, designCalculations); break; case DesignStrategy.NoDesignPossible: throw new NotImplementedException("No design is possible for this failure mechanism"); case DesignStrategy.SlopeAdaptionBeforeShoulderAdaption: DesignCalculatorFirstSlopeAdaptionThenShoulderAdaption.PerformDesignCalculationFirstSlopeAdaptionThenShoulderAdaption( kernelWrapper, kernelDataInput, kernelDataOutput, damKernelInput, calculationMessages, designCalculations); break; case DesignStrategy.OptimizedSlopeAndShoulderAdaption: DesignCalculatorCombinedSlopeAndShoulderAdaption.PerformDesignCalculationCombinedSlopeAdaptionAndShoulderAdaption( kernelWrapper, kernelDataInput, kernelDataOutput, damKernelInput, calculationMessages, designCalculations); break; } } } finally { // Restore the original surfaceline location.SurfaceLine = orgLocationSurfaceLine; } } /// /// Registers the progress. /// /// The progress delegate. /// public CalculationResult RegisterProgress(ProgressDelegate aProgressDelegate) { progressDelegate = aProgressDelegate; return CalculationResult.Succeeded; } private int DetermineRelevantCombinations(DamProjectData damProjectData, Location location, List designCalculatorTasks, int relevantCombinationsCount) { for (var subSoilScenarioIndex = 0; subSoilScenarioIndex < location.Segment.SoilProfileProbabilities.Count; subSoilScenarioIndex++) { // Check if the subSoilScenario is applicable for the failuremechanism that is being calculated if (location.Segment.SoilProfileProbabilities[subSoilScenarioIndex].SegmentFailureMechanismType.HasValue && (location.Segment.SoilProfileProbabilities[subSoilScenarioIndex].SegmentFailureMechanismType.Value.In( ConversionHelper.ConvertToSegmentFailureMechanismType(damProjectData.DamProjectCalculationSpecification .CurrentSpecification.FailureMechanismSystemType), SegmentFailureMechanismType.All))) { SoilGeometryProbability soiProfileProbability = location.Segment.SoilProfileProbabilities[subSoilScenarioIndex]; for (var designScenarioIndex = 0; designScenarioIndex < location.Scenarios.Count; designScenarioIndex++) { DesignScenario designScenario = location.Scenarios[designScenarioIndex]; designScenario.LocationName = location.Name; string projectPath = damProjectData.ProjectPath != "" ? damProjectData.ProjectPath : Directory.GetCurrentDirectory(); var designResults = new List(); var calculationMessages = new List(); designCalculatorTasks.Add(new DesignCalculatorTask { Location = location, SoiProfileProbability = soiProfileProbability, DesignScenario = designScenario, ProjectPath = projectPath, CalculationMap = damProjectData.CalculationMap, FailureMechanismeCalculationSpecification = damProjectData.DamProjectCalculationSpecification.CurrentSpecification.Copy(), DesignResults = designResults, CalculationMessages = calculationMessages }); } relevantCombinationsCount++; } } return relevantCombinationsCount; } private void HandleRelevantCombinations(DamProjectData damProjectData, int relevantCombinationsCount, List designCalculatorTasks) { if (relevantCombinationsCount > 0) { // Perform the calculation Parallel.Run(designCalculatorTasks, RunDesignCalculatorTask, progressDelegate, damProjectData.MaxCalculationCores); foreach (DesignCalculatorTask designCalculatorTask in designCalculatorTasks) { damProjectData.CalculationMessages.AddRange(designCalculatorTask.CalculationMessages); damProjectData.DesignCalculations.AddRange(designCalculatorTask.DesignResults); } } 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 PrepareCalculationMessages(DamProjectData damProjectData) { if (damProjectData.CalculationMessages == null) { damProjectData.CalculationMessages = new List(); } else { damProjectData.CalculationMessages.Clear(); } } private void RunDesignCalculatorTask(object designCalculatorTask) { var task = (DesignCalculatorTask) designCalculatorTask; Debug.WriteLine("Start calculation Location '{0}', soilprofile '{1}'", task.Location, task.SoiProfileProbability); CalculateOneScenario(task.Location, task.SoiProfileProbability, task.DesignScenario, task.ProjectPath, task.CalculationMap, task.FailureMechanismeCalculationSpecification, task.DesignResults, task.CalculationMessages); Debug.WriteLine("End calculation Location '{0}', soilprofile '{1}'", task.Location, task.SoiProfileProbability); } private void CalculateOneScenario(Location originalLocation, SoilGeometryProbability soiProfileProbability, DesignScenario designScenario, string projectPath, string calculationMap, DamFailureMechanismeCalculationSpecification damFailureMechanismeCalculationSpecification, List designResults, List calculationMessages) { try { // Prepare input Location location = originalLocation.Copy(); var damKernelInput = new DamKernelInput(); damKernelInput.ProjectDir = projectPath; damKernelInput.CalculationDir = Path.Combine(projectPath, calculationMap); damKernelInput.Location = location; damKernelInput.SubSoilScenario = soiProfileProbability; damKernelInput.DamFailureMechanismeCalculationSpecification = damFailureMechanismeCalculationSpecification; damKernelInput.RiverLevelHigh = designScenario.RiverLevel; damKernelInput.RiverLevelLow = designScenario.RiverLevelLow; damKernelInput.FilenamePrefix = $"Loc({location.Name})_Sce({designScenario.LocationScenarioID})"; AnalysisType analysisType = DamProjectCalculationSpecification.SelectedAnalysisType; SynchronizeScenarioDataWithLocationData(designScenario, location); IKernelDataInput kernelDataInput; IKernelDataOutput kernelDataOutput; // Create kernelwrapper IKernelWrapper kernelWrapper = KernelWrapperHelper.CreateKernelWrapper(damFailureMechanismeCalculationSpecification); if (kernelWrapper == null) { throw new NotImplementedException(Resources.DesignCalculatorKernelNotImplemented); } // During the design calculation the location.SurfaceLine is adapted; so store a copy to restore it after the calculation SurfaceLine2 orgLocationSurfaceLine = null; try { orgLocationSurfaceLine = AdaptSurfaceLineWhenNeededForDesign(location, analysisType, damKernelInput); PrepareResult prepareResult = kernelWrapper.Prepare(damKernelInput, 0, out kernelDataInput, out kernelDataOutput); // Sometimes the kernelDataInput is not created (e.g. when SoilProfileProbability is meant for // stability where Piping calc is wanted). In that case, do nothing but just skip. if (prepareResult == PrepareResult.Successful) { switch (analysisType) { case AnalysisType.AdaptGeometry: PerformDesignCalculation(kernelWrapper, kernelDataInput, kernelDataOutput, damKernelInput, calculationMessages, designResults); break; case AnalysisType.NoAdaption: DesignCalculatorSingle.PerformSingleCalculation(kernelWrapper, kernelDataInput, kernelDataOutput, damKernelInput, calculationMessages, designResults); break; } } else { if (prepareResult == PrepareResult.NotRelevant) { calculationMessages.Add(new LogMessage(LogMessageType.Info, null, string.Format(Resources.DesignCalculatorPrepareNotRelevant, location.Name, soiProfileProbability, designScenario.LocationScenarioID))); } if (prepareResult == PrepareResult.Failed) { calculationMessages.Add(new LogMessage(LogMessageType.Error, null, string.Format(Resources.DesignCalculatorPrepareError, location.Name, soiProfileProbability, designScenario.LocationScenarioID))); var mo = (MacroStabilityOutput) kernelDataOutput; if (mo != null) { calculationMessages.Add(mo.Message); } } } } finally { if (orgLocationSurfaceLine != null) { location.SurfaceLine = orgLocationSurfaceLine; } } } catch (Exception e) { calculationMessages.Add(new LogMessage(LogMessageType.Error, null, string.Format(Resources.DesignCalculatorGeneralException, originalLocation.Name, soiProfileProbability, designScenario.LocationScenarioID, e.Message))); } } private SurfaceLine2 AdaptSurfaceLineWhenNeededForDesign(Location location, AnalysisType analysisType, DamKernelInput damKernelInput) { SurfaceLine2 orgLocationSurfaceLine = null; // Make sure that in case of AdaptGeometry (and RedesignDikeHeight), the surface line is adapted for the given DTH BEFORE // the call to Prepare as otherwise the Prepare will be done on the original surface line which is wrong. if (analysisType == AnalysisType.AdaptGeometry && damKernelInput.Location.RedesignDikeHeight) { double? dikeHeight = location.SurfaceLine.GetDikeHeight(); if (dikeHeight.HasValue && location.CurrentScenario.DikeTableHeight > dikeHeight.Value) { orgLocationSurfaceLine = location.SurfaceLine.FullDeepClone(); // Redesign the surface line to the desired Dike Table Height var surfaceLineHeightAdapter = new SurfaceLineHeightAdapter(location.SurfaceLine, location, location.CurrentScenario.PolderLevel); SurfaceLine2 adaptedSurfaceLine = surfaceLineHeightAdapter.ConstructNewSurfaceLine( location.CurrentScenario.DikeTableHeight ?? location.SurfaceLine.GetDefaultDikeTableHeight() ?? 0); location.CurrentScenario.SetRedesignedSurfaceLine(damKernelInput.SubSoilScenario.SoilProfile1D, damKernelInput.SubSoilScenario.SoilProfile2D, adaptedSurfaceLine); damKernelInput.Location.SurfaceLine = adaptedSurfaceLine; } } return orgLocationSurfaceLine; } /// /// Synchronizes the scenario data with location data. /// Note that scenario data is leading when available. /// /// The scenario. /// The location. private void SynchronizeScenarioDataWithLocationData(DesignScenario designScenario, Location location) { // Synchronize scenario data (Note: do not attempt to synchronize the ModelFactors as these are set by the direct // properties in the scenario such as the Required Safeties. ModelFactors is just a place holder. location.CurrentScenario = location.SynchronizeCurrentScenarioWithScenarioData(designScenario); location.Scenarios.Clear(); location.PlLineOffsetBelowDikeToeAtPolder = designScenario.PlLineOffsetBelowDikeToeAtPolder; location.PlLineOffsetBelowDikeTopAtPolder = designScenario.PlLineOffsetBelowDikeTopAtPolder; location.PlLineOffsetBelowDikeTopAtRiver = designScenario.PlLineOffsetBelowDikeTopAtRiver; location.PlLineOffsetBelowShoulderBaseInside = designScenario.PlLineOffsetBelowShoulderBaseInside; if (designScenario.PlLineOffsetBelowDikeCrestMiddle.HasValue) { location.PlLineOffsetBelowDikeCrestMiddle = designScenario.PlLineOffsetBelowDikeCrestMiddle; } if (designScenario.PlLineOffsetFactorBelowShoulderCrest.HasValue) { location.PlLineOffsetFactorBelowShoulderCrest = designScenario.PlLineOffsetFactorBelowShoulderCrest; } if (designScenario.UsePlLineOffsetBelowDikeCrestMiddle.HasValue) { location.UsePlLineOffsetBelowDikeCrestMiddle = designScenario.UsePlLineOffsetBelowDikeCrestMiddle; } if (designScenario.UsePlLineOffsetFactorBelowShoulderCrest.HasValue) { location.UsePlLineOffsetFactorBelowShoulderCrest = designScenario.UsePlLineOffsetFactorBelowShoulderCrest; } // Synchronize piping design parameters location.UpliftCriterionPiping = designScenario.UpliftCriterionPiping; location.ModelFactors.RequiredSafetyFactorPiping = designScenario.RequiredSafetyFactorPiping; // Synchronize stability design parameters location.UpliftCriterionStability = designScenario.UpliftCriterionStability; location.ModelFactors.RequiredSafetyFactorStabilityInnerSlope = designScenario.RequiredSafetyFactorStabilityInnerSlope; location.ModelFactors.RequiredSafetyFactorStabilityOuterSlope = designScenario.RequiredSafetyFactorStabilityOuterSlope; if (designScenario.DikeTableHeight.HasValue) { location.DikeTableHeight = designScenario.DikeTableHeight ?? location.SurfaceLine.GetDefaultDikeTableHeight() ?? 0; } } }