diff --git a/src/PKSim.Core/Services/SimulationBuildingBlockUpdater.cs b/src/PKSim.Core/Services/SimulationBuildingBlockUpdater.cs
index 8605d533a..2691f6c44 100644
--- a/src/PKSim.Core/Services/SimulationBuildingBlockUpdater.cs
+++ b/src/PKSim.Core/Services/SimulationBuildingBlockUpdater.cs
@@ -42,8 +42,10 @@ public interface ISimulationBuildingBlockUpdater
/// Returns false if a simple parameter value update cannot be performed (i.e. structural change was made)
///
/// Template building block as defined in repository
- /// Used building block (the one based on the template building block at a given time)
- ///
+ ///
+ /// Used building block (the one based on the template building block at a given time). It
+ /// it assumed that the object is loaded (e.g. reference to building block can be used
+ ///
bool QuickUpdatePossibleFor(IPKSimBuildingBlock templateBuildingBlock, UsedBuildingBlock usedBuildingBlock);
///
@@ -61,7 +63,7 @@ public interface ISimulationBuildingBlockUpdater
bool BuildingBlockSupportsQuickUpdate(IPKSimBuildingBlock templateBuildingBlock);
///
- /// Returns whether a building block comparison is available for the building block
+ /// Returns whether a building block comparison is available for the building block
///
bool BuildingBlockSupportComparison(IPKSimBuildingBlock templateBuildingBlock);
}
@@ -140,7 +142,28 @@ public bool QuickUpdatePossibleFor(IPKSimBuildingBlock templateBuildingBlock, Us
if (!BuildingBlockSupportsQuickUpdate(templateBuildingBlock))
return false;
- return templateBuildingBlock.StructureVersion == usedBuildingBlock.StructureVersion;
+ //not the same structure, easy return
+ var sameStructureVersion = templateBuildingBlock.StructureVersion == usedBuildingBlock.StructureVersion;
+ if (!sameStructureVersion)
+ return false;
+
+ //For individual, there is a special handling required as we need to also check that the used expression profile have the same structure version
+ if (templateBuildingBlock is not Individual individualTemplate)
+ return true;
+
+ //It has to be available by construction
+ var usedIndividual = usedBuildingBlock.BuildingBlock as Individual;
+ //but we return false just in case :)
+ if (usedIndividual == null)
+ return false;
+
+ //let's compare the expression profile in each individuals and see if they are comparable
+ return individualTemplate.AllExpressionProfiles().All(x =>
+ {
+ //assume we can find it by name. Otherwise => structural change
+ var usedExpressionProfile = usedIndividual.AllExpressionProfiles().FindByName(x.Name);
+ return usedExpressionProfile != null && x.StructureVersion == usedExpressionProfile.StructureVersion;
+ });
}
public void UpdateProtocolsInSimulation(Simulation simulation)
@@ -151,7 +174,7 @@ public void UpdateProtocolsInSimulation(Simulation simulation)
UpdateMultipleUsedBuildingBlockInSimulationFromTemplate(simulation, protocolProperties.Select(x => x.Protocol), PKSimBuildingBlockType.Protocol);
var allSimulationProtocols = simulation.AllBuildingBlocks().ToList();
- //update selected protocol with references in simulation instead of templats
+ //update selected protocol with references in simulation instead of templates
protocolProperties.Each(x => x.Protocol = allSimulationProtocols.FindByName(x.Protocol.Name));
}
diff --git a/src/PKSim.Presentation/Services/SimulationTask.cs b/src/PKSim.Presentation/Services/SimulationTask.cs
index bb55e6319..24f6e433d 100644
--- a/src/PKSim.Presentation/Services/SimulationTask.cs
+++ b/src/PKSim.Presentation/Services/SimulationTask.cs
@@ -95,8 +95,11 @@ public override Simulation AddToProject()
_buildingBlockTask.Load(templateBuildingBlock);
_buildingBlockTask.Load(simulation);
+ //Now that the simulation is loaded, we want to make sure that the captured ref to used building block also has a valid reference to the underlying building block
+ var loadedUsedBuildingBlock = simulation.UsedBuildingBlockByTemplateId(usedBuildingBlock.TemplateId);
+
//check if quick update possible. if yes =>perform quick update
- if (_simulationBuildingBlockUpdater.QuickUpdatePossibleFor(templateBuildingBlock, usedBuildingBlock))
+ if (_simulationBuildingBlockUpdater.QuickUpdatePossibleFor(templateBuildingBlock, loadedUsedBuildingBlock))
{
var updateCommand = _blockParametersToSimulationUpdater.UpdateParametersFromBuildingBlockInSimulation(templateBuildingBlock, simulation);
_buildingBlockTask.AddCommandToHistory(updateCommand);
@@ -115,8 +118,11 @@ public override Simulation AddToProject()
_buildingBlockTask.Load(templateBuildingBlock);
_buildingBlockTask.Load(simulation);
+ //Now that the simulation is loaded, we want to make sure that the captured ref to used building block also has a valid reference to the underlying building block
+ var loadedUsedBuildingBlock = simulation.UsedBuildingBlockByTemplateId(usedBuildingBlock.TemplateId);
+
//check if quick update possible. if yes =>perform quick update
- if (_simulationBuildingBlockUpdater.QuickUpdatePossibleFor(templateBuildingBlock, usedBuildingBlock))
+ if (_simulationBuildingBlockUpdater.QuickUpdatePossibleFor(templateBuildingBlock, loadedUsedBuildingBlock))
{
var updateCommand = _simulationParametersToBlockUpdater.UpdateParametersFromSimulationInBuildingBlock(simulation, templateBuildingBlock);
_buildingBlockTask.AddCommandToHistory(updateCommand);
diff --git a/tests/PKSim.Tests/Core/SimulationBuildingBlockUpdaterSpecs.cs b/tests/PKSim.Tests/Core/SimulationBuildingBlockUpdaterSpecs.cs
index fab3cae93..57171ebc2 100644
--- a/tests/PKSim.Tests/Core/SimulationBuildingBlockUpdaterSpecs.cs
+++ b/tests/PKSim.Tests/Core/SimulationBuildingBlockUpdaterSpecs.cs
@@ -1,10 +1,10 @@
+using FakeItEasy;
using OSPSuite.BDDHelper;
using OSPSuite.BDDHelper.Extensions;
-using FakeItEasy;
+using OSPSuite.Core.Domain;
using PKSim.Core.Mappers;
using PKSim.Core.Model;
using PKSim.Core.Services;
-using OSPSuite.Core.Domain;
namespace PKSim.Core
{
@@ -133,6 +133,94 @@ public void should_return_false_if_the_building_block_being_updated_is_a_populat
}
}
+ public abstract class concern_for_SimulationBuildingBlockUpdaterForIndividual : concern_for_SimulationBuildingBlockUpdater
+ {
+ protected Individual _templateIndividual;
+ protected UsedBuildingBlock _usedIndividual;
+ protected ExpressionProfile _templateExpression;
+ protected Individual _individual;
+
+ protected override void Context()
+ {
+ base.Context();
+ _templateIndividual = new Individual
+ {
+ Id = "templateId",
+ StructureVersion = 1
+ };
+
+ _templateExpression = DomainHelperForSpecs.CreateExpressionProfile("Human", "CYP");
+ _templateExpression.Id = "templateExpressionProfile";
+ _templateExpression.StructureVersion = 5;
+
+ _templateIndividual.AddExpressionProfile(_templateExpression);
+
+ _individual = new Individual
+ {
+ StructureVersion = _templateIndividual.StructureVersion,
+ };
+
+
+ _usedIndividual = new UsedBuildingBlock(_templateIndividual.Id, PKSimBuildingBlockType.Individual)
+ {
+ StructureVersion = _templateIndividual.StructureVersion,
+ BuildingBlock = _individual
+ };
+ }
+ }
+
+ public class When_asked_if_a_quick_update_possible_between_an_individual_an_a_used_individual_with_expression_profile_presenting_a_structural_change : concern_for_SimulationBuildingBlockUpdaterForIndividual
+ {
+ protected override void Context()
+ {
+ base.Context();
+ var expression = DomainHelperForSpecs.CreateExpressionProfile("Human", "CYP");
+ expression.StructureVersion = _templateExpression.StructureVersion + 1;
+ _individual.AddExpressionProfile(expression);
+ }
+
+ [Observation]
+ public void should_not_allow_for_a_quick_update()
+ {
+ sut.QuickUpdatePossibleFor(_templateIndividual, _usedIndividual).ShouldBeFalse();
+ }
+ }
+
+ public class When_asked_if_a_quick_update_possible_between_an_individual_an_a_used_individual_with_expression_profile_presenting_no_structural_change : concern_for_SimulationBuildingBlockUpdaterForIndividual
+ {
+ protected override void Context()
+ {
+ base.Context();
+ var expression = DomainHelperForSpecs.CreateExpressionProfile("Human", "CYP");
+ expression.StructureVersion = _templateExpression.StructureVersion;
+ _individual.AddExpressionProfile(expression);
+ }
+
+ [Observation]
+ public void should_allow_for_a_quick_update()
+ {
+ sut.QuickUpdatePossibleFor(_templateIndividual, _usedIndividual).ShouldBeTrue();
+ }
+ }
+
+ public class When_asked_if_a_quick_update_possible_between_an_individual_an_a_used_individual_with_expression_profile_missing_by_name : concern_for_SimulationBuildingBlockUpdaterForIndividual
+ {
+ protected override void Context()
+ {
+ base.Context();
+
+ var expression = DomainHelperForSpecs.CreateExpressionProfile("Human", "AnotherCYP");
+ expression.StructureVersion = _templateExpression.StructureVersion;
+ _individual.AddExpressionProfile(expression);
+ }
+
+ [Observation]
+ public void should_not_allow_for_a_quick_update()
+ {
+ sut.QuickUpdatePossibleFor(_templateIndividual, _usedIndividual).ShouldBeFalse();
+ }
+ }
+
public class When_updating_the_formulation_defined_in_a_simulation_according_to_the_formulation_mapping : concern_for_SimulationBuildingBlockUpdater
{
private Simulation _simulation;
@@ -169,23 +257,23 @@ public void should_add_one_instance_of_each_used_formulation_to_the_simulation()
public class When_updating_the_formulation_defined_in_a_simulation_according_to_the_formulation_mapping_using_both_a_template_and_a_simulation_formulation_for_the_same_formulation : concern_for_SimulationBuildingBlockUpdater
{
private Simulation _simulation;
-
+
protected override void Context()
{
base.Context();
var formulation = new Formulation().WithName("F");
- _simulation = new IndividualSimulation { Properties = new SimulationProperties() };
+ _simulation = new IndividualSimulation {Properties = new SimulationProperties()};
var formulationUsedInSimulation = new Formulation().WithName("F");
var compoundProperties = new CompoundProperties();
- compoundProperties.ProtocolProperties.AddFormulationMapping(new FormulationMapping { Formulation = formulation });
- compoundProperties.ProtocolProperties.AddFormulationMapping(new FormulationMapping { Formulation = formulationUsedInSimulation });
+ compoundProperties.ProtocolProperties.AddFormulationMapping(new FormulationMapping {Formulation = formulation});
+ compoundProperties.ProtocolProperties.AddFormulationMapping(new FormulationMapping {Formulation = formulationUsedInSimulation});
_simulation.Properties.AddCompoundProperties(compoundProperties);
}
[Observation]
public void should_throw_an_exception()
{
- The.Action(()=>sut.UpdateFormulationsInSimulation(_simulation)).ShouldThrowAn();
+ The.Action(() => sut.UpdateFormulationsInSimulation(_simulation)).ShouldThrowAn();
}
}
diff --git a/tests/PKSim.Tests/Presentation/SimulationTaskSpecs.cs b/tests/PKSim.Tests/Presentation/SimulationTaskSpecs.cs
index 83ed33148..9e3523903 100644
--- a/tests/PKSim.Tests/Presentation/SimulationTaskSpecs.cs
+++ b/tests/PKSim.Tests/Presentation/SimulationTaskSpecs.cs
@@ -70,6 +70,7 @@ public class When_the_simulation_task_is_asked_to_perform_an_update_from_a_templ
private IPKSimBuildingBlock _templateBuildingBlock;
private IPKSimCommand _updateCommand;
private UsedBuildingBlock _usedBuildingBlock;
+ private UsedBuildingBlock _loadedUsedBuildingBlock;
protected override void Context()
{
@@ -78,7 +79,10 @@ protected override void Context()
_templateBuildingBlock = A.Fake();
_updateCommand = A.Fake();
_usedBuildingBlock = A.Fake();
- A.CallTo(() => _simulationBuildingBlockUpdater.QuickUpdatePossibleFor(_templateBuildingBlock, _usedBuildingBlock)).Returns(true);
+ _loadedUsedBuildingBlock = A.Fake();
+ //make sure we a new instance to ensure usage of loaded used building block
+ A.CallTo(() => _simulation.UsedBuildingBlockByTemplateId(_usedBuildingBlock.TemplateId)).Returns(_loadedUsedBuildingBlock);
+ A.CallTo(() => _simulationBuildingBlockUpdater.QuickUpdatePossibleFor(_templateBuildingBlock, _loadedUsedBuildingBlock)).Returns(true);
A.CallTo(() => _blockParametersToSimulationUpdater.UpdateParametersFromBuildingBlockInSimulation(_templateBuildingBlock, _simulation)).Returns(_updateCommand);
}