Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,55 @@
*/
public class PottsModuleFlyGMCDifferentiation extends PottsModuleProliferationVolumeBasedDivision {

/**
* Indicates whether GMC growth rate is based on individual cell conditions or average cell
* conditions.
*/
Boolean pdeLike;

/**
* Creates a fly GMC proliferation module.
*
* @param cell the cell to which this module is attached
*/
public PottsModuleFlyGMCDifferentiation(PottsCellFlyGMC cell) {
super(cell);
pdeLike = (cell.getParameters().getInt("proliferation/PDELIKE") != 0);
}

/**
* Computes the expected equilibrium average GMC volume over one cell cycle.
*
* <p>In the Potts model, a cell's target volume is initialized to {@code criticalVolume} on
* reset. The Potts energy immediately drives the cell's actual volume toward this target,
* regardless of the current growth rate. As a result, the volume-regulated growth phase
* effectively begins at {@code criticalVolume} (not the birth volume), even when {@code
* VOLUME_BASED_CRITICAL_VOLUME} is off and birth volume is below {@code criticalVolume}.
*
* <p>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:
*
* <pre>
* V_ref = (criticalVolume + sizeTarget * criticalVolume) / 2
* = criticalVolume * (1 + sizeTarget) / 2
* </pre>
*
* <p>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.
*
* <p>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
Expand All @@ -55,7 +90,7 @@ void addCell(MersenneTwisterFast random, Simulation sim) {
(PottsCell) newContainer.convert(sim.getCellFactory(), newLocation, random);
sim.getGrid().addObject(newCell, null);
potts.register(newCell);
newCell.reset(potts.ids, potts.regions);
newCell.initialize(potts.ids, potts.regions);
newCell.schedule(sim.getSchedule());

// remove old GMC cell from simulation
Expand Down Expand Up @@ -88,7 +123,56 @@ void addCell(MersenneTwisterFast random, Simulation sim) {

sim.getGrid().addObject(differentiatedGMC, null);
potts.register(differentiatedGMC);
differentiatedGMC.reset(potts.ids, potts.regions);
differentiatedGMC.initialize(potts.ids, potts.regions);
differentiatedGMC.schedule(sim.getSchedule());
}

/**
* Updates the effective growth rate according to boolean flags specified in parameters.
*
* @param sim the simulation
*/
public void updateGrowthRate(Simulation sim) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nitpick comments so feel free to ignore:

  • Consider factoring out the volume averaging logic to its own function

