// 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.Diagnostics; using System.IO; using System.Linq; using Deltares.DamEngine.Calculators.KernelWrappers.Common; using Deltares.DamEngine.Calculators.KernelWrappers.DamMacroStabilityCommon; using Deltares.DamEngine.Calculators.KernelWrappers.Interfaces; using Deltares.DamEngine.Calculators.Properties; using Deltares.DamEngine.Data.General; using Deltares.DamEngine.Data.General.Results; using Deltares.DamEngine.Data.Geotechnics; using Deltares.DamEngine.Data.RegionalAssessmentResults; using Deltares.DamEngine.Data.Standard.Calculation; using Deltares.DamEngine.Data.Standard.Language; using Deltares.DamEngine.Data.Standard.Logging; namespace Deltares.DamEngine.Calculators.DikesAssessmentRegional { /// /// Exception for RegionalAssessmentCalculator class /// public class RegionalAssessmentCalculatorException : ApplicationException { public RegionalAssessmentCalculatorException(string message) : base(message) { } } /// /// Class to perform the calculation of reginal scenarios /// public class RegionalAssessmentCalculator { private DamProjectData damProjectData; private EvaluationJob evaluationJob; private ProgressDelegate progressDelegate; private int maxCalculationCores = 255; private readonly Dictionary runningJobs = new Dictionary(); private bool isSkipStabilityCalculation; private PipingModelType pipingModelType; /// /// Executes the specified a dam project data. /// /// a dam project data. /// public void Execute(DamProjectData aDamProjectData) { damProjectData = aDamProjectData; if (damProjectData.CalculationMessages == null) { damProjectData.CalculationMessages = new List(); } else { damProjectData.CalculationMessages.Clear(); } maxCalculationCores = damProjectData.MaxCalculationCores; pipingModelType = damProjectData.DamProjectCalculationSpecification.CurrentSpecification.PipingModelType; // Next line seems nonsense but is needed to properly initialize all jobs. So the unused dj can NOT be removed. var dj = damProjectData.DikeJob; foreach (var locationJob in damProjectData.LocationJobs) { locationJob.Location.DamType = DamType.Regional; } evaluationJob = damProjectData.GetEvaluationJob(); Run(); evaluationJob.AttachResults(aDamProjectData.LocationJobs); } /// /// Gets the results. /// /// public EvaluationJob GetResults() { return evaluationJob; } /// /// Registers the progress. /// /// The progress delegate. /// public CalculationResult RegisterProgress(ProgressDelegate aProgressDelegate) { progressDelegate = aProgressDelegate; return CalculationResult.Succeeded; } /// /// Runs this instance. /// /// public CalculationResult Run() { try { List tasks = FillQueue(); General.Parallel.Run(tasks, RunTask, progressDelegate, maxCalculationCores); FillResults(tasks); return CalculationResult.Succeeded; } catch(Exception exception) { StoreMessage(new LogMessage(LogMessageType.Warning, null, "Unexpected error:" + exception.Message)); throw; } } /// /// Gets or sets a value indicating whether this instance is skip stability calculation. /// Only used for test puposes. /// /// /// true if this instance is skip stability calculation; otherwise, false. /// public bool IsSkipStabilityCalculation { get { return isSkipStabilityCalculation; } set { isSkipStabilityCalculation = value; } } private List FillQueue() { List tasks = new List(); evaluationJob.FailedEvaluatedLocations = new List(); foreach (var location in evaluationJob.Locations) { if (location.Segment == null) { // Add this location to the failed locations if (evaluationJob.FailedEvaluatedLocations.IndexOf(location) < 0) { evaluationJob.FailedEvaluatedLocations.Add(location); var locationHasNoSegment = LocalizationManager.GetTranslatedText(GetType(), "LocationHasNoSegment"); StoreMessage(new LogMessage(LogMessageType.Error, location, locationHasNoSegment)); } } else { // TODO: Ask Erik Vastenburg how to handle piping and stability soilprofiles when determining RWScenarios // For now we only use the stability profiles. var soilGeometryProbabilities = location.Segment.SoilProfileProbabilities.Where(s => (s.SegmentFailureMechanismType == null) || (s.SegmentFailureMechanismType.Value == FailureMechanismSystemType.StabilityInside)).ToList(); if (soilGeometryProbabilities.Count == 0) { evaluationJob.FailedEvaluatedLocations.Add(location); StoreMessage(new LogMessage(LogMessageType.Warning, location, "Location has no soilprofiles: " + string.Format("Segment: {0}", location.Segment.Name))); } else { foreach (SoilGeometryProbability soilGeometryProbability in soilGeometryProbabilities) { if (soilGeometryProbability.SoilProfileType == SoilProfileType.ProfileType2D || soilGeometryProbability.SoilProfileType == SoilProfileType.ProfileTypeStiFile) { evaluationJob.FailedEvaluatedLocations.Add(location); StoreMessage(new LogMessage(LogMessageType.Warning, location, LocalizationManager.GetTranslatedText(this, "Geometry2DNotSupportedInRegionalAssessment") + string.Format("Segment: {0}", location.Segment.Name))); } else { SoilProfile soilProfile = soilGeometryProbability.SoilProfile1D; IList regionalScenarios; try { regionalScenarios = GetRegionalScenarios(location, soilGeometryProbability); } catch (Exception e) { regionalScenarios = null; // Add this location to the failed locations if (evaluationJob.FailedEvaluatedLocations.IndexOf(location) < 0) { evaluationJob.FailedEvaluatedLocations.Add(location); StoreMessage(new LogMessage(LogMessageType.Warning, location, String.Format("Cannot generate scenarios: {0}", e.Message) + String.Format("Soilprofile: {0}", soilProfile.Name))); } } if (regionalScenarios != null) { foreach (RegionalScenarioProfileJob job in regionalScenarios) { tasks.Add(job); } } } } } } } return tasks; } private IList GetRegionalScenarios(Location location, SoilGeometryProbability soilGeometryProbability) { RegionalScenarioSelector selector = new RegionalScenarioSelector() { PipingModelType = pipingModelType }; return selector.GetScenarios(location, soilGeometryProbability); } private void RunTask(object task) { var job = (RegionalScenarioProfileJob)task; try { if (!IsSkipStabilityCalculation) { ProcessJob(job); } else { job.CalculationResult = CalculationResult.NoRun; } } catch (Exception e) { job.CalculationResult = CalculationResult.UnexpectedError; StoreMessage(new LogMessage(LogMessageType.Warning, job, string.Format(job.LocationName + " Error: {0}", e.Message))); } } /// /// Select which job processor to use, depending on failuremechanism /// /// private void ProcessJob(RegionalScenarioProfileJob job) { Debug.WriteLine("Job {0}, Location {1}, Profile {2}, Scenario {3}, LoadSituation {4}", job.FailureMechanismType.ToString(), job.LocationName, job.SoilProfileName, job.ScenarioType.ToString(), job.LoadSituation.ToString()); switch (job.FailureMechanismType) { case FailureMechanismSystemType.StabilityInside: ProcessJobStability(job); break; case FailureMechanismSystemType.Piping: ProcessJobPiping(job); break; default: throw new RegionalAssessmentCalculatorException(String.Format("Failuremechanism {0} not yet implemented for scenario calculation", job.FailureMechanismType)); } } /// /// Process a job for failuremechanism Stability /// /// private void ProcessJobStability(RegionalScenarioProfileJob job) { job.CurrentSpecification = damProjectData.DamProjectCalculationSpecification.CurrentSpecification.Clone(); job.CurrentSpecification.CalculationModel = job.MstabModelOption; job.CurrentSpecification.StabilityModelType = job.MstabModelOption; job.CurrentSpecification.FailureMechanismSystemType = job.FailureMechanismType; job.CurrentSpecification.FailureMechanismParametersMStab.MStabParameters.Model = job.MstabModelOption; job.CurrentSpecification.AssessmentScenarioJobSettings.LoadSituation = job.LoadSituation; job.CurrentSpecification.AssessmentScenarioJobSettings.DikeDrySensitivity = job.DikeDrySensitivity; job.CurrentSpecification.AssessmentScenarioJobSettings.HydraulicShortcutType = job.HydraulicShortcutType; var kernelWrapper = CreateKernelWrapper(job.CurrentSpecification); job.Hbp = job.Location.BoezemLevelHbp; job.Lbp = job.Location.BoezemLevelLbp; //Adapt used waterlevel(s) when needed if (job.LoadSituation == LoadSituation.Wet) { job.Hbp = job.Location.BoezemLevelTp; job.Lbp = job.Location.BoezemLevelTp; } PerformJob(job, kernelWrapper); } /// /// Process a job for failuremechanism Piping /// /// private void ProcessJobPiping(RegionalScenarioProfileJob job) { if (job.Location.ModelFactors.UpliftCriterionPiping.HasValue) { job.CurrentSpecification = damProjectData.DamProjectCalculationSpecification.CurrentSpecification.Clone(); job.CurrentSpecification.FailureMechanismSystemType = job.FailureMechanismType; job.CurrentSpecification.PipingModelType = job.PipingModelOption; job.CurrentSpecification.AssessmentScenarioJobSettings.LoadSituation = job.LoadSituation; job.CurrentSpecification.AssessmentScenarioJobSettings.DikeDrySensitivity = job.DikeDrySensitivity; job.CurrentSpecification.AssessmentScenarioJobSettings.HydraulicShortcutType = job.HydraulicShortcutType; var kernelWrapper = CreateKernelWrapper(job.CurrentSpecification); // For piping waterlevel needs to be set here. Piping kernel only uses waterlevelhigh which is set from BoezemLevelHbp. job.Hbp = job.Location.BoezemLevelHbp; switch (job.LoadSituation) { case LoadSituation.Dry: job.Hbp = job.Location.BoezemLevelLbp; break; case LoadSituation.Wet: job.Hbp = job.Location.BoezemLevelTp; break; } PerformJob(job, kernelWrapper); } else { throw new RegionalAssessmentCalculatorException(String.Format("Uplift criterion not defined for location {0}", job.Location.Name)); } } private IKernelWrapper CreateKernelWrapper(DamFailureMechanismeCalculationSpecification currentSpecification) { IKernelWrapper kernelWrapper = KernelWrapperHelper.CreateKernelWrapper(currentSpecification); if (kernelWrapper == null) { throw new NotImplementedException(Resources.DesignCalculatorKernelNotImplemented); } return kernelWrapper; } private void PerformJob(RegionalScenarioProfileJob job, IKernelWrapper kernelWrapper) { lock (runningJobs) { runningJobs[kernelWrapper] = job; } PrepareKernelWrapperforJob(kernelWrapper, job, damProjectData.ProjectPath, damProjectData.CalculationMap); var kernelDataInput = (job.KernelDataInput as IKernelDataInput); var kernelDataOutput = (job.KernelDataOutput as IKernelDataOutput); var damKernelInput = (job.DamKernelInput as DamKernelInput); // Check if prepare is ok var errorFound = false; foreach (var calculationCreationMessage in job.CalculationMessages) { if (calculationCreationMessage.MessageType == LogMessageType.Error) { job.CalculationResult = CalculationResult.InvalidInputData; errorFound = true; break; } } if (!errorFound && job.CalculationMessages.Count > 0) { job.CalculationResult = CalculationResult.NoRun; } // if preparation has error or shows not relevant, then skip this if (!errorFound && job.CalculationMessages.Count == 0) { List messages; kernelWrapper.Execute(kernelDataInput, kernelDataOutput, out messages); job.CalculationResult = CalculationResult.Succeeded; foreach (var logMessage in messages) { if (logMessage.MessageType == LogMessageType.Error || logMessage.MessageType == LogMessageType.FatalError) { job.CalculationResult = CalculationResult.UnexpectedError; } } if (job.CalculationResult == CalculationResult.Succeeded) { string resultMessage = ""; List results; kernelWrapper.PostProcess(damKernelInput, kernelDataOutput, null, resultMessage, out results); job.Results = results; var stbOutput = kernelDataOutput as DamMacroStabilityOutput; if (stbOutput != null) { var inputfile = stbOutput.StabilityOutputItems[0].ProjectFileName; inputfile = inputfile.Replace(damKernelInput.CalculationDir, @"").Replace(".sti", ""); job.BaseFileName = inputfile; } if (results[0].SafetyFactor != null) { job.SafetyFactor = results[0].SafetyFactor.Value; } job.CalculationResult = results[0].CalculationResult; job.RegionalResultType = RegionalResultType.SafetyFactor; job.ProbabilityOfFailure = double.NaN; } else { if (job.FailureMechanismType == FailureMechanismSystemType.Piping) { job.RegionalResultType = RegionalResultType.SafetyFactor; } else { job.RegionalResultType = (job.CurrentSpecification.FailureMechanismParametersMStab.MStabParameters.IsProbabilistic ? RegionalResultType.ProbabilityOfFailure : RegionalResultType.SafetyFactor); } job.SafetyFactor = double.NaN; job.ProbabilityOfFailure = double.NaN; } } lock (runningJobs) { runningJobs.Remove(kernelWrapper); } } private void PrepareKernelWrapperforJob(IKernelWrapper kernelWrapper, RegionalScenarioProfileJob job, string projectPath, string calculationMap) { job.CalculationMessages = new List(); // Prepare input job.DamKernelInput = new DamKernelInput(); var locProjectPath = projectPath != "" ? projectPath : Directory.GetCurrentDirectory(); var damKernelInput = job.DamKernelInput as DamKernelInput; damKernelInput.ProjectDir = locProjectPath; damKernelInput.CalculationDir = Path.Combine(locProjectPath, calculationMap); damKernelInput.Location = job.Location; damKernelInput.SubSoilScenario = job.SoilGeometryProbability; damKernelInput.DamFailureMechanismeCalculationSpecification = job.CurrentSpecification; damKernelInput.DamFailureMechanismeCalculationSpecification.FailureMechanismSystemType = job.FailureMechanismType; damKernelInput.RiverLevelHigh = job.Hbp; damKernelInput.RiverLevelLow = job.Lbp; damKernelInput.FilenamePrefix = string.Format("Loc({0})_Sce({1})", job.Location.Name, job.ScenarioType); damKernelInput.SubSoilScenario.SegmentFailureMechanismType = job.FailureMechanismType; IKernelDataInput kernelDataInput; IKernelDataOutput kernelDataOutput; PrepareResult prepareResult = kernelWrapper.Prepare(job.DamKernelInput as DamKernelInput, 0, out kernelDataInput, out kernelDataOutput); job.KernelDataInput = kernelDataInput; job.KernelDataOutput = 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) { } else { if (prepareResult == PrepareResult.NotRelevant) { // Do nothing. Calculation will be skipped. } if (prepareResult == PrepareResult.Failed) { job.CalculationMessages.Add(new LogMessage(LogMessageType.Error, null, string.Format(Resources.DesignCalculatorPrepareError, job.Location.Name, job.SoilGeometryProbability, ""))); // TODO #The correct regional scenario ID? } } } /// /// Fill the results for the scenarios /// private void FillResults(List tasks) { // Fill scenariosResult structure with jobs just run foreach (Location location in evaluationJob.Locations) { try { RegionalScenariosResult scenariosResult = new RegionalScenariosResult(); if (evaluationJob.FailedEvaluatedLocations.IndexOf(location) < 0) { // scenarios were succesfully created, so results are available foreach (RegionalScenarioProfileJob job in tasks) { if (job.LocationName.Equals(location.Name)) { RegionalScenarioResult scenarioResult = null; foreach (var existingScenarioResult in scenariosResult.RegionalScenarioResults) { if (existingScenarioResult.ScenarioType == job.ScenarioType) { scenarioResult = existingScenarioResult; } } if (scenarioResult == null) { scenarioResult = new RegionalScenarioResult { ScenarioType = job.ScenarioType }; scenariosResult.RegionalScenarioResults.Add(scenarioResult); } scenarioResult.RegionalScenarioProfileResults.Add(job); } } // Combine results foreach (var scenarioResult in scenariosResult.RegionalScenarioResults) { CombineProfiles(scenarioResult); } CombineScenarios(scenariosResult); } else { // scenarios were not succesfully created, so results are not available // no succesful calculations found scenariosResult.CalculationResult = CalculationResult.RunFailed; scenariosResult.SafetyFactor = double.NaN; } // scenariosResult are the results of all scenarios for one location. evaluationJob.Results.Add(scenariosResult); } catch (Exception e) { RegionalScenariosResult scenariosResult = new RegionalScenariosResult { CalculationResult = CalculationResult.RunFailed, SafetyFactor = double.NaN }; evaluationJob.Results.Add(scenariosResult); StoreMessage(new LogMessage(LogMessageType.Warning, location, string.Format("Error in location {0}: {1}", location.Name, e.Message))); } } } private void CombineProfiles(RegionalScenarioResult scenarioResult) { // combine results of profiles scenarioResult.SafetyFactor = Double.MaxValue; foreach (var profileResult in scenarioResult.RegionalScenarioProfileResults) { if (profileResult.CalculationResult == CalculationResult.Succeeded) { if (profileResult.SafetyFactor < scenarioResult.SafetyFactor) { scenarioResult.SafetyFactor = profileResult.SafetyFactor; } scenarioResult.CalculationResult = CalculationResult.Succeeded; } } if (scenarioResult.CalculationResult != CalculationResult.Succeeded) { // no succesful calculations found scenarioResult.CalculationResult = scenarioResult.RegionalScenarioProfileResults[0].CalculationResult; scenarioResult.SafetyFactor = scenarioResult.RegionalScenarioProfileResults[0].SafetyFactor; } } private void CombineScenarios(RegionalScenariosResult scenariosResult) { // combine results of scenarios scenariosResult.SafetyFactor = Double.MaxValue; foreach (var scenarioResult in scenariosResult.RegionalScenarioResults) { if (scenarioResult.CalculationResult == CalculationResult.Succeeded) { if (scenarioResult.SafetyFactor < scenariosResult.SafetyFactor) { scenariosResult.SafetyFactor = scenarioResult.SafetyFactor; } scenariosResult.CalculationResult = CalculationResult.Succeeded; } } } private void StoreMessage(LogMessage logMessage) { damProjectData.CalculationMessages.Add(logMessage); } } }