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"); + } }