diff --git a/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java b/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java
index 4405e45d..299116b0 100644
--- a/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java
+++ b/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java
@@ -20,6 +20,12 @@
*/
public class PottsModuleFlyGMCDifferentiation extends PottsModuleProliferationVolumeBasedDivision {
+ /**
+ * Indicates whether GMC growth rate is based on individual cell conditions or average cell
+ * conditions.
+ */
+ Boolean pdeLike;
+
/**
* Creates a fly GMC proliferation module.
*
@@ -27,13 +33,42 @@ public class PottsModuleFlyGMCDifferentiation extends PottsModuleProliferationVo
*/
public PottsModuleFlyGMCDifferentiation(PottsCellFlyGMC cell) {
super(cell);
+ pdeLike = (cell.getParameters().getInt("proliferation/PDELIKE") != 0);
+ }
+
+ /**
+ * Computes the expected equilibrium average GMC volume over one cell cycle.
+ *
+ *
In the Potts model, a cell's target volume is initialized to {@code criticalVolume} on
+ * reset. The Potts energy immediately drives the cell's actual volume toward this target,
+ * regardless of the current growth rate. As a result, the volume-regulated growth phase
+ * effectively begins at {@code criticalVolume} (not the birth volume), even when {@code
+ * VOLUME_BASED_CRITICAL_VOLUME} is off and birth volume is below {@code criticalVolume}.
+ *
+ *
The regulated growth phase therefore runs from {@code criticalVolume} to {@code sizeTarget
+ * * criticalVolume}. Under constant-rate growth, the time-average volume over this phase is the
+ * arithmetic mean of the two endpoints:
+ *
+ *
+ * V_ref = (criticalVolume + sizeTarget * criticalVolume) / 2
+ * = criticalVolume * (1 + sizeTarget) / 2
+ *
+ *
+ * This formula is consistent with the PDE-like branch, which uses {@code avgCritVol * (1 +
+ * sizeTarget) / 2}, and holds whether or not {@code VOLUME_BASED_CRITICAL_VOLUME} is enabled.
+ *
+ * @return the expected equilibrium average GMC volume
+ */
+ double computeEquilibriumVolume() {
+ return cell.getCriticalVolume() * (1.0 + sizeTarget) / 2.0;
}
/**
* Adds a cell to the simulation.
*
*
The cell location is split. The new neuron cell is created, initialized, and added to the
- * schedule. This cell's location is also assigned to a new Neuron cell.
+ * schedule. This cell's location is also assigned to a new Neuron cell. The critical volume of
+ * both neurons is set to the initial volume of each neuron's location.
*
* @param random the random number generator
* @param sim the simulation instance
@@ -55,7 +90,7 @@ void addCell(MersenneTwisterFast random, Simulation sim) {
(PottsCell) newContainer.convert(sim.getCellFactory(), newLocation, random);
sim.getGrid().addObject(newCell, null);
potts.register(newCell);
- newCell.reset(potts.ids, potts.regions);
+ newCell.initialize(potts.ids, potts.regions);
newCell.schedule(sim.getSchedule());
// remove old GMC cell from simulation
@@ -88,7 +123,56 @@ void addCell(MersenneTwisterFast random, Simulation sim) {
sim.getGrid().addObject(differentiatedGMC, null);
potts.register(differentiatedGMC);
- differentiatedGMC.reset(potts.ids, potts.regions);
+ differentiatedGMC.initialize(potts.ids, potts.regions);
differentiatedGMC.schedule(sim.getSchedule());
}
+
+ /**
+ * Updates the effective growth rate according to boolean flags specified in parameters.
+ *
+ * @param sim the simulation
+ */
+ public void updateGrowthRate(Simulation sim) {
+ if (!dynamicGrowthRateVolume) {
+ cellGrowthRate = cellGrowthRateBase;
+ } else {
+ if (!pdeLike) {
+ updateCellVolumeBasedGrowthRate(
+ cell.getLocation().getVolume(), computeEquilibriumVolume());
+ } else {
+ // PDE-like: use population-wide averages for GMCs (same pop as this cell).
+ // The reference volume is the population-average equilibrium volume:
+ // avgVRef = avgCritVol * (1 + sizeTarget) / 2
+ sim.util.Bag objs = sim.getGrid().getAllObjects();
+
+ double volSum = 0.0;
+ double critSum = 0.0;
+ int count = 0;
+
+ for (int i = 0; i < objs.numObjs; i++) {
+ Object o = objs.objs[i];
+ if (!(o instanceof arcade.potts.agent.cell.PottsCell)) {
+ continue;
+ }
+
+ arcade.potts.agent.cell.PottsCell c = (arcade.potts.agent.cell.PottsCell) o;
+ if (c.getPop() != cell.getPop()) {
+ continue; // keep to same population
+ }
+
+ if (o instanceof arcade.potts.agent.cell.PottsCellFlyGMC) {
+ arcade.potts.agent.cell.PottsCellFlyGMC gmc =
+ (arcade.potts.agent.cell.PottsCellFlyGMC) o;
+ volSum += gmc.getLocation().getVolume();
+ critSum += gmc.getCriticalVolume();
+ count++;
+ }
+ }
+ double avgVolume = volSum / count;
+ double avgCritVol = critSum / count;
+ double avgVRef = avgCritVol * (1.0 + sizeTarget) / 2.0;
+ updateCellVolumeBasedGrowthRate(avgVolume, avgVRef);
+ }
+ }
+ }
}
diff --git a/src/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivision.java b/src/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivision.java
index e154be0d..aae4bc94 100644
--- a/src/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivision.java
+++ b/src/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivision.java
@@ -7,13 +7,16 @@
import arcade.potts.util.PottsEnums.Phase;
/**
- * Implementation of {@link PottsModule} for fly GMC agents. The links must be set in the setup file
- * so that 100% of the daughter cells are Neurons.
+ * Implementation of {@link PottsModule} for agents that divide upon reaching a volume threshold
+ * without any cell-cycle duration requirements.
*/
public abstract class PottsModuleProliferationVolumeBasedDivision extends PottsModuleProliferation {
- /** Overall growth rate for cell (voxels/tick). */
- final double cellGrowthRate;
+ /** Base growth rate for cells (voxels/tick). */
+ final double cellGrowthRateBase;
+
+ /** Current growth rate for stem cells (voxels/tick). */
+ double cellGrowthRate;
/**
* Target ratio of critical volume for division size checkpoint (cell must reach CRITICAL_VOLUME
@@ -21,6 +24,14 @@ public abstract class PottsModuleProliferationVolumeBasedDivision extends PottsM
*/
final double sizeTarget;
+ /** Boolean flag indicating whether the growth rate should follow volume-sensitive ruleset. */
+ final boolean dynamicGrowthRateVolume;
+
+ /**
+ * Sensitivity of growth rate to cell volume, only relevant if dynamicGrowthRateVolume is true.
+ */
+ final double growthRateVolumeSensitivity;
+
/**
* Creates a proliferation module in which division is solely dependent on cell volume.
*
@@ -30,16 +41,52 @@ public PottsModuleProliferationVolumeBasedDivision(PottsCell cell) {
super(cell);
Parameters parameters = cell.getParameters();
sizeTarget = parameters.getDouble("proliferation/SIZE_TARGET");
- cellGrowthRate = parameters.getDouble("proliferation/CELL_GROWTH_RATE");
+ cellGrowthRateBase = parameters.getDouble("proliferation/CELL_GROWTH_RATE");
+ dynamicGrowthRateVolume =
+ (parameters.getInt("proliferation/DYNAMIC_GROWTH_RATE_VOLUME") != 0);
+ growthRateVolumeSensitivity =
+ parameters.getDouble("proliferation/GROWTH_RATE_VOLUME_SENSITIVITY");
setPhase(Phase.UNDEFINED);
+ cellGrowthRate = cellGrowthRateBase;
}
@Override
public void step(MersenneTwisterFast random, Simulation sim) {
+ updateGrowthRate(sim);
cell.updateTarget(cellGrowthRate, sizeTarget);
boolean sizeCheck = cell.getVolume() >= sizeTarget * cell.getCriticalVolume();
if (sizeCheck) {
addCell(random, sim);
}
}
+
+ /**
+ * Updates the effective growth rate according to boolean flags specified in parameters.
+ *
+ * @param sim the simulation
+ */
+ public abstract void updateGrowthRate(Simulation sim);
+
+ /**
+ * Updates {@code cellGrowthRate} from a power-law relationship between current volume and a
+ * reference volume.
+ *
+ *
The updated rate is
+ *
+ *
+ * cellGrowthRate = cellGrowthRateBase * (volume / referenceVolume)^growthRateVolumeSensitivity
+ *
+ *
+ * The reference volume is the cell volume at which the basal growth rate is recovered. In
+ * the simplest case this can be the cell's critical volume, but users may use another
+ * biologically motivated reference such as an equilibrium or population-averaged volume.
+ *
+ * @param volume the current volume used in the growth-rate scaling
+ * @param referenceVolume the reference volume that defines the baseline growth-rate scale
+ */
+ public void updateCellVolumeBasedGrowthRate(double volume, double referenceVolume) {
+ double refVol = referenceVolume;
+ cellGrowthRate =
+ cellGrowthRateBase * Math.pow((volume / refVol), growthRateVolumeSensitivity);
+ }
}
diff --git a/src/arcade/potts/parameter.potts.xml b/src/arcade/potts/parameter.potts.xml
index ebc46e0d..c95d0ce6 100644
--- a/src/arcade/potts/parameter.potts.xml
+++ b/src/arcade/potts/parameter.potts.xml
@@ -64,6 +64,13 @@
+
+
+
+
+
+
+
diff --git a/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java b/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java
index c1b04e27..1334d992 100644
--- a/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java
+++ b/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java
@@ -133,7 +133,8 @@ final void tearDown() {
@Test
public void addCell_called_callsExpectedMethods() {
- // When the module calls make() on the cell, return Quiescent PottsCellContainer mock
+ // When the module calls make() on the cell, return Quiescent PottsCellContainer
+ // mock
container = mock(PottsCellContainer.class);
when(gmcCell.make(eq(123), eq(State.QUIESCENT), any(MersenneTwisterFast.class)))
.thenReturn(container);
@@ -150,7 +151,7 @@ public void addCell_called_callsExpectedMethods() {
verify(grid).addObject(newCell, null);
verify(potts).register(newCell);
- verify(newCell).reset(dummyIDs, dummyRegions);
+ verify(newCell).initialize(dummyIDs, dummyRegions);
verify(newCell).schedule(schedule);
verify(grid).removeObject(gmcCell, location);
@@ -161,7 +162,161 @@ public void addCell_called_callsExpectedMethods() {
(PottsCellFlyNeuron) constructed.convert(cellFactory, location, random);
verify(grid).addObject(diffCell, null);
verify(potts).register(diffCell);
- verify(diffCell).reset(dummyIDs, dummyRegions);
+ verify(diffCell).initialize(dummyIDs, dummyRegions);
verify(diffCell).schedule(schedule);
}
+
+ @Test
+ public void updateGrowthRate_dynamicOff_setsBaseRate() {
+ // dynamicGrowthRateVolume = 0; base rate used
+ when(gmcCell.getParameters()).thenReturn(parameters);
+ when(parameters.getInt("proliferation/DYNAMIC_GROWTH_RATE_VOLUME")).thenReturn(0);
+ when(parameters.getDouble("proliferation/CELL_GROWTH_RATE")).thenReturn(7.5);
+
+ PottsModuleFlyGMCDifferentiation module = new PottsModuleFlyGMCDifferentiation(gmcCell);
+
+ module.updateGrowthRate(sim);
+ org.junit.jupiter.api.Assertions.assertEquals(7.5, module.cellGrowthRate, 1e-9);
+ }
+
+ @Test
+ public void updateGrowthRate_dynamicOn_pdeLikeFalse_usesSelfVolumeAndEquilibriumRef() {
+ when(gmcCell.getParameters()).thenReturn(parameters);
+ when(parameters.getInt("proliferation/DYNAMIC_GROWTH_RATE_VOLUME")).thenReturn(1);
+ when(parameters.getDouble("proliferation/CELL_GROWTH_RATE")).thenReturn(4.0);
+ when(parameters.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2);
+ when(parameters.getInt("proliferation/PDELIKE")).thenReturn(0);
+
+ // critVol = 150.0; sizeTarget = 1.2
+ // vRef = critVol * (1 + sizeTarget) / 2 = 150.0 * 2.2 / 2 = 165.0
+ when(gmcCell.getCriticalVolume()).thenReturn(150.0);
+ when(gmcCell.getLocation().getVolume()).thenReturn(30.0);
+
+ PottsModuleFlyGMCDifferentiation module =
+ org.mockito.Mockito.spy(new PottsModuleFlyGMCDifferentiation(gmcCell));
+
+ org.mockito.Mockito.doNothing()
+ .when(module)
+ .updateCellVolumeBasedGrowthRate(
+ org.mockito.ArgumentMatchers.anyDouble(),
+ org.mockito.ArgumentMatchers.anyDouble());
+
+ module.updateGrowthRate(sim);
+
+ double expectedVRef = 150.0 * (1.0 + 1.2) / 2.0; // 165.0
+ org.mockito.Mockito.verify(module)
+ .updateCellVolumeBasedGrowthRate(
+ org.mockito.ArgumentMatchers.eq(30.0),
+ org.mockito.ArgumentMatchers.eq(expectedVRef));
+ }
+
+ @Test
+ public void
+ updateGrowthRate_dynamicOnPdeLikeTrue_usesAverageVolumeAndEquilibriumRefAcrossGMCs() {
+ // Flags
+ when(gmcCell.getParameters()).thenReturn(parameters);
+ when(parameters.getInt("proliferation/DYNAMIC_GROWTH_RATE_VOLUME")).thenReturn(1);
+ when(parameters.getDouble("proliferation/CELL_GROWTH_RATE")).thenReturn(4.0);
+ when(parameters.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2);
+ when(parameters.getInt("proliferation/PDELIKE")).thenReturn(1);
+
+ // Same population for all GMCs we want included
+ when(gmcCell.getPop()).thenReturn(3);
+
+ // Self (included in average)
+ when(gmcCell.getLocation().getVolume()).thenReturn(30.0);
+ when(gmcCell.getCriticalVolume()).thenReturn(150.0);
+
+ // Two more GMCs in same population
+ PottsCellFlyGMC gmcB = mock(PottsCellFlyGMC.class);
+ PottsCellFlyGMC gmcC = mock(PottsCellFlyGMC.class);
+ when(gmcB.getPop()).thenReturn(3);
+ when(gmcC.getPop()).thenReturn(3);
+
+ PottsLocation locB = mock(PottsLocation.class);
+ PottsLocation locC = mock(PottsLocation.class);
+ when(gmcB.getLocation()).thenReturn(locB);
+ when(gmcC.getLocation()).thenReturn(locC);
+ when(locB.getVolume()).thenReturn(10.0);
+ when(locC.getVolume()).thenReturn(20.0);
+ when(gmcB.getCriticalVolume()).thenReturn(100.0);
+ when(gmcC.getCriticalVolume()).thenReturn(200.0);
+
+ // Noise: different type and/or different pop → must be ignored
+ PottsCell randomOtherPop = mock(PottsCell.class);
+ when(randomOtherPop.getPop()).thenReturn(99);
+ PottsCellFlyNeuron neuronSamePop = mock(PottsCellFlyNeuron.class);
+ when(neuronSamePop.getPop()).thenReturn(3);
+
+ // Bag with self + two GMCs + noise
+ sim.util.Bag bag = new sim.util.Bag();
+ bag.add(gmcCell); // self GMC (pop 3)
+ bag.add(gmcB); // GMC (pop 3)
+ bag.add(gmcC); // GMC (pop 3)
+ bag.add(randomOtherPop); // different pop → ignored
+ bag.add(neuronSamePop); // not a GMC → ignored
+ when(sim.getGrid().getAllObjects()).thenReturn(bag);
+
+ PottsModuleFlyGMCDifferentiation module =
+ org.mockito.Mockito.spy(new PottsModuleFlyGMCDifferentiation(gmcCell));
+
+ // Observe the averaged args
+ org.mockito.Mockito.doNothing()
+ .when(module)
+ .updateCellVolumeBasedGrowthRate(
+ org.mockito.ArgumentMatchers.anyDouble(),
+ org.mockito.ArgumentMatchers.anyDouble());
+
+ module.updateGrowthRate(sim);
+
+ // avgVol = (30 + 10 + 20) / 3 = 20.0
+ // avgCritVol = (150 + 100 + 200) / 3 = 150.0
+ // avgVRef = avgCritVol * (1 + sizeTarget) / 2 = 150.0 * (1 + 1.2) / 2 = 165.0
+ double expectedAvgVol = (30.0 + 10.0 + 20.0) / 3.0; // 20.0
+ double expectedAvgCrit = (150.0 + 100.0 + 200.0) / 3.0; // 150.0
+ double expectedAvgVRef = expectedAvgCrit * (1.0 + 1.2) / 2.0; // 165.0
+
+ org.mockito.Mockito.verify(module)
+ .updateCellVolumeBasedGrowthRate(
+ org.mockito.ArgumentMatchers.eq(expectedAvgVol),
+ org.mockito.ArgumentMatchers.eq(expectedAvgVRef));
+ }
+
+ // computeEquilibriumVolume tests
+
+ @Test
+ public void computeEquilibriumVolume_returnsArithmeticMeanOfCritAndDivisionVolumes() {
+ // critVol = 150.0; sizeTarget = 1.2
+ // vRef = critVol * (1 + sizeTarget) / 2 = 150.0 * 2.2 / 2 = 165.0
+ when(parameters.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2);
+ when(gmcCell.getCriticalVolume()).thenReturn(150.0);
+
+ PottsModuleFlyGMCDifferentiation module = new PottsModuleFlyGMCDifferentiation(gmcCell);
+ org.junit.jupiter.api.Assertions.assertEquals(
+ 165.0, module.computeEquilibriumVolume(), 1e-9);
+ }
+
+ @Test
+ public void computeEquilibriumVolume_differentSizeTarget_scalesCorrectly() {
+ // critVol = 100.0; sizeTarget = 2.0
+ // vRef = 100.0 * (1 + 2.0) / 2 = 150.0
+ when(parameters.getDouble("proliferation/SIZE_TARGET")).thenReturn(2.0);
+ when(gmcCell.getCriticalVolume()).thenReturn(100.0);
+
+ PottsModuleFlyGMCDifferentiation module = new PottsModuleFlyGMCDifferentiation(gmcCell);
+ org.junit.jupiter.api.Assertions.assertEquals(
+ 150.0, module.computeEquilibriumVolume(), 1e-9);
+ }
+
+ @Test
+ public void computeEquilibriumVolume_differentCritVol_scalesCorrectly() {
+ // critVol = 200.0; sizeTarget = 1.2
+ // vRef = 200.0 * (1 + 1.2) / 2 = 220.0
+ when(parameters.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2);
+ when(gmcCell.getCriticalVolume()).thenReturn(200.0);
+
+ PottsModuleFlyGMCDifferentiation module = new PottsModuleFlyGMCDifferentiation(gmcCell);
+ org.junit.jupiter.api.Assertions.assertEquals(
+ 220.0, module.computeEquilibriumVolume(), 1e-9);
+ }
}
diff --git a/test/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivisionTest.java b/test/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivisionTest.java
index 4856f81e..733aaf89 100644
--- a/test/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivisionTest.java
+++ b/test/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivisionTest.java
@@ -5,14 +5,20 @@
import arcade.core.sim.Simulation;
import arcade.core.util.Parameters;
import arcade.potts.agent.cell.PottsCellFlyGMC;
+import arcade.potts.env.location.PottsLocation2D;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.Mockito.*;
public class PottsModuleProliferationVolumeBasedDivisionTest {
static class PottsModuleProliferationVolumeBasedDivisionMock
extends PottsModuleProliferationVolumeBasedDivision {
+
boolean addCellCalled = false;
+ boolean growthRateUpdated = false;
+
PottsModuleProliferationVolumeBasedDivisionMock(PottsCellFlyGMC cell) {
super(cell);
}
@@ -21,15 +27,43 @@ static class PottsModuleProliferationVolumeBasedDivisionMock
void addCell(MersenneTwisterFast random, Simulation sim) {
addCellCalled = true;
}
+
+ @Override
+ public void updateGrowthRate(Simulation sim) {
+ growthRateUpdated = true;
+ cellGrowthRate = 7.5;
+ }
}
@Test
- public void step_belowCheckpoint_updatesTargetOnly() {
+ public void step_called_usesGrowthRateSetByUpdateGrowthRate() {
PottsCellFlyGMC cell = mock(PottsCellFlyGMC.class);
Parameters params = mock(Parameters.class);
+ Simulation sim = mock(Simulation.class);
+
when(cell.getParameters()).thenReturn(params);
when(params.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2);
when(params.getDouble("proliferation/CELL_GROWTH_RATE")).thenReturn(4.0);
+ when(params.getInt("proliferation/DYNAMIC_GROWTH_RATE_VOLUME")).thenReturn(0);
+ when(params.getDouble("proliferation/GROWTH_RATE_VOLUME_SENSITIVITY")).thenReturn(2.0);
+
+ when(cell.getVolume()).thenReturn(50.0);
+ when(cell.getCriticalVolume()).thenReturn(100.0);
+
+ PottsModuleProliferationVolumeBasedDivisionMock module =
+ new PottsModuleProliferationVolumeBasedDivisionMock(cell);
+ module.step(new MersenneTwisterFast(), sim);
+
+ verify(cell).updateTarget(7.5, 1.2);
+ assertFalse(module.addCellCalled);
+ }
+
+ @Test
+ public void step_belowCheckpoint_updates() {
+ PottsCellFlyGMC cell = mock(PottsCellFlyGMC.class);
+ Parameters params = mock(Parameters.class);
+ when(cell.getParameters()).thenReturn(params);
+ when(params.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2);
when(cell.getCriticalVolume()).thenReturn(100.0);
when(cell.getVolume()).thenReturn(50.0); // below checkpoint
@@ -38,7 +72,8 @@ public void step_belowCheckpoint_updatesTargetOnly() {
module.step(mock(MersenneTwisterFast.class), mock(Simulation.class));
- verify(cell).updateTarget(4.0, 1.2);
+ verify(cell).updateTarget(7.5, 1.2);
+ assert module.growthRateUpdated : "growth rate should be updated on every step";
assert !module.addCellCalled : "addCell should not be called below checkpoint";
}
@@ -49,7 +84,6 @@ public void step_atOrAboveCheckpoint_triggersAddCell() {
Parameters params = mock(Parameters.class);
when(cell.getParameters()).thenReturn(params);
when(params.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2);
- when(params.getDouble("proliferation/CELL_GROWTH_RATE")).thenReturn(4.0);
when(cell.getCriticalVolume()).thenReturn(100.0);
when(cell.getVolume()).thenReturn(120.0); // at or above checkpoint
@@ -58,7 +92,108 @@ public void step_atOrAboveCheckpoint_triggersAddCell() {
module.step(mock(MersenneTwisterFast.class), mock(Simulation.class));
- verify(cell).updateTarget(4.0, 1.2);
+ verify(cell).updateTarget(7.5, 1.2);
+ assert module.growthRateUpdated : "growth rate should be updated on every step";
assert module.addCellCalled : "addCell should be called at or above checkpoint";
}
+
+ @Test
+ public void updateVolumeBasedGrowthRate_ratioOne_keepsBaseRate() {
+ // baseGrowth = 4.0, volume = Ka => growth = 4.0
+ PottsCellFlyGMC cell = mock(PottsCellFlyGMC.class);
+ Parameters params = mock(Parameters.class);
+ PottsLocation2D loc = mock(PottsLocation2D.class);
+
+ when(cell.getParameters()).thenReturn(params);
+ when(params.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2);
+ when(params.getDouble("proliferation/CELL_GROWTH_RATE")).thenReturn(4.0);
+ when(params.getInt("proliferation/DYNAMIC_GROWTH_RATE_VOLUME")).thenReturn(1);
+ when(params.getDouble("proliferation/GROWTH_RATE_VOLUME_SENSITIVITY")).thenReturn(2.0);
+
+ when(cell.getLocation()).thenReturn(loc);
+ when(loc.getVolume()).thenReturn(100.0);
+ when(cell.getCriticalVolume()).thenReturn(100.0);
+
+ PottsModuleProliferationVolumeBasedDivisionTest
+ .PottsModuleProliferationVolumeBasedDivisionMock
+ module = new PottsModuleProliferationVolumeBasedDivisionMock(cell);
+
+ module.updateCellVolumeBasedGrowthRate(loc.getVolume(), cell.getCriticalVolume());
+ assertEquals(4.0, module.cellGrowthRate, 1e-9);
+ }
+
+ @Test
+ public void updateVolumeBasedGrowthRate_ratioGreaterThanOne_scalesUpByPowerLaw() {
+ // baseGrowth = 2.0, ratio = 2.0, sensitivity = 3 => 2 * 2^3 = 2 * 8 = 12
+ PottsCellFlyGMC cell = mock(PottsCellFlyGMC.class);
+ Parameters params = mock(Parameters.class);
+ PottsLocation2D loc = mock(PottsLocation2D.class);
+
+ when(cell.getParameters()).thenReturn(params);
+ when(params.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2);
+ when(params.getDouble("proliferation/CELL_GROWTH_RATE")).thenReturn(2.0);
+ when(params.getInt("proliferation/DYNAMIC_GROWTH_RATE_VOLUME")).thenReturn(1);
+ when(params.getDouble("proliferation/GROWTH_RATE_VOLUME_SENSITIVITY")).thenReturn(3.0);
+
+ when(cell.getLocation()).thenReturn(loc);
+ when(loc.getVolume()).thenReturn(200.0);
+ when(cell.getCriticalVolume()).thenReturn(100.0);
+
+ PottsModuleProliferationVolumeBasedDivisionTest
+ .PottsModuleProliferationVolumeBasedDivisionMock
+ module = new PottsModuleProliferationVolumeBasedDivisionMock(cell);
+
+ module.updateCellVolumeBasedGrowthRate(loc.getVolume(), cell.getCriticalVolume());
+ assertEquals(16.0, module.cellGrowthRate, 1e-9);
+ }
+
+ @Test
+ public void updateVolumeBasedGrowthRate_ratioLessThanOne_scalesDownByPowerLaw() {
+ // baseGrowth = 4.0, ratio = 0.5, sensitivity = 2.0 => 4 * 0.5^2 = 1.0
+ PottsCellFlyGMC cell = mock(PottsCellFlyGMC.class);
+ Parameters params = mock(Parameters.class);
+ PottsLocation2D loc = mock(PottsLocation2D.class);
+
+ when(cell.getParameters()).thenReturn(params);
+ when(params.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2);
+ when(params.getDouble("proliferation/CELL_GROWTH_RATE")).thenReturn(4.0);
+ when(params.getInt("proliferation/DYNAMIC_GROWTH_RATE_VOLUME")).thenReturn(1);
+ when(params.getDouble("proliferation/GROWTH_RATE_VOLUME_SENSITIVITY")).thenReturn(2.0);
+
+ when(cell.getLocation()).thenReturn(loc);
+ when(loc.getVolume()).thenReturn(50.0);
+ when(cell.getCriticalVolume()).thenReturn(100.0);
+
+ PottsModuleProliferationVolumeBasedDivisionTest
+ .PottsModuleProliferationVolumeBasedDivisionMock
+ module = new PottsModuleProliferationVolumeBasedDivisionMock(cell);
+
+ module.updateCellVolumeBasedGrowthRate(loc.getVolume(), cell.getCriticalVolume());
+ assertEquals(1.0, module.cellGrowthRate, 1e-9);
+ }
+
+ @Test
+ public void updateVolumeBasedGrowthRate_zeroSensitivity_returnsBaseRateRegardlessOfVolume() {
+ // sensitivity = 0 => growth = baseGrowth * ratio^0 = baseGrowth
+ PottsCellFlyGMC cell = mock(PottsCellFlyGMC.class);
+ Parameters params = mock(Parameters.class);
+ PottsLocation2D loc = mock(PottsLocation2D.class);
+
+ when(cell.getParameters()).thenReturn(params);
+ when(params.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2);
+ when(params.getDouble("proliferation/CELL_GROWTH_RATE")).thenReturn(3.5);
+ when(params.getInt("proliferation/DYNAMIC_GROWTH_RATE_VOLUME")).thenReturn(1);
+ when(params.getDouble("proliferation/GROWTH_RATE_VOLUME_SENSITIVITY")).thenReturn(0.0);
+
+ when(cell.getLocation()).thenReturn(loc);
+ when(loc.getVolume()).thenReturn(250.0);
+ when(cell.getCriticalVolume()).thenReturn(100.0);
+
+ PottsModuleProliferationVolumeBasedDivisionTest
+ .PottsModuleProliferationVolumeBasedDivisionMock
+ module = new PottsModuleProliferationVolumeBasedDivisionMock(cell);
+
+ module.updateCellVolumeBasedGrowthRate(loc.getVolume(), cell.getCriticalVolume());
+ assertEquals(3.5, module.cellGrowthRate, 1e-9);
+ }
}