  • For readability it might be easier to do switch statements or if/else statements where the boolean is true. For example something like:

default:
    cellGrowthRate = cellGrowthRateBase

case pdelike
     cellGrowthRate = averageVolume()

case dynamicGrowthVolume
     cellGrowthRate = updateCellVolumeBasedGrowthRate()

if (!dynamicGrowthRateVolume) {
cellGrowthRate = cellGrowthRateBase;
} else {
if (!pdeLike) {
updateCellVolumeBasedGrowthRate(
cell.getLocation().getVolume(), computeEquilibriumVolume());
} else {
// PDE-like: use population-wide averages for GMCs (same pop as this cell).
// The reference volume is the population-average equilibrium volume:
// avgVRef = avgCritVol * (1 + sizeTarget) / 2
sim.util.Bag objs = sim.getGrid().getAllObjects();

double volSum = 0.0;
double critSum = 0.0;
int count = 0;

for (int i = 0; i < objs.numObjs; i++) {
Object o = objs.objs[i];
if (!(o instanceof arcade.potts.agent.cell.PottsCell)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, is this if statement for avoiding environmental objects (like vasculature)? Otherwise, we can't have a simulation where Patch/Potts cells both exist right?

continue;
}

arcade.potts.agent.cell.PottsCell c = (arcade.potts.agent.cell.PottsCell) o;
if (c.getPop() != cell.getPop()) {
continue; // keep to same population
}

if (o instanceof arcade.potts.agent.cell.PottsCellFlyGMC) {
arcade.potts.agent.cell.PottsCellFlyGMC gmc =
(arcade.potts.agent.cell.PottsCellFlyGMC) o;
volSum += gmc.getLocation().getVolume();
critSum += gmc.getCriticalVolume();
count++;
}
}
double avgVolume = volSum / count;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Allison touched on this in an earlier comment, but I think that finding the average volume of the Potts cells can be moved out into their own function like getAvgVolume(Simulation sim) or something.

double avgCritVol = critSum / count;
double avgVRef = avgCritVol * (1.0 + sizeTarget) / 2.0;
updateCellVolumeBasedGrowthRate(avgVolume, avgVRef);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,31 @@
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
* * SIZE_TARGET * SIZE_CHECKPOINT to divide).
*/
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;
Copy link
Copy Markdown
Contributor

@allison-li-1016 allison-li-1016 Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore if this was implemented elsewhere (or if its impossible to make this error), but might be good to throw an error if growthRateVolumeSensitivity is set but dynamicGrowthRateVolume is false. If you do implement this error throw it would be good to add a test for it as well


/**
* Creates a proliferation module in which division is solely dependent on cell volume.
*
Expand All @@ -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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the updateGrowthRate method look different for each type of volume-based proliferation?

I am a little confused as to why the updateGrowthRate logic is a method rather than some switch statements in the abstract class


/**
* Updates {@code cellGrowthRate} from a power-law relationship between current volume and a
* reference volume.
*
* <p>The updated rate is
*
* <pre>
* cellGrowthRate = cellGrowthRateBase * (volume / referenceVolume)^growthRateVolumeSensitivity
* </pre>
*
* <p>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);
}
}
7 changes: 7 additions & 0 deletions src/arcade/potts/parameter.potts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@
<population.module module="proliferation" id="NUCLEUS_GROWTH_RATE" value="30" units="um^3/hour" conversion="DS^-3.DT" description="basal rate of nucleus growth" />
<population.module module="proliferation" id="NUCLEUS_CONDENSATION_FRACTION" value="0.5" description="fraction of nuclear volume when condensed" />

<!-- ── Fly cell proliferation module parameters ───────────────────── -->
<!-- ── Dictating whether NB behavior should be the same across all NBs ──────── -->
Copy link
Copy Markdown
Contributor

@allison-li-1016 allison-li-1016 Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another very nitpick comment so feel free to ignore

  • Currently the comments feel more like parameter descriptions rather than categories -- consider organizing the proliferation parameters by model type?

Eg:

 <!-- ── Dynamic volume model parameters ──────── -->
...

 <!-- ── Constant volume parameters ──────── -->
...

 <!-- ── PDE like model parameters ──────── -->
...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that the <!-- ── Dictating whether NB behavior should be the same across all NBs ──────── --> and <!-- ── Growth rate sensitivity to cell volume dynamics ───────────────────── --> read more like descriptions than categories. I think part of it is because all the other labels end in "parameters"
I'm wondering why we need to further categorize PDELIKE, DYNAMIC_GROWTH_RATE_VOLUME, and GROWTH_RATE_VOLUME_SENSITIVITY... would it be okay to just remove the sublabels?

<population.module module="proliferation" id="PDELIKE" value="0" description="1 → all cells of a given type have same growth and division dynamics based on average metrics across the population; 0 → Cell growth and division is determined on a cell-by-cell basis, cells can differ from other cells of the same type" />
<!-- ── Growth rate sensitivity to cell volume dynamics ───────────────────── -->
Copy link
Copy Markdown
Contributor

@allison-li-1016 allison-li-1016 Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recall that there were non-volume based proliferation options, do you intend to have a section for that too?

After some reading I do think it would make sense to have something like:

<---- fly stem cell proliferation module params ----->
<---- booleans that modulate proliferation growth rate ----->
<------ volume based proliferation parameters ----->
<----- time based proliferation parameters ----->
<----- (other types of fly cell proliferation ----->

<population.module module="proliferation" id="DYNAMIC_GROWTH_RATE_VOLUME" value="0" description="1 → enable volume-sensitive growth rate; 0 → disabled" />
<population.module module="proliferation" id="GROWTH_RATE_VOLUME_SENSITIVITY" value="3.0" description="exponent in (volume/criticalVolume)^sensitivity when volume rule is enabled" />

<!-- apoptosis module parameters -->
<population.module module="apoptosis" id="RATE_EARLY" value="4" units="steps/hour" conversion="DT" description="rate of events in early apoptosis phase" />
<population.module module="apoptosis" id="RATE_LATE" value="1.8" units="steps/hour" conversion="DT" description="rate of events in late apoptosis phase" />
Expand Down
Loading
Loading