diff --git a/src/arcade/potts/agent/module/PottsModuleFlyStemProliferation.java b/src/arcade/potts/agent/module/PottsModuleFlyStemProliferation.java
index be50874d..129c31cf 100644
--- a/src/arcade/potts/agent/module/PottsModuleFlyStemProliferation.java
+++ b/src/arcade/potts/agent/module/PottsModuleFlyStemProliferation.java
@@ -22,6 +22,9 @@
import arcade.potts.env.location.Voxel;
import arcade.potts.sim.Potts;
import arcade.potts.sim.PottsSimulation;
+import arcade.potts.util.PottsEnums.Direction;
+import arcade.potts.util.PottsEnums.Phase;
+import arcade.potts.util.PottsEnums.State;
import static arcade.potts.util.PottsEnums.Direction;
import static arcade.potts.util.PottsEnums.Phase;
import static arcade.potts.util.PottsEnums.State;
@@ -81,8 +84,15 @@ public class PottsModuleFlyStemProliferation extends PottsModuleProliferationVol
*/
final double nbContactHillN;
+ /*
+ * Boolean flag for whether the daughter cell's differentiation is determined deterministically.
+ */
+ final boolean hasDeterministicDifferentiation;
+
final double initialSize;
+ public static final double EPSILON = 1e-8;
+
/**
* Creates a proliferation {@code Module} for the given {@link PottsCellFlyStem}.
*
@@ -127,13 +137,20 @@ public PottsModuleFlyStemProliferation(PottsCellFlyStem cell) {
nbContactHalfMax = parameters.getDouble("proliferation/NB_CONTACT_HALF_MAX");
nbContactHillN = parameters.getDouble("proliferation/NB_CONTACT_HILL_N");
+ String hasDeterministicDifferentiationString =
+ parameters.getString("proliferation/HAS_DETERMINISTIC_DIFFERENTIATION");
+ if (!hasDeterministicDifferentiationString.equals("TRUE")
+ && !hasDeterministicDifferentiationString.equals("FALSE")) {
+ throw new InvalidParameterException(
+ "hasDeterministicDifferentiation must be either TRUE or FALSE");
+ }
+ hasDeterministicDifferentiation = hasDeterministicDifferentiationString.equals("TRUE");
+
initialSize = cell.getVolume();
setPhase(Phase.UNDEFINED);
}
- public static final double EPSILON = 1e-8;
-
@Override
public void addCell(MersenneTwisterFast random, Simulation sim) {
Potts potts = ((PottsSimulation) sim).getPotts();
@@ -143,7 +160,7 @@ public void addCell(MersenneTwisterFast random, Simulation sim) {
PottsLocation2D parentLoc = (PottsLocation2D) cell.getLocation();
PottsLocation daughterLoc = (PottsLocation) parentLoc.split(random, divisionPlane);
- boolean isDaughterStem = daughterStem(parentLoc, daughterLoc);
+ boolean isDaughterStem = daughterStem(parentLoc, daughterLoc, divisionPlane);
if (isDaughterStem) {
makeDaughterStemCell(daughterLoc, sim, potts, random);
@@ -301,6 +318,15 @@ public Plane getMUDDivisionPlane(PottsCellFlyStem cell) {
Direction.XY_PLANE.vector,
StemType.MUDMUT.splitDirectionRotation);
Voxel splitVoxel = getCellSplitVoxel(StemType.MUDMUT, cell, defaultNormal);
+ System.out.println(
+ "in getMUDDivisionPlane, default Normal = ("
+ + defaultNormal.getX()
+ + ", "
+ + +defaultNormal.getY()
+ + ", "
+ + +defaultNormal.getZ()
+ + ", "
+ + ")");
return new Plane(new Double3D(splitVoxel.x, splitVoxel.y, splitVoxel.z), defaultNormal);
}
@@ -328,7 +354,7 @@ public static Voxel getCellSplitVoxel(
* @param loc2 the other cell location post division
* @return whether or not the daughter cell should be a stem cell
*/
- public boolean daughterStem(PottsLocation loc1, PottsLocation loc2) {
+ private boolean daughterStemRuleBasedDifferentiation(PottsLocation loc1, PottsLocation loc2) {
if (((PottsCellFlyStem) cell).getStemType() == StemType.WT) {
return false;
} else if (((PottsCellFlyStem) cell).getStemType() == StemType.MUDMUT) {
@@ -351,6 +377,48 @@ public boolean daughterStem(PottsLocation loc1, PottsLocation loc2) {
"Invalid differentiation ruleset: " + differentiationRuleset);
}
+ /*
+ * Determines whether the daughter cell should be a neuroblast or a GMC according to the orientation.
+ * This is deterministic.
+ *
+ * @param divisionPlane
+ * @return {@code true} if the daughter should be a stem cell. {@code false} if the daughter should be a GMC.
+ */
+ private boolean daughterStemDeterministic(Plane divisionPlane) {
+
+ Vector normalVector = divisionPlane.getUnitNormalVector();
+
+ Vector apicalAxis = ((PottsCellFlyStem) cell).getApicalAxis();
+ Vector expectedMUDNormalVector =
+ Vector.rotateVectorAroundAxis(
+ apicalAxis,
+ Direction.XY_PLANE.vector,
+ StemType.MUDMUT.splitDirectionRotation);
+ // If TRUE, the daughter should be stem. Otherwise, should be GMC
+ return Math.abs(normalVector.getX() - expectedMUDNormalVector.getX()) <= EPSILON
+ && Math.abs(normalVector.getY() - expectedMUDNormalVector.getY()) <= EPSILON
+ && Math.abs(normalVector.getZ() - expectedMUDNormalVector.getZ()) <= EPSILON;
+ }
+
+ /**
+ * Determines whether a daughter cell should remain a stem cell or differentiate into a GMC.
+ *
+ *
This method serves as a wrapper that delegates to either a deterministic or rule-based
+ * differentiation mechanism depending on the value of {@code hasDeterministicDifferentiation}.
+ *
+ * @param parentsLoc the location of the parent cell before division
+ * @param daughterLoc the location of the daughter cell after division
+ * @param divisionPlane the plane of division for the daughter cell
+ * @return {@code true} if the daughter should remain a stem cell; {@code false} if it should be
+ * a GMC
+ */
+ public boolean daughterStem(
+ PottsLocation2D parentsLoc, PottsLocation daughterLoc, Plane divisionPlane) {
+ return hasDeterministicDifferentiation
+ ? daughterStemDeterministic(divisionPlane)
+ : daughterStemRuleBasedDifferentiation(parentsLoc, daughterLoc);
+ }
+
/**
* Determines if the distance between two centroids, projected along the apical axis, is less
* than or equal to the given range.
diff --git a/src/arcade/potts/parameter.potts.xml b/src/arcade/potts/parameter.potts.xml
index 73f91b6c..f373d98f 100644
--- a/src/arcade/potts/parameter.potts.xml
+++ b/src/arcade/potts/parameter.potts.xml
@@ -80,6 +80,7 @@
+
diff --git a/test/arcade/potts/agent/module/PottsModuleFlyStemProliferationTest.java b/test/arcade/potts/agent/module/PottsModuleFlyStemProliferationTest.java
index 3773f102..95ddcc7e 100644
--- a/test/arcade/potts/agent/module/PottsModuleFlyStemProliferationTest.java
+++ b/test/arcade/potts/agent/module/PottsModuleFlyStemProliferationTest.java
@@ -27,6 +27,7 @@
import arcade.potts.sim.Potts;
import arcade.potts.sim.PottsSimulation;
import arcade.potts.util.PottsEnums.Phase;
+import arcade.potts.util.PottsEnums.State;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
@@ -61,6 +62,8 @@ public class PottsModuleFlyStemProliferationTest {
float EPSILON = 1e-6f;
+ int stemCellPop;
+
@BeforeEach
public final void setup() {
// Core mocks
@@ -104,6 +107,8 @@ public final void setup() {
when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("volume");
when(parameters.getDouble("proliferation/DIFFERENTIATION_RULESET_EQUALITY_RANGE"))
.thenReturn(0.5);
+ when(parameters.getString("proliferation/HAS_DETERMINISTIC_DIFFERENTIATION"))
+ .thenReturn("TRUE");
// Link selection
GrabBag links = mock(GrabBag.class);
@@ -111,7 +116,8 @@ public final void setup() {
when(links.next(random)).thenReturn(2);
// Other defaults
- when(stemCell.getPop()).thenReturn(3);
+ stemCellPop = 3;
+ when(stemCell.getPop()).thenReturn(stemCellPop);
when(stemCell.getCriticalVolume()).thenReturn(100.0);
}
@@ -453,7 +459,8 @@ public void step_volumeAtCheckpoint_callsAddCellPhaseStaysUndefined() {
// Needed by calculateGMCDaughterCellCriticalVolume(...)
when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.WT);
- // Plane/voxel path (chooseDivisionPlane -> WT -> getWTDivisionPlaneWithRotationalVariance)
+ // Plane/voxel path (chooseDivisionPlane -> WT ->
+ // getWTDivisionPlaneWithRotationalVariance)
when(parameters.getString("proliferation/APICAL_AXIS_RULESET")).thenReturn("global");
when(stemCell.getApicalAxis()).thenReturn(new Vector(0, 1, 0));
when(stemLoc.getOffsetInApicalFrame(any(), any(Vector.class)))
@@ -484,94 +491,6 @@ public void step_volumeAtCheckpoint_callsAddCellPhaseStaysUndefined() {
assertEquals(Phase.UNDEFINED, module.phase); // remains UNDEFINED
}
- // Differentiation rule tests
-
- @Test
- public void daughterStem_stemTypeWT_returnsFalse() {
- when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.WT);
-
- module = new PottsModuleFlyStemProliferation(stemCell);
- boolean result = module.daughterStem(stemLoc, daughterLoc);
-
- assertFalse(result);
- }
-
- @Test
- public void daughterStem_volumeRule_differenceWithinRange_returnsTrue() {
- when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.MUDMUT);
- when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("volume");
- when(parameters.getDouble("proliferation/DIFFERENTIATION_RULESET_EQUALITY_RANGE"))
- .thenReturn(1.0);
- when(stemLoc.getVolume()).thenReturn(10.0);
- when(daughterLoc.getVolume()).thenReturn(10.5); // difference = 0.5 < 1.0
-
- module = new PottsModuleFlyStemProliferation(stemCell);
- boolean result = module.daughterStem(stemLoc, daughterLoc);
-
- assertTrue(result);
- }
-
- @Test
- public void daughterStem_volumeRule_differenceOutsideRange_returnsFalse() {
- when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.MUDMUT);
- when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("volume");
- when(parameters.getDouble("proliferation/DIFFERENTIATION_RULESET_EQUALITY_RANGE"))
- .thenReturn(0.5);
- when(stemLoc.getVolume()).thenReturn(10.0);
- when(daughterLoc.getVolume()).thenReturn(11.0); // difference = 1.0 > 0.5
-
- module = new PottsModuleFlyStemProliferation(stemCell);
- boolean result = module.daughterStem(stemLoc, daughterLoc);
-
- assertFalse(result);
- }
-
- @Test
- public void daughterStem_locationRule_differenceWithinRange_returnsTrue() {
- when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.MUDMUT);
- when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("location");
- when(parameters.getDouble("proliferation/DIFFERENTIATION_RULESET_EQUALITY_RANGE"))
- .thenReturn(0.5);
- when(stemLoc.getCentroid()).thenReturn(new double[] {0, 1.0, 0});
- when(daughterLoc.getCentroid()).thenReturn(new double[] {0, 1.3, 0}); // difference = 0.3
- when(stemCell.getApicalAxis()).thenReturn(new Vector(0, 1, 0));
-
- module = new PottsModuleFlyStemProliferation(stemCell);
- boolean result = module.daughterStem(stemLoc, daughterLoc);
-
- assertTrue(result);
- }
-
- @Test
- public void daughterStem_locationRule_differenceOutsideRange_returnsFalse() {
- when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.MUDMUT);
- when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("location");
- when(parameters.getDouble("proliferation/DIFFERENTIATION_RULESET_EQUALITY_RANGE"))
- .thenReturn(0.5);
- when(stemLoc.getCentroid()).thenReturn(new double[] {0, 1.0, 0});
- when(daughterLoc.getCentroid()).thenReturn(new double[] {0, 1.7, 0}); // difference = 0.7
- when(stemCell.getApicalAxis()).thenReturn(new Vector(0, 1, 0));
-
- module = new PottsModuleFlyStemProliferation(stemCell);
- boolean result = module.daughterStem(stemLoc, daughterLoc);
-
- assertFalse(result);
- }
-
- @Test
- public void daughterStem_invalidRule_throwsException() {
- when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.MUDMUT);
- when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("banana");
- when(parameters.getDouble("proliferation/DIFFERENTIATION_RULESET_EQUALITY_RANGE"))
- .thenReturn(0.5);
- when(stemLoc.getCentroid()).thenReturn(new double[] {0, 1.0, 0});
- when(daughterLoc.getCentroid()).thenReturn(new double[] {0, 1.2, 0});
-
- module = new PottsModuleFlyStemProliferation(stemCell);
- assertThrows(
- IllegalArgumentException.class, () -> module.daughterStem(stemLoc, daughterLoc));
- }
-
// Apical axis rule tests
@Test
@@ -679,7 +598,7 @@ public void calculateGMCDaughterCellCriticalVolume_volumeBasedOn_returnsScaledVa
when(popParametersMiniBox.getDouble("proliferation/SIZE_TARGET")).thenReturn(2.0);
when(sim.getCellFactory()).thenReturn(factory);
- when(factory.getParameters(3)).thenReturn(popParametersMiniBox);
+ when(factory.getParameters(stemCellPop)).thenReturn(popParametersMiniBox);
when(parameters.getInt("proliferation/VOLUME_BASED_CRITICAL_VOLUME")).thenReturn(1);
when(parameters.getDouble("proliferation/VOLUME_BASED_CRITICAL_VOLUME_MULTIPLIER"))
@@ -695,52 +614,45 @@ public void calculateGMCDaughterCellCriticalVolume_volumeBasedOn_returnsScaledVa
@Test
public void addCell_WTVolumeSwap_swapsVoxelsAndCreatesNewCell() {
- // Arrange: WT stem cell, using volume-based differentiation
when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.WT);
when(parameters.getString("proliferation/APICAL_AXIS_RULESET")).thenReturn("global");
+ when(parameters.getString("proliferation/HAS_DETERMINISTIC_DIFFERENTIATION"))
+ .thenReturn("FALSE"); // ⬅️ force rule-based path
when(stemCell.getApicalAxis()).thenReturn(new Vector(0, 1, 0));
- when(parameters.getDouble("proliferation/SIZE_TARGET"))
- .thenReturn(1.0); // default for volume
- when(parameters.getInt("proliferation/VOLUME_BASED_CRITICAL_VOLUME"))
- .thenReturn(0); // use classic mode
+ when(parameters.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.0);
+ when(parameters.getInt("proliferation/VOLUME_BASED_CRITICAL_VOLUME")).thenReturn(0);
- // Set up the condition that parent volume < daughter volume → stem/daughter swap required
+ // parent smaller than daughter -> rule-based 'volume' says parent is GMC ->
+ // triggers swap
when(stemLoc.getVolume()).thenReturn(5.0);
when(daughterLoc.getVolume()).thenReturn(10.0);
- // Stub division plane
Plane dummyPlane = mock(Plane.class);
when(dummyPlane.getUnitNormalVector()).thenReturn(new Vector(1, 0, 0));
when(stemLoc.split(eq(random), eq(dummyPlane))).thenReturn(daughterLoc);
- // Stub cell creation
PottsCellContainer container = mock(PottsCellContainer.class);
PottsCellFlyStem newStemCell = mock(PottsCellFlyStem.class);
- when(stemCell.make(eq(42), eq(State.PROLIFERATIVE), eq(random), eq(2), eq(25.0)))
- .thenReturn(container);
+ when(stemCell.make(eq(42), eq(State.PROLIFERATIVE), eq(random), anyInt(), anyDouble()))
+ .thenReturn(container); // ⬅️ relax CV match
when(container.convert(eq(factory), eq(daughterLoc), eq(random))).thenReturn(newStemCell);
- // Spy on the module so we can override plane selection
PottsModuleFlyStemProliferation module = spy(new PottsModuleFlyStemProliferation(stemCell));
+ doReturn(0.0).when(module).sampleDivisionPlaneOffset();
doReturn(dummyPlane)
.when(module)
.getWTDivisionPlaneWithRotationalVariance(eq(stemCell), anyDouble());
- // Act: call addCell
try (MockedStatic mocked = mockStatic(PottsLocation.class)) {
module.addCell(random, sim);
-
- // Assert: verify voxels were swapped and new cell scheduled
mocked.verify(() -> PottsLocation.swapVoxels(stemLoc, daughterLoc));
}
- // Assert: new stem cell was scheduled
verify(newStemCell).schedule(any());
}
@Test
public void addCell_WTVolumeNoSwap_doesNotSwapVoxelsAndCreatesNewCell() {
- // Arrange: WT stem cell, using volume-based differentiation
when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.WT);
when(parameters.getString("proliferation/APICAL_AXIS_RULESET")).thenReturn("global");
when(stemCell.getApicalAxis()).thenReturn(new Vector(0, 1, 0));
@@ -759,7 +671,8 @@ public void addCell_WTVolumeNoSwap_doesNotSwapVoxelsAndCreatesNewCell() {
// Stub cell creation
PottsCellContainer container = mock(PottsCellContainer.class);
PottsCellFlyStem newStemCell = mock(PottsCellFlyStem.class);
- when(stemCell.make(eq(42), eq(State.PROLIFERATIVE), eq(random), eq(2), eq(25.0)))
+ when(stemCell.make(
+ eq(42), eq(State.PROLIFERATIVE), eq(random), eq(stemCellPop), anyDouble()))
.thenReturn(container);
when(container.convert(eq(factory), eq(daughterLoc), eq(random))).thenReturn(newStemCell);
@@ -769,21 +682,17 @@ public void addCell_WTVolumeNoSwap_doesNotSwapVoxelsAndCreatesNewCell() {
.when(module)
.getWTDivisionPlaneWithRotationalVariance(eq(stemCell), anyDouble());
- // Act
try (MockedStatic mocked = mockStatic(PottsLocation.class)) {
module.addCell(random, sim);
-
- // Assert: swapVoxels should NOT be called
mocked.verify(() -> PottsLocation.swapVoxels(any(), any()), never());
}
-
- // Assert: new stem cell was created and scheduled
verify(newStemCell).schedule(any());
}
@Test
public void addCell_MUDMUTOffsetAboveThreshold_createsStemCell() {
when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.MUDMUT);
+
when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("volume");
when(parameters.getString("proliferation/APICAL_AXIS_RULESET")).thenReturn("global");
when(stemCell.getApicalAxis()).thenReturn(new Vector(0, 1, 0));
@@ -803,17 +712,17 @@ public void addCell_MUDMUTOffsetAboveThreshold_createsStemCell() {
PottsCellContainer container = mock(PottsCellContainer.class);
PottsCellFlyStem newCell = mock(PottsCellFlyStem.class);
- when(stemCell.make(eq(42), eq(State.PROLIFERATIVE), eq(random), eq(3), eq(100.0)))
+ when(stemCell.make(eq(42), eq(State.PROLIFERATIVE), eq(random), eq(stemCellPop), eq(100.0)))
.thenReturn(container);
when(container.convert(eq(factory), eq(daughterLoc), eq(random))).thenReturn(newCell);
when(stemCell.getCriticalVolume()).thenReturn(100.0);
- when(stemCell.getPop()).thenReturn(3);
+ when(stemCell.getPop()).thenReturn(stemCellPop);
module = spy(new PottsModuleFlyStemProliferation(stemCell));
Plane dummyPlane = mock(Plane.class);
doReturn(dummyPlane).when(module).getMUDDivisionPlane(eq(stemCell));
when(stemLoc.split(eq(random), eq(dummyPlane))).thenReturn(daughterLoc);
- doReturn(true).when(module).daughterStem(any(), any());
+ doReturn(true).when(module).daughterStem(any(), any(), any());
module.addCell(random, sim);
@@ -823,6 +732,7 @@ public void addCell_MUDMUTOffsetAboveThreshold_createsStemCell() {
@Test
public void addCell_MUDMUTOffsetBelowThreshold_createsGMCWithVolumeSwap() {
when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.MUDMUT);
+
when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("volume");
when(parameters.getString("proliferation/APICAL_AXIS_RULESET")).thenReturn("global");
when(stemCell.getApicalAxis()).thenReturn(new Vector(0, 1, 0));
@@ -837,7 +747,7 @@ public void addCell_MUDMUTOffsetBelowThreshold_createsGMCWithVolumeSwap() {
.thenReturn(container);
when(container.convert(eq(factory), eq(daughterLoc), eq(random))).thenReturn(newCell);
when(stemCell.getCriticalVolume()).thenReturn(100.0);
- when(stemCell.getPop()).thenReturn(3);
+ when(stemCell.getPop()).thenReturn(stemCellPop);
module = spy(new PottsModuleFlyStemProliferation(stemCell));
Plane dummyPlane = mock(Plane.class);
@@ -845,7 +755,7 @@ public void addCell_MUDMUTOffsetBelowThreshold_createsGMCWithVolumeSwap() {
.when(module)
.getWTDivisionPlaneWithRotationalVariance(eq(stemCell), anyDouble());
when(stemLoc.split(eq(random), eq(dummyPlane))).thenReturn(daughterLoc);
- doReturn(false).when(module).daughterStem(any(), any());
+ doReturn(false).when(module).daughterStem(any(), any(), any());
try (MockedStatic mocked = mockStatic(PottsLocation.class)) {
module.addCell(random, sim);
@@ -885,8 +795,8 @@ public void getNumNBNeighbors_withTwoUniqueStemNeighbors_returnsCorrectCount() {
when(neighbor12.getID()).thenReturn(12);
when(stemCell.getID()).thenReturn(42);
- when(neighbor10.getPop()).thenReturn(3); // match cell.getPop
- when(neighbor11.getPop()).thenReturn(3); // match cell.getPop
+ when(neighbor10.getPop()).thenReturn(stemCellPop); // match cell.getPop
+ when(neighbor11.getPop()).thenReturn(stemCellPop); // match cell.getPop
when(neighbor12.getPop()).thenReturn(99); // no match
when(grid.getObjectAt(10)).thenReturn(neighbor10);
@@ -997,4 +907,86 @@ public void updateNBContactGrowthRate_highNeighbors_returnsLowGrowthRate() {
// Hill repression = 25 / (25 + 400) = 25 / 425 ≈ 0.0588
assertEquals(10.0 * (25.0 / 425.0), module.cellGrowthRate, 1e-6);
}
+
+ // TODO: Have Danielle rename and fix
+ // @Test
+ // void daughterStem_DeterministicTrue() {
+ // // Mock parameters
+ // when(parameters.getString("proliferation/HAS_DETERMINISTIC_DIFFERENTIATION"))
+ // .thenReturn("TRUE");
+ //
+ // when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("volume");
+ // when(parameters.getDouble("proliferation/DIFFERENTIATION_RULESET_EQUALITY_RANGE"))
+ // .thenReturn(0.1);
+
+ // // Mock cell type + division plane normal vector
+ // Plane plane = mock(Plane.class);
+ // when(plane.getUnitNormalVector()).thenReturn(new Vector(1.0, 0, 0));
+
+ // // Construct module
+ // PottsModuleFlyStemProliferation module = new
+ // PottsModuleFlyStemProliferation(stemCell);
+
+ // // Call
+ // boolean result = module.daughterStem(stemLoc, daughterLoc, plane);
+
+ // // Verify
+ // assertTrue(
+ // result,
+ // "Expected daughterStemWrapper to return true for deterministic orientation");
+ // }
+
+ // @Test
+ // void testDaughterStem_DeterministicFalse() {
+ // when(parameters.getString("proliferation/HAS_DETERMINISTIC_DIFFERENTIATION"))
+ // .thenReturn("TRUE");
+ //
+ // when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("volume");
+ // when(parameters.getDouble("proliferation/DIFFERENTIATION_RULESET_EQUALITY_RANGE"))
+ // .thenReturn(0.1);
+
+ // Plane plane = mock(Plane.class);
+ // when(plane.getUnitNormalVector()).thenReturn(new Vector(0, 1.0, 0));
+
+ // PottsModuleFlyStemProliferation module = new
+ // PottsModuleFlyStemProliferation(stemCell);
+
+ // boolean result = module.daughterStem(stemLoc, daughterLoc, plane);
+
+ // assertFalse(result, "Expected false when division plane normal is not (1,0,0)");
+ // }
+
+ @Test
+ void testDaughterStem_RuleBased_VolumeTrue() {
+ when(parameters.getString("proliferation/HAS_DETERMINISTIC_DIFFERENTIATION"))
+ .thenReturn("FALSE");
+ when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("volume");
+ when(parameters.getDouble("proliferation/DIFFERENTIATION_RULESET_EQUALITY_RANGE"))
+ .thenReturn(10.0); // large enough for |10 - 5| < 10
+
+ when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.MUDMUT);
+
+ PottsModuleFlyStemProliferation module = new PottsModuleFlyStemProliferation(stemCell);
+
+ boolean result = module.daughterStem(stemLoc, daughterLoc, mock(Plane.class));
+
+ assertTrue(result, "Expected true since |10-5| < range");
+ }
+
+ @Test
+ void testDaughterStem_RuleBased_VolumeFalse() {
+ when(parameters.getString("proliferation/HAS_DETERMINISTIC_DIFFERENTIATION"))
+ .thenReturn("FALSE");
+ when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("volume");
+ when(parameters.getDouble("proliferation/DIFFERENTIATION_RULESET_EQUALITY_RANGE"))
+ .thenReturn(1.0); // |10 - 5| = 5 > 1
+
+ when(stemCell.getStemType()).thenReturn(PottsCellFlyStem.StemType.MUDMUT);
+
+ PottsModuleFlyStemProliferation module = new PottsModuleFlyStemProliferation(stemCell);
+
+ boolean result = module.daughterStem(stemLoc, daughterLoc, mock(Plane.class));
+
+ assertFalse(result, "Expected false since |10-5| > range");
+ }
}