using System; using System.Collections.Generic; using System.Linq; using Deltares.Geotechnics; using Deltares.Geotechnics.Soils; using Deltares.Standard; using Deltares.Standard.Data; using Deltares.Standard.EventPublisher; using Deltares.Standard.Extensions; using Deltares.Standard.Language; using Deltares.Standard.Reflection; using Deltares.Standard.Validation; namespace Deltares.AssessmentMechanism { /// /// Controller for managing instances for a /// with regards to failure mechanism calculations. /// public abstract class AssessmentScenarioManager : IDisposable where T : DikeLocationInfo { protected readonly T ScenarioOwner; protected readonly CreatingDelegatedList scenarios; protected AssessmentScenario activeScenario; /// /// Creates a scenario controller for managing calculation scenarios. /// /// The owner of the scenarios. protected AssessmentScenarioManager(T locationInfo) { scenarios = new CreatingDelegatedList { AddMethod = AddMethod, DeleteRequestMethod = DeleteRequestMethod, CreateMethod = CopyFromActiveScenario }; ScenarioOwner = locationInfo; } public virtual void Dispose() { DataEventPublisher.OnAfterChange -= DataEventPublisherOnOnAfterChange; scenarios.AddMethod = null; scenarios.DeleteRequestMethod = null; if (SupportsSoilProfile1D || SupportsSoilProfile2D) { foreach (var assessmentScenario in Scenarios) { foreach (var scenarioItem in assessmentScenario.ScenarioItems) { if (scenarioItem.Name == StaticReflection.GetMemberName(dli => dli.SoilSurfaceProfile) && scenarioItem.Value != null) { ((SoilSurfaceProfile)scenarioItem.Value).Dispose(); } else if (scenarioItem.Name == StaticReflection.GetMemberName(dli => dli.SoilSurfaceProfile2D) && scenarioItem.Value != null) { ((SoilSurfaceProfile2D)scenarioItem.Value).Dispose(); } } } } } /// /// Gets or sets the currently active scenario. /// public virtual AssessmentScenario ActiveScenario { get { return activeScenario; } set { if (ReferenceEquals(activeScenario, value)) { return; } try { Delayed.BeginDelay(); DataEventPublisher.BeforeChange(this, m => m.ActiveScenario); if (activeScenario != null) { activeScenario.Synchronize(); // optimization: Only send events for the final property change: if (value == null) { activeScenario.Unapply(); } else { DataEventPublisher.InvokeWithoutPublishingEvents(() => activeScenario.Unapply()); } } activeScenario = value; if (activeScenario != null) { activeScenario.Apply(); } DataEventPublisher.AfterChange(this, m => m.ActiveScenario); DataEventPublisher.ActionCompleted(); // Trigger validation } finally { Delayed.EndDelay(); } } } /// /// Collection of all managed scenarios. /// public virtual IList Scenarios { get { return scenarios; } } /// /// Creates the scenarios for the passed in to /// . /// public void InitializeScenarios() { DataEventPublisher.InvokeWithoutPublishingEvents(() => { ActiveScenario = null; scenarios.AddMethod = null; scenarios.DeleteRequestMethod = null; // Clear any previous scenarios scenarios.Clear(); // Create and add scenarios scenarios.AddRange(CreateAssessmentScenarios()); // If necessary, add a dummy scenario if (scenarios.Count == 0) { scenarios.Add(new AssessmentScenario { Probability = 1 }); } scenarios.AddMethod = AddMethod; scenarios.DeleteRequestMethod = DeleteRequestMethod; }); ActiveScenario = scenarios.First(); } /// /// Updates the dike embankment material in all scenarios. /// /// The new dike embankement material. public void UpdateDikeEmbankmentMaterial(Soil newDikeEmbankementMaterial) { if (!SupportsSoilProfile1D && !SupportsSoilProfile2D) { return; } foreach (var assessmentScenario in Scenarios.Where(s => !IsDefaultAssessmentScenario(s))) { foreach (var scenarioItem in assessmentScenario.ScenarioItems) { var soilSurfaceProfile1D = scenarioItem.Value as SoilSurfaceProfile; var soilSurfaceProfile2D = scenarioItem.Value as SoilSurfaceProfile2D; if (soilSurfaceProfile1D != null && scenarioItem.Name == StaticReflection.GetMemberName(dli => dli.SoilSurfaceProfile)) { soilSurfaceProfile1D.DikeEmbankmentMaterial = newDikeEmbankementMaterial; } else if (soilSurfaceProfile2D != null && scenarioItem.Name == StaticReflection.GetMemberName(dli => dli.SoilSurfaceProfile2D)) { soilSurfaceProfile2D.DikeEmbankmentMaterial = newDikeEmbankementMaterial; } } } } /// /// Validates that all scenarios have valid probabilities and they combine to create /// a full 100% definition. /// /// A collection of all validation issues found. /// [Validate] public IValidationResult[] ValidateScenarioProbabilities() { var results = new List(); if (!Scenarios.Sum(s => s.Probability).AlmostEquals(1.0)) { string messageText = LocalizationManager.GetTranslatedText(typeof(AssessmentScenarioManager<>), "SumOfScenarioProbabilitiesMustBeOne"); results.Add(new ValidationResult(ValidationResultType.Error, messageText, new ScenarioSubject { ActualSelectedObject = ScenarioOwner })); } foreach (var assessmentScenario in Scenarios) { foreach (var result in Validator.Validate(assessmentScenario)) { results.Add(new ScenarioValidationResult(result, ScenarioOwner, assessmentScenario, true, false)); } } return results.ToArray(); } /// /// Validates all miscellaneous scenarios related topics. /// /// A collection of all validation issues found. /// [Validate] public IValidationResult[] Validate() { var results = new List(); // Set subject so that we communicate scenario specific validation for the 'scenarioOwner' var stabilityScenarioSubject = new ScenarioSubject { ActualSelectedObject = ScenarioOwner }; if (Scenarios.Count > 15) { string messageText = LocalizationManager.GetTranslatedText(typeof(AssessmentScenarioManager<>), "TooManyScenariosCanHaveNegativePerformance"); results.Add(new ValidationResult(ValidationResultType.Warning, messageText, stabilityScenarioSubject)); } var allPossibleSoilProfiles = GetAvailableStochasticSoilProfiles(); var soilProfilesNotInScenarios = allPossibleSoilProfiles.Select(ssp => ssp.Profile).Except(Scenarios.Select(s => s.SoilProfile)).ToArray(); if (soilProfilesNotInScenarios.Any()) { string translatedText = LocalizationManager.GetTranslatedText(typeof(AssessmentScenarioManager<>), "MissingScenariosForFollowingProfiles_0_"); string missingProfileNames = String.Join(", ", soilProfilesNotInScenarios.Select(sp => sp.Name)); string messageText = String.Format(translatedText, missingProfileNames); results.Add(new ValidationResult(ValidationResultType.Error, messageText, stabilityScenarioSubject)); } return results.ToArray(); } /// /// Gets a value indicating whether this scenario manager supports /// instances from a . If true, a /// will be created for for that soil profile. /// If false, the soil profile will be ignored. /// protected abstract bool SupportsSoilProfile1D { get; } /// /// Gets a value indicating whether this scenario manager supports /// instances from a . If true, a /// will be created for for that soil profile. /// If false, the soil profile will be ignored. /// protected abstract bool SupportsSoilProfile2D { get; } /// /// Creates all the assessment scenarios for . /// /// A collection of assessment mechanism specific scenarios, or a 'default scenario' /// in case data is lacking to derive scenarios (A 'default scenario' causes no changes /// to ). protected virtual IEnumerable CreateAssessmentScenarios() { if (ScenarioOwner.StochasticSoilModel != null) { for (int index = 0; index < ScenarioOwner.StochasticSoilModel.StochasticSoilProfiles.Count; index++) { StochasticSoilProfile stochasticSoilProfile = ScenarioOwner.StochasticSoilModel.StochasticSoilProfiles[index]; var scenario = new AssessmentScenario { Name = "Scenario " + index, Probability = stochasticSoilProfile.Probability, GetAvailableStochasticSoilProfiles = GetAvailableStochasticSoilProfiles }; // Add items for input scenario.AddScenarioItem(ScenarioOwner, ScenarioOwner.GetMemberName(sdli => sdli.CurrentStochasticSoilProfile), stochasticSoilProfile.Clone(), stochasticSoilProfile.Profile.Name, true); if (SupportsSoilProfile1D) { var profileToUse = stochasticSoilProfile.Profile as SoilProfile1D; SoilSurfaceProfile soilSurfaceProfile1D = CreateSoilSurfaceProfile1DForScenario(profileToUse); string soilSurfaceProfilePropertyName1 = StaticReflection.GetMemberName(dli => dli.SoilSurfaceProfile); scenario.AddScenarioItem(ScenarioOwner, soilSurfaceProfilePropertyName1, soilSurfaceProfile1D, soilSurfaceProfilePropertyName1, false); } if (SupportsSoilProfile2D) { var profileToUse = stochasticSoilProfile.Profile as SoilProfile2D; SoilSurfaceProfile2D soilSurfaceProfile2D = CreateSoilSurfaceProfile2DForScenario(profileToUse); string soilSurfaceProfile2DPropertyName1 = StaticReflection.GetMemberName(dli => dli.SoilSurfaceProfile2D); scenario.AddScenarioItem(ScenarioOwner, soilSurfaceProfile2DPropertyName1, soilSurfaceProfile2D, soilSurfaceProfile2DPropertyName1, false); } AddAssessmentMechanismSpecificScenarioItems(scenario); yield return scenario; } } } /// /// Adds the assessment mechanism specific scenario items for each scenario dependent /// parameter. /// /// The target scenario. protected abstract void AddAssessmentMechanismSpecificScenarioItems(AssessmentScenario scenario); /// /// Gets the available stochastic soil profiles for . /// protected virtual StochasticSoilProfile[] GetAvailableStochasticSoilProfiles() { return ScenarioOwner.StochasticSoilModel.StochasticSoilProfiles.ToArray(); } /// /// Determines whether a scenario is a 'default scenario' that will not change a /// when applied or not.. /// /// The argument. /// True if the scenario is a 'default scenario'; False otherwise. protected virtual bool IsDefaultAssessmentScenario(AssessmentScenario scenarioToCheck) { return !scenarioToCheck.ScenarioItems.Any(); } /// /// Gets a unique name for a scenario based on the ones in . /// protected virtual string GetUniqueScenarioName() { const string nameFormat = "Scenario {0}"; var i = 0; var name = string.Format(nameFormat, i++); while (Scenarios.Any(s => s.Name == name)) { name = string.Format(nameFormat, i++); } return name; } /// /// Checks if a is allowed to be removed from /// or not. /// /// The scenario to be removed. /// True if deletion is allowed; False if not. protected virtual bool DeleteRequestMethod(AssessmentScenario scenarioToDelete) { return scenarios.Count > 1; } /// /// Handles the addition of a new scenario, copying /// from if the added scenarios doesn't have any /// items defined yet. /// /// The newly added scenario. protected virtual void AddMethod(AssessmentScenario scenario) { scenario.GetAvailableStochasticSoilProfiles = GetAvailableStochasticSoilProfiles; } protected virtual AssessmentScenario CopyFromActiveScenario() { ActiveScenario.Synchronize(); var scenario = new AssessmentScenario { Name = GetUniqueScenarioName(), GetAvailableStochasticSoilProfiles = GetAvailableStochasticSoilProfiles }; foreach (var scenarioItem in ActiveScenario.ScenarioItems) { if (scenarioItem.Name == StaticReflection.GetMemberName(dli => dli.SoilSurfaceProfile)) { var scenarioValue = CreateCopyOfSoilSurfaceProfile((SoilSurfaceProfile)scenarioItem.Value); scenario.AddScenarioItem(scenarioItem.Target, scenarioItem.Property, scenarioValue, scenarioItem.Name, scenarioItem.Visible); } else if (scenarioItem.Name == StaticReflection.GetMemberName(dli => dli.SoilSurfaceProfile2D)) { var scenarioValue = CreateCopyOfSoilSurfaceProfile2D((SoilSurfaceProfile2D)scenarioItem.Value); scenario.AddScenarioItem(scenarioItem.Target, scenarioItem.Property, scenarioValue, scenarioItem.Name, scenarioItem.Visible); } else { ICloneable cloneableValue = scenarioItem.Value as ICloneable; object scenarioValue = cloneableValue != null ? cloneableValue.Clone() : scenarioItem.Value; scenario.AddScenarioItem(scenarioItem.Target, scenarioItem.Property, scenarioValue, scenarioItem.Name, scenarioItem.Visible); } } return scenario; } private SoilSurfaceProfile CreateSoilSurfaceProfile1DForScenario(SoilProfile1D soilProfile1D) { SoilSurfaceProfile soilSurfaceProfile1D = null; if (soilProfile1D != null) { soilSurfaceProfile1D = new SoilSurfaceProfile { DikeEmbankmentMaterial = ScenarioOwner.DefaultDikeEmbankmentMaterial, // TODO RT-2486: Sync when another value is set on scenarioOwner SurfaceLine = ScenarioOwner.SurfaceLine, SoilProfile = soilProfile1D }; } return soilSurfaceProfile1D; } private SoilSurfaceProfile2D CreateSoilSurfaceProfile2DForScenario(SoilProfile2D soilProfile2D) { SoilSurfaceProfile2D soilSurfaceProfile2D = null; if (soilProfile2D != null) { soilSurfaceProfile2D = new SoilSurfaceProfile2D { DikeEmbankmentMaterial = ScenarioOwner.DefaultDikeEmbankmentMaterial, // TODO RT-2486: Sync when another value is set on scenarioOwner SurfaceLine = ScenarioOwner.SurfaceLine, SoilProfile2D = soilProfile2D }; } return soilSurfaceProfile2D; } private SoilSurfaceProfile CreateCopyOfSoilSurfaceProfile(SoilSurfaceProfile originalSoilSurfaceProfile) { SoilSurfaceProfile scenarioValue = null; if (originalSoilSurfaceProfile != null) { scenarioValue = (SoilSurfaceProfile)originalSoilSurfaceProfile.Clone(); scenarioValue.DikeEmbankmentMaterial = ScenarioOwner.DefaultDikeEmbankmentMaterial; scenarioValue.SurfaceLine = ScenarioOwner.SurfaceLine; scenarioValue.SoilProfile = originalSoilSurfaceProfile.SoilProfile; // Restore IsAquifer because that data was lost from the clone when assigning original SoilProfile. for (int i = 0; i < originalSoilSurfaceProfile.Surfaces.Count; i++) { scenarioValue.Surfaces[i].IsAquifer = originalSoilSurfaceProfile.Surfaces[i].IsAquifer; } } return scenarioValue; } private SoilSurfaceProfile2D CreateCopyOfSoilSurfaceProfile2D(SoilSurfaceProfile2D originalSoilSurfaceProfile) { SoilSurfaceProfile2D scenarioValue = null; if (originalSoilSurfaceProfile != null) { scenarioValue = (SoilSurfaceProfile2D)originalSoilSurfaceProfile.Clone(); scenarioValue.DikeEmbankmentMaterial = ScenarioOwner.DefaultDikeEmbankmentMaterial; scenarioValue.SurfaceLine = ScenarioOwner.SurfaceLine; scenarioValue.SoilProfile2D = originalSoilSurfaceProfile.SoilProfile2D; // Restore IsAquifer because that data was lost from the clone when assigning original SoilProfile2D. for (int i = 0; i < originalSoilSurfaceProfile.Surfaces.Count; i++) { scenarioValue.Surfaces[i].IsAquifer = originalSoilSurfaceProfile.Surfaces[i].IsAquifer; } } return scenarioValue; } private void DataEventPublisherOnOnAfterChange(object sender, PublishEventArgs e) { var assessmentScenario = sender as AssessmentScenario; // If SoilProfile has changed by user, we should update CurrentStochasticSoilProfile accordingly: if (assessmentScenario != null && e.Property == assessmentScenario.GetMemberName(s => s.SoilProfile) && scenarios.Contains(sender)) { var stochsticSoilProfileItem = assessmentScenario.ScenarioItems.First(si => si.Value is StochasticSoilProfile); var target = (DikeLocationInfo)stochsticSoilProfileItem.Target; if (ReferenceEquals(target.CurrentScenario, assessmentScenario)) { target.CurrentStochasticSoilProfile = (StochasticSoilProfile)stochsticSoilProfileItem.Value; } } } } }