// Copyright (C) Stichting Deltares 2019. All rights reserved. // // This file is part of Riskeer. // // Riskeer is free software: you can redistribute it and/or modify // it under the terms of the GNU 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 General Public License for more details. // // You should have received a copy of the GNU 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.Linq; using Core.Common.Base.Geometry; using Core.Common.Base.IO; using log4net; using Riskeer.Common.Data.AssessmentSection; using Riskeer.Common.Data.Exceptions; using Riskeer.Common.Data.Hydraulics; using Riskeer.Common.Data.IllustrationPoints; using Riskeer.Common.Data.Probabilistics; using Riskeer.Common.IO.HydraRing; using Riskeer.Common.Service; using Riskeer.Common.Service.IllustrationPoints; using Riskeer.HydraRing.Calculation.Calculator; using Riskeer.HydraRing.Calculation.Calculator.Factory; using Riskeer.HydraRing.Calculation.Data.Input; using Riskeer.HydraRing.Calculation.Data.Input.Piping; using Riskeer.HydraRing.Calculation.Exceptions; using Riskeer.Piping.Data; using Riskeer.Piping.Data.Probabilistic; using Riskeer.Piping.Service.Properties; using RiskeerCommonServiceResources = Riskeer.Common.Service.Properties.Resources; using HydraRingGeneralResult = Riskeer.HydraRing.Calculation.Data.Output.IllustrationPoints.GeneralResult; namespace Riskeer.Piping.Service.Probabilistic { /// /// Service that provides methods for performing Hydra-Ring calculations for piping probabilistic. /// public class ProbabilisticPipingCalculationService { private const int numberOfCalculators = 2; private static readonly ILog log = LogManager.GetLogger(typeof(ProbabilisticPipingCalculationService)); private IPipingCalculator profileSpecificCalculator; private IPipingCalculator sectionSpecificCalculator; private bool canceled; /// /// Fired when the calculation progress changed. /// public event OnProgressChanged OnProgressChanged; /// /// Performs validation over the values on the given . Error and status information is logged during /// the execution of the operation. /// /// The for which to validate the values. /// The for which to validate the values. /// The for which to validate the values. /// true if has no validation errors; false otherwise. /// Thrown when any parameter is null. public static bool Validate(ProbabilisticPipingCalculation calculation, PipingFailureMechanism failureMechanism, IAssessmentSection assessmentSection) { if (calculation == null) { throw new ArgumentNullException(nameof(calculation)); } if (failureMechanism == null) { throw new ArgumentNullException(nameof(failureMechanism)); } if (assessmentSection == null) { throw new ArgumentNullException(nameof(assessmentSection)); } CalculationServiceHelper.LogValidationBegin(); LogAnyWarnings(calculation); bool hasErrors = LogAnyErrors(calculation, failureMechanism, assessmentSection); CalculationServiceHelper.LogValidationEnd(); return !hasErrors; } /// /// Cancels any currently running piping calculation. /// public void Cancel() { profileSpecificCalculator?.Cancel(); sectionSpecificCalculator?.Cancel(); canceled = true; } /// /// Performs a piping calculation based on the supplied and sets /// if the calculation was successful. Error and status information is logged during the execution of the operation. /// /// The that holds all the information required to perform the calculation. /// The to derive values from during the calculation. /// The with the /// hydraulic boundary calculation settings. /// The length of the section the calculation belongs to. /// Preprocessing is disabled when the preprocessor directory equals . /// Thrown when , /// or is null. /// Thrown when the hydraulic boundary database file path /// contains invalid characters. /// Thrown when: /// /// No settings database file could be found at the location of the hydraulic boundary database file path /// with the same name. /// Unable to open settings database file. /// Unable to read required data from database file. /// /// Thrown when an error occurs during parsing of the Hydra-Ring output. /// Thrown when an error occurs during the calculation. internal void Calculate(ProbabilisticPipingCalculation calculation, GeneralPipingInput generalInput, HydraulicBoundaryCalculationSettings calculationSettings, double sectionLength) { if (calculation == null) { throw new ArgumentNullException(nameof(calculation)); } if (generalInput == null) { throw new ArgumentNullException(nameof(generalInput)); } if (calculationSettings == null) { throw new ArgumentNullException(nameof(calculationSettings)); } string hydraulicBoundaryDatabaseFilePath = calculationSettings.HydraulicBoundaryDatabaseFilePath; bool usePreprocessor = !string.IsNullOrEmpty(calculationSettings.PreprocessorDirectory); HydraRingCalculationSettings hydraRingCalculationSettings = HydraRingCalculationSettingsFactory.CreateSettings(calculationSettings); profileSpecificCalculator = HydraRingCalculatorFactory.Instance.CreatePipingCalculator( hydraRingCalculationSettings); sectionSpecificCalculator = HydraRingCalculatorFactory.Instance.CreatePipingCalculator( hydraRingCalculationSettings); CalculationServiceHelper.LogCalculationBegin(); try { IPartialProbabilisticPipingOutput profileSpecificOutput = CalculateProfileSpecific( calculation, generalInput, hydraulicBoundaryDatabaseFilePath, usePreprocessor); if (canceled) { return; } IPartialProbabilisticPipingOutput sectionSpecificOutput = CalculateSectionSpecific( calculation, generalInput, sectionLength, hydraulicBoundaryDatabaseFilePath, usePreprocessor); if (canceled) { return; } calculation.Output = new ProbabilisticPipingOutput(sectionSpecificOutput, profileSpecificOutput); } finally { CalculationServiceHelper.LogCalculationEnd(); profileSpecificCalculator = null; sectionSpecificCalculator = null; } } /// /// Performs a profile specific calculation. /// /// The calculation containing the input for the profile specific calculation. /// The general piping calculation input parameters. /// The path which points to the hydraulic boundary database file. /// Indicator whether to use the preprocessor in the calculation. /// A . /// Thrown when an error occurs while performing the calculation. private IPartialProbabilisticPipingOutput CalculateProfileSpecific(ProbabilisticPipingCalculation calculation, GeneralPipingInput generalInput, string hydraulicBoundaryDatabaseFilePath, bool usePreprocessor) { NotifyProgress(string.Format(Resources.ProbabilisticPipingCalculationService_Calculate_Executing_calculation_of_type_0, Resources.ProbabilisticPipingCalculationService_ProfileSpecific), 1, numberOfCalculators); PipingCalculationInput profileSpecificCalculationInput = CreateInput( calculation, generalInput, 0, hydraulicBoundaryDatabaseFilePath, usePreprocessor); PerformCalculation(() => profileSpecificCalculator.Calculate(profileSpecificCalculationInput), () => profileSpecificCalculator.LastErrorFileContent, () => profileSpecificCalculator.OutputDirectory, calculation.Name, Resources.ProbabilisticPipingCalculationService_ProfileSpecific); GeneralResult generalResult = null; try { generalResult = calculation.InputParameters.ShouldProfileSpecificIllustrationPointsBeCalculated ? ConvertIllustrationPointsResult(profileSpecificCalculator.IllustrationPointsResult, profileSpecificCalculator.IllustrationPointsParserErrorMessage) : null; } catch (ArgumentException e) { log.WarnFormat(Resources.ProbabilisticPipingCalculationService_Calculate_Error_in_reading_illustrationPoints_for_CalculationName_0_CalculationType_1_with_ErrorMessage_2, calculation.Name, Resources.ProbabilisticPipingCalculationService_ProfileSpecific, e.Message); } return new PartialProbabilisticFaultTreePipingOutput(profileSpecificCalculator.ExceedanceProbabilityBeta, generalResult); } /// /// Performs a section specific calculation. /// /// The calculation containing the input for the section specific calculation. /// The general piping calculation input parameters. /// The length of the section. /// The path which points to the hydraulic boundary database file. /// Indicator whether to use the preprocessor in the calculation. /// A . /// Thrown when an error occurs while performing the calculation. private IPartialProbabilisticPipingOutput CalculateSectionSpecific(ProbabilisticPipingCalculation calculation, GeneralPipingInput generalInput, double sectionLength, string hydraulicBoundaryDatabaseFilePath, bool usePreprocessor) { NotifyProgress(string.Format(Resources.ProbabilisticPipingCalculationService_Calculate_Executing_calculation_of_type_0, Resources.ProbabilisticPipingCalculationService_SectionSpecific), 2, numberOfCalculators); PipingCalculationInput sectionSpecificCalculationInput = CreateInput( calculation, generalInput, sectionLength, hydraulicBoundaryDatabaseFilePath, usePreprocessor); PerformCalculation(() => sectionSpecificCalculator.Calculate(sectionSpecificCalculationInput), () => sectionSpecificCalculator.LastErrorFileContent, () => sectionSpecificCalculator.OutputDirectory, calculation.Name, Resources.ProbabilisticPipingCalculationService_SectionSpecific); GeneralResult generalResult = null; try { generalResult = calculation.InputParameters.ShouldSectionSpecificIllustrationPointsBeCalculated ? ConvertIllustrationPointsResult(sectionSpecificCalculator.IllustrationPointsResult, sectionSpecificCalculator.IllustrationPointsParserErrorMessage) : null; } catch (ArgumentException e) { log.WarnFormat(Resources.ProbabilisticPipingCalculationService_Calculate_Error_in_reading_illustrationPoints_for_CalculationName_0_CalculationType_1_with_ErrorMessage_2, calculation.Name, Resources.ProbabilisticPipingCalculationService_SectionSpecific, e.Message); } return new PartialProbabilisticFaultTreePipingOutput(sectionSpecificCalculator.ExceedanceProbabilityBeta, generalResult); } /// /// Performs a probabilistic piping calculation. /// /// The action that performs the calculation. /// The function for obtaining the last error file content. /// The function for obtaining the output directory. /// The name of the calculation to perform. /// The name of the step to perform. /// Thrown when an error occurs while performing the calculation. private void PerformCalculation(Action performCalculation, Func getLastErrorFileContent, Func getOutputDirectory, string calculationName, string stepName) { var exceptionThrown = false; try { performCalculation(); } catch (HydraRingCalculationException) { if (!canceled) { string lastErrorFileContent = getLastErrorFileContent(); if (string.IsNullOrEmpty(lastErrorFileContent)) { log.ErrorFormat( Resources.ProbabilisticPipingCalculationService_Calculate_Error_in_calculation_of_type_0_for_calculation_with_name_1_no_error_report, stepName, calculationName); } else { log.ErrorFormat( Resources.ProbabilisticPipingCalculationService_Calculate_Error_in_calculation_of_type_0_for_calculation_with_name_1_click_details_for_last_error_report_2, stepName, calculationName, lastErrorFileContent); } exceptionThrown = true; throw; } } finally { string lastErrorFileContent = getLastErrorFileContent(); bool errorOccurred = CalculationServiceHelper.HasErrorOccurred(canceled, exceptionThrown, lastErrorFileContent); if (errorOccurred) { log.ErrorFormat( Resources.ProbabilisticPipingCalculationService_Calculate_Error_in_calculation_of_type_0_for_calculation_with_name_1_click_details_for_last_error_report_2, stepName, calculationName, lastErrorFileContent); } log.InfoFormat( Resources.ProbabilisticPipingCalculationService_Calculate_Calculation_of_type_0_performed_in_temporary_directory_1, stepName, getOutputDirectory()); if (errorOccurred) { throw new HydraRingCalculationException(lastErrorFileContent); } } } private static PipingCalculationInput CreateInput(ProbabilisticPipingCalculation calculation, GeneralPipingInput generalInput, double sectionLength, string hydraulicBoundaryDatabaseFilePath, bool usePreprocessor) { ProbabilisticPipingInput pipingInput = calculation.InputParameters; LogNormalDistribution thicknessCoverageLayer = DerivedPipingInput.GetThicknessCoverageLayer(pipingInput); VariationCoefficientLogNormalDistribution seepageLength = DerivedPipingInput.GetSeepageLength(pipingInput); LogNormalDistribution thicknessAquiferLayer = DerivedPipingInput.GetThicknessAquiferLayer(pipingInput); VariationCoefficientLogNormalDistribution darcyPermeability = DerivedPipingInput.GetDarcyPermeability(pipingInput); VariationCoefficientLogNormalDistribution diameterD70 = DerivedPipingInput.GetDiameterD70(pipingInput); PipingCalculationInput input; if (double.IsNaN(thicknessCoverageLayer.Mean)) { input = new PipingCalculationInput( pipingInput.HydraulicBoundaryLocation.Id, sectionLength, pipingInput.PhreaticLevelExit.Mean, pipingInput.PhreaticLevelExit.StandardDeviation, generalInput.WaterVolumetricWeight, generalInput.UpliftModelFactor.Mean, generalInput.UpliftModelFactor.StandardDeviation, pipingInput.DampingFactorExit.Mean, pipingInput.DampingFactorExit.StandardDeviation, seepageLength.Mean, seepageLength.CoefficientOfVariation, thicknessAquiferLayer.Mean, thicknessAquiferLayer.StandardDeviation, generalInput.SandParticlesVolumicWeight, generalInput.SellmeijerModelFactor.Mean, generalInput.SellmeijerModelFactor.StandardDeviation, generalInput.BeddingAngle, generalInput.WhitesDragCoefficient, generalInput.WaterKinematicViscosity, darcyPermeability.Mean, darcyPermeability.CoefficientOfVariation, diameterD70.Mean, diameterD70.CoefficientOfVariation, generalInput.Gravity, generalInput.CriticalHeaveGradient.Mean, generalInput.CriticalHeaveGradient.StandardDeviation); } else { LogNormalDistribution effectiveThicknessCoverageLayer = DerivedPipingInput.GetEffectiveThicknessCoverageLayer(pipingInput, generalInput); LogNormalDistribution saturatedVolumicWeightOfCoverageLayer = DerivedPipingInput.GetSaturatedVolumicWeightOfCoverageLayer(pipingInput); input = new PipingCalculationInput( pipingInput.HydraulicBoundaryLocation.Id, sectionLength, pipingInput.PhreaticLevelExit.Mean, pipingInput.PhreaticLevelExit.StandardDeviation, generalInput.WaterVolumetricWeight, effectiveThicknessCoverageLayer.Mean, effectiveThicknessCoverageLayer.StandardDeviation, saturatedVolumicWeightOfCoverageLayer.Mean, saturatedVolumicWeightOfCoverageLayer.StandardDeviation, saturatedVolumicWeightOfCoverageLayer.Shift, generalInput.UpliftModelFactor.Mean, generalInput.UpliftModelFactor.StandardDeviation, pipingInput.DampingFactorExit.Mean, pipingInput.DampingFactorExit.StandardDeviation, seepageLength.Mean, seepageLength.CoefficientOfVariation, thicknessAquiferLayer.Mean, thicknessAquiferLayer.StandardDeviation, generalInput.SandParticlesVolumicWeight, generalInput.SellmeijerModelFactor.Mean, generalInput.SellmeijerModelFactor.StandardDeviation, generalInput.BeddingAngle, generalInput.WhitesDragCoefficient, generalInput.WaterKinematicViscosity, darcyPermeability.Mean, darcyPermeability.CoefficientOfVariation, diameterD70.Mean, diameterD70.CoefficientOfVariation, generalInput.Gravity, generalInput.CriticalHeaveGradient.Mean, generalInput.CriticalHeaveGradient.StandardDeviation); } HydraRingSettingsDatabaseHelper.AssignSettingsFromDatabase(input, hydraulicBoundaryDatabaseFilePath, usePreprocessor); return input; } private static GeneralResult ConvertIllustrationPointsResult(HydraRingGeneralResult result, string errorMessage) { if (result == null) { log.Warn(errorMessage); return null; } try { GeneralResult generalResult = GeneralResultConverter.ConvertToGeneralResultTopLevelFaultTreeIllustrationPoint(result); return generalResult; } catch (IllustrationPointConversionException e) { log.Warn(RiskeerCommonServiceResources.SetGeneralResult_Converting_IllustrationPointResult_Failed, e); } return null; } private static void LogAnyWarnings(ProbabilisticPipingCalculation calculation) { CalculationServiceHelper.LogMessagesAsWarning(PipingCalculationValidationHelper.GetValidationWarnings(calculation.InputParameters).ToArray()); } private static bool LogAnyErrors(ProbabilisticPipingCalculation calculation, PipingFailureMechanism failureMechanism, IAssessmentSection assessmentSection) { string[] messages = ValidateHydraulicBoundaryDatabase(assessmentSection).ToArray(); if (messages.Length == 0) { messages = ValidateFailureMechanismHasSections(failureMechanism).ToArray(); } if (messages.Length == 0) { messages = ValidateInput(calculation.InputParameters, failureMechanism.GeneralInput).ToArray(); } if (messages.Length == 0) { messages = ValidateCalculationInMultipleSections(calculation, failureMechanism).ToArray(); } if (messages.Length > 0) { CalculationServiceHelper.LogMessagesAsError(messages); return true; } return false; } private static IEnumerable ValidateHydraulicBoundaryDatabase(IAssessmentSection assessmentSection) { string preprocessorDirectory = assessmentSection.HydraulicBoundaryDatabase.EffectivePreprocessorDirectory(); string databaseValidationProblem = HydraulicBoundaryDatabaseConnectionValidator.Validate(assessmentSection.HydraulicBoundaryDatabase); if (!string.IsNullOrEmpty(databaseValidationProblem)) { yield return databaseValidationProblem; } string preprocessorDirectoryValidationProblem = HydraulicBoundaryDatabaseHelper.ValidatePreprocessorDirectory(preprocessorDirectory); if (!string.IsNullOrEmpty(preprocessorDirectoryValidationProblem)) { yield return preprocessorDirectoryValidationProblem; } } private static IEnumerable ValidateFailureMechanismHasSections(PipingFailureMechanism failureMechanism) { if (!failureMechanism.Sections.Any()) { yield return Resources.ProbabilisticPipingCalculationService_ValidateFailureMechanismHasSections_No_sections_imported; } } private static IEnumerable ValidateInput(ProbabilisticPipingInput input, GeneralPipingInput generalInput) { var validationResults = new List(); if (input.HydraulicBoundaryLocation == null) { validationResults.Add(RiskeerCommonServiceResources.CalculationService_ValidateInput_No_hydraulic_boundary_location_selected); } validationResults.AddRange(PipingCalculationValidationHelper.GetValidationErrors(input)); if (!validationResults.Any()) { validationResults.AddRange(ValidateCoverageLayers(input, generalInput)); } return validationResults; } private static IEnumerable ValidateCoverageLayers(PipingInput input, GeneralPipingInput generalInput) { if (!double.IsNaN(DerivedPipingInput.GetThicknessCoverageLayer(input).Mean)) { LogNormalDistribution saturatedVolumicWeightOfCoverageLayer = DerivedPipingInput.GetSaturatedVolumicWeightOfCoverageLayer(input); if (saturatedVolumicWeightOfCoverageLayer.Shift < generalInput.WaterVolumetricWeight) { yield return Resources.ProbabilisticPipingCalculationService_ValidateInput_SaturatedVolumicWeightCoverageLayer_shift_must_be_larger_than_WaterVolumetricWeight; } } } private static IEnumerable ValidateCalculationInMultipleSections(ProbabilisticPipingCalculation calculation, PipingFailureMechanism failureMechanism) { int numberOfSections = failureMechanism .Sections .Count(section => calculation.IsSurfaceLineIntersectionWithReferenceLineInSection(Math2D.ConvertPointsToLineSegments(section.Points))); if (numberOfSections > 1) { yield return Resources.ProbabilisticPipingCalculationService_ValidateCalculationInMultipleSections_Cannot_determine_section_for_calculation; } } private void NotifyProgress(string stepName, int currentStepNumber, int totalStepNumber) { OnProgressChanged?.Invoke(stepName, currentStepNumber, totalStepNumber); } } }