diff --git a/build.gradle b/build.gradle index 2e3a6e31b..21da1d2ca 100644 --- a/build.gradle +++ b/build.gradle @@ -163,6 +163,8 @@ task updateVersion (group: "versioning", description: "Syncs gradle version with } } +copyJar.mustRunAfter spotlessMisc, spotlessJava, compileTestJava, test, jacocoTestReport + build.dependsOn copyJar test.finalizedBy jacocoTestReport diff --git a/input/1.xml b/input/1.xml new file mode 100644 index 000000000..c47975017 --- /dev/null +++ b/input/1.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/1.xml b/setup/1.xml new file mode 100644 index 000000000..ca9ab286c --- /dev/null +++ b/setup/1.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/arcade/potts/PottsARCADE.java b/src/arcade/potts/PottsARCADE.java index f3a552e9e..ac685a4ee 100644 --- a/src/arcade/potts/PottsARCADE.java +++ b/src/arcade/potts/PottsARCADE.java @@ -31,6 +31,8 @@ public OutputLoader getLoader(Series series) { @Override public OutputSaver getSaver(Series series) { - return new PottsOutputSaver(series); + PottsOutputSaver saver = new PottsOutputSaver(series); + saver.saveProspero = settings.contains("SAVE_PROSPERO"); + return saver; } } diff --git a/src/arcade/potts/agent/cell/PottsCellContainer.java b/src/arcade/potts/agent/cell/PottsCellContainer.java index 10e3f170b..4567d6cc9 100644 --- a/src/arcade/potts/agent/cell/PottsCellContainer.java +++ b/src/arcade/potts/agent/cell/PottsCellContainer.java @@ -105,6 +105,8 @@ public PottsCellContainer( /** * Creates a {@code PottsCellContainer} instance. * + *

The container does not have prospero. + * * @param id the cell ID * @param parent the parent ID * @param pop the cell population index diff --git a/src/arcade/potts/agent/cell/PottsCellFly.java b/src/arcade/potts/agent/cell/PottsCellFly.java new file mode 100644 index 000000000..c792aef7d --- /dev/null +++ b/src/arcade/potts/agent/cell/PottsCellFly.java @@ -0,0 +1,42 @@ +package arcade.potts.agent.cell; + +import arcade.core.env.location.Location; +import arcade.core.util.GrabBag; +import arcade.core.util.Parameters; + +/** + * Implementation of {@link PottsCell} for Potts Fly models. + * + *

Cells follow {@link PottsCell} rules, but additionally keep track of the amount of prospero. + * + *

[TODO: fix class comment] + */ +public abstract class PottsCellFly extends PottsCell { + + /** Amount of prospero in cell. */ + private double prospero; + + public PottsCellFly( + PottsCellContainer container, Location location, Parameters parameters, GrabBag links) { + super(container, location, parameters, links); + this.prospero = 0; + } + + /** + * Gets the amount of prospero in the cell. + * + * @return the amount of prospero in the cell. + */ + public double getProspero() { + return prospero; + } + + /** + * Sets the amount of prospero for the cell. + * + * @param prospero the amount of prospero in the cell. + */ + public void setProspero(double prospero) { + this.prospero = prospero; + } +} diff --git a/src/arcade/potts/agent/cell/PottsCellFlyGMC.java b/src/arcade/potts/agent/cell/PottsCellFlyGMC.java index f88ad4fc2..28c3f7a67 100644 --- a/src/arcade/potts/agent/cell/PottsCellFlyGMC.java +++ b/src/arcade/potts/agent/cell/PottsCellFlyGMC.java @@ -16,7 +16,7 @@ * PottsModuleProliferationVolumeBasedDivision} module. The basal apoptosis rate of this cell should * be set to 0 in the setup file. */ -public class PottsCellFlyGMC extends PottsCell { +public class PottsCellFlyGMC extends PottsCellFly { /** * Creates a fly GMC {@code PottsCell} agent. diff --git a/src/arcade/potts/agent/cell/PottsCellFlyNeuron.java b/src/arcade/potts/agent/cell/PottsCellFlyNeuron.java index ac1187aaf..90ac50de4 100644 --- a/src/arcade/potts/agent/cell/PottsCellFlyNeuron.java +++ b/src/arcade/potts/agent/cell/PottsCellFlyNeuron.java @@ -9,7 +9,7 @@ import static arcade.potts.util.PottsEnums.State; /** Represents a fly neuron cell in the Potts model. This cell is quiescent. */ -public final class PottsCellFlyNeuron extends PottsCell { +public final class PottsCellFlyNeuron extends PottsCellFly { /** * Creates a {@code PottsCellFlyNeuron} {@code PottsCell} agent. diff --git a/src/arcade/potts/agent/cell/PottsCellFlyStem.java b/src/arcade/potts/agent/cell/PottsCellFlyStem.java index 410e0c6e3..a10f64a95 100644 --- a/src/arcade/potts/agent/cell/PottsCellFlyStem.java +++ b/src/arcade/potts/agent/cell/PottsCellFlyStem.java @@ -11,7 +11,7 @@ import arcade.potts.util.PottsEnums.Phase; import static arcade.potts.util.PottsEnums.State; -public class PottsCellFlyStem extends PottsCell { +public class PottsCellFlyStem extends PottsCellFly { /** Enum outlining parameters for each cell type. */ public enum StemType { /** Wild type stem cell. */ diff --git a/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java b/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java index 0bfff0219..1ff9c237b 100644 --- a/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java +++ b/src/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiation.java @@ -4,10 +4,7 @@ import arcade.core.agent.cell.CellContainer; import arcade.core.env.location.Location; import arcade.core.sim.Simulation; -import arcade.potts.agent.cell.PottsCell; -import arcade.potts.agent.cell.PottsCellContainer; -import arcade.potts.agent.cell.PottsCellFlyGMC; -import arcade.potts.agent.cell.PottsCellFlyNeuron; +import arcade.potts.agent.cell.*; import arcade.potts.env.location.PottsLocation2D; import arcade.potts.sim.Potts; import arcade.potts.sim.PottsSimulation; @@ -22,6 +19,9 @@ public class PottsModuleFlyGMCDifferentiation extends PottsModuleProliferationVo Boolean pdeLike; + /* Rate of Prospero degradation (ticks^-1). */ + final double prosperoDegradationRate; + /** * Creates a fly GMC proliferation module. * @@ -30,6 +30,21 @@ public class PottsModuleFlyGMCDifferentiation extends PottsModuleProliferationVo public PottsModuleFlyGMCDifferentiation(PottsCellFlyGMC cell) { super(cell); pdeLike = (cell.getParameters().getInt("proliferation/PDELIKE") != 0); + prosperoDegradationRate = + cell.getParameters().getDouble("proliferation/PROSPERO_DEGRADATION_RATE"); + if (prosperoDegradationRate < 0) { + throw new IllegalArgumentException("Prospero degradation rate should not be negative"); + } + } + + @Override + public void step(MersenneTwisterFast random, Simulation sim) { + super.step(random, sim); + ((PottsCellFly) cell) + .setProspero( + Math.max(0, ((PottsCellFly) cell).getProspero() - prosperoDegradationRate)); + System.out.println( + "GMC ID " + cell.getID() + " prospero: " + ((PottsCellFly) cell).getProspero()); } /** diff --git a/src/arcade/potts/agent/module/PottsModuleFlyStemProliferation.java b/src/arcade/potts/agent/module/PottsModuleFlyStemProliferation.java index a37fa386d..1ed446108 100644 --- a/src/arcade/potts/agent/module/PottsModuleFlyStemProliferation.java +++ b/src/arcade/potts/agent/module/PottsModuleFlyStemProliferation.java @@ -16,6 +16,7 @@ import arcade.core.util.distributions.UniformDistribution; import arcade.potts.agent.cell.PottsCell; import arcade.potts.agent.cell.PottsCellContainer; +import arcade.potts.agent.cell.PottsCellFly; import arcade.potts.agent.cell.PottsCellFlyStem; import arcade.potts.agent.cell.PottsCellFlyStem.StemType; import arcade.potts.env.location.PottsLocation; @@ -38,6 +39,9 @@ public class PottsModuleFlyStemProliferation extends PottsModuleProliferationVol /** Basal rate of apoptosis (ticks^-1). */ final double basalApoptosisRate; + /** Rate of Prospero synthesis (ticks^-1). */ + final double prosperoSynthesisRate; + /** Distribution that determines rotational offset of cell's division plane. */ final NormalDistribution splitDirectionDistribution; @@ -116,6 +120,7 @@ public PottsModuleFlyStemProliferation(PottsCellFlyStem cell) { Parameters parameters = cell.getParameters(); basalApoptosisRate = parameters.getDouble("proliferation/BASAL_APOPTOSIS_RATE"); + prosperoSynthesisRate = parameters.getDouble("proliferation/PROSPERO_SYNTHESIS_RATE"); splitDirectionDistribution = (NormalDistribution) parameters.getDistribution("proliferation/DIV_ROTATION_DISTRIBUTION"); @@ -160,6 +165,15 @@ public PottsModuleFlyStemProliferation(PottsCellFlyStem cell) { setPhase(Phase.UNDEFINED); } + @Override + public void step(MersenneTwisterFast random, Simulation sim) { + super.step(random, sim); + ((PottsCellFly) cell) + .setProspero(((PottsCellFly) cell).getProspero() + prosperoSynthesisRate); + System.out.println( + "Stem ID " + cell.getID() + " prospero: " + ((PottsCellFly) cell).getProspero()); + } + @Override public void addCell(MersenneTwisterFast random, Simulation sim) { Potts potts = ((PottsSimulation) sim).getPotts(); @@ -478,7 +492,10 @@ private void makeDaughterStemCell( PottsCellContainer container = ((PottsCellFlyStem) cell) .make(newID, State.PROLIFERATIVE, random, cell.getPop(), criticalVol); - scheduleNewCell(container, daughterLoc, sim, potts, random); + + double daughterProspero = splitStemProspero(((PottsCellFly) cell).getProspero(), random); + System.out.print("Creating daughter stem cell with prospero " + daughterProspero + " and "); + scheduleNewCell(container, daughterLoc, sim, potts, random, daughterProspero); } /** @@ -510,7 +527,39 @@ private void makeDaughterGMC( PottsCellContainer container = ((PottsCellFlyStem) cell) .make(newID, State.PROLIFERATIVE, random, newPop, criticalVolume); - scheduleNewCell(container, daughterLoc, sim, potts, random); + PottsCellFlyStem flyStemCell = (PottsCellFlyStem) cell; + + System.out.print( + "Creating daughter GMC with prospero " + + ((PottsCellFly) cell).getProspero() + + " and "); + scheduleNewCell( + container, daughterLoc, sim, potts, random, ((PottsCellFly) cell).getProspero()); + } + + /** + * Determines how prospero is split between the parent and daughter cell. Can be edited to + * change behavior. Current prototype behavior divides the prospero evenly 50% of the time, and + * randomly selects which cell gets all prospero the other 50% of the time. + * + * @param parentProspero the parent cell's prospero to be divided + * @param random the random number generator + * @return double with daughter prospero amount + */ + static double splitStemProspero(double parentProspero, MersenneTwisterFast random) { + if (parentProspero <= 0) { + return 0; + } + + if (random.nextBoolean()) { // 50% of the time, split prospero evenly + return parentProspero / 2; + } else { // 50% of the time, randomly choose which cell gets all the prospero + if (random.nextBoolean()) { + return parentProspero; + } else { + return 0; + } + } } /** @@ -527,7 +576,8 @@ private void scheduleNewCell( PottsLocation daughterLoc, Simulation sim, Potts potts, - MersenneTwisterFast random) { + MersenneTwisterFast random, + double daughterProspero) { PottsCell newCell = (PottsCell) container.convert(sim.getCellFactory(), daughterLoc, random); if (newCell.getClass() == PottsCellFlyStem.class) { @@ -537,6 +587,10 @@ private void scheduleNewCell( potts.register(newCell); newCell.reset(potts.ids, potts.regions); newCell.schedule(sim.getSchedule()); + System.out.println("ID " + newCell.getID()); + + ((PottsCellFly) newCell).setProspero(daughterProspero); + ((PottsCellFly) cell).setProspero(((PottsCellFly) cell).getProspero() - daughterProspero); } /** diff --git a/src/arcade/potts/command.potts.xml b/src/arcade/potts/command.potts.xml index bae804332..42a48bddd 100644 --- a/src/arcade/potts/command.potts.xml +++ b/src/arcade/potts/command.potts.xml @@ -1,2 +1,3 @@ + diff --git a/src/arcade/potts/parameter.potts.xml b/src/arcade/potts/parameter.potts.xml index ce000fd3a..c7c40a19c 100644 --- a/src/arcade/potts/parameter.potts.xml +++ b/src/arcade/potts/parameter.potts.xml @@ -86,6 +86,9 @@ + + + @@ -96,4 +99,5 @@ + diff --git a/src/arcade/potts/sim/PottsSimulation.java b/src/arcade/potts/sim/PottsSimulation.java index 5249a414d..4ab58e2e3 100644 --- a/src/arcade/potts/sim/PottsSimulation.java +++ b/src/arcade/potts/sim/PottsSimulation.java @@ -1,7 +1,10 @@ package arcade.potts.sim; +import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; +import com.google.gson.reflect.TypeToken; import sim.engine.Schedule; import sim.engine.SimState; import arcade.core.agent.action.Action; @@ -19,12 +22,16 @@ import arcade.core.util.MiniBox; import arcade.potts.agent.cell.PottsCell; import arcade.potts.agent.cell.PottsCellFactory; +import arcade.potts.agent.cell.PottsCellFly; import arcade.potts.env.grid.PottsGrid; import arcade.potts.env.location.PottsLocationFactory; import static arcade.potts.util.PottsEnums.Ordering; /** Abstract implementation for potts {@link Simulation} instances. */ public abstract class PottsSimulation extends SimState implements Simulation { + + public static final Type PROSPERO_TYPE = new TypeToken>() {}.getType(); + /** {@link arcade.core.sim.Series} object containing this simulation. */ final PottsSeries series; @@ -281,4 +288,15 @@ public void doOutput(boolean isScheduled) { series.saver.save(tick); } } + + public final HashMap getAllProspero() { + HashMap prosperoMap = new HashMap<>(); + + for (Object obj : grid.getAllObjects()) { + PottsCellFly cell = (PottsCellFly) obj; + prosperoMap.put(cell.getID(), cell.getProspero()); + } + + return prosperoMap; + } } diff --git a/src/arcade/potts/sim/output/PottsOutputSaver.java b/src/arcade/potts/sim/output/PottsOutputSaver.java index 16c267699..e2ce2a2d4 100644 --- a/src/arcade/potts/sim/output/PottsOutputSaver.java +++ b/src/arcade/potts/sim/output/PottsOutputSaver.java @@ -3,6 +3,8 @@ import com.google.gson.Gson; import arcade.core.sim.Series; import arcade.core.sim.output.OutputSaver; +import arcade.potts.sim.PottsSimulation; +import static arcade.potts.sim.PottsSimulation.PROSPERO_TYPE; /** Custom saver for potts-specific serialization. */ public final class PottsOutputSaver extends OutputSaver { @@ -15,8 +17,27 @@ public PottsOutputSaver(Series series) { super(series); } + /** {@code true} to save prospero, {@code false} otherwise. */ + public boolean saveProspero; + @Override protected Gson makeGSON() { return PottsOutputSerializer.makeGSON(); } + + public void saveProspero(int tick) { + if (sim instanceof PottsSimulation) { + String json = gson.toJson(((PottsSimulation) sim).getAllProspero(), PROSPERO_TYPE); + String patch = prefix + String.format("_%06d.PROSPERO.json", tick); + write(patch, format(json, FORMAT_ELEMENTS)); + } + } + + @Override + public void save(int tick) { + super.save(tick); + if (saveProspero) { + saveProspero(tick); + } + } } diff --git a/src/arcade/potts/sim/output/PottsOutputSerializer.java b/src/arcade/potts/sim/output/PottsOutputSerializer.java index e8eaacb9b..dc9a9a66a 100644 --- a/src/arcade/potts/sim/output/PottsOutputSerializer.java +++ b/src/arcade/potts/sim/output/PottsOutputSerializer.java @@ -2,6 +2,7 @@ import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.HashMap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; @@ -18,6 +19,7 @@ import arcade.potts.env.location.Voxel; import arcade.potts.sim.PottsSeries; import static arcade.potts.env.location.Voxel.VOXEL_COMPARATOR; +import static arcade.potts.sim.PottsSimulation.PROSPERO_TYPE; import static arcade.potts.util.PottsEnums.Region; import static arcade.potts.util.PottsEnums.State; @@ -53,6 +55,7 @@ static Gson makeGSON() { gsonBuilder.registerTypeAdapter( PottsLocationContainer.class, new PottsLocationSerializer()); gsonBuilder.registerTypeAdapter(Voxel.class, new VoxelSerializer()); + gsonBuilder.registerTypeAdapter(PROSPERO_TYPE, new ProsperoSerializer()); return gsonBuilder.create(); } @@ -275,4 +278,19 @@ public JsonElement serialize(Voxel src, Type typeOfSrc, JsonSerializationContext return json; } } + + static class ProsperoSerializer implements JsonSerializer> { + @Override + public JsonElement serialize( + HashMap src, Type typeOfSrc, JsonSerializationContext context) { + JsonArray json = new JsonArray(); + for (Integer id : src.keySet()) { + JsonObject entry = new JsonObject(); + entry.addProperty("id", id); + entry.addProperty("prospero", src.get(id)); + json.add(entry); + } + return json; + } + } } diff --git a/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java b/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java index b3babde5d..6ebe0e608 100644 --- a/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java +++ b/test/arcade/potts/agent/module/PottsModuleFlyGMCDifferentiationTest.java @@ -22,10 +22,13 @@ import arcade.potts.sim.PottsSimulation; import arcade.potts.util.PottsEnums.Region; import arcade.potts.util.PottsEnums.State; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -62,6 +65,8 @@ public class PottsModuleFlyGMCDifferentiationTest { private MockedConstruction mockedConstruction; + float EPSILON = 1e-6f; + @BeforeEach public final void setupMocks() { dummyIDs = new int[1][1][1]; @@ -131,6 +136,44 @@ final void tearDown() { mockedConstruction.close(); } + @Test + public void constructor_setsParameters() { + when(parameters.getInt("proliferation/PDELIKE")).thenReturn(0); + when(parameters.getDouble("proliferation/PROSPERO_DEGRADATION_RATE")).thenReturn(1.0); + + PottsModuleFlyGMCDifferentiation module = new PottsModuleFlyGMCDifferentiation(gmcCell); + + org.junit.jupiter.api.Assertions.assertFalse(module.pdeLike); + org.junit.jupiter.api.Assertions.assertEquals(1.0, module.prosperoDegradationRate, EPSILON); + } + + @Test + public void constructor_negativeProsperoDegradationRate_throwsException() { + when(parameters.getDouble("proliferation/PROSPERO_DEGRADATION_RATE")).thenReturn(-1.0); + + assertThrows( + IllegalArgumentException.class, + () -> new PottsModuleFlyGMCDifferentiation(gmcCell)); + } + + @Test + public void step_decrementsProspero_prosperoIsUpdated() { + when(parameters.getDouble("proliferation/PROSPERO_DEGRADATION_RATE")).thenReturn(6.0); + + PottsModuleFlyGMCDifferentiation module = + spy(new PottsModuleFlyGMCDifferentiation(gmcCell)); + + doNothing().when(module).addCell(any(MersenneTwisterFast.class), any(Simulation.class)); + + when(gmcCell.getProspero()).thenReturn(10.0); + module.step(random, sim); + verify(gmcCell).setProspero(4.0); + + when(gmcCell.getProspero()).thenReturn(4.0); + module.step(random, sim); + verify(gmcCell).setProspero(0.0); + } + @Test public void addCell_called_callsExpectedMethods() { // When the module calls make() on the cell, return Quiescent PottsCellContainer diff --git a/test/arcade/potts/agent/module/PottsModuleFlyStemProliferationTest.java b/test/arcade/potts/agent/module/PottsModuleFlyStemProliferationTest.java index ec3b6a778..2c65fc618 100644 --- a/test/arcade/potts/agent/module/PottsModuleFlyStemProliferationTest.java +++ b/test/arcade/potts/agent/module/PottsModuleFlyStemProliferationTest.java @@ -130,6 +130,30 @@ final void tearDown() { // Constructor tests + @Test + public void constructor_setsParameters() { + when(parameters.getDouble("proliferation/BASAL_APOPTOSIS_RATE")).thenReturn(0.04); + when(parameters.getDouble("proliferation/PROSPERO_SYNTHESIS_RATE")).thenReturn(0.895); + when(parameters.getString("proliferation/APICAL_AXIS_RULESET")).thenReturn("global"); + when(parameters.getDistribution("proliferation/APICAL_AXIS_ROTATION_DISTRIBUTION")) + .thenReturn(dist); + when(parameters.getInt("proliferation/VOLUME_BASED_CRITICAL_VOLUME")).thenReturn(0); + when(parameters.getInt("proliferation/DYNAMIC_GROWTH_RATE_NB_SELF_REPRESSION")) + .thenReturn(0); + + module = new PottsModuleFlyStemProliferation(stemCell); + + assertEquals(0.04, module.basalApoptosisRate, EPSILON); + assertEquals(0.895, module.prosperoSynthesisRate, EPSILON); + assertNotNull(module.splitDirectionDistribution); + assertEquals("volume", module.differentiationRuleset); + assertEquals(0.5, module.range, EPSILON); + assertEquals("global", module.apicalAxisRuleset); + assertNotNull(module.apicalAxisRotationDistribution); + assertFalse(module.volumeBasedCriticalVolume); + assertFalse(module.dynamicGrowthRateNBSelfRepression); + } + @Test public void constructor_volumeRuleset_setsExpectedFields() { when(parameters.getString("proliferation/DIFFERENTIATION_RULESET")).thenReturn("volume"); @@ -493,6 +517,43 @@ public void step_volumeAtCheckpoint_callsAddCellPhaseStaysUndefined() { assertEquals(Phase.UNDEFINED, module.phase); // remains UNDEFINED } + @Test + public void step_incrementsProspero_prosperoIsUpdated() { + when(parameters.getInt("proliferation/DYNAMIC_GROWTH_RATE_VOLUME")).thenReturn(0); + when(parameters.getDouble("proliferation/CELL_GROWTH_RATE")).thenReturn(4.0); + when(parameters.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2); + when(parameters.getDouble("proliferation/PROSPERO_SYNTHESIS_RATE")).thenReturn(1.0); + module = new PottsModuleFlyStemProliferation(stemCell); + when(stemCell.getVolume()).thenReturn(0.0); // we don't want addCell to be called + when(stemCell.getProspero()).thenReturn(5.0); + module.step(random, sim); + verify(stemCell).setProspero(6.0); + } + + @Test + public void splitStemProspero_zeroProspero_returnsZero() { + assertEquals(0.0, PottsModuleFlyStemProliferation.splitStemProspero(0.0, random), EPSILON); + } + + @Test + public void splitStemProspero_evenSplit_returnsHalf() { + when(random.nextBoolean()).thenReturn(true); + assertEquals(5.0, PottsModuleFlyStemProliferation.splitStemProspero(10.0, random), EPSILON); + } + + @Test + public void splitStemProspero_unevenSplitDaughterGetsAll_returnsAll() { + when(random.nextBoolean()).thenReturn(false, true); + assertEquals( + 10.0, PottsModuleFlyStemProliferation.splitStemProspero(10.0, random), EPSILON); + } + + @Test + public void splitStemProspero_unevenSplitDaughterGetsNone_returnsZero() { + when(random.nextBoolean()).thenReturn(false, false); + assertEquals(0.0, PottsModuleFlyStemProliferation.splitStemProspero(10.0, random), EPSILON); + } + // Apical axis rule tests @Test @@ -1050,7 +1111,7 @@ public void updateGrowthRateBasedOnOtherNBs_pdeLikeTrue_usesPopulationBranch() { // N = 6 in-simulation (K = 3, n = 2 → 9/(9+36)=0.2 → 4.0) HashSet six = new HashSet<>(); - for (int i = 0; i < 6; i++) { + for (int i = 0; i <= 6; i++) { PottsCellFlyStem n = mock(PottsCellFlyStem.class); when(n.getID()).thenReturn(200 + i); six.add(n); diff --git a/test/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivisionTest.java b/test/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivisionTest.java index 4c41c1661..248e6b7eb 100644 --- a/test/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivisionTest.java +++ b/test/arcade/potts/agent/module/PottsModuleProliferationVolumeBasedDivisionTest.java @@ -4,19 +4,21 @@ import ec.util.MersenneTwisterFast; import arcade.core.sim.Simulation; import arcade.core.util.Parameters; -import arcade.potts.agent.cell.PottsCellFlyGMC; +import arcade.potts.agent.cell.PottsCell; import arcade.potts.env.location.PottsLocation2D; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.*; public class PottsModuleProliferationVolumeBasedDivisionTest { + private static final double EPSILON = 1E-10; static class PottsModuleProliferationVolumeBasedDivisionMock extends PottsModuleProliferationVolumeBasedDivision { boolean addCellCalled = false; boolean growthRateUpdated = false; - PottsModuleProliferationVolumeBasedDivisionMock(PottsCellFlyGMC cell) { + PottsModuleProliferationVolumeBasedDivisionMock(PottsCell cell) { super(cell); } @@ -31,9 +33,29 @@ public void updateGrowthRate(Simulation sim) { } } + @Test + public void constructor_setsParameters() { + PottsCell cell = mock(PottsCell.class); + Parameters parameters = mock(Parameters.class); + doReturn(parameters).when(cell).getParameters(); + + when(parameters.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.5); + when(parameters.getDouble("proliferation/CELL_GROWTH_RATE")).thenReturn(3.0); + when(parameters.getInt("proliferation/DYNAMIC_GROWTH_RATE_VOLUME")).thenReturn(1); + when(parameters.getDouble("proliferation/GROWTH_RATE_VOLUME_SENSITIVITY")).thenReturn(2.0); + + PottsModuleProliferationVolumeBasedDivisionMock module = + new PottsModuleProliferationVolumeBasedDivisionMock(cell); + + assertEquals(1.5, module.sizeTarget, EPSILON); + assertEquals(3.0, module.cellGrowthRateBase, EPSILON); + assertTrue(module.dynamicGrowthRateVolume); + assertEquals(2.0, module.growthRateVolumeSensitivity, EPSILON); + } + @Test public void step_belowCheckpoint_updatesTarget() { - PottsCellFlyGMC cell = mock(PottsCellFlyGMC.class); + PottsCell cell = mock(PottsCell.class); Parameters params = mock(Parameters.class); when(cell.getParameters()).thenReturn(params); when(params.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2); @@ -54,7 +76,7 @@ public void step_belowCheckpoint_updatesTarget() { @Test public void step_atOrAboveCheckpoint_triggersAddCell() { - PottsCellFlyGMC cell = mock(PottsCellFlyGMC.class); + PottsCell cell = mock(PottsCell.class); Parameters params = mock(Parameters.class); when(cell.getParameters()).thenReturn(params); when(params.getDouble("proliferation/SIZE_TARGET")).thenReturn(1.2); @@ -75,7 +97,7 @@ public void step_atOrAboveCheckpoint_triggersAddCell() { @Test public void updateVolumeBasedGrowthRate_ratioOne_keepsBaseRate() { // baseGrowth = 4.0, volume = Ka => growth = 4.0 - PottsCellFlyGMC cell = mock(PottsCellFlyGMC.class); + PottsCell cell = mock(PottsCell.class); Parameters params = mock(Parameters.class); PottsLocation2D loc = mock(PottsLocation2D.class); @@ -100,7 +122,7 @@ public void updateVolumeBasedGrowthRate_ratioOne_keepsBaseRate() { @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); + PottsCell cell = mock(PottsCell.class); Parameters params = mock(Parameters.class); PottsLocation2D loc = mock(PottsLocation2D.class); @@ -125,7 +147,7 @@ public void updateVolumeBasedGrowthRate_ratioGreaterThanOne_scalesUpByPowerLaw() @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); + PottsCell cell = mock(PottsCell.class); Parameters params = mock(Parameters.class); PottsLocation2D loc = mock(PottsLocation2D.class); @@ -150,7 +172,7 @@ public void updateVolumeBasedGrowthRate_ratioLessThanOne_scalesDownByPowerLaw() @Test public void updateVolumeBasedGrowthRate_zeroSensitivity_returnsBaseRateRegardlessOfVolume() { // sensitivity = 0 => growth = baseGrowth * ratio^0 = baseGrowth - PottsCellFlyGMC cell = mock(PottsCellFlyGMC.class); + PottsCell cell = mock(PottsCell.class); Parameters params = mock(Parameters.class); PottsLocation2D loc = mock(PottsLocation2D.class); diff --git a/test/arcade/potts/sim/PottsSimulationTest.java b/test/arcade/potts/sim/PottsSimulationTest.java index 0a3f6cd75..09495d549 100644 --- a/test/arcade/potts/sim/PottsSimulationTest.java +++ b/test/arcade/potts/sim/PottsSimulationTest.java @@ -18,6 +18,7 @@ import arcade.potts.agent.cell.PottsCell; import arcade.potts.agent.cell.PottsCellContainer; import arcade.potts.agent.cell.PottsCellFactory; +import arcade.potts.agent.cell.PottsCellFly; import arcade.potts.env.location.PottsLocation; import arcade.potts.env.location.PottsLocationContainer; import arcade.potts.env.location.PottsLocationFactory; @@ -34,6 +35,8 @@ public class PottsSimulationTest { private static final int TOTAL_LOCATIONS = 6; + float EPSILON = 1e-6f; + static Series seriesZeroPop; static Series seriesOnePop; @@ -652,4 +655,42 @@ public void doOutput_isNotScheduled_savesOutput() { verify(saver, never()).schedule(eq(schedule)); verify(saver).save((int) time + 1); } + + @Test + public void getAllProspero_multipleCells_returnsCorrectMap() { + PottsSimulation sim = mock(PottsSimulation.class, CALLS_REAL_METHODS); + sim.grid = mock(Grid.class); + + PottsCellFly cell1 = mock(PottsCellFly.class); + PottsCellFly cell2 = mock(PottsCellFly.class); + when(cell1.getID()).thenReturn(1); + when(cell1.getProspero()).thenReturn(3.0); + when(cell2.getID()).thenReturn(2); + when(cell2.getProspero()).thenReturn(7.0); + + Bag objects = new Bag(); + objects.add(cell1); + objects.add(cell2); + + doReturn(objects).when(sim.grid).getAllObjects(); + HashMap result = sim.getAllProspero(); + + assertEquals(2, result.size()); + assertEquals(3.0, result.get(1), EPSILON); + assertEquals(7.0, result.get(2), EPSILON); + } + + @Test + public void getAllProspero_emptyGrid_returnsEmptyMap() { + PottsSimulation sim = mock(PottsSimulation.class, CALLS_REAL_METHODS); + sim.grid = mock(Grid.class); + Bag empty = new Bag(); + + doReturn(empty).when(sim.grid).getAllObjects(); + + HashMap result = sim.getAllProspero(); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } } diff --git a/test/arcade/potts/sim/output/PottsOutputSaverTest.java b/test/arcade/potts/sim/output/PottsOutputSaverTest.java index c40bc8487..d316e4ac0 100644 --- a/test/arcade/potts/sim/output/PottsOutputSaverTest.java +++ b/test/arcade/potts/sim/output/PottsOutputSaverTest.java @@ -1,10 +1,18 @@ package arcade.potts.sim.output; +import java.lang.reflect.Field; +import java.util.HashMap; import org.junit.jupiter.api.Test; import com.google.gson.Gson; import arcade.core.sim.Series; +import arcade.core.sim.output.OutputSaver; import arcade.core.sim.output.OutputSerializerTest; +import arcade.potts.sim.PottsSeries; +import arcade.potts.sim.PottsSimulation; import static org.mockito.Mockito.*; +import static arcade.core.ARCADETestUtilities.randomIntBetween; +import static arcade.core.ARCADETestUtilities.randomString; +import static arcade.potts.sim.PottsSimulation.PROSPERO_TYPE; public class PottsOutputSaverTest { @Test @@ -15,4 +23,38 @@ public void makeGSON_called_returnsObjects() { OutputSerializerTest.checkAdaptors(gson); PottsOutputSerializerTest.checkAdaptors(gson); } + + @Test + public void saveProspero_called_savesContents() { + HashMap prospero = new HashMap<>(); + int tick = randomIntBetween(0, 10); + PottsSimulation sim = mock(PottsSimulation.class); + doReturn(prospero).when(sim).getAllProspero(); + + PottsSeries series = mock(PottsSeries.class); + PottsOutputSaver saver = spy(new PottsOutputSaver(series)); + doNothing().when(saver).write(anyString(), anyString()); + + try { + Field field = OutputSaver.class.getDeclaredField("sim"); + field.setAccessible(true); + field.set(saver, sim); + } catch (Exception ignored) { + } + + Gson gson = mock(Gson.class); + String contents = randomString(); + doReturn(contents).when(gson).toJson(prospero, PROSPERO_TYPE); + + try { + Field field = OutputSaver.class.getDeclaredField("gson"); + field.setAccessible(true); + field.set(saver, gson); + } catch (Exception ignored) { + } + + saver.saveProspero(tick); + verify(gson).toJson(prospero, PROSPERO_TYPE); + verify(saver).write(saver.prefix + String.format("_%06d.PROSPERO.json", tick), contents); + } } diff --git a/test/arcade/potts/sim/output/PottsOutputSerializerTest.java b/test/arcade/potts/sim/output/PottsOutputSerializerTest.java index 3b4fc0a94..f63230c44 100644 --- a/test/arcade/potts/sim/output/PottsOutputSerializerTest.java +++ b/test/arcade/potts/sim/output/PottsOutputSerializerTest.java @@ -3,6 +3,7 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.EnumMap; +import java.util.HashMap; import org.junit.jupiter.api.Test; import com.google.gson.Gson; import com.google.gson.JsonArray; @@ -513,4 +514,36 @@ public void serialize_forVoxel_createsJSON() { JsonElement json = serializer.serialize(voxel, null, null); assertEquals(expected, json.toString()); } + + @Test + public void serialize_forProspero_createsJSON() { + ProsperoSerializer serializer = new ProsperoSerializer(); + HashMap cells = new HashMap<>(); + int numCells = randomIntBetween(1, 100); + int startId = randomIntBetween(1, 100); + + for (int i = 0; i < numCells; i++) { + cells.put(startId, randomDoubleBetween(1, 100)); + startId++; + } + + StringBuilder expected = new StringBuilder(); + int i = 0; + expected.append("["); + for (Integer id : cells.keySet()) { + expected.append("{\"id\":") + .append(id) + .append(",\"prospero\":") + .append(cells.get(id)) + .append("}"); + if (i < cells.size() - 1) { + expected.append(","); // to match JSON formatting + } + i++; + } + expected.append("]"); + + JsonElement json = serializer.serialize(cells, null, null); + assertEquals(expected.toString(), json.toString()); + } }