// 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.Linq; using Core.Common.Base; using Core.Common.Base.Data; using Core.Common.Util.Extensions; using Ringtoets.Common.IO.Properties; namespace Ringtoets.Common.IO.Structures { /// /// This class contains validations methods for objects. /// public static class StructuresParameterRowsValidator { /// /// Denotes a small enough value, taking possible rounding into account, that the /// value is too close to the value 0.0 that makes a coefficient of variation /// too unreliable. /// private const double valueTooCloseToZero = 1e-4; private static readonly List closingStructureInflowModelTypeRuleKeywords = new List { StructureFilesKeywords.InflowModelTypeVerticalWall, StructureFilesKeywords.InflowModelTypeLowSill, StructureFilesKeywords.InflowModelTypeFloodedCulvert }; private static readonly List stabilityPointStructureInflowModelTypeRuleKeywords = new List { StructureFilesKeywords.InflowModelTypeLowSill, StructureFilesKeywords.InflowModelTypeFloodedCulvert }; private static readonly Dictionary>> heightStructuresRules = new Dictionary>> { { StructureFilesKeywords.HeightStructureParameterKeyword1, StructureNormalOrientation }, { StructureFilesKeywords.HeightStructureParameterKeyword2, NormalDistributionRule }, { StructureFilesKeywords.HeightStructureParameterKeyword3, LogNormalDistributionRule }, { StructureFilesKeywords.HeightStructureParameterKeyword4, VariationCoefficientLogNormalDistributionRule }, { StructureFilesKeywords.HeightStructureParameterKeyword5, VariationCoefficientNormalDistributionRule }, { StructureFilesKeywords.HeightStructureParameterKeyword6, ProbabilityRule }, { StructureFilesKeywords.HeightStructureParameterKeyword7, VariationCoefficientLogNormalDistributionRule }, { StructureFilesKeywords.HeightStructureParameterKeyword8, LogNormalDistributionRule } }; private static readonly Dictionary>> closingStructuresRules = new Dictionary>> { { StructureFilesKeywords.ClosingStructureParameterKeyword1, VariationCoefficientLogNormalDistributionRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword2, LogNormalDistributionRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword3, StructureNormalOrientation }, { StructureFilesKeywords.ClosingStructureParameterKeyword4, VariationCoefficientNormalDistributionRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword5, NormalDistributionRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword6, NormalDistributionRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword7, NormalDistributionRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword8, LogNormalDistributionRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword9, VariationCoefficientLogNormalDistributionRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword10, LogNormalDistributionRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword11, ProbabilityRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword12, ProbabilityRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword13, IdenticalApertures }, { StructureFilesKeywords.ClosingStructureParameterKeyword14, ProbabilityRule }, { StructureFilesKeywords.ClosingStructureParameterKeyword15, ClosingStructureInflowModelTypeRule } }; private static readonly Dictionary>> stabilityPointStructuresRules = new Dictionary>> { { StructureFilesKeywords.StabilityPointStructureParameterKeyword1, StructureNormalOrientation }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword2, VariationCoefficientLogNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword3, LogNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword4, VariationCoefficientNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword5, NormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword6, NormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword7, VariationCoefficientLogNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword8, LogNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword9, VariationCoefficientLogNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword10, VariationCoefficientLogNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword11, NormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword12, NormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword13, DoubleRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword14, NormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword15, PositiveDoubleRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword16, ProbabilityRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword17, LogNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword18, VariationCoefficientNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword19, VariationCoefficientNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword20, PositiveIntRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword21, ProbabilityRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword22, NormalDistributionMeanRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword23, VariationCoefficientLogNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword24, VariationCoefficientLogNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword25, LogNormalDistributionRule }, { StructureFilesKeywords.StabilityPointStructureParameterKeyword26, StabilityPointStructureInflowModelTypeRule } }; private static readonly Range meanValidityRange = new Range(0, 1); private static readonly Range orientationValidityRange = new Range(0, 360); /// /// Validates a collection of for a height structure. /// /// The objects to validate. /// A object containing the validation result. /// Thrown when is null. public static ValidationResult ValidateHeightStructuresParameters(IEnumerable structureParameterRows) { return ValidateStructuresParameters(structureParameterRows, heightStructuresRules); } /// /// Validates a collection of for a closing structure. /// /// The objects to validate. /// A object containing the validation result. /// Thrown when is null. public static ValidationResult ValidateClosingStructuresParameters(IEnumerable structureParameterRows) { return ValidateStructuresParameters(structureParameterRows, closingStructuresRules); } /// /// Validates a collection of for a stability point structure. /// /// The objects to validate. /// A object containing the validation result. /// Thrown when is null. public static ValidationResult ValidateStabilityPointStructuresParameters(IEnumerable structureParameterRows) { return ValidateStructuresParameters(structureParameterRows, stabilityPointStructuresRules); } /// /// Gets the relevant parameters for a height structure from a collection of . /// /// The collection of to /// retrieve the relevant parameters from. /// A collection of that are relevant /// for a height structure. /// Thrown when /// is null. /// Thrown when /// contains duplicate elements. public static IEnumerable GetRelevantHeightStructuresParameters(IEnumerable structureParameterRows) { return GetStructuresParameters(structureParameterRows, heightStructuresRules).ToArray(); } /// /// Gets the relevant parameters for a closing structure from a collection of . /// /// The collection of to /// retrieve the relevant parameters from. /// A collection of that are relevant /// for a closing structure. /// Thrown when /// is null. /// Thrown when /// contains duplicate elements. public static IEnumerable GetRelevantClosingStructuresParameters(IEnumerable structureParameterRows) { return GetStructuresParameters(structureParameterRows, closingStructuresRules).ToArray(); } /// /// Gets the relevant parameters for a stability point structure from a collection of . /// /// The collection of to /// retrieve the relevant parameters from. /// A collection of that are relevant /// for a stability point structure. /// Thrown when /// is null. /// Thrown when /// contains duplicate elements. public static IEnumerable GetRelevantStabilityPointStructuresParameters(IEnumerable structureParameterRows) { return GetStructuresParameters(structureParameterRows, stabilityPointStructuresRules).ToArray(); } /// /// Retrieves all the relevant structure parameters from the /// based on given . /// /// The structure parameters which need to be filtered. /// The rules that determine which parameters should be retrieved. /// An with /// based on the . /// Thrown when /// is null. /// Thrown when /// contains duplicate elements. private static IEnumerable GetStructuresParameters(IEnumerable structureParameterRows, Dictionary>> rules) { if (structureParameterRows == null) { throw new ArgumentNullException(nameof(structureParameterRows)); } foreach (string parameterName in rules.Keys) { int count = structureParameterRows.Count(row => GetMatchingStructuresParameterRow(row.ParameterId, parameterName)); if (count > 1) { string exceptionMessage = string.Format(Resources.StructuresParameterRowsValidator_Parameter_0_repeated, parameterName); throw new ArgumentException(exceptionMessage); } if (count == 1) { yield return structureParameterRows.Single(row => GetMatchingStructuresParameterRow(row.ParameterId, parameterName)); } } } /// /// Validates the relevant parameters in /// based on rules. /// /// The /// which need to be validated. /// The rules to be used for the validation. /// A containing the validation result. /// Thrown when /// is null. private static ValidationResult ValidateStructuresParameters(IEnumerable structureParameterRows, Dictionary>> rules) { if (structureParameterRows == null) { throw new ArgumentNullException(nameof(structureParameterRows)); } var errorMessages = new List(); var parametersMissing = 0; foreach (string name in rules.Keys) { int count = structureParameterRows.Count(row => GetMatchingStructuresParameterRow(row.ParameterId, name)); if (count < 1) { parametersMissing++; continue; } if (count > 1) { errorMessages.Add(string.Format(Resources.StructuresParameterRowsValidator_Parameter_0_repeated, name)); continue; } List validationMessages = rules[name](structureParameterRows.First( row => GetMatchingStructuresParameterRow(row.ParameterId, name))); if (validationMessages.Count > 0) { errorMessages.AddRange(validationMessages); } } if (parametersMissing == rules.Count) { errorMessages.Add(Resources.StructuresParameterRowsValidator_ValidateStructuresParameters_No_parameters_found); } return new ValidationResult(errorMessages); } private static bool GetMatchingStructuresParameterRow(string parameterId, string parameterName) { return string.Equals(parameterId, parameterName, StringComparison.OrdinalIgnoreCase); } private static List DoubleRule(StructuresParameterRow row) { return ValidateDoubleParameter(row, StructureFilesKeywords.NumericalValueColumnName); } private static List PositiveDoubleRule(StructuresParameterRow row) { return ValidatePositiveDoubleParameter(row, StructureFilesKeywords.NumericalValueColumnName); } private static List PositiveIntRule(StructuresParameterRow row) { return ValidatePositiveIntParameter(row, StructureFilesKeywords.NumericalValueColumnName); } private static List ValidateDoubleParameter(StructuresParameterRow row, string columnName) { var messages = new List(); double value = GetValueFromRowForColumn(row, columnName); if (double.IsNaN(value) || double.IsInfinity(value)) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ValidateDoubleParameter_ParameterId_0_Line_1_ColumnName_2_not_number, row.ParameterId, row.LineNumber, columnName.FirstToUpper())); } return messages; } private static List ValidatePositiveDoubleParameter(StructuresParameterRow row, string columnName) { var messages = new List(); double value = GetValueFromRowForColumn(row, columnName); if (double.IsNaN(value) || double.IsInfinity(value) || value < 0) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ValidatePositiveDoubleParameter_ParameterId_0_Line_1_ColumnName_2_must_be_a_positive_number, row.ParameterId, row.LineNumber, columnName.FirstToUpper())); } return messages; } private static List ValidatePositiveIntParameter(StructuresParameterRow row, string columnName) { var messages = new List(); double value = GetValueFromRowForColumn(row, columnName); if (!IsValueWholeNumber(value) || value < 0) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ParameterId_0_Line_1_ColumnName_2_value_must_be_positive_whole_number, row.ParameterId, row.LineNumber, columnName.FirstToUpper())); } return messages; } private static List ValidateGreaterThanZeroDoubleParameter(StructuresParameterRow row, string columnName) { var messages = new List(); double value = GetValueFromRowForColumn(row, columnName); if (double.IsNaN(value) || double.IsInfinity(value) || value <= 0) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ValidatePositiveDoubleParameter_ParameterId_0_Line_1_ColumnName_2_must_be_greater_than_zero, row.ParameterId, row.LineNumber, columnName.FirstToUpper())); } return messages; } private static double GetValueFromRowForColumn(StructuresParameterRow row, string columnName) { switch (columnName) { case StructureFilesKeywords.NumericalValueColumnName: return row.NumericalValue; case StructureFilesKeywords.VariationValueColumnName: return row.VarianceValue; default: throw new NotSupportedException(); } } private static List ProbabilityRule(StructuresParameterRow row) { var messages = new List(); double mean = row.NumericalValue; if (!meanValidityRange.InRange(mean)) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ProbabilityRule_ParameterId_0_Line_1_ColumnName_2_probability_out_of_Range_3_, row.ParameterId, row.LineNumber, StructureFilesKeywords.NumericalValueColumnName.FirstToUpper(), meanValidityRange.ToString(FormattableConstants.ShowAtLeastOneDecimal, CultureInfo.CurrentCulture))); } return messages; } private static List NormalDistributionRule(StructuresParameterRow row) { return ValidateStochasticVariableParameters(row, false, true); } private static List VariationCoefficientNormalDistributionRule(StructuresParameterRow row) { return ValidateStochasticVariableParameters(row, false, false); } private static List LogNormalDistributionRule(StructuresParameterRow row) { return ValidateStochasticVariableParameters(row, true, true); } private static List VariationCoefficientLogNormalDistributionRule(StructuresParameterRow row) { return ValidateStochasticVariableParameters(row, true, false); } private static List NormalDistributionMeanRule(StructuresParameterRow row) { return ValidateStochasticVariableMeanParameter(row, false); } private static List ValidateStochasticVariableParameters(StructuresParameterRow row, bool meanMustBeGreaterThanZero, bool variationAsStandardDeviation) { List messages = ValidateStochasticVariableMeanParameter(row, meanMustBeGreaterThanZero); VarianceType type = row.VarianceType; if (type != VarianceType.StandardDeviation && type != VarianceType.CoefficientOfVariation) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ParameterId_0_Line_1_ColumnName_2_invalid_variancetype_value, row.ParameterId, row.LineNumber, StructureFilesKeywords.VariationTypeColumnName.FirstToUpper())); } messages.AddRange(ValidatePositiveDoubleParameter(row, StructureFilesKeywords.VariationValueColumnName)); double mean = row.NumericalValue; double absoluteMean = Math.Abs(mean); if (variationAsStandardDeviation) { if (row.VarianceType == VarianceType.CoefficientOfVariation && absoluteMean < valueTooCloseToZero) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ParameterId_0_Line_1_ColumnName_2_mean_too_small_for_reliable_variation_value_conversion, row.ParameterId, row.LineNumber, StructureFilesKeywords.NumericalValueColumnName.FirstToUpper())); } } else { if (row.VarianceType == VarianceType.StandardDeviation && absoluteMean < valueTooCloseToZero) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ParameterId_0_Line_1_ColumnName_2_mean_too_small_for_reliable_variation_value_conversion, row.ParameterId, row.LineNumber, StructureFilesKeywords.NumericalValueColumnName.FirstToUpper())); } } return messages; } private static List ValidateStochasticVariableMeanParameter(StructuresParameterRow row, bool meanMustBeGreaterThanZero) { var messages = new List(); const string numericalValueColumn = StructureFilesKeywords.NumericalValueColumnName; messages.AddRange(meanMustBeGreaterThanZero ? ValidateGreaterThanZeroDoubleParameter(row, numericalValueColumn) : ValidateDoubleParameter(row, numericalValueColumn)); return messages; } private static List StructureNormalOrientation(StructuresParameterRow row) { var messages = new List(); double orientation = row.NumericalValue; if (!orientationValidityRange.InRange(orientation)) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ParameterId_0_Line_1_ColumnName_2_orientation_out_of_Range_3_, row.ParameterId, row.LineNumber, StructureFilesKeywords.NumericalValueColumnName.FirstToUpper(), orientationValidityRange.ToString(FormattableConstants.ShowAtLeastOneDecimal, CultureInfo.CurrentCulture))); } return messages; } private static List IdenticalApertures(StructuresParameterRow row) { var messages = new List(); double value = row.NumericalValue; if (!IsValueWholeNumber(value) || value < 1) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ParameterId_0_Line_1_ColumnName_2_value_must_be_whole_number_greater_or_equal_to_one, row.ParameterId, row.LineNumber, StructureFilesKeywords.NumericalValueColumnName.FirstToUpper())); } return messages; } private static bool IsValueWholeNumber(double value) { return value % 1 < double.Epsilon; } private static List ClosingStructureInflowModelTypeRule(StructuresParameterRow row) { var messages = new List(); string value = row.AlphanumericValue.ToLower(); if (!closingStructureInflowModelTypeRuleKeywords.Contains(value)) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ParameterId_0_Line_1_ColumnName_2_structure_type_invalid, row.ParameterId, row.LineNumber, StructureFilesKeywords.AlphanumericalValueColumnName.FirstToUpper())); } return messages; } private static List StabilityPointStructureInflowModelTypeRule(StructuresParameterRow row) { var messages = new List(); string value = row.AlphanumericValue.ToLower(); if (!stabilityPointStructureInflowModelTypeRuleKeywords.Contains(value)) { messages.Add(string.Format(Resources.StructuresParameterRowsValidator_ParameterId_0_Line_1_ColumnName_2_structure_type_invalid, row.ParameterId, row.LineNumber, StructureFilesKeywords.AlphanumericalValueColumnName.FirstToUpper())); } return messages; } } }