diff --git a/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/SetModuleValueTests.cs b/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/SetModuleValueTests.cs new file mode 100644 index 0000000..726d3f7 --- /dev/null +++ b/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/SetModuleValueTests.cs @@ -0,0 +1,297 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using BH.Engine.LifeCycleAssessment; +using BH.oM.Base.Debugging; +using BH.oM.LifeCycleAssessment; +using BH.oM.LifeCycleAssessment.MaterialFragments; +using BH.oM.Test.NUnit; +using FluentAssertions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BH.Tests.Engine.LifeCycleAssessment +{ + public class SetModuleValueTests : NUnitTest + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [TestCaseSource(nameof(SingleModuleCases))] + [Description("Tests SetModuleValue for a dictionary. Checks that the value is set in place, that exactly the expected overlapping combinations and parts are removed (and nothing at all when removeOverlaps is false), and that all other values are untouched. A null removeOverlaps means the default is used, which is to remove overlaps. Expected removed modules are derived by hand from the module hierarchy.")] + public void SetModuleValueDictionary(Module module, Module[] expectedRemoved, bool? removeOverlaps) + { + Dictionary values = AllModuleValues(); + + if (removeOverlaps.HasValue) + values.SetModuleValue(module, 9, removeOverlaps.Value); + else + values.SetModuleValue(module, 9); + + AssertResult(values, AllModules(), new List { module }, new List { 9 }, expectedRemoved); + } + + /***************************************************/ + + [TestCaseSource(nameof(MultipleModuleCases))] + [Description("Tests SetModuleValues for a dictionary. Checks that the values are set in place, that exactly the expected overlapping combinations and parts are removed (and nothing at all when removeOverlaps is false), that modules set are never removed by each other, and that all other values are untouched. The result must be the same regardless of the order of the modules. A null removeOverlaps means the default is used, which is to remove overlaps. Expected removed modules are derived by hand from the module hierarchy.")] + public void SetModuleValuesDictionary(Module[] existing, Module[] modules, Module[] expectedRemoved, bool? removeOverlaps) + { + List existingModules = existing?.ToList() ?? AllModules(); + List moduleList = modules.ToList(); + List valueList = moduleList.Select((x, i) => 10.0 + i).ToList(); + + //Run in both the order given and the reverse, as the order should not matter + for (int i = 0; i < 2; i++) + { + Dictionary values = existingModules.ToDictionary(x => x, x => 1.0); + + if (removeOverlaps.HasValue) + values.SetModuleValues(moduleList, valueList, removeOverlaps.Value); + else + values.SetModuleValues(moduleList, valueList); + + AssertResult(values, existingModules, moduleList, valueList, expectedRemoved); + + moduleList.Reverse(); + valueList.Reverse(); + } + } + + /***************************************************/ + + [TestCase(true)] + [TestCase(false)] + [Description("Tests that SetModuleValue for an IEnvironmentalMetric updates the indicators of the metric in place, in the same way as for a dictionary. Setting A1toA3 removes A1, A2 and A3 only when removeOverlaps is true.")] + public void SetModuleValueMetric(bool removeOverlaps) + { + Dictionary indicators = AllModuleValues(); + IEnvironmentalMetric metric = new ClimateChangeTotalMetric { Indicators = indicators }; + + metric.SetModuleValue(Module.A1toA3, 9, removeOverlaps); + + metric.Indicators.Should().BeSameAs(indicators, "the metric should be updated in place"); + AssertResult(indicators, AllModules(), new List { Module.A1toA3 }, new List { 9 }, + removeOverlaps ? new[] { Module.A1, Module.A2, Module.A3 } : new Module[] { }); + } + + /***************************************************/ + + [TestCase(true)] + [TestCase(false)] + [Description("Tests that SetModuleValues for an IEnvironmentalMetric updates the indicators of the metric in place, in the same way as for a dictionary. Setting C3 and C3toC4 keeps both, and removes C1toC4 and C4 only when removeOverlaps is true.")] + public void SetModuleValuesMetric(bool removeOverlaps) + { + Dictionary indicators = AllModuleValues(); + IEnvironmentalMetric metric = new ClimateChangeTotalMetric { Indicators = indicators }; + + metric.SetModuleValues(new List { Module.C3, Module.C3toC4 }, new List { 2, 3 }, removeOverlaps); + + metric.Indicators.Should().BeSameAs(indicators, "the metric should be updated in place"); + AssertResult(indicators, AllModules(), new List { Module.C3, Module.C3toC4 }, new List { 2, 3 }, + removeOverlaps ? new[] { Module.C1toC4, Module.C4 } : new Module[] { }); + } + + /***************************************************/ + + [Test] + [Description("Tests that SetModuleValue for a null dictionary records a single error and does not throw.")] + public void SetModuleValueDictionaryInvalidInputs() + { + BH.Engine.Base.Compute.ClearCurrentEvents(); + Dictionary values = null; + + Action act = () => values.SetModuleValue(Module.A1, 1); + act.Should().NotThrow(); + + ErrorCount().Should().Be(1); + } + + /***************************************************/ + + [TestCase(true, TestName = "Null metric")] + [TestCase(false, TestName = "Null indicators")] + [Description("Tests that SetModuleValue for a null metric, or a metric with null indicators, records a single error and does not throw.")] + public void SetModuleValueMetricInvalidInputs(bool nullMetric) + { + BH.Engine.Base.Compute.ClearCurrentEvents(); + IEnvironmentalMetric metric = nullMetric ? null : new ClimateChangeTotalMetric { Indicators = null }; + + Action act = () => metric.SetModuleValue(Module.A1, 1); + act.Should().NotThrow(); + + ErrorCount().Should().Be(1); + } + + /***************************************************/ + + [TestCaseSource(nameof(InvalidInputs))] + [Description("Tests that SetModuleValues for a dictionary records a single error, leaves the values unchanged and does not throw for invalid inputs: null dictionary, null modules or values, mismatched number of modules and values, and duplicate modules.")] + public void SetModuleValuesDictionaryInvalidInputs(bool nullDictionary, List modules, List valuesToSet) + { + BH.Engine.Base.Compute.ClearCurrentEvents(); + Dictionary values = nullDictionary ? null : AllModuleValues(); + + Action act = () => values.SetModuleValues(modules, valuesToSet); + act.Should().NotThrow(); + + ErrorCount().Should().Be(1); + if (values != null) + values.Should().BeEquivalentTo(AllModuleValues(), "the values should be unchanged"); + } + + /***************************************************/ + + [TestCase(true, TestName = "Null metric")] + [TestCase(false, TestName = "Null indicators")] + [Description("Tests that SetModuleValues for a null metric, or a metric with null indicators, records a single error and does not throw.")] + public void SetModuleValuesMetricInvalidInputs(bool nullMetric) + { + BH.Engine.Base.Compute.ClearCurrentEvents(); + IEnvironmentalMetric metric = nullMetric ? null : new ClimateChangeTotalMetric { Indicators = null }; + + Action act = () => metric.SetModuleValues(new List { Module.A1 }, new List { 1 }); + act.Should().NotThrow(); + + ErrorCount().Should().Be(1); + } + + /***************************************************/ + /**** Private Methods ****/ + /***************************************************/ + + private static IEnumerable SingleModuleCases() + { + Module[] none = new Module[] { }; + + yield return SingleCase("Stand-alone module removes nothing", Module.A4, none); + yield return SingleCase("Part removes its combination", Module.A1, new[] { Module.A1toA3 }); + yield return SingleCase("Nested part removes all combinations above it", Module.B1_1, new[] { Module.B1, Module.B1toB3, Module.B1toB5, Module.B1toB7 }); + yield return SingleCase("Combination removes its parts", Module.A5, new[] { Module.A5_1, Module.A5_2, Module.A5_3, Module.A5_4 }); + yield return SingleCase("Combination removes parts of its parts", Module.C1toC4, new[] { Module.C1, Module.C2, Module.C3, Module.C4, Module.C3toC4 }); + yield return SingleCase("Combination in the middle removes both its parts and the combinations above", Module.B1, new[] { Module.B1_1, Module.B1_2, Module.B1toB3, Module.B1toB5, Module.B1toB7 }); + yield return SingleCase("Top combination removes all parts recursively", Module.B1toB7, new[] { Module.B1toB5, Module.B1toB3, Module.B4toB5, Module.B1, Module.B1_1, Module.B1_2, Module.B2, Module.B3, Module.B4, Module.B4_1, Module.B4_2, Module.B5, Module.B6, Module.B7, Module.B7_1, Module.B7_2, Module.B7_3 }); + yield return SingleCase("No removal when turned off", Module.B1toB7, none, false); + yield return SingleCase("Removal is the default", Module.A1toA3, new[] { Module.A1, Module.A2, Module.A3 }, null); + } + + /***************************************************/ + + private static IEnumerable MultipleModuleCases() + { + Module[] none = new Module[] { }; + + //Modules set must never be removed by each other + yield return MultipleCase("Part and its direct combination", new[] { Module.C3, Module.C3toC4 }, new[] { Module.C1toC4, Module.C4 }); + yield return MultipleCase("Part and its indirect combination", new[] { Module.C3, Module.C1toC4 }, new[] { Module.C1, Module.C2, Module.C4, Module.C3toC4 }); + yield return MultipleCase("Whole chain set", new[] { Module.C1toC4, Module.C3toC4, Module.C3 }, new[] { Module.C1, Module.C2, Module.C4 }); + yield return MultipleCase("Deeply nested part and top combination", new[] { Module.B1_1, Module.B1toB7 }, new[] { Module.B1toB5, Module.B1toB3, Module.B4toB5, Module.B1, Module.B1_2, Module.B2, Module.B3, Module.B4, Module.B4_1, Module.B4_2, Module.B5, Module.B6, Module.B7, Module.B7_1, Module.B7_2, Module.B7_3 }); + yield return MultipleCase("Two separate branches", new[] { Module.A1toA3, Module.B1_1 }, new[] { Module.A1, Module.A2, Module.A3, Module.B1, Module.B1toB3, Module.B1toB5, Module.B1toB7 }); + yield return MultipleCase("Two parts of the same combination", new[] { Module.A5_2, Module.A5_3 }, new[] { Module.A5 }); + + //Removal turned off, and default + yield return MultipleCase("No removal when turned off", new[] { Module.A1toA3, Module.B1_1 }, none, false); + yield return MultipleCase("Removal is the default", new[] { Module.A1toA3, Module.B1_1 }, new[] { Module.A1, Module.A2, Module.A3, Module.B1, Module.B1toB3, Module.B1toB5, Module.B1toB7 }, null); + + //Only modules that exist can be removed + yield return MultipleCase("Only existing modules removed", new[] { Module.B1_1, Module.A5_1 }, new[] { Module.B1toB3 }, true, new[] { Module.A1, Module.B1toB3 }); + } + + /***************************************************/ + + private static IEnumerable InvalidInputs() + { + List modules = new List { Module.A1, Module.A2 }; + List values = new List { 1, 2 }; + + yield return InvalidCase("Null dictionary", true, modules, values); + yield return InvalidCase("Null modules", false, null, values); + yield return InvalidCase("Null values", false, modules, null); + yield return InvalidCase("Different number of modules and values", false, modules, new List { 1 }); + yield return InvalidCase("Duplicate modules", false, new List { Module.A1, Module.A2, Module.A1 }, new List { 1, 2, 3 }); + } + + /***************************************************/ + + private static TestCaseData SingleCase(string name, Module module, Module[] expectedRemoved, bool? removeOverlaps = true) + { + return new TestCaseData(module, expectedRemoved, removeOverlaps).SetArgDisplayNames(name); + } + + /***************************************************/ + + private static TestCaseData MultipleCase(string name, Module[] modules, Module[] expectedRemoved, bool? removeOverlaps = true, Module[] existing = null) + { + return new TestCaseData(existing, modules, expectedRemoved, removeOverlaps).SetArgDisplayNames(name); + } + + /***************************************************/ + + private static TestCaseData InvalidCase(string name, bool nullDictionary, List modules, List values) + { + return new TestCaseData(nullDictionary, modules, values).SetArgDisplayNames(name); + } + + /***************************************************/ + + private static List AllModules() + { + return Enum.GetValues(typeof(Module)).Cast().ToList(); + } + + /***************************************************/ + + private static Dictionary AllModuleValues() + { + return AllModules().ToDictionary(x => x, x => 1.0); + } + + /***************************************************/ + + private static void AssertResult(Dictionary values, List existing, List modulesSet, List valuesSet, Module[] expectedRemoved) + { + Assert.Multiple(() => + { + values.Keys.Should().BeEquivalentTo(existing.Union(modulesSet).Except(expectedRemoved), "exactly the expected modules should be left"); + + foreach (KeyValuePair item in values) + { + int index = modulesSet.IndexOf(item.Key); + double expected = index >= 0 ? valuesSet[index] : 1.0; //Set value for modules set, untouched for all others + item.Value.Should().Be(expected, $"the value for {item.Key} should be correct"); + } + }); + } + + /***************************************************/ + + private static int ErrorCount() + { + return BH.Engine.Base.Query.CurrentEvents().Count(x => x.Type == EventType.Error); + } + + /***************************************************/ + } +} diff --git a/LifeCycleAssessment_Engine/Modify/SetModuleValue.cs b/LifeCycleAssessment_Engine/Modify/SetModuleValue.cs new file mode 100644 index 0000000..de29f1c --- /dev/null +++ b/LifeCycleAssessment_Engine/Modify/SetModuleValue.cs @@ -0,0 +1,198 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + + +using BH.oM.Base.Attributes; +using BH.oM.Geometry; +using BH.oM.LifeCycleAssessment; +using BH.oM.LifeCycleAssessment.MaterialFragments; +using BH.oM.LifeCycleAssessment.MaterialFragments.Construction; +using BH.oM.LifeCycleAssessment.MaterialFragments.Transport; +using BH.oM.LifeCycleAssessment.Results; +using BH.oM.Physical.Materials; +using BH.oM.Quantities; +using BH.oM.Quantities.Attributes; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace BH.Engine.LifeCycleAssessment +{ + public static partial class Modify + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [Description("Sets the value of a module in the indicators of the provided metric. The metric is updated in place.\n" + + "Optionally removes any existing combination modules the module is part of, as well as all parts of the module if it is itself a combination, as these would no longer be consistent with the value set.\n" + + "For example, setting A5_1 removes A5, and setting A1toA3 removes A1, A2 and A3.")] + [Input("metric", "The metric to set the module value for. The indicators of the metric are modified.")] + [Input("module", "The module to set the value for.")] + [Input("value", "The value to set for the module.")] + [Input("removeOverlaps", "If true, any existing combinations that the module is part of, and any existing parts of the module if it is a combination, are removed from the indicators.")] + public static void SetModuleValue(this IEnvironmentalMetric metric, Module module, double value, bool removeOverlaps = true) + { + if (metric == null) + { + BH.Engine.Base.Compute.RecordError("Unable to set module value, metric is null."); + return; + } + + SetModuleValue(metric.Indicators, module, value, removeOverlaps); + } + + /***************************************************/ + + [Description("Sets the values of multiple modules in the indicators of the provided metric. The metric is updated in place.\n" + + "Optionally removes any existing combination modules the modules are part of, as well as all parts of any of the modules that are combinations, as these would no longer be consistent with the values set.\n" + + "Modules provided as inputs are never removed, so for example setting both C3 and C3toC4 results in both being present in the indicators.\n" + + "The modules provided must be unique. If a module is provided more than once, an error is raised and no values are set.")] + [Input("metric", "The metric to set the module values for. The indicators of the metric are modified.")] + [Input("modules", "The modules to set the values for. All modules must be unique.")] + [Input("values", "The values to set for the modules. Must be the same length as the list of modules.")] + [Input("removeOverlaps", "If true, any existing combinations that any of the modules are part of, and any existing parts of any of the modules that are combinations, are removed from the indicators. Modules in the modules input are never removed.")] + public static void SetModuleValues(this IEnvironmentalMetric metric, List modules, List values, bool removeOverlaps = true) + { + if (metric == null) + { + BH.Engine.Base.Compute.RecordError("Unable to set module values, metric is null."); + return; + } + + SetModuleValues(metric.Indicators, modules, values, removeOverlaps); + } + + /***************************************************/ + + [Description("Sets the value of a module in the provided dictionary of module values. The dictionary is updated in place.\n" + + "Optionally removes any existing combination modules the module is part of, as well as all parts of the module if it is itself a combination, as these would no longer be consistent with the value set.\n" + + "For example, setting A5_1 removes A5, and setting A1toA3 removes A1, A2 and A3.")] + [Input("moduleValues", "The dictionary of module values to set the value in. The dictionary is modified.")] + [Input("module", "The module to set the value for.")] + [Input("value", "The value to set for the module.")] + [Input("removeOverlaps", "If true, any existing combinations that the module is part of, and any existing parts of the module if it is a combination, are removed from the dictionary.")] + public static void SetModuleValue(this Dictionary moduleValues, Module module, double value, bool removeOverlaps = true) + { + if (moduleValues == null) + { + BH.Engine.Base.Compute.RecordError("Unable to set module value, moduleValues is null."); + return; + } + + SetModuleValues(moduleValues, new List { module }, new List { value }, removeOverlaps); + } + + /***************************************************/ + + [Description("Sets the values of multiple modules in the provided dictionary of module values. The dictionary is updated in place.\n" + + "Optionally removes any existing combination modules the modules are part of, as well as all parts of any of the modules that are combinations, as these would no longer be consistent with the values set.\n" + + "Modules provided as inputs are never removed, so for example setting both C3 and C3toC4 results in both being present in the dictionary.\n" + + "The modules provided must be unique. If a module is provided more than once, an error is raised and no values are set.")] + [Input("moduleValues", "The dictionary of module values to set the values in. The dictionary is modified.")] + [Input("modules", "The modules to set the values for. All modules must be unique.")] + [Input("values", "The values to set for the modules. Must be the same length as the list of modules.")] + [Input("removeOverlaps", "If true, any existing combinations that any of the modules are part of, and any existing parts of any of the modules that are combinations, are removed from the dictionary. Modules in the modules input are never removed.")] + public static void SetModuleValues(this Dictionary moduleValues, List modules, List values, bool removeOverlaps = true) + { + if (moduleValues == null) + { + BH.Engine.Base.Compute.RecordError("Unable to set module values, moduleValues is null."); + return; + } + + if (modules == null || values == null) + { + BH.Engine.Base.Compute.RecordError("Unable to set module values, modules and values must both be provided."); + return; + } + + if (modules.Count != values.Count) + { + BH.Engine.Base.Compute.RecordError($"Unable to set module values, the number of modules ({modules.Count}) does not match the number of values ({values.Count})."); + return; + } + + List duplicates = modules.GroupBy(x => x).Where(x => x.Count() > 1).Select(x => x.Key).ToList(); + if (duplicates.Count > 0) + { + BH.Engine.Base.Compute.RecordError($"Unable to set module values, the modules provided must be unique. Duplicate modules: {string.Join(", ", duplicates)}."); + return; + } + + //Set all values first, then remove any overlapping combinations and parts afterwards + for (int i = 0; i < modules.Count; i++) + { + moduleValues[modules[i]] = values[i]; + } + + if (!removeOverlaps) + return; + + IReadOnlyDictionary> partOfCombinations = Query.PartOfCombinationModules(); //For each module, all the combinations it is part of, directly or indirectly + IReadOnlyDictionary> combinationModules = Query.CombinationModules(); //For each combination, its direct parts + + foreach (Module module in modules) + { + //Remove any combination that includes the module, directly or indirectly, as its value could now be different than the sum of its parts + if (partOfCombinations.TryGetValue(module, out IReadOnlyList combinations)) + { + foreach (Module combination in combinations) + { + //Combinations that are among the modules provided are kept, as their values have just been set explicitly. + //For example, when setting both C3 and C3toC4, C3toC4 is a combination that includes C3, but must be kept. + if (!modules.Contains(combination)) + moduleValues.Remove(combination); + } + } + + //If the module is a combination, remove all its parts, directly or indirectly, as their values could now be different than the combination value + moduleValues.RemoveCombinationParts(module, combinationModules, modules); + } + } + + /***************************************************/ + /**** Private Methods ****/ + /***************************************************/ + + private static void RemoveCombinationParts(this Dictionary moduleValues, Module module, IReadOnlyDictionary> combinations, List modulesSet) + { + if (combinations.TryGetValue(module, out var parts)) + { + foreach (var part in parts) + { + if (!modulesSet.Contains(part.Item1)) //Parts that have explicitly been set are kept + moduleValues.Remove(part.Item1); + RemoveCombinationParts(moduleValues, part.Item1, combinations, modulesSet); //Recursively remove parts of parts, also for parts that were set as their own parts would not match their value + } + } + } + + /***************************************************/ + + } +} + + + diff --git a/LifeCycleAssessment_Engine/Query/ResultingModuleValues.cs b/LifeCycleAssessment_Engine/Query/ResultingModuleValues.cs index b3fceaf..a34c1d8 100644 --- a/LifeCycleAssessment_Engine/Query/ResultingModuleValues.cs +++ b/LifeCycleAssessment_Engine/Query/ResultingModuleValues.cs @@ -79,56 +79,29 @@ public static Dictionary ResultingModuleValues(this IEnvironment //This is critical to ensure that precomputed values are respected and that any combinations that include the precomputed modules are removed from the resulting values as they will be different than the sum of their parts. //This is, if a precomputed value is provided for for example A5_1, then the resulting values will not include A5, as it will be different than the sum of its parts (A5_1 + A5_2 + A5_3). Similarly, if a precomputed value is provided for A1toA3, then the resulting values will not include A1, A2 or A3, as they will be different than the combination value. //When called as part of generating MaterialResults, the Combination modules will be computed at the end of the evaluation, so that they are always included in the resulting values, hence do not need to be computed here. + //The culling of combinations and parts is handled by the SetModuleValues method. if (precomputedValues != null) { - var partModules = PartOfCombinationModules(); //Get a dictionary that for each modules lists the combinations it is part of - var combinationModules = CombinationModules(); //Get a dictionary that for each combination lists its parts - HashSet precompuedValueSet = new HashSet(); - foreach (var precomputed in precomputedValues) + List modules = new List(); + List values = new List(); + MetricType metricType = metric.IMetricType(); + //Loop through the precomputed values and store out the modules and values for the cases where the precomputed values contain a value for the metric type. + foreach (var preComputed in precomputedValues) { - bool setValue = precomputed.Value.OverwriteExistingValues || !resultingValues.ContainsKey(precomputed.Key); //Only set value if overwrite is true or if no value exists yet - if (!setValue) - continue; - - if (precomputed.Value.ModuleValues.TryGetValue(metric.IMetricType(), out double val)) //Check if precomputed value exists for the metric type + if (preComputed.Value.ModuleValues.TryGetValue(metricType, out double val)) { - precompuedValueSet.Add(precomputed.Key); - resultingValues[precomputed.Key] = val; //Set the precomputed value. It is assumed that the precomputed value is already evaluated for the quantity - if (partModules.TryGetValue(precomputed.Key, out var combinations)) - { - foreach (var combination in combinations) - { - if (!precompuedValueSet.Contains(combination)) //If not explicitly set - resultingValues.Remove(combination); //Remove any combination that includes the precomputed module as it will be different than the sum of its parts - } - } - resultingValues.RemoveCombinationParts(precomputed.Key, combinationModules, precompuedValueSet); //If precomputed value is a combination, then remove all its part as they will be different than the combination value + modules.Add(preComputed.Key); + values.Add(val); } - } + //Set the precomputed values on the resulting values. Called with boolean set to true which will overwrite any existing values for the modules and remove any combinations or parts that are included in the precomputed values. + resultingValues.SetModuleValues(modules, values, true); } return resultingValues; } /***************************************************/ - /**** Private Methods ****/ - /***************************************************/ - - private static void RemoveCombinationParts(this Dictionary resultingValues, Module module, IReadOnlyDictionary> combinations, HashSet precompuedValueSet) - { - if (combinations.TryGetValue(module, out var parts)) - { - foreach (var part in parts) - { - if (!precompuedValueSet.Contains(part.Item1)) //If not explicitly set - resultingValues.Remove(part.Item1); - RemoveCombinationParts(resultingValues, part.Item1, combinations, precompuedValueSet); //Recursively remove parts of parts - } - } - } - - /***************************************************/ } }