diff --git a/src/arcade/potts/agent/cell/PottsCellFlyGMC.java b/src/arcade/potts/agent/cell/PottsCellFlyGMC.java
index 48f4a212a..5fdd15cf5 100644
--- a/src/arcade/potts/agent/cell/PottsCellFlyGMC.java
+++ b/src/arcade/potts/agent/cell/PottsCellFlyGMC.java
@@ -55,6 +55,41 @@ public PottsCellContainer make(int newID, CellState newState, MersenneTwisterFas
criticalRegionHeights);
}
+ /**
+ * Creates a container for a new daughter cell with an explicit critical volume.
+ *
+ *
Unlike {@link #make(int, CellState, MersenneTwisterFast)}, which copies this cell's
+ * critical volume, this overload lets the caller set the daughter's critical volume from the
+ * volume it is actually born into rather than inheriting the parent's.
+ *
+ * @param newID the daughter cell ID
+ * @param newState the daughter cell state
+ * @param assignedCritVol the critical volume assigned to the daughter cell
+ * @param random the random number generator
+ * @return a container for the daughter cell
+ */
+ public PottsCellContainer make(
+ int newID, CellState newState, double assignedCritVol, MersenneTwisterFast random) {
+ divisions++;
+
+ int newPop = links.next(random);
+
+ return new PottsCellContainer(
+ newID,
+ id,
+ newPop,
+ age,
+ divisions,
+ newState,
+ null,
+ 0,
+ null,
+ assignedCritVol,
+ criticalHeight,
+ criticalRegionVolumes,
+ criticalRegionHeights);
+ }
+
@Override
void setStateModule(CellState newState) {
switch ((State) newState) {
diff --git a/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java b/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java
index 4405e45dc..f85e244c6 100644
--- a/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java
+++ b/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java
@@ -29,11 +29,37 @@ public PottsModuleFlyGMCDifferentiation(PottsCellFlyGMC cell) {
super(cell);
}
+ /**
+ * Computes the expected equilibrium average GMC volume over one cell cycle.
+ *
+ *
In the Potts model, 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
@@ -50,12 +76,14 @@ void addCell(MersenneTwisterFast random, Simulation sim) {
// Create and schedule new neuron cell
int newID = sim.getID();
- CellContainer newContainer = cell.make(newID, State.QUIESCENT, random);
+ CellContainer newContainer =
+ ((PottsCellFlyGMC) cell)
+ .make(newID, State.QUIESCENT, newLocation.getVolume(), random);
PottsCell newCell =
(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
@@ -78,7 +106,7 @@ void addCell(MersenneTwisterFast random, Simulation sim) {
null,
0,
null,
- oldCell.getCriticalVolume(),
+ location.getVolume(),
oldCell.getCriticalHeight(),
oldCell.getCriticalRegionVolumes(),
oldCell.getCriticalRegionHeights());
@@ -88,7 +116,25 @@ 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.
+ *
+ *
The rule is selected as follows. When {@code DYNAMIC_GROWTH_RATE_VOLUME} is off the growth
+ * rate is simply the basal rate. When it is on, cells use a per-cell rule that compares each
+ * cell's own volume against its equilibrium volume
+ *
+ * @param sim the simulation
+ */
+ public void updateGrowthRate(Simulation sim) {
+ if (!dynamicGrowthRateVolume) {
+ cellGrowthRate = cellGrowthRateBase;
+ } else {
+ updateCellVolumeBasedGrowthRate(
+ cell.getLocation().getVolume(), computeEquilibriumVolume());
+ }
+ }
}
diff --git a/src/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivision.java b/src/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivision.java
index e154be0d4..aae4bc94f 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 ebc46e0da..1cee61d43 100644
--- a/src/arcade/potts/parameter.potts.xml
+++ b/src/arcade/potts/parameter.potts.xml
@@ -64,6 +64,10 @@
+
+
+
+
diff --git a/test/arcade/potts/agent/cell/PottsCellFlyGMCTest.java b/test/arcade/potts/agent/cell/PottsCellFlyGMCTest.java
index adcd0cc7c..aa807a328 100644
--- a/test/arcade/potts/agent/cell/PottsCellFlyGMCTest.java
+++ b/test/arcade/potts/agent/cell/PottsCellFlyGMCTest.java
@@ -125,4 +125,26 @@ public void make_called_returnsCorrectNewContainer() {
() -> assertNull(container.criticalRegionVolumes),
() -> assertNull(container.criticalRegionHeights));
}
+
+ @Test
+ public void make_withExplicitCritVol_usesProvidedCritVol() {
+ PottsCellFlyGMC gmc =
+ new PottsCellFlyGMC(baseContainer, locationMock, parametersMock, links);
+ double explicitCritVol = cellCriticalVolume + 50.0;
+ PottsCellContainer container = gmc.make(cellID, State.QUIESCENT, explicitCritVol, random);
+ assertAll(
+ () -> assertNotNull(container),
+ () -> assertEquals(cellID, container.parent),
+ () -> assertEquals(1, container.pop),
+ () -> assertEquals(cellAge, container.age),
+ () -> assertEquals(cellDivisions + 1, container.divisions),
+ () -> assertEquals(State.QUIESCENT, container.state),
+ () -> assertNull(container.phase),
+ () -> assertEquals(0, container.voxels),
+ () -> assertNull(container.regionVoxels),
+ () -> assertEquals(explicitCritVol, container.criticalVolume, EPSILON),
+ () -> assertEquals(cellCriticalHeight, container.criticalHeight, EPSILON),
+ () -> assertNull(container.criticalRegionVolumes),
+ () -> assertNull(container.criticalRegionHeights));
+ }
}
diff --git a/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java b/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java
index c1b04e272..7c808e485 100644
--- a/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java
+++ b/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java
@@ -133,9 +133,18 @@ final void tearDown() {
@Test
public void addCell_called_callsExpectedMethods() {
+ double newLocVol = 42.0;
+ double locVol = 50.0;
+ when(newLocation.getVolume()).thenReturn(newLocVol);
+ when(location.getVolume()).thenReturn(locVol);
+
// 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)))
+ when(gmcCell.make(
+ eq(123),
+ eq(State.QUIESCENT),
+ eq(newLocVol),
+ any(MersenneTwisterFast.class)))
.thenReturn(container);
newCell = mock(PottsCell.class);
when(container.convert(eq(cellFactory), eq(newLocation), any(MersenneTwisterFast.class)))
@@ -146,11 +155,11 @@ public void addCell_called_callsExpectedMethods() {
module.addCell(random, sim);
verify(location).split(random);
verify(gmcCell).reset(dummyIDs, dummyRegions);
- verify(gmcCell).make(123, State.QUIESCENT, random);
+ verify(gmcCell).make(123, State.QUIESCENT, newLocVol, random);
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 +170,86 @@ 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_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);
+ // 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));
+ }
+
+ // 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_doubleSizeTarget_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 4856f81e0..733aaf896 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);
+ }
}