From 238845eb1dfbd131208a65c1f35eedf2442c5d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Fri, 23 Oct 2026 12:13:09 +0200 Subject: [PATCH 1/3] Update combination modules to make use of the new PartOfAttribute --- .../Query/CombinationModules.cs | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/LifeCycleAssessment_Engine/Query/CombinationModules.cs b/LifeCycleAssessment_Engine/Query/CombinationModules.cs index 0181980..79835ab 100644 --- a/LifeCycleAssessment_Engine/Query/CombinationModules.cs +++ b/LifeCycleAssessment_Engine/Query/CombinationModules.cs @@ -47,24 +47,39 @@ public static partial class Query { if (m_CombinationModules == null) { - // Define the combinations of modules and their parts. + // The combinations of modules and their parts are defined by the PartOf attribute on each module in the Module enum. // The bool indicates whether the part is required for the combination to be computed, i.e. true for required parts and false for optional parts. // Optional parts are added to the combination if present, but their absence does not prevent the combination from being computed. - // The order of the parts is important, as it defines the order in which they are combined and caluculated. Parts lower down the lift can be made up of parts from above in the list + // The order is important, and is defined by the order of the Module enum: + // - The parts of a combination are listed in the order they are defined in the enum. + // - The combinations are listed in the order they are defined in the enum. Combinations lower down the list can be made up of combinations from above in the list, + // which relies on every combination being defined after the combinations it is made up of. + List allModules = Enum.GetValues(typeof(Module)).Cast().ToList(); + + // Collect the parts of each combination. The enum is looped through in order, which gives the parts in the correct order. + Dictionary> parts = new Dictionary>(); + foreach (Module module in allModules) + { + PartOfAttribute partOf = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(typeof(Module).GetField(module.ToString())); + if (partOf == null) + continue; + + if (!parts.TryGetValue(partOf.PartOf, out List<(Module, bool)> combinationParts)) + { + combinationParts = new List<(Module, bool)>(); + parts[partOf.PartOf] = combinationParts; + } + combinationParts.Add((module, partOf.IsRequired)); + } + + // Add the combinations in the order of the enum, i.e. by the position of the combination itself, not by the position of its first part. + // The latter would for example put B1toB3 before B1, as B1 is defined before B1_1. Dictionary> modules = new Dictionary>(); - modules[Module.A1toA3] = new List<(Module, bool)>() { (Module.A1, true), (Module.A2,true), (Module.A3, true) }; - modules[Module.A5] = new List<(Module, bool)>() { (Module.A5_1, false), (Module.A5_2, true), (Module.A5_3, true), (Module.A5_4, false) }; - modules[Module.B1] = new List<(Module, bool)>() { (Module.B1_1, true), (Module.B1_2, true) }; - modules[Module.B1toB3] = new List<(Module, bool)>() { (Module.B1, true), (Module.B2, true), (Module.B3, true) }; - modules[Module.B4] = new List<(Module, bool)>() { (Module.B4_1, true), (Module.B4_2, true) }; - modules[Module.B4toB5] = new List<(Module, bool)>() { (Module.B4, true), (Module.B5, true) }; - modules[Module.B1toB5] = new List<(Module, bool)>() { (Module.B1toB3, true), (Module.B4toB5, true) }; - modules[Module.B7] = new List<(Module, bool)>() { (Module.B7_1, true), (Module.B7_2, true), (Module.B7_3, true) }; - modules[Module.B1toB7] = new List<(Module, bool)>() { (Module.B1toB5, true), (Module.B6, true), (Module.B7, true) }; - modules[Module.B8] = new List<(Module, bool)>() { (Module.B8_1, false), (Module.B8_2, false), (Module.B8_3, false) }; - modules[Module.C3toC4] = new List<(Module, bool)>() { (Module.C3, true), (Module.C4, true) }; - modules[Module.C1toC4] = new List<(Module, bool)>() { (Module.C1, true), (Module.C2, true), (Module.C3toC4, true) }; - modules[Module.D] = new List<(Module, bool)>() { (Module.D_1, true), (Module.D_2, true) }; + foreach (Module module in allModules) + { + if (parts.TryGetValue(module, out List<(Module, bool)> combinationParts)) + modules[module] = combinationParts; + } m_CombinationModules = modules; } From 3f7b6c3858b395de5e9556a39352685e3ab45418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Fri, 23 Oct 2026 12:50:25 +0200 Subject: [PATCH 2/3] Update combination module method to make sure order is correct Not technically needed right now, given the enum is defined in an order where combinations are always entered after their parts (for example B1toB7 is defined after B1toB5,B6andB7) but that technically could change in the future, and this change makes sure the data inserted is correctly capturing the order --- .../Query/CombinationModules.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/LifeCycleAssessment_Engine/Query/CombinationModules.cs b/LifeCycleAssessment_Engine/Query/CombinationModules.cs index 79835ab..a750601 100644 --- a/LifeCycleAssessment_Engine/Query/CombinationModules.cs +++ b/LifeCycleAssessment_Engine/Query/CombinationModules.cs @@ -50,10 +50,10 @@ public static partial class Query // The combinations of modules and their parts are defined by the PartOf attribute on each module in the Module enum. // The bool indicates whether the part is required for the combination to be computed, i.e. true for required parts and false for optional parts. // Optional parts are added to the combination if present, but their absence does not prevent the combination from being computed. - // The order is important, and is defined by the order of the Module enum: - // - The parts of a combination are listed in the order they are defined in the enum. - // - The combinations are listed in the order they are defined in the enum. Combinations lower down the list can be made up of combinations from above in the list, - // which relies on every combination being defined after the combinations it is made up of. + // The order is important: + // - The parts of a combination are listed in the order they are defined in the Module enum. + // - The combinations are listed in the order they are defined in the Module enum, except that a combination is always listed after all the combinations it is made up of. + // Combinations lower down the list can hence be made up of combinations from above in the list. List allModules = Enum.GetValues(typeof(Module)).Cast().ToList(); // Collect the parts of each combination. The enum is looped through in order, which gives the parts in the correct order. @@ -74,11 +74,12 @@ public static partial class Query // Add the combinations in the order of the enum, i.e. by the position of the combination itself, not by the position of its first part. // The latter would for example put B1toB3 before B1, as B1 is defined before B1_1. + // For each combination, all parts that are combinations themselves are added first, so the result is correct also if the enum is not ordered that way. Dictionary> modules = new Dictionary>(); + HashSet inProgress = new HashSet(); foreach (Module module in allModules) { - if (parts.TryGetValue(module, out List<(Module, bool)> combinationParts)) - modules[module] = combinationParts; + AddCombination(module, parts, modules, inProgress); } m_CombinationModules = modules; } @@ -111,6 +112,30 @@ public static IReadOnlyDictionary> PartOfCombinati /**** Private Methods ****/ /***************************************************/ + private static void AddCombination(Module module, Dictionary> parts, Dictionary> result, HashSet inProgress) + { + //Not a combination, or already added + if (!parts.TryGetValue(module, out List<(Module, bool)> combinationParts) || result.ContainsKey(module)) + return; + + if (!inProgress.Add(module)) + { + BH.Engine.Base.Compute.RecordError($"The module {module} is, directly or indirectly, defined as a part of itself via the PartOf attribute. The combination is ignored."); + return; + } + + //Make sure all parts that are combinations themselves are added before this one + foreach ((Module, bool) part in combinationParts) + { + AddCombination(part.Item1, parts, result, inProgress); + } + + inProgress.Remove(module); + result[module] = combinationParts; + } + + /***************************************************/ + private static List CollectParts(Module module, IReadOnlyDictionary> combinationModules) { List parts = new List(); From 46b2ff1b302a3bd2f848945ea549a80382ee1a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Fri, 23 Oct 2026 14:20:55 +0200 Subject: [PATCH 3/3] Update LifeCycleAssessment_Engine/Query/CombinationModules.cs agree with that change Co-authored-by: Thomas Edward Kingstone --- LifeCycleAssessment_Engine/Query/CombinationModules.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LifeCycleAssessment_Engine/Query/CombinationModules.cs b/LifeCycleAssessment_Engine/Query/CombinationModules.cs index a750601..827f566 100644 --- a/LifeCycleAssessment_Engine/Query/CombinationModules.cs +++ b/LifeCycleAssessment_Engine/Query/CombinationModules.cs @@ -52,7 +52,7 @@ public static partial class Query // Optional parts are added to the combination if present, but their absence does not prevent the combination from being computed. // The order is important: // - The parts of a combination are listed in the order they are defined in the Module enum. - // - The combinations are listed in the order they are defined in the Module enum, except that a combination is always listed after all the combinations it is made up of. + // - The combinations are listed in the order they are defined in the Module enum, except that a combination is always listed after all the parts it is made up of. // Combinations lower down the list can hence be made up of combinations from above in the list. List allModules = Enum.GetValues(typeof(Module)).Cast().ToList();