// Copyright (C) Stichting Deltares 2017. 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.Globalization;
using System.IO;
using System.Linq;
using Core.Common.Base.Data;
using Core.Common.Base.IO;
using log4net;
using Ringtoets.Common.Data.Hydraulics;
using Ringtoets.Common.IO.HydraRing;
using Ringtoets.Common.Service;
using Ringtoets.Common.Service.ValidationRules;
using Ringtoets.HydraRing.Calculation.Calculator;
using Ringtoets.HydraRing.Calculation.Calculator.Factory;
using Ringtoets.HydraRing.Calculation.Data.Input.WaveConditions;
using Ringtoets.HydraRing.Calculation.Exceptions;
using Ringtoets.Revetment.Data;
using Ringtoets.Revetment.Service.Properties;
using RingtoetsCommonFormsResources = Ringtoets.Common.Forms.Properties.Resources;
using RingtoetsCommonServiceResources = Ringtoets.Common.Service.Properties.Resources;
namespace Ringtoets.Revetment.Service
{
///
/// Base class for calculating wave conditions for failure mechanisms.
///
public abstract class WaveConditionsCalculationServiceBase
{
private static readonly ILog log = LogManager.GetLogger(typeof(WaveConditionsCalculationServiceBase));
protected int TotalWaterLevelCalculations;
private int currentStep = 1;
private IWaveConditionsCosineCalculator calculator;
///
/// Fired when the calculation progress changed.
///
public event OnProgressChanged OnProgressChanged;
///
/// Cancels any currently running wave conditions calculation.
///
public void Cancel()
{
calculator?.Cancel();
Canceled = true;
}
///
/// Performs validation over the given input parameters. Error and status information is logged
/// during the execution of the operation.
///
/// The input of the calculation.
/// The assessment level to use for determining water levels.
/// The hydraulic boundary database to validate.
/// The norm to validate.
/// true if there were no validation errors; false otherwise.
/// Thrown when or
/// is null.
public static bool Validate(WaveConditionsInput waveConditionsInput,
RoundedDouble assessmentLevel,
HydraulicBoundaryDatabase hydraulicBoundaryDatabase,
double norm)
{
if (waveConditionsInput == null)
{
throw new ArgumentNullException(nameof(waveConditionsInput));
}
if (hydraulicBoundaryDatabase == null)
{
throw new ArgumentNullException(nameof(hydraulicBoundaryDatabase));
}
CalculationServiceHelper.LogValidationBegin();
string[] messages = ValidateInput(hydraulicBoundaryDatabase,
waveConditionsInput,
assessmentLevel,
norm);
CalculationServiceHelper.LogMessagesAsError(messages);
CalculationServiceHelper.LogValidationEnd();
return !messages.Any();
}
///
/// Gets whether the calculation is canceled or not.
///
protected bool Canceled { get; private set; }
///
/// Performs a wave conditions calculation based on the supplied
/// and returns the output. Error and status information is logged during the execution
/// of the operation.
///
/// The that holds all the information
/// required to perform the calculation.
/// The assessment level to use for determining water levels.
/// The 'a' factor decided on failure mechanism level.
/// The 'b' factor decided on failure mechanism level.
/// The 'c' factor decided on failure mechanism level.
/// The norm to use as the target.
/// The file path of the hydraulic boundary database.
/// The preprocessor directory.
/// An of .
/// Preprocessing is disabled when equals .
/// Thrown when ,
/// or
/// is null.
/// 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.
///
///
/// Thrown when an error occurs during
/// the calculations.
protected IEnumerable CalculateWaveConditions(WaveConditionsInput waveConditionsInput,
RoundedDouble assessmentLevel,
RoundedDouble a,
RoundedDouble b,
RoundedDouble c,
double norm,
string hydraulicBoundaryDatabaseFilePath,
string preprocessorDirectory)
{
if (waveConditionsInput == null)
{
throw new ArgumentNullException(nameof(waveConditionsInput));
}
var calculationsFailed = 0;
var outputs = new List();
RoundedDouble[] waterLevels = waveConditionsInput.GetWaterLevels(assessmentLevel).ToArray();
foreach (RoundedDouble waterLevel in waterLevels.TakeWhile(waterLevel => !Canceled))
{
try
{
log.Info(string.Format(CultureInfo.CurrentCulture,
Resources.WaveConditionsCalculationService_OnRun_Calculation_for_waterlevel_0_started,
waterLevel));
NotifyProgress(waterLevel, currentStep++, TotalWaterLevelCalculations);
WaveConditionsOutput output = CalculateWaterLevel(waterLevel,
a,
b,
c,
norm,
waveConditionsInput,
hydraulicBoundaryDatabaseFilePath,
preprocessorDirectory);
if (output != null)
{
outputs.Add(output);
}
else
{
calculationsFailed++;
outputs.Add(WaveConditionsOutputFactory.CreateFailedOutput(waterLevel, norm));
}
}
finally
{
log.Info(string.Format(CultureInfo.CurrentCulture,
Resources.WaveConditionsCalculationService_OnRun_Calculation_for_waterlevel_0_ended,
waterLevel));
}
}
if (calculationsFailed == waterLevels.Length)
{
string message = string.Format(CultureInfo.CurrentCulture,
Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_for_all_waterlevels);
log.Error(message);
throw new HydraRingCalculationException(message);
}
return outputs;
}
private static string[] ValidateInput(HydraulicBoundaryDatabase hydraulicBoundaryDatabase,
WaveConditionsInput input,
RoundedDouble assessmentLevel,
double norm)
{
var validationResults = new List();
string databaseFilePathValidationProblem = HydraulicBoundaryDatabaseConnectionValidator.Validate(hydraulicBoundaryDatabase);
if (!string.IsNullOrEmpty(databaseFilePathValidationProblem))
{
validationResults.Add(databaseFilePathValidationProblem);
}
string preprocessorDirectoryValidationProblem = HydraulicBoundaryDatabaseHelper.ValidatePreprocessorDirectory(hydraulicBoundaryDatabase.EffectivePreprocessorDirectory());
if (!string.IsNullOrEmpty(preprocessorDirectoryValidationProblem))
{
validationResults.Add(preprocessorDirectoryValidationProblem);
}
TargetProbabilityCalculationServiceHelper.ValidateTargetProbability(norm, message => validationResults.Add(message));
if (validationResults.Any())
{
return validationResults.ToArray();
}
validationResults.AddRange(ValidateWaveConditionsInput(input, assessmentLevel));
return validationResults.ToArray();
}
private void NotifyProgress(RoundedDouble waterLevel, int currentStepNumber, int totalStepsNumber)
{
string message = string.Format(Resources.WaveConditionsCalculationService_OnRun_Calculate_for_waterlevel_0_, waterLevel);
OnProgressChanged?.Invoke(message, currentStepNumber, totalStepsNumber);
}
///
/// Calculates values for a single water level.
///
/// The level of the water.
/// The 'a' factor decided on failure mechanism level.
/// The 'b' factor decided on failure mechanism level.
/// The 'c' factor decided on failure mechanism level.
/// The norm to use as the target.
/// The input that is different per calculation.
/// The path which points to the hydraulic boundary database file.
/// The preprocessor directory.
/// A if the calculation was successful; or null if it was canceled.
/// Preprocessing is disabled when equals .
/// Thrown when or
/// is null.
/// 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 WaveConditionsOutput CalculateWaterLevel(RoundedDouble waterLevel,
RoundedDouble a,
RoundedDouble b,
RoundedDouble c,
double norm,
WaveConditionsInput input,
string hydraulicBoundaryDatabaseFilePath,
string preprocessorDirectory)
{
string hlcdDirectory = Path.GetDirectoryName(hydraulicBoundaryDatabaseFilePath);
calculator = HydraRingCalculatorFactory.Instance.CreateWaveConditionsCosineCalculator(hlcdDirectory, preprocessorDirectory);
WaveConditionsCosineCalculationInput calculationInput = CreateInput(waterLevel, a, b, c, norm, input, hydraulicBoundaryDatabaseFilePath,
!string.IsNullOrEmpty(preprocessorDirectory));
WaveConditionsOutput output;
var exceptionThrown = false;
try
{
calculator.Calculate(calculationInput);
output = WaveConditionsOutputFactory.CreateOutput(waterLevel,
calculator.WaveHeight,
calculator.WavePeakPeriod,
calculator.WaveAngle,
calculator.WaveDirection,
norm,
calculator.ReliabilityIndex,
calculator.Converged);
}
catch (Exception e) when (e is HydraRingCalculationException || e is ArgumentOutOfRangeException)
{
if (!Canceled)
{
string lastErrorContent = calculator.LastErrorFileContent;
if (string.IsNullOrEmpty(lastErrorContent))
{
log.ErrorFormat(CultureInfo.CurrentCulture,
Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_for_waterlevel_0_no_error_report,
waterLevel);
}
else
{
log.ErrorFormat(CultureInfo.CurrentCulture,
Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_for_waterlevel_0_click_details_for_last_error_report_1,
waterLevel,
lastErrorContent);
}
exceptionThrown = true;
}
output = null;
}
finally
{
string lastErrorFileContent = calculator.LastErrorFileContent;
bool errorOccurred = CalculationServiceHelper.HasErrorOccurred(Canceled, exceptionThrown, lastErrorFileContent);
if (errorOccurred)
{
log.ErrorFormat(CultureInfo.CurrentCulture,
Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_for_waterlevel_0_click_details_for_last_error_report_1,
waterLevel,
lastErrorFileContent);
}
log.InfoFormat(CultureInfo.CurrentCulture,
Resources.WaveConditionsCalculationService_CalculateWaterLevel_Calculation_temporary_directory_can_be_found_on_location_0,
calculator.OutputDirectory);
if (errorOccurred)
{
output = null;
}
}
return output;
}
///
/// Creates the input for a calculation for the given .
///
/// The level of the water.
/// The 'a' factor decided on failure mechanism level.
/// The 'b' factor decided on failure mechanism level.
/// The 'c' factor decided on failure mechanism level.
/// The norm to use as the target.
/// The input that is different per calculation.
/// The path to the hydraulic boundary database file.
/// Indicator whether to use the preprocessor in the calculation.
/// 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 WaveConditionsCosineCalculationInput CreateInput(RoundedDouble waterLevel,
RoundedDouble a,
RoundedDouble b,
RoundedDouble c,
double norm,
WaveConditionsInput input,
string hydraulicBoundaryDatabaseFilePath,
bool usePreprocessor)
{
var waveConditionsCosineCalculationInput = new WaveConditionsCosineCalculationInput(
1,
input.Orientation,
input.HydraulicBoundaryLocation.Id,
norm,
HydraRingInputParser.ParseForeshore(input),
HydraRingInputParser.ParseBreakWater(input),
waterLevel,
a,
b,
c);
HydraRingSettingsDatabaseHelper.AssignSettingsFromDatabase(waveConditionsCosineCalculationInput, hydraulicBoundaryDatabaseFilePath, usePreprocessor);
return waveConditionsCosineCalculationInput;
}
private static IEnumerable ValidateWaveConditionsInput(WaveConditionsInput input,
RoundedDouble assessmentLevel)
{
var messages = new List();
if (input.HydraulicBoundaryLocation == null)
{
messages.Add(RingtoetsCommonServiceResources.CalculationService_ValidateInput_No_hydraulic_boundary_location_selected);
}
else if (double.IsNaN(assessmentLevel))
{
messages.Add(Resources.WaveConditionsCalculationService_ValidateInput_No_AssessmentLevel_calculated);
}
else
{
if (!input.GetWaterLevels(assessmentLevel).Any())
{
messages.Add(Resources.WaveConditionsCalculationService_ValidateInput_No_derived_WaterLevels);
}
messages.AddRange(new UseBreakWaterRule(input).Validate());
messages.AddRange(new NumericInputRule(input.Orientation, ParameterNameExtractor.GetFromDisplayName(RingtoetsCommonFormsResources.Orientation_DisplayName)).Validate());
}
return messages;
}
}
}