From 559cb0fe9ddb700d206ec89d237d6217c64e702a Mon Sep 17 00:00:00 2001 From: Michael Sevestre Date: Sun, 10 Nov 2024 01:22:49 -0500 Subject: [PATCH 1/4] WIP #2359 add criteria and test. Implementation missing --- src/OSPSuite.Core/Domain/Constants.cs | 1 + .../DescriptorCriteriaExpressions.cs | 6 ++ .../Domain/Descriptors/InChildrenCondition.cs | 20 +++++ .../Domain/Services/QuantityValuesUpdater.cs | 2 +- .../Xml/DescriptorCriteriaXmlSerializer.cs | 8 ++ .../Domain/FormulaTaskSpecs.cs | 86 +++++++++++++++++++ .../Domain/InParentConditionSpecs.cs | 3 +- 7 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 src/OSPSuite.Core/Domain/Descriptors/InChildrenCondition.cs diff --git a/src/OSPSuite.Core/Domain/Constants.cs b/src/OSPSuite.Core/Domain/Constants.cs index 6933521b5..e9f1d26d5 100644 --- a/src/OSPSuite.Core/Domain/Constants.cs +++ b/src/OSPSuite.Core/Domain/Constants.cs @@ -668,6 +668,7 @@ public static class ParameterExport public const string IN_CONTAINER = "In container"; public const string NOT_IN_CONTAINER = "Not in container"; public const string IN_PARENT = "In parent"; + public const string IN_CHILDREN = "In children"; public const string LLOQ = "LLOQ"; public static string NameWithUnitFor(string name, IDimension dimension) => NameWithUnitFor(name, dimension?.DefaultUnit); diff --git a/src/OSPSuite.Core/Domain/Descriptors/DescriptorCriteriaExpressions.cs b/src/OSPSuite.Core/Domain/Descriptors/DescriptorCriteriaExpressions.cs index fd0870598..154c95ea6 100644 --- a/src/OSPSuite.Core/Domain/Descriptors/DescriptorCriteriaExpressions.cs +++ b/src/OSPSuite.Core/Domain/Descriptors/DescriptorCriteriaExpressions.cs @@ -47,6 +47,12 @@ public DescriptorCriteriaBuilder InParent() return this; } + public DescriptorCriteriaBuilder InChildren() + { + _criteria.Add(new InChildrenCondition()); + return this; + } + public DescriptorCriteriaBuilder NotInContainer(string containerName) { _criteria.Add(new NotInContainerCondition(containerName)); diff --git a/src/OSPSuite.Core/Domain/Descriptors/InChildrenCondition.cs b/src/OSPSuite.Core/Domain/Descriptors/InChildrenCondition.cs new file mode 100644 index 000000000..a91d66766 --- /dev/null +++ b/src/OSPSuite.Core/Domain/Descriptors/InChildrenCondition.cs @@ -0,0 +1,20 @@ +namespace OSPSuite.Core.Domain.Descriptors +{ + public class InChildrenCondition : TagCondition + { + public InChildrenCondition() : base(Constants.IN_CHILDREN, Constants.IN_CHILDREN) + { + } + + public override string Condition { get; } = Constants.IN_CHILDREN.ToUpper(); + + public override bool IsSatisfiedBy(EntityDescriptor item) => false; + + public override ITagCondition CloneCondition() => new InChildrenCondition(); + + public override void Replace(string keyword, string replacement) + { + /*nothing to do*/ + } + } +} \ No newline at end of file diff --git a/src/OSPSuite.Core/Domain/Services/QuantityValuesUpdater.cs b/src/OSPSuite.Core/Domain/Services/QuantityValuesUpdater.cs index 0b7383391..ec3c8470e 100644 --- a/src/OSPSuite.Core/Domain/Services/QuantityValuesUpdater.cs +++ b/src/OSPSuite.Core/Domain/Services/QuantityValuesUpdater.cs @@ -116,7 +116,7 @@ private IParameter getOrAddModelParameter(ValueUpdaterParams valueUpdater, Param var potentialParameter = parentContainer.Parameter(parameterValue.Name); parentContainer.RemoveChild(potentialParameter); - //for sure it's not there. Add it + //for sure it's not there. Add itIn return parameter.WithParentContainer(parentContainer); } diff --git a/src/OSPSuite.Core/Serialization/Xml/DescriptorCriteriaXmlSerializer.cs b/src/OSPSuite.Core/Serialization/Xml/DescriptorCriteriaXmlSerializer.cs index 7dfc37cee..a134070a5 100644 --- a/src/OSPSuite.Core/Serialization/Xml/DescriptorCriteriaXmlSerializer.cs +++ b/src/OSPSuite.Core/Serialization/Xml/DescriptorCriteriaXmlSerializer.cs @@ -45,6 +45,14 @@ public override void PerformMapping() } } + public class InChildrenConditionXmlSerializer : TagConditionXmlSerializer + { + public override void PerformMapping() + { + /*nothing to do*/ + } + } + public class InContainerConditionXmlSerializer : TagConditionXmlSerializer { } diff --git a/tests/OSPSuite.Core.Tests/Domain/FormulaTaskSpecs.cs b/tests/OSPSuite.Core.Tests/Domain/FormulaTaskSpecs.cs index 327069908..6e2998a9f 100644 --- a/tests/OSPSuite.Core.Tests/Domain/FormulaTaskSpecs.cs +++ b/tests/OSPSuite.Core.Tests/Domain/FormulaTaskSpecs.cs @@ -366,6 +366,92 @@ public void should_throw_an_exception() } } + internal class When_expanding_all_dynamic_formula_in_a_model_using_in_children_criteria : concern_for_FormulaTask + { + private IModel _model; + private IParameter _sumVolume; + private IParameter _volume1_1; + private IParameter _volume1_2; + private IParameter _volume2_1; + private Container _container1; + private Container _container2; + private Container _subContainer1_1; + private Container _subContainer1_2; + private Container _subContainer2_1; + private Parameter _volume1; + + protected override void Context() + { + base.Context(); + A.CallTo(() => _objectBaseFactory.Create()).Returns(new ExplicitFormula()); + _model = new Model(); + _container1 = new Container().WithName("Container1"); + _container2 = new Container().WithName("Container2"); + _subContainer1_1 = new Container().WithName("SubContainer1_1").WithParentContainer(_container1); + _subContainer1_2 = new Container().WithName("SubContainer1_2").WithParentContainer(_container1); + _subContainer2_1 = new Container().WithName("SubContainer2_1").WithParentContainer(_container2); + + _volume1_1 = new Parameter().WithName("volume").WithFormula(new ConstantFormula(1)); + _volume1_2 = new Parameter().WithName("volume").WithFormula(new ConstantFormula(2)); + _volume2_1 = new Parameter().WithName("volume").WithFormula(new ConstantFormula(3)); + _volume1 = new Parameter().WithName("volume").WithFormula(new ConstantFormula(4)); + _sumVolume = new Parameter().WithName("dynamic").WithFormula(new ConstantFormula(3)); + + //simulate a real ROOT Container + var root = new Container().WithContainerType(ContainerType.Simulation).WithName("root"); + _model.Root = root; + + //creating the following structure + //root + // |Container1 + // | |Dynamic = sum of all volume in children of Container 1 + // | |volume = 4 (this parameter should not be used) + // | |SubContainer1_1 + // | | |volume = 1 + // | |SubContainer1_2 + // | | |volume = 2 + // |Container2 + // | |SubContainer2_2 + // | | |volume = 3 (this parameter should not be used) + + + root.Add(_container1); + root.Add(_container2); + + _container1.Add(_sumVolume); + _container1.Add(_volume1); + _subContainer1_1.Add(_volume1_1); + _subContainer1_2.Add(_volume1_2); + //this parameter is not in the same parent and should not be used + _subContainer2_1.Add(_volume2_1); + var sumFormula = new SumFormula + { + Criteria = Create.Criteria(x => x.With("volume").InChildren()) + }; + _sumVolume.Formula = sumFormula; + } + + protected override void Because() + { + sut.ExpandDynamicFormulaIn(_model); + } + + [Observation] + public void should_replace_all_dynamic_formula_with_the_corresponding_explicit_formula() + { + _sumVolume.Formula.IsExplicit().ShouldBeTrue(); + } + + [Observation] + public void should_have_created_an_explicit_formula_that_is_the_sum_of_the_defined_parameters() + { + var explicitFormula = _sumVolume.Formula.DowncastTo(); + //volume_1_1 + volume_1_2 but not volume_2_1 and not volume1 + explicitFormula.FormulaString.ShouldBeEqualTo("P_1 + P_2"); + explicitFormula.ObjectPaths.Count().ShouldBeEqualTo(2); + } + } + internal class When_replacing_the_neighborhood_keyword_in_a_well_defined_path : concern_for_FormulaTask { private IParameter _liverCellParameter; diff --git a/tests/OSPSuite.Core.Tests/Domain/InParentConditionSpecs.cs b/tests/OSPSuite.Core.Tests/Domain/InParentConditionSpecs.cs index c38d1baca..994b29744 100644 --- a/tests/OSPSuite.Core.Tests/Domain/InParentConditionSpecs.cs +++ b/tests/OSPSuite.Core.Tests/Domain/InParentConditionSpecs.cs @@ -1,5 +1,4 @@ -using FakeItEasy; -using OSPSuite.BDDHelper; +using OSPSuite.BDDHelper; using OSPSuite.BDDHelper.Extensions; using OSPSuite.Core.Domain.Descriptors; From b2ffc6628b2303bfe19e43b746e65560f1c0ad78 Mon Sep 17 00:00:00 2001 From: Michael Sevestre Date: Sun, 10 Nov 2024 01:24:04 -0500 Subject: [PATCH 2/4] WIP #2359 add criteria and test. Implementation missing --- src/OSPSuite.Core/Domain/Services/QuantityValuesUpdater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OSPSuite.Core/Domain/Services/QuantityValuesUpdater.cs b/src/OSPSuite.Core/Domain/Services/QuantityValuesUpdater.cs index ec3c8470e..0b7383391 100644 --- a/src/OSPSuite.Core/Domain/Services/QuantityValuesUpdater.cs +++ b/src/OSPSuite.Core/Domain/Services/QuantityValuesUpdater.cs @@ -116,7 +116,7 @@ private IParameter getOrAddModelParameter(ValueUpdaterParams valueUpdater, Param var potentialParameter = parentContainer.Parameter(parameterValue.Name); parentContainer.RemoveChild(potentialParameter); - //for sure it's not there. Add itIn + //for sure it's not there. Add it return parameter.WithParentContainer(parentContainer); } From a2f263177d99499fcbecb7425258a51288463013 Mon Sep 17 00:00:00 2001 From: Michael Sevestre Date: Sun, 10 Nov 2024 13:44:55 -0500 Subject: [PATCH 3/4] Fixes #2359 --- .../Domain/Descriptors/InChildrenCondition.cs | 2 + .../Domain/Services/FormulaTask.cs | 56 +++++++++++++++---- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/OSPSuite.Core/Domain/Descriptors/InChildrenCondition.cs b/src/OSPSuite.Core/Domain/Descriptors/InChildrenCondition.cs index a91d66766..c20b6efc1 100644 --- a/src/OSPSuite.Core/Domain/Descriptors/InChildrenCondition.cs +++ b/src/OSPSuite.Core/Domain/Descriptors/InChildrenCondition.cs @@ -8,6 +8,8 @@ public InChildrenCondition() : base(Constants.IN_CHILDREN, Constants.IN_CHILDREN public override string Condition { get; } = Constants.IN_CHILDREN.ToUpper(); + //false because this condition should never be evaluated as it will need to be replaced + //by some InContainer conditions in the model public override bool IsSatisfiedBy(EntityDescriptor item) => false; public override ITagCondition CloneCondition() => new InChildrenCondition(); diff --git a/src/OSPSuite.Core/Domain/Services/FormulaTask.cs b/src/OSPSuite.Core/Domain/Services/FormulaTask.cs index 5e0038735..61afd5c4c 100644 --- a/src/OSPSuite.Core/Domain/Services/FormulaTask.cs +++ b/src/OSPSuite.Core/Domain/Services/FormulaTask.cs @@ -6,6 +6,7 @@ using OSPSuite.Core.Domain.Formulas; using OSPSuite.Core.Domain.UnitSystem; using OSPSuite.Core.Extensions; +using OSPSuite.Utility; using OSPSuite.Utility.Collections; using OSPSuite.Utility.Exceptions; using OSPSuite.Utility.Extensions; @@ -164,8 +165,6 @@ public void ExpandDynamicReferencesIn(IContainer rootContainer) ExpandLumenSegmentReferencesIn(rootContainer); } - - private bool referencesNeighborhood(ObjectPath path) => path.Contains(NBH); /// @@ -234,7 +233,7 @@ internal void ExpandLumenSegmentReferencesIn(IContainer rootContainer) private void updateLumenSegmentReferencingPath(EntityFormulaPath entityFormulaPath, IContainer rootContainer) { - var (entity, path) = entityFormulaPath; + var (entity, path) = entityFormulaPath; var pathAsList = path.ToList(); var firstIndex = pathAsList.IndexOf(LUMEN_SEGMENT); var lastIndex = pathAsList.LastIndexOf(LUMEN_SEGMENT); @@ -311,7 +310,7 @@ private IContainer getContainerOrThrow(IReadOnlyList path, IEntity entit public void ExpandDynamicFormulaIn(IContainer rootContainer) { - var allFormulaUsable = rootContainer.GetAllChildren().ToEntityDescriptorMapList(); + var allFormulaUsable = rootContainer.GetAllChildren(); var allEntityUsingDynamicFormula = rootContainer.GetAllChildren(x => x.Formula.IsDynamic()); allEntityUsingDynamicFormula.Each(entityUsingFormula => @@ -322,31 +321,66 @@ public void ExpandDynamicFormulaIn(IContainer rootContainer) throw new CircularReferenceInSumFormulaException(dynamicFormula.Name, entityUsingFormula.Name); dynamicFormula.Criteria = updateDynamicFormulaCriteria(dynamicFormula, entityUsingFormula); - entityUsingFormula.Formula = dynamicFormula.ExpandUsing(allFormulaUsable, _objectPathFactory, _objectBaseFactory); + + //this needs to be done after updating the criteria as dynamic tags might be added to the criteria and would + //render the descriptor map list invalid + var allFormulaUsableDescriptor = allFormulaUsable.ToEntityDescriptorMapList(); + entityUsingFormula.Formula = dynamicFormula.ExpandUsing(allFormulaUsableDescriptor, _objectPathFactory, _objectBaseFactory); }); } private DescriptorCriteria updateDynamicFormulaCriteria(DynamicFormula formula, IUsingFormula usingFormula) { - //we need to replace IN PARENT criteria with actual criteria matching the parent of the usingFormula var criteria = formula.Criteria; - var allInParentTags = criteria.Where(x => x.IsAnImplementationOf()).ToList(); var parent = usingFormula.ParentContainer; - if (!allInParentTags.Any() || parent == null) + if (parent == null) + return criteria; + + + var modifiedCriteria = modifyInParentFormulaCriteria(criteria, parent); + modifiedCriteria = modifyInChildrenFormulaCriteria(modifiedCriteria, parent); + + return modifiedCriteria; + } + + private DescriptorCriteria modifyInParentFormulaCriteria(DescriptorCriteria criteria, IContainer parent) => modifyDynamicCriteria(criteria, parent).criteria; + + private DescriptorCriteria modifyInChildrenFormulaCriteria(DescriptorCriteria criteria, IContainer parent) { + var (modifyCriteria, modified) = modifyDynamicCriteria(criteria, parent); + if(!modified) return criteria; + //in case of IN CHILDREN, we need to exclude the direct children from the parent from the search. + //The only way to do this is to add a condition to exclude the children explicitly. We create a random string as tag + //to avoid collision and set it in all DIRECT only children so that they will be excluded + + var uniqueTag = ShortGuid.NewGuid(); + parent.GetChildren().Each(x => x.AddTag(uniqueTag)); + modifyCriteria.Add(new NotMatchTagCondition(uniqueTag)); + + return modifyCriteria; + } + + private (DescriptorCriteria criteria, bool modified) modifyDynamicCriteria(DescriptorCriteria criteria, IContainer parent) where T : TagCondition + { + var allDynamicTags = criteria.Where(x => x.IsAnImplementationOf()).ToList(); + + if (!allDynamicTags.Any()) + return (criteria, false); + //because we need to restrict operations by adding criteria automatically, only AND makes sense if (criteria.Operator != CriteriaOperator.And) throw new OSPSuiteException(Error.InParentTagCanOnlyBeUsedWithAndOperator); - //we clone the criteria and remove all instances of InParentCondition. Then we add the criteria to the parent specifically + //we clone the criteria and remove all instances of the dynamic condition. Then we add the criteria to the parent specifically var modifiedCriteria = criteria.Clone(); - allInParentTags.Each(x => modifiedCriteria.RemoveByTag(x.Tag)); + allDynamicTags.Each(x => modifiedCriteria.RemoveByTag(x.Tag)); //add to the formula the link to parent. We use the consolidated path here so that we do not deal with the root container as criteria var parentPath = _entityPathResolver.PathFor(parent).ToPathArray(); parentPath.Each(x => modifiedCriteria.Add(new InContainerCondition(x))); - return modifiedCriteria; + + return (modifiedCriteria, true); } public string AddParentVolumeReferenceToFormula(IFormula formula) From 35ba6427d8b85f0d6d2002ad57f619880013958e Mon Sep 17 00:00:00 2001 From: Michael Sevestre Date: Mon, 11 Nov 2024 11:27:11 -0500 Subject: [PATCH 4/4] Fixes formatting issue --- src/OSPSuite.Core/Domain/Services/FormulaTask.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OSPSuite.Core/Domain/Services/FormulaTask.cs b/src/OSPSuite.Core/Domain/Services/FormulaTask.cs index 61afd5c4c..5dfe35816 100644 --- a/src/OSPSuite.Core/Domain/Services/FormulaTask.cs +++ b/src/OSPSuite.Core/Domain/Services/FormulaTask.cs @@ -336,7 +336,6 @@ private DescriptorCriteria updateDynamicFormulaCriteria(DynamicFormula formula, if (parent == null) return criteria; - var modifiedCriteria = modifyInParentFormulaCriteria(criteria, parent); modifiedCriteria = modifyInChildrenFormulaCriteria(modifiedCriteria, parent); @@ -345,9 +344,10 @@ private DescriptorCriteria updateDynamicFormulaCriteria(DynamicFormula formula, private DescriptorCriteria modifyInParentFormulaCriteria(DescriptorCriteria criteria, IContainer parent) => modifyDynamicCriteria(criteria, parent).criteria; - private DescriptorCriteria modifyInChildrenFormulaCriteria(DescriptorCriteria criteria, IContainer parent) { + private DescriptorCriteria modifyInChildrenFormulaCriteria(DescriptorCriteria criteria, IContainer parent) + { var (modifyCriteria, modified) = modifyDynamicCriteria(criteria, parent); - if(!modified) + if (!modified) return criteria; //in case of IN CHILDREN, we need to exclude the direct children from the parent from the search.