// Copyright (C) Stichting Deltares 2016. All rights reserved.
//
// This file is part of Ringtoets.
//
// Ringtoets 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.ComponentModel;
using System.IO;
using System.Linq;
using System.Security;
using Core.Common.Base.IO;
using Core.Common.Utils;
using log4net;
using Ringtoets.Common.Data.AssessmentSection;
using Ringtoets.Common.Data.DikeProfiles;
using Ringtoets.Common.Data.Hydraulics;
using Ringtoets.Common.IO.HydraRing;
using Ringtoets.Common.Service;
using Ringtoets.Common.Service.ValidationRules;
using Ringtoets.GrassCoverErosionInwards.Data;
using Ringtoets.GrassCoverErosionInwards.Service.Properties;
using Ringtoets.HydraRing.Calculation.Calculator;
using Ringtoets.HydraRing.Calculation.Calculator.Factory;
using Ringtoets.HydraRing.Calculation.Data.Input;
using Ringtoets.HydraRing.Calculation.Data.Input.Hydraulics;
using Ringtoets.HydraRing.Calculation.Data.Input.Overtopping;
using Ringtoets.HydraRing.Calculation.Exceptions;
using RingtoetsCommonServiceResources = Ringtoets.Common.Service.Properties.Resources;
using RingtoetsCommonForms = Ringtoets.Common.Forms.Properties.Resources;
namespace Ringtoets.GrassCoverErosionInwards.Service
{
///
/// Service that provides methods for performing Hydra-Ring calculations for grass cover erosion inwards calculations.
///
public class GrassCoverErosionInwardsCalculationService
{
private static readonly ILog log = LogManager.GetLogger(typeof(GrassCoverErosionInwardsCalculationService));
public OnProgressChanged OnProgress;
private IOvertoppingCalculator overtoppingCalculator;
private IDikeHeightCalculator dikeHeightCalculator;
private bool canceled;
///
/// 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.
/// True if has no validation errors; False otherwise.
public static bool Validate(GrassCoverErosionInwardsCalculation calculation, IAssessmentSection assessmentSection)
{
CalculationServiceHelper.LogValidationBeginTime(calculation.Name);
string[] messages = ValidateInput(calculation.InputParameters, assessmentSection);
CalculationServiceHelper.LogMessagesAsError(RingtoetsCommonServiceResources.Error_in_validation_0, messages);
CalculationServiceHelper.LogValidationEndTime(calculation.Name);
return !messages.Any();
}
///
/// Cancels any currently running grass cover erosion inwards calculation.
///
public void Cancel()
{
overtoppingCalculator?.Cancel();
dikeHeightCalculator?.Cancel();
canceled = true;
}
///
/// Performs a grass cover erosion inwards 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 that holds information about the norm used in the calculation.
/// Calculation input parameters that apply to all instances.
/// The amount of contribution for this failure mechanism in the assessment section.
/// The path which points to the hydraulic boundary database file.
/// Thrown when one of the following parameter is null:
///
///
///
///
///
/// Thrown when:
///
/// - The contains invalid characters.
/// - The contribution of the failure mechanism is zero.
/// - The target probability or the calculated probability of a dike height calculation falls outside
/// the [0.0, 1.0] range and is not .
///
/// Thrown when:
///
/// - No settings database file could be found at the location of
/// with the same name.
/// - Unable to open settings database file.
/// - Unable to read required data from database file.
///
/// Thrown when the temporary working directory can't be accessed due to missing permissions.
/// Thrown when the specified path is not valid or the network name is not known
/// or an I/O error occurred while opening the file
/// Thrown when the directory can't be created due to missing
/// the required permissions.
/// Thrown when
/// is not the same with already added input.
/// Thrown when there was an error in opening the associated file
/// or the wait setting could not be accessed.
/// Thrown when an error occurs during parsing of the Hydra-Ring output.
/// Thrown when an error occurs during the calculation.
internal void Calculate(GrassCoverErosionInwardsCalculation calculation,
IAssessmentSection assessmentSection,
GeneralGrassCoverErosionInwardsInput generalInput,
double failureMechanismContribution,
string hydraulicBoundaryDatabaseFilePath)
{
if (calculation == null)
{
throw new ArgumentNullException(nameof(calculation));
}
if (assessmentSection == null)
{
throw new ArgumentNullException(nameof(assessmentSection));
}
if (generalInput == null)
{
throw new ArgumentNullException(nameof(generalInput));
}
string hlcdDirectory = Path.GetDirectoryName(hydraulicBoundaryDatabaseFilePath);
bool calculateDikeHeight = calculation.InputParameters.DikeHeightCalculationType != DikeHeightCalculationType.NoCalculation;
int totalSteps = calculateDikeHeight ? 2 : 1;
string calculationName = calculation.Name;
NotifyProgress(Resources.GrassCoverErosionInwardsCalculationService_Calculate_Executing_overtopping_calculation, 1, totalSteps);
CalculationServiceHelper.LogCalculationBeginTime(calculationName);
overtoppingCalculator = HydraRingCalculatorFactory.Instance.CreateOvertoppingCalculator(hlcdDirectory);
OvertoppingCalculationInput overtoppingCalculationInput = CreateOvertoppingInput(calculation, generalInput, hydraulicBoundaryDatabaseFilePath);
DikeHeightAssessmentOutput dikeHeight = null;
try
{
CalculateOvertopping(overtoppingCalculationInput, calculationName);
if (canceled)
{
return;
}
if (calculateDikeHeight)
{
NotifyProgress(Resources.GrassCoverErosionInwardsCalculationService_Calculate_Executing_dikeheight_calculation, 2, totalSteps);
dikeHeightCalculator = HydraRingCalculatorFactory.Instance.CreateDikeHeightCalculator(hlcdDirectory);
double norm = GetProbabilityToUse(assessmentSection.FailureMechanismContribution.Norm,
generalInput, failureMechanismContribution,
calculation.InputParameters.DikeHeightCalculationType);
DikeHeightCalculationInput dikeHeightCalculationInput = CreateDikeHeightInput(calculation, norm,
generalInput,
hydraulicBoundaryDatabaseFilePath);
bool dikeHeightCalculated = CalculateDikeHeight(dikeHeightCalculationInput, calculationName);
if (canceled)
{
return;
}
if (dikeHeightCalculated)
{
dikeHeight = CreateDikeHeightAssessmentOutput(calculationName, dikeHeightCalculationInput.Beta, norm);
}
}
calculation.Output = new GrassCoverErosionInwardsOutput(
overtoppingCalculator.WaveHeight,
overtoppingCalculator.IsOvertoppingDominant,
ProbabilityAssessmentService.Calculate(
assessmentSection.FailureMechanismContribution.Norm,
failureMechanismContribution,
generalInput.N,
overtoppingCalculator.ExceedanceProbabilityBeta),
dikeHeight,
null);
}
finally
{
CalculationServiceHelper.LogCalculationEndTime(calculationName);
}
}
///
/// Create the output of a dike height calculation.
///
/// The name of the calculation.
/// The target reliability for the calculation.
/// The target probability for the calculation.
/// A .
/// Thrown when
/// or the calculated probability falls outside the [0.0, 1.0] range and is not .
private DikeHeightAssessmentOutput CreateDikeHeightAssessmentOutput(string calculationName,
double targetReliability,
double targetProbability)
{
double dikeHeight = dikeHeightCalculator.DikeHeight;
double reliability = dikeHeightCalculator.ReliabilityIndex;
double probability = StatisticsConverter.ReliabilityToProbability(reliability);
CalculationConvergence converged = RingtoetsCommonDataCalculationService.GetCalculationConvergence(dikeHeightCalculator.Converged);
if (converged != CalculationConvergence.CalculatedConverged)
{
log.Warn(string.Format(Resources.GrassCoverErosionInwardsCalculationService_DikeHeight_calculation_for_calculation_0_not_converged, calculationName));
}
return new DikeHeightAssessmentOutput(dikeHeight, targetProbability,
targetReliability, probability, reliability,
converged);
}
private static double GetProbabilityToUse(double assessmentSectionNorm, GeneralGrassCoverErosionInwardsInput generalInput,
double failureMechanismContribution, DikeHeightCalculationType calculateDikeHeight)
{
return calculateDikeHeight == DikeHeightCalculationType.CalculateByAssessmentSectionNorm
? assessmentSectionNorm
: RingtoetsCommonDataCalculationService.ProfileSpecificRequiredProbability(
assessmentSectionNorm,
failureMechanismContribution,
generalInput.N);
}
private void NotifyProgress(string stepName, int currentStepNumber, int totalStepNumber)
{
OnProgress?.Invoke(stepName, currentStepNumber, totalStepNumber);
}
///
/// Performs an overtopping calculation.
///
/// The input of the calculation.
/// The name of the calculation.
/// Thrown when an error occurs while performing the calculation.
private void CalculateOvertopping(OvertoppingCalculationInput overtoppingCalculationInput, string calculationName)
{
var exceptionThrown = false;
try
{
overtoppingCalculator.Calculate(overtoppingCalculationInput);
}
catch (HydraRingCalculationException)
{
if (!canceled)
{
string lastErrorContent = overtoppingCalculator.LastErrorFileContent;
if (string.IsNullOrEmpty(lastErrorContent))
{
log.ErrorFormat(Resources.GrassCoverErosionInwardsCalculationService_Calculate_Error_in_grass_cover_erosion_inwards_0_calculation_no_error_report, calculationName);
}
else
{
log.ErrorFormat(Resources.GrassCoverErosionInwardsCalculationService_Calculate_Error_in_grass_cover_erosion_inwards_0_calculation_click_details_for_last_error_report_1, calculationName, lastErrorContent);
}
exceptionThrown = true;
throw;
}
}
finally
{
string lastErrorFileContent = overtoppingCalculator.LastErrorFileContent;
bool errorOccurred = CalculationServiceHelper.HasErrorOccurred(canceled, exceptionThrown, lastErrorFileContent);
if (errorOccurred)
{
log.ErrorFormat(Resources.GrassCoverErosionInwardsCalculationService_Calculate_Error_in_grass_cover_erosion_inwards_0_calculation_click_details_for_last_error_report_1, calculationName, lastErrorFileContent);
}
log.InfoFormat(Resources.GrassCoverErosionInwardsCalculationService_CalculateOvertopping_calculation_temporary_directory_can_be_found_on_location_0, overtoppingCalculator.OutputDirectory);
if (errorOccurred)
{
throw new HydraRingCalculationException(lastErrorFileContent);
}
}
}
///
/// Performs the dike height calculation.
///
/// The input of the dike height calculation.
/// The name of the calculation.
/// True when the calculation was successful. False otherwise.
private bool CalculateDikeHeight(DikeHeightCalculationInput dikeHeightCalculationInput, string calculationName)
{
var exceptionThrown = false;
var dikeHeightCalculated = false;
if (!canceled)
{
try
{
dikeHeightCalculator.Calculate(dikeHeightCalculationInput);
}
catch (HydraRingCalculationException)
{
if (!canceled)
{
string lastErrorContent = dikeHeightCalculator.LastErrorFileContent;
if (string.IsNullOrEmpty(lastErrorContent))
{
log.ErrorFormat(Resources.GrassCoverErosionInwardsCalculationService_Calculate_Error_in_hbn_grass_cover_erosion_inwards_0_calculation_no_error_report, calculationName);
}
else
{
log.ErrorFormat(Resources.GrassCoverErosionInwardsCalculationService_Calculate_Error_in_hbn_grass_cover_erosion_inwards_0_calculation_click_details_for_last_error_report_1, calculationName, lastErrorContent);
}
exceptionThrown = true;
}
}
finally
{
string lastErrorFileContent = dikeHeightCalculator.LastErrorFileContent;
if (CalculationServiceHelper.HasErrorOccurred(canceled, exceptionThrown, lastErrorFileContent))
{
log.ErrorFormat(Resources.GrassCoverErosionInwardsCalculationService_Calculate_Error_in_hbn_grass_cover_erosion_inwards_0_calculation_click_details_for_last_error_report_1, calculationName, lastErrorFileContent);
}
if (!exceptionThrown && string.IsNullOrEmpty(lastErrorFileContent))
{
dikeHeightCalculated = true;
}
log.InfoFormat(Resources.GrassCoverErosionInwardsCalculationService_CalculateDikeHeight_calculation_temporary_directory_can_be_found_on_location_0, dikeHeightCalculator.OutputDirectory);
}
}
return dikeHeightCalculated;
}
///
/// Creates the input for an overtopping calculation.
///
/// The that holds all the information required to perform the calculation.
/// Calculation input parameters that apply to all instances.
/// The path to the hydraulic boundary database file.
/// An .
/// Thrown when the
/// contains invalid characters.
/// Thrown when:
///
/// - No settings database file could be found at the location of
/// with the same name.
/// - Unable to open settings database file.
/// - Unable to read required data from database file.
///
///
private static OvertoppingCalculationInput CreateOvertoppingInput(GrassCoverErosionInwardsCalculation calculation,
GeneralGrassCoverErosionInwardsInput generalInput,
string hydraulicBoundaryDatabaseFilePath)
{
var overtoppingCalculationInput = new OvertoppingCalculationInput(calculation.InputParameters.HydraulicBoundaryLocation.Id,
calculation.InputParameters.Orientation,
ParseProfilePoints(calculation.InputParameters.DikeGeometry),
HydraRingInputParser.ParseForeshore(calculation.InputParameters),
HydraRingInputParser.ParseBreakWater(calculation.InputParameters),
calculation.InputParameters.DikeHeight,
generalInput.CriticalOvertoppingModelFactor,
generalInput.FbFactor.Mean,
generalInput.FbFactor.StandardDeviation,
generalInput.FbFactor.LowerBoundary,
generalInput.FbFactor.UpperBoundary,
generalInput.FnFactor.Mean,
generalInput.FnFactor.StandardDeviation,
generalInput.FnFactor.LowerBoundary,
generalInput.FnFactor.UpperBoundary,
generalInput.OvertoppingModelFactor,
calculation.InputParameters.CriticalFlowRate.Mean,
calculation.InputParameters.CriticalFlowRate.StandardDeviation,
generalInput.FrunupModelFactor.Mean,
generalInput.FrunupModelFactor.StandardDeviation,
generalInput.FrunupModelFactor.LowerBoundary,
generalInput.FrunupModelFactor.UpperBoundary,
generalInput.FshallowModelFactor.Mean,
generalInput.FshallowModelFactor.StandardDeviation,
generalInput.FshallowModelFactor.LowerBoundary,
generalInput.FshallowModelFactor.UpperBoundary);
HydraRingSettingsDatabaseHelper.AssignSettingsFromDatabase(overtoppingCalculationInput, hydraulicBoundaryDatabaseFilePath);
return overtoppingCalculationInput;
}
///
/// Creates the input for a dike height calculation.
///
/// The that holds all the information required to perform the calculation.
/// The norm to use in the calculation.
/// Calculation input parameters that apply to all instances.
/// The path to the hydraulic boundary database file.
/// A .
/// Thrown when the
/// contains invalid characters.
/// Thrown when:
///
/// - No settings database file could be found at the location of
/// with the same name.
/// - Unable to open settings database file.
/// - Unable to read required data from database file.
///
///
private static DikeHeightCalculationInput CreateDikeHeightInput(GrassCoverErosionInwardsCalculation calculation,
double norm,
GeneralGrassCoverErosionInwardsInput generalInput,
string hydraulicBoundaryDatabaseFilePath)
{
var dikeHeightCalculationInput = new DikeHeightCalculationInput(calculation.InputParameters.HydraulicBoundaryLocation.Id,
norm,
calculation.InputParameters.Orientation,
ParseProfilePoints(calculation.InputParameters.DikeGeometry),
HydraRingInputParser.ParseForeshore(calculation.InputParameters),
HydraRingInputParser.ParseBreakWater(calculation.InputParameters),
generalInput.CriticalOvertoppingModelFactor,
generalInput.FbFactor.Mean,
generalInput.FbFactor.StandardDeviation,
generalInput.FbFactor.LowerBoundary,
generalInput.FbFactor.UpperBoundary,
generalInput.FnFactor.Mean,
generalInput.FnFactor.StandardDeviation,
generalInput.FnFactor.LowerBoundary,
generalInput.FnFactor.UpperBoundary,
generalInput.OvertoppingModelFactor,
calculation.InputParameters.CriticalFlowRate.Mean,
calculation.InputParameters.CriticalFlowRate.StandardDeviation,
generalInput.FrunupModelFactor.Mean,
generalInput.FrunupModelFactor.StandardDeviation,
generalInput.FrunupModelFactor.LowerBoundary,
generalInput.FrunupModelFactor.UpperBoundary,
generalInput.FshallowModelFactor.Mean,
generalInput.FshallowModelFactor.StandardDeviation,
generalInput.FshallowModelFactor.LowerBoundary,
generalInput.FshallowModelFactor.UpperBoundary);
HydraRingSettingsDatabaseHelper.AssignSettingsFromDatabase(dikeHeightCalculationInput, hydraulicBoundaryDatabaseFilePath);
return dikeHeightCalculationInput;
}
private static IEnumerable ParseProfilePoints(RoughnessPoint[] roughnessProfilePoints)
{
return roughnessProfilePoints.Select(roughnessPoint => new HydraRingRoughnessProfilePoint(roughnessPoint.Point.X, roughnessPoint.Point.Y, roughnessPoint.Roughness));
}
private static string[] ValidateInput(GrassCoverErosionInwardsInput inputParameters, IAssessmentSection assessmentSection)
{
var validationResult = new List();
string validationProblem = HydraulicDatabaseHelper.ValidatePathForCalculation(assessmentSection.HydraulicBoundaryDatabase.FilePath);
if (!string.IsNullOrEmpty(validationProblem))
{
validationResult.Add(validationProblem);
return validationResult.ToArray();
}
if (inputParameters.HydraulicBoundaryLocation == null)
{
validationResult.Add(RingtoetsCommonServiceResources.CalculationService_ValidateInput_No_hydraulic_boundary_location_selected);
}
if (inputParameters.DikeProfile == null)
{
validationResult.Add(RingtoetsCommonServiceResources.CalculationService_ValidateInput_No_dike_profile_selected);
}
else
{
validationResult.AddRange(new NumericInputRule(inputParameters.Orientation, ParameterNameExtractor.GetFromDisplayName(RingtoetsCommonForms.Orientation_DisplayName)).Validate());
validationResult.AddRange(new NumericInputRule(inputParameters.DikeHeight, ParameterNameExtractor.GetFromDisplayName(RingtoetsCommonForms.DikeHeight_DisplayName)).Validate());
}
validationResult.AddRange(new UseBreakWaterRule(inputParameters).Validate());
return validationResult.ToArray();
}
}
}