diff --git a/src/arcade/patch/agent/action/PatchActionKill.java b/src/arcade/patch/agent/action/PatchActionKill.java
new file mode 100644
index 000000000..a0119a7dd
--- /dev/null
+++ b/src/arcade/patch/agent/action/PatchActionKill.java
@@ -0,0 +1,100 @@
+package arcade.patch.agent.action;
+
+import sim.engine.Schedule;
+import sim.engine.SimState;
+import ec.util.MersenneTwisterFast;
+import arcade.core.agent.action.Action;
+import arcade.core.sim.Series;
+import arcade.core.sim.Simulation;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.agent.cell.PatchCellTissue;
+import arcade.patch.agent.process.PatchProcessInflammation;
+import arcade.patch.util.PatchEnums.AntigenFlag;
+import arcade.patch.util.PatchEnums.Domain;
+import arcade.patch.util.PatchEnums.Ordering;
+import arcade.patch.util.PatchEnums.State;
+
+/**
+ * Implementation of {@link Action} for killing tissue agents.
+ *
+ *
{@code PatchActionKill} is stepped once after a CD8 CAR T-cell binds to a target tissue cell.
+ * The {@code PatchActionKill} determines if cell has enough granzyme to kill. If so, it kills cell
+ * and calls the reset to neutral helper to return to neutral state. If not, it waits until it has
+ * enough granzyme to kill cell.
+ */
+public class PatchActionKill implements Action {
+
+ /** Target cell cytotoxic CAR T-cell is bound to */
+ PatchCellTissue target;
+
+ /** CAR T-cell inflammation module */
+ PatchProcessInflammation inflammation;
+
+ /** Amount of granzyme inside CAR T-cell */
+ double granzyme;
+
+ /** CAR T-cell that the module is linked to */
+ PatchCellCART c;
+
+ /** Time delay before calling the action [min]. */
+ private final int timeDelay;
+
+ /**
+ * Creates a {@code PatchActionKill} for the given {@link PatchCellCART}.
+ *
+ * @param c the {@link PatchCellCART} the helper is associated with
+ * @param target the {@link PatchCellTissue} the CAR T-cell is bound to
+ */
+ public PatchActionKill(
+ PatchCellCART c,
+ PatchCellTissue target,
+ MersenneTwisterFast random,
+ Series series,
+ Parameters parameters) {
+ this.c = c;
+ this.target = target;
+ this.inflammation = (PatchProcessInflammation) c.getProcess(Domain.INFLAMMATION);
+ this.granzyme = inflammation.getInternal("granzyme");
+ timeDelay = 0;
+ }
+
+ @Override
+ public void schedule(Schedule schedule) {
+ schedule.scheduleOnce(schedule.getTime() + timeDelay, Ordering.ACTIONS.ordinal(), this);
+ }
+
+ @Override
+ public void register(Simulation sim, String population) {}
+
+ @Override
+ public void step(SimState state) {
+ Simulation sim = (Simulation) state;
+
+ // If current CAR T-cell is stopped, stop helper.
+ if (c.isStopped()) {
+ return;
+ }
+
+ // If bound target cell is stopped, stop helper.
+ if (target.isStopped()) {
+ if (c.getBindingFlag() == AntigenFlag.BOUND_ANTIGEN) {
+ c.setBindingFlag(AntigenFlag.UNBOUND);
+ } else {
+ c.setBindingFlag(AntigenFlag.BOUND_CELL_RECEPTOR);
+ }
+ return;
+ }
+
+ if (granzyme >= 1) {
+
+ // Kill bound target cell.
+ PatchCellTissue tissueCell = (PatchCellTissue) target;
+ tissueCell.setState(State.APOPTOTIC);
+
+ // Use up some granzyme in the process.
+ granzyme--;
+ inflammation.setInternal("granzyme", granzyme);
+ }
+ }
+}
diff --git a/src/arcade/patch/agent/action/PatchActionReset.java b/src/arcade/patch/agent/action/PatchActionReset.java
new file mode 100644
index 000000000..78ffa48c9
--- /dev/null
+++ b/src/arcade/patch/agent/action/PatchActionReset.java
@@ -0,0 +1,67 @@
+package arcade.patch.agent.action;
+
+import sim.engine.Schedule;
+import sim.engine.SimState;
+import ec.util.MersenneTwisterFast;
+import arcade.core.agent.action.Action;
+import arcade.core.sim.Series;
+import arcade.core.sim.Simulation;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.agent.process.PatchProcessInflammation;
+import arcade.patch.util.PatchEnums.AntigenFlag;
+import arcade.patch.util.PatchEnums.Ordering;
+import arcade.patch.util.PatchEnums.State;
+
+/**
+ * Implementation of {@link Action} for resetting T cell agents.
+ *
+ *
{@code PatchActionReset} is stepped once after a CD8 CAR T-cell binds to a target tissue cell,
+ * or after a CD4 CAR T-cell gets stimulated. The {@code PatchReset} unbinds to any target cell that
+ * the T cell is bound to, and sets the cell state back to undefined.
+ */
+public class PatchActionReset implements Action {
+
+ /** CAR T-cell inflammation module */
+ PatchProcessInflammation inflammation;
+
+ /** CAR T-cell that the module is linked to */
+ PatchCellCART c;
+
+ /** Time delay before calling the action [min]. */
+ private final int timeDelay;
+
+ /**
+ * Creates a {@code PatchActionReset} for the given {@link PatchCellCART}.
+ *
+ * @param c the {@link PatchCellCART} the helper is associated with
+ */
+ public PatchActionReset(
+ PatchCellCART c, MersenneTwisterFast random, Series series, Parameters parameters) {
+ this.c = c;
+ double boundTime = parameters.getInt("BOUND_TIME");
+ double boundRange = parameters.getInt("BOUND_RANGE");
+ timeDelay = (int) (boundTime + Math.round((boundRange * (2 * random.nextInt() - 1))));
+ }
+
+ @Override
+ public void schedule(Schedule schedule) {
+ schedule.scheduleOnce(schedule.getTime() + timeDelay, Ordering.ACTIONS.ordinal(), this);
+ }
+
+ @Override
+ public void register(Simulation sim, String population) {}
+
+ @Override
+ public void step(SimState state) {
+ // If current CAR T-cell is stopped, stop helper.
+ if (c.isStopped()) {
+ return;
+ }
+
+ if (c.getState() == State.CYTOTOXIC || c.getState() == State.STIMULATORY) {
+ c.setBindingFlag(AntigenFlag.UNBOUND);
+ c.setState(State.UNDEFINED);
+ }
+ }
+}
diff --git a/src/arcade/patch/agent/action/PatchActionTreat.java b/src/arcade/patch/agent/action/PatchActionTreat.java
new file mode 100644
index 000000000..071d1942a
--- /dev/null
+++ b/src/arcade/patch/agent/action/PatchActionTreat.java
@@ -0,0 +1,368 @@
+package arcade.patch.agent.action;
+
+import java.util.ArrayList;
+import java.util.Objects;
+import sim.engine.Schedule;
+import sim.engine.SimState;
+import sim.util.Bag;
+import arcade.core.agent.action.Action;
+import arcade.core.env.location.Location;
+import arcade.core.env.location.LocationContainer;
+import arcade.core.sim.Series;
+import arcade.core.sim.Simulation;
+import arcade.core.util.Graph;
+import arcade.core.util.MiniBox;
+import arcade.core.util.Utilities;
+import arcade.patch.agent.cell.PatchCell;
+import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.agent.cell.PatchCellContainer;
+import arcade.patch.agent.cell.PatchCellTissue;
+import arcade.patch.env.component.PatchComponentSites;
+import arcade.patch.env.component.PatchComponentSitesGraph;
+import arcade.patch.env.component.PatchComponentSitesGraph.SiteEdge;
+import arcade.patch.env.component.PatchComponentSitesGraphRect;
+import arcade.patch.env.component.PatchComponentSitesGraphTri;
+import arcade.patch.env.component.PatchComponentSitesPattern;
+import arcade.patch.env.component.PatchComponentSitesSource;
+import arcade.patch.env.grid.PatchGrid;
+import arcade.patch.env.location.Coordinate;
+import arcade.patch.env.location.CoordinateXYZ;
+import arcade.patch.env.location.PatchLocation;
+import arcade.patch.env.location.PatchLocationContainer;
+import arcade.patch.sim.PatchSeries;
+import arcade.patch.sim.PatchSimulation;
+import arcade.patch.util.PatchEnums.Ordering;
+
+/**
+ * Implementation of {@link Action} for removing cell agents.
+ *
+ *
The action is stepped once after {@code TIME_DELAY}. The {@code TreatAction} will add CAR
+ * T-cell agents of specified dose and ratio next to source points or vasculature.
+ */
+public class PatchActionTreat implements Action {
+ /** Serialization version identifier */
+ private static final long serialVersionUID = 0;
+
+ /** Delay before calling the helper (in minutes) */
+ private final int delay;
+
+ /** Total number of CAR T-cells to treat with */
+ private final int dose;
+
+ /** List of fraction of each population to treat with. CD4 to CD8 ratio */
+ private final double treatFrac;
+
+ /** Maximum damage value at which T-cells can spawn next to in source or pattern source */
+ private double max_damage;
+
+ /** Minimum radius value at which T- cells can spawn next to in graph source */
+ private final double min_damage_radius;
+
+ /** Number of agent positions per lattice site */
+ private int latPositions;
+
+ /** Coordinate system used for simulation */
+ private final String coord;
+
+ /** List of populations. */
+ private final ArrayList populations;
+
+ /** parameters */
+ MiniBox parameters;
+
+ /** Maximum confluency of cells in any location */
+ final int maxConfluency;
+
+ /** location of available places to insert T cells. For testing purposes only */
+ private ArrayList siteLocations;
+
+ /**
+ * Creates an {@code Action} to add agents after a delay.
+ *
+ * @param series the simulation series
+ * @param parameters the component parameters dictionary
+ */
+ public PatchActionTreat(Series series, MiniBox parameters) {
+ // Set loaded parameters.
+ this.delay = parameters.getInt("TIME_DELAY");
+ this.dose = parameters.getInt("DOSE");
+ this.treatFrac = parameters.getDouble("RATIO");
+ this.max_damage = parameters.getDouble("MAX_DAMAGE_SEED");
+ this.min_damage_radius = parameters.getDouble("MIN_RADIUS_SEED");
+ this.maxConfluency = 54;
+ this.parameters = parameters;
+
+ this.coord =
+ ((PatchSeries) series).patch.get("GEOMETRY").equalsIgnoreCase("HEX")
+ ? "Hex"
+ : "Rect";
+ if (coord.equals("Hex")) {
+ latPositions = 9;
+ }
+ if (coord.equals("Rect")) {
+ latPositions = 16;
+ }
+
+ // Initialize population register.
+ populations = new ArrayList<>();
+ }
+
+ @Override
+ public void schedule(Schedule schedule) {
+ schedule.scheduleOnce(delay, Ordering.ACTIONS.ordinal(), this);
+ }
+
+ @Override
+ public void register(Simulation sim, String population) {
+ populations.add(sim.getSeries().populations.get(population));
+ }
+
+ /**
+ * Steps the helper to insert cells of the treatment population(s).
+ *
+ * @param simstate the MASON simulation state
+ */
+ public void step(SimState simstate) {
+
+ PatchSimulation sim = (PatchSimulation) simstate;
+ String type = "null";
+ PatchGrid grid = (PatchGrid) sim.getGrid();
+ PatchComponentSites comp = (PatchComponentSites) sim.getComponent("SITES");
+
+ ArrayList locs = sim.getLocations();
+
+ ArrayList siteLocs0 = new ArrayList();
+ ArrayList siteLocs1 = new ArrayList();
+ ArrayList siteLocs2 = new ArrayList();
+ ArrayList siteLocs3 = new ArrayList();
+ ArrayList siteLocs = new ArrayList();
+
+ // Determine type of sites component implemented.
+ if (comp instanceof PatchComponentSitesSource) {
+ type = "source";
+ } else if (comp instanceof PatchComponentSitesPattern) {
+ type = "pattern";
+ } else if (comp instanceof PatchComponentSitesGraph) {
+ type = "graph";
+ }
+
+ // Find sites without specified level of damage based on component type.
+ switch (type) {
+ case "source":
+ case "pattern":
+ double[][][] damage;
+ boolean[][][] sitesLat;
+
+ if (type.equals("source")) {
+ damage = ((PatchComponentSitesSource) comp).getDamage();
+ sitesLat = ((PatchComponentSitesSource) comp).getSources();
+ } else {
+ damage = ((PatchComponentSitesPattern) comp).getDamage();
+ sitesLat = ((PatchComponentSitesPattern) comp).getPatterns();
+ }
+
+ // Iterate through list of locations and remove locations not next to a site.
+ for (LocationContainer l : locs) {
+ PatchLocationContainer contain = (PatchLocationContainer) l;
+ PatchLocation loc =
+ (PatchLocation)
+ contain.convert(
+ sim.locationFactory,
+ sim.cellFactory.createCellForPopulation(
+ 0, populations.get(0).getInt("CODE")));
+ CoordinateXYZ coord = (CoordinateXYZ) loc.getSubcoordinate();
+ int z = coord.z;
+ if (sitesLat[z][coord.x][coord.y]
+ && damage[z][coord.x][coord.y] <= this.max_damage) {
+ addCellsIntoList(grid, loc, siteLocs0, siteLocs1, siteLocs2, siteLocs3);
+ }
+ }
+ break;
+
+ case "graph":
+ Graph G = ((PatchComponentSitesGraph) comp).getGraph();
+ Bag allEdges = new Bag(G.getAllEdges());
+ PatchComponentSitesGraph graphSites = (PatchComponentSitesGraph) comp;
+
+ for (Object edgeObj : allEdges) {
+ SiteEdge edge = (SiteEdge) edgeObj;
+ Bag allEdgeLocs = new Bag();
+ if (Objects.equals(coord, "Hex")) {
+ allEdgeLocs.add(
+ ((PatchComponentSitesGraphTri) graphSites)
+ .getSpan(edge.getFrom(), edge.getTo()));
+ } else {
+ allEdgeLocs.add(
+ ((PatchComponentSitesGraphRect) graphSites)
+ .getSpan(edge.getFrom(), edge.getTo()));
+ }
+
+ for (Object locObj : allEdgeLocs) {
+ Location loc = (Location) locObj;
+ if (locs.contains(loc)) {
+ if (edge.getRadius() >= min_damage_radius) {
+ addCellsIntoList(
+ grid, loc, siteLocs0, siteLocs1, siteLocs2, siteLocs3);
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ Utilities.shuffleList(siteLocs3, sim.random);
+ Utilities.shuffleList(siteLocs2, sim.random);
+ Utilities.shuffleList(siteLocs1, sim.random);
+ Utilities.shuffleList(siteLocs0, sim.random);
+ siteLocs.addAll(siteLocs3);
+ siteLocs.addAll(siteLocs2);
+ siteLocs.addAll(siteLocs1);
+ siteLocs.addAll(siteLocs0);
+ siteLocations = (ArrayList) siteLocs.clone();
+ insert(siteLocs, simstate);
+ }
+
+ private void addCellsIntoList(
+ PatchGrid grid,
+ Location loc,
+ ArrayList siteLocs0,
+ ArrayList siteLocs1,
+ ArrayList siteLocs2,
+ ArrayList siteLocs3) {
+ Bag bag = new Bag(grid.getObjectsAtLocation(loc));
+ int numAgents = bag.numObjs;
+
+ if (numAgents == 0) {
+ for (int p = 0; p < latPositions; p++) {
+ siteLocs0.add(loc);
+ }
+ } else if (numAgents == 1) {
+ for (int p = 0; p < latPositions; p++) {
+ siteLocs1.add(loc);
+ }
+ } else if (numAgents == 2) {
+ for (int p = 0; p < latPositions; p++) {
+ siteLocs2.add(loc);
+ }
+ } else {
+ for (int p = 0; p < latPositions; p++) {
+ siteLocs3.add(loc);
+ }
+ }
+ // Remove break statement if more than one per hex can appear
+ // with break statement, each location can only be added to list once
+ // without it, places with more vasc sites get added more times to list
+ // break;
+ }
+
+ private void insert(ArrayList coordinates, SimState simstate) {
+ PatchSimulation sim = (PatchSimulation) simstate;
+ PatchGrid grid = (PatchGrid) sim.getGrid();
+ Utilities.shuffleList(coordinates, sim.random);
+
+ int cd4Code = 0;
+ int cd8Code = 0;
+
+ for (MiniBox population : populations) {
+ String className = population.get("CLASS");
+ if (className.equals("cart_cd4")) {
+ cd4Code = population.getInt("CODE");
+ }
+ if (className.equals("cart_cd8")) {
+ cd8Code = population.getInt("CODE");
+ }
+ }
+
+ for (int i = 0; i < dose; i++) {
+
+ int id = sim.getID();
+
+ int pop = cd4Code;
+
+ if (sim.random.nextDouble() > treatFrac) {
+ pop = cd8Code;
+ }
+
+ PatchLocation loc = ((PatchLocation) coordinates.remove(0));
+ Coordinate coord = loc.getCoordinate();
+
+ // find available location space
+ while (!coordinates.isEmpty() && !checkLocationSpace(loc, grid)) {
+ loc = (PatchLocation) coordinates.remove(0);
+ }
+
+ if (coordinates.isEmpty()) {
+ break;
+ }
+
+ PatchLocationContainer locationContainer = new PatchLocationContainer(id, coord);
+ PatchCellContainer cellContainer = sim.cellFactory.createCellForPopulation(id, pop);
+ Location location = locationContainer.convert(sim.locationFactory, cellContainer);
+ PatchCell cell =
+ (PatchCell) cellContainer.convert(sim.cellFactory, location, sim.random);
+ grid.addObject(cell, location);
+ cell.schedule(sim.getSchedule());
+ }
+ }
+
+ protected boolean checkLocationSpace(Location loc, PatchGrid grid) {
+ boolean available;
+ int locMax = this.maxConfluency;
+ double locVolume = ((PatchLocation) loc).getVolume();
+ double locArea = ((PatchLocation) loc).getArea();
+
+ Bag bag = new Bag(grid.getObjectsAtLocation(loc));
+ int n = bag.numObjs; // number of agents in location
+
+ if (n == 0) {
+ available = true;
+ } // no cells in location
+ else if (n >= locMax) {
+ available = false;
+ } // location already full
+ else {
+ available = true;
+ double totalVol = PatchCell.calculateTotalVolume(bag);
+ double currentHeight = totalVol / locArea;
+
+ if (totalVol > locVolume) {
+ available = false;
+ }
+
+ for (Object cellObj : bag) {
+ PatchCell cell = (PatchCell) cellObj;
+ if (cell instanceof PatchCellCART) {
+ totalVol =
+ PatchCell.calculateTotalVolume(bag)
+ + parameters.getDouble("T_CELL_VOL_AVG");
+ currentHeight = totalVol / locArea;
+ }
+ if (cell instanceof PatchCellTissue) {
+ if (currentHeight > cell.getCriticalHeight()) {
+ available = false;
+ }
+ }
+ }
+ }
+
+ return available;
+ }
+
+ /**
+ * Returns registered populations for the action. Exists for testing purposes only
+ *
+ * @return registered populations for the action
+ */
+ public ArrayList getPopulations() {
+ return populations;
+ }
+
+ /**
+ * Returns locations of sites available for insertion. Exists for testing purposes only
+ *
+ * @return Returns locations of sites available for insertion
+ */
+ public ArrayList getSiteLocs() {
+ return siteLocations;
+ }
+}
diff --git a/src/arcade/patch/agent/cell/PatchCell.java b/src/arcade/patch/agent/cell/PatchCell.java
index b495215ae..1e6e71cac 100644
--- a/src/arcade/patch/agent/cell/PatchCell.java
+++ b/src/arcade/patch/agent/cell/PatchCell.java
@@ -18,11 +18,13 @@
import arcade.core.util.MiniBox;
import arcade.core.util.Parameters;
import arcade.patch.agent.module.PatchModuleApoptosis;
+import arcade.patch.agent.module.PatchModuleCytotoxicity;
import arcade.patch.agent.module.PatchModuleMigration;
import arcade.patch.agent.module.PatchModuleNecrosis;
import arcade.patch.agent.module.PatchModuleProliferation;
import arcade.patch.agent.module.PatchModuleQuiescence;
import arcade.patch.agent.module.PatchModuleSenescence;
+import arcade.patch.agent.module.PatchModuleStimulation;
import arcade.patch.agent.process.PatchProcessInflammation;
import arcade.patch.agent.process.PatchProcessMetabolism;
import arcade.patch.agent.process.PatchProcessSignaling;
@@ -331,6 +333,27 @@ public boolean isStopped() {
return isStopped;
}
+ /**
+ * Makes the specified {@link Process} object.
+ *
+ * @param domain the process domain
+ * @param version the process version
+ * @return the process instance
+ */
+ public Process makeProcess(ProcessDomain domain, String version) {
+ switch ((Domain) domain) {
+ case METABOLISM:
+ return PatchProcessMetabolism.make(this, version);
+ case SIGNALING:
+ return PatchProcessSignaling.make(this, version);
+ case INFLAMMATION:
+ return PatchProcessInflammation.make(this, version);
+ case UNDEFINED:
+ default:
+ return null;
+ }
+ }
+
@Override
public void setState(CellState state) {
this.state = state;
@@ -355,37 +378,18 @@ public void setState(CellState state) {
case SENESCENT:
module = new PatchModuleSenescence(this);
break;
- case CYTOTOXIC:
- throw new UnsupportedOperationException();
case STIMULATORY:
- throw new UnsupportedOperationException();
+ module = new PatchModuleStimulation(this);
+ break;
+ case CYTOTOXIC:
+ module = new PatchModuleCytotoxicity(this);
+ break;
default:
module = null;
break;
}
}
- /**
- * Makes the specified {@link Process} object.
- *
- * @param domain the process domain
- * @param version the process version
- * @return the process instance
- */
- public Process makeProcess(ProcessDomain domain, String version) {
- switch ((Domain) domain) {
- case METABOLISM:
- return PatchProcessMetabolism.make(this, version);
- case SIGNALING:
- return PatchProcessSignaling.make(this, version);
- case INFLAMMATION:
- return PatchProcessInflammation.make(this, version);
- case UNDEFINED:
- default:
- return null;
- }
- }
-
@Override
public void schedule(Schedule schedule) {
stopper = schedule.scheduleRepeating(this, Ordering.CELLS.ordinal(), 1);
diff --git a/src/arcade/patch/agent/cell/PatchCellCART.java b/src/arcade/patch/agent/cell/PatchCellCART.java
index 8f8cba716..25cc71835 100644
--- a/src/arcade/patch/agent/cell/PatchCellCART.java
+++ b/src/arcade/patch/agent/cell/PatchCellCART.java
@@ -48,6 +48,10 @@
* parameter classes have support for loading in distributions to reflect heterogeneity.
*/
public abstract class PatchCellCART extends PatchCell {
+
+ /** Constant for amount of minutes in a day. */
+ public static final int MINUTES_IN_DAY = 1440;
+
/** Cell activation flag. */
protected boolean activated;
@@ -108,6 +112,9 @@ public abstract class PatchCellCART extends PatchCell {
/** Fraction of proliferative cells that become apoptotic. */
protected final double proliferativeFraction;
+ /** Target cell that current T cell is bound to. */
+ protected PatchCell boundTarget;
+
/**
* Creates a {@code PatchCellCART} agent. *
*
@@ -155,6 +162,7 @@ public PatchCellCART(
boundSelfAntigensCount = 0;
lastActiveTicker = 0;
activated = true;
+ boundTarget = null;
// Set loaded parameters.
exhaustedFraction = parameters.getDouble("EXHAUSTED_FRAC");
@@ -395,4 +403,19 @@ private double computeAffinity(double affinity, PatchLocation loc) {
private void updateSelfReceptors() {
selfReceptors += (int) ((double) selfReceptorsStart * (0.95 + Math.random() / 10));
}
+
+ /**
+ * Returns bound cell.
+ *
+ * @return the bound cell
+ */
+ public PatchCell getBoundTarget() {
+ return this.boundTarget;
+ }
+
+ /** Sets binding flag to unbound and binding target to null. */
+ public void unbind() {
+ super.setBindingFlag(AntigenFlag.UNBOUND);
+ this.boundTarget = null;
+ }
}
diff --git a/src/arcade/patch/agent/cell/PatchCellCARTCD4.java b/src/arcade/patch/agent/cell/PatchCellCARTCD4.java
new file mode 100644
index 000000000..fb39f96a8
--- /dev/null
+++ b/src/arcade/patch/agent/cell/PatchCellCARTCD4.java
@@ -0,0 +1,173 @@
+package arcade.patch.agent.cell;
+
+import sim.engine.SimState;
+import ec.util.MersenneTwisterFast;
+import arcade.core.agent.cell.CellState;
+import arcade.core.env.location.Location;
+import arcade.core.sim.Simulation;
+import arcade.core.util.GrabBag;
+import arcade.core.util.Parameters;
+import arcade.patch.util.PatchEnums.AntigenFlag;
+import arcade.patch.util.PatchEnums.Domain;
+import arcade.patch.util.PatchEnums.State;
+
+public class PatchCellCARTCD4 extends PatchCellCART {
+
+ /**
+ * Creates a T cell {@code PatchCellCARTCD4} agent. *
+ *
+ * @param container the cell container
+ * @param location the {@link Location} of the cell
+ * @param parameters the dictionary of parameters
+ */
+ public PatchCellCARTCD4(
+ PatchCellContainer container, Location location, Parameters parameters) {
+ this(container, location, parameters, null);
+ }
+
+ /**
+ * Creates a T cell {@code PatchCellCARTCD4} agent. *
+ *
+ * @param container the cell container
+ * @param location the {@link Location} of the cell
+ * @param parameters the dictionary of parameters
+ * @param links the map of population links
+ */
+ public PatchCellCARTCD4(
+ PatchCellContainer container, Location location, Parameters parameters, GrabBag links) {
+ super(container, location, parameters, links);
+ }
+
+ @Override
+ public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFast random) {
+ divisions++;
+ return new PatchCellContainer(
+ newID,
+ id,
+ pop,
+ age,
+ divisions,
+ newState,
+ volume,
+ height,
+ criticalVolume,
+ criticalHeight);
+ }
+
+ @Override
+ public void step(SimState simstate) {
+ Simulation sim = (Simulation) simstate;
+
+ // Increase age of cell.
+ super.age++;
+
+ if (state != State.APOPTOTIC && age > apoptosisAge) {
+ setState(State.APOPTOTIC);
+ super.setBindingFlag(AntigenFlag.UNBOUND);
+ this.activated = false;
+ }
+
+ // Increase time since last active ticker
+ super.lastActiveTicker++;
+ if (super.lastActiveTicker != 0 && super.lastActiveTicker % MINUTES_IN_DAY == 0) {
+ if (super.boundCARAntigensCount != 0) super.boundCARAntigensCount--;
+ }
+ if (super.lastActiveTicker / MINUTES_IN_DAY >= 7) super.activated = false;
+
+ // Step metabolism process.
+ super.processes.get(Domain.METABOLISM).step(simstate.random, sim);
+
+ // Check energy status. If cell has less energy than threshold, it will
+ // apoptose. If overall energy is negative, then cell enters quiescence.
+ if (state != State.APOPTOTIC) {
+ if (super.energy < super.energyThreshold) {
+
+ super.setState(State.APOPTOTIC);
+ super.unbind();
+ this.activated = false;
+ } else if (state != State.ANERGIC
+ && state != State.SENESCENT
+ && state != State.EXHAUSTED
+ && state != State.STARVED
+ && energy < 0) {
+
+ super.setState(State.STARVED);
+ super.unbind();
+ } else if (state == State.STARVED && energy >= 0) {
+ super.setState(State.UNDEFINED);
+ }
+ }
+
+ // Step inflammation process.
+ super.processes.get(Domain.INFLAMMATION).step(simstate.random, sim);
+
+ // Change state from undefined.
+ if (super.state == State.UNDEFINED || super.state == State.PAUSED) {
+ if (divisions == 0) {
+ if (simstate.random.nextDouble() > super.senescentFraction) {
+ super.setState(State.APOPTOTIC);
+ } else {
+ super.setState(State.SENESCENT);
+ }
+ super.unbind();
+ this.activated = false;
+ } else {
+ // Cell attempts to bind to a target
+ PatchCellTissue target =
+ super.bindTarget(sim, location, new MersenneTwisterFast(simstate.seed()));
+ super.boundTarget = target;
+ // If cell is bound to both antigen and self it will become anergic.
+ if (super.getBindingFlag() == AntigenFlag.BOUND_ANTIGEN_CELL_RECEPTOR) {
+ if (simstate.random.nextDouble() > super.anergicFraction) {
+ super.setState(State.APOPTOTIC);
+ } else {
+ super.setState(State.ANERGIC);
+ }
+ super.unbind();
+ this.activated = false;
+ } else if (super.getBindingFlag() == AntigenFlag.BOUND_ANTIGEN) {
+ // If cell is only bound to target antigen, the cell
+ // can potentially become properly activated.
+
+ // Check overstimulation. If cell has bound to
+ // target antigens too many times, becomes exhausted.
+ if (boundCARAntigensCount > maxAntigenBinding) {
+ if (simstate.random.nextDouble() > super.exhaustedFraction) {
+ super.setState(State.APOPTOTIC);
+ } else {
+ super.setState(State.EXHAUSTED);
+ }
+ super.unbind();
+ this.activated = false;
+ } else {
+ // if CD4 cell is properly activated, it can stimulate
+ super.setState(State.STIMULATORY);
+ this.lastActiveTicker = 0;
+ this.activated = true;
+ }
+ } else {
+ // If self binding, unbind
+ if (super.getBindingFlag() == AntigenFlag.BOUND_CELL_RECEPTOR) {
+ super.unbind();
+ }
+ // Check activation status. If cell has been activated before,
+ // it will proliferate. If not, it will migrate.
+ if (activated) {
+ super.setState(State.PROLIFERATIVE);
+ } else {
+ if (simstate.random.nextDouble() > super.proliferativeFraction) {
+ super.setState(State.MIGRATORY);
+ } else {
+ super.setState(State.PROLIFERATIVE);
+ }
+ }
+ }
+ }
+ }
+
+ // Step the module for the cell state.
+ if (super.module != null) {
+ super.module.step(simstate.random, sim);
+ }
+ }
+}
diff --git a/src/arcade/patch/agent/cell/PatchCellCARTCD8.java b/src/arcade/patch/agent/cell/PatchCellCARTCD8.java
new file mode 100644
index 000000000..708bd8682
--- /dev/null
+++ b/src/arcade/patch/agent/cell/PatchCellCARTCD8.java
@@ -0,0 +1,170 @@
+package arcade.patch.agent.cell;
+
+import sim.engine.SimState;
+import ec.util.MersenneTwisterFast;
+import arcade.core.agent.cell.CellState;
+import arcade.core.env.location.Location;
+import arcade.core.sim.Simulation;
+import arcade.core.util.GrabBag;
+import arcade.core.util.Parameters;
+import arcade.patch.util.PatchEnums.AntigenFlag;
+import arcade.patch.util.PatchEnums.Domain;
+import arcade.patch.util.PatchEnums.State;
+
+/** Extension of {@link PatchCellCART} for CD8 CART-cells with selected module versions. */
+public class PatchCellCARTCD8 extends PatchCellCART {
+ /**
+ * Creates a T cell {@code PatchCellCARTCD8} agent. *
+ *
+ * @param container the cell container
+ * @param location the {@link Location} of the cell
+ * @param parameters the dictionary of parameters
+ */
+ public PatchCellCARTCD8(
+ PatchCellContainer container, Location location, Parameters parameters) {
+ this(container, location, parameters, null);
+ }
+
+ /**
+ * Creates a T cell {@code PatchCellCARTCD8} agent. *
+ *
+ * @param container the cell container
+ * @param location the {@link Location} of the cell
+ * @param parameters the dictionary of parameters
+ * @param links the map of population links
+ */
+ public PatchCellCARTCD8(
+ PatchCellContainer container, Location location, Parameters parameters, GrabBag links) {
+ super(container, location, parameters, links);
+ }
+
+ @Override
+ public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFast random) {
+ divisions++;
+ return new PatchCellContainer(
+ newID,
+ id,
+ pop,
+ age,
+ divisions,
+ newState,
+ volume,
+ height,
+ criticalVolume,
+ criticalHeight);
+ }
+
+ @Override
+ public void step(SimState simstate) {
+ Simulation sim = (Simulation) simstate;
+
+ super.age++;
+
+ if (state != State.APOPTOTIC && age > apoptosisAge) {
+ setState(State.APOPTOTIC);
+ super.unbind();
+ this.activated = false;
+ }
+
+ super.lastActiveTicker++;
+ if (super.lastActiveTicker != 0 && super.lastActiveTicker % MINUTES_IN_DAY == 0) {
+ if (super.boundCARAntigensCount != 0) {
+ super.boundCARAntigensCount--;
+ }
+ }
+ if (super.lastActiveTicker / MINUTES_IN_DAY >= 7) {
+ super.activated = false;
+ }
+
+ super.processes.get(Domain.METABOLISM).step(simstate.random, sim);
+
+ // Check energy status. If cell has less energy than threshold, it will
+ // apoptose. If overall energy is negative, then cell enters quiescence.
+ if (state != State.APOPTOTIC) {
+ if (super.energy < super.energyThreshold) {
+
+ super.setState(State.APOPTOTIC);
+ super.unbind();
+ this.activated = false;
+ } else if (state != State.ANERGIC
+ && state != State.SENESCENT
+ && state != State.EXHAUSTED
+ && state != State.STARVED
+ && energy < 0) {
+
+ super.setState(State.STARVED);
+ super.unbind();
+ } else if (state == State.STARVED && energy >= 0) {
+ super.setState(State.UNDEFINED);
+ }
+ }
+
+ super.processes.get(Domain.INFLAMMATION).step(simstate.random, sim);
+
+ if (super.state == State.UNDEFINED || super.state == State.PAUSED) {
+ if (divisions == divisionPotential) {
+ if (simstate.random.nextDouble() > super.senescentFraction) {
+ super.setState(State.APOPTOTIC);
+ } else {
+ super.setState(State.SENESCENT);
+ }
+ super.unbind();
+ this.activated = false;
+ } else {
+ PatchCellTissue target = super.bindTarget(sim, location, simstate.random);
+ super.boundTarget = target;
+
+ // If cell is bound to both antigen and self it will become anergic.
+ if (super.getBindingFlag() == AntigenFlag.BOUND_ANTIGEN_CELL_RECEPTOR) {
+ if (simstate.random.nextDouble() > super.anergicFraction) {
+ super.setState(State.APOPTOTIC);
+ } else {
+ super.setState(State.ANERGIC);
+ }
+ super.unbind();
+ this.activated = false;
+ } else if (super.getBindingFlag() == AntigenFlag.BOUND_ANTIGEN) {
+ // If cell is only bound to target antigen, the cell
+ // can potentially become properly activated.
+
+ // Check overstimulation. If cell has bound to
+ // target antigens too many times, becomes exhausted.
+ if (boundCARAntigensCount > maxAntigenBinding) {
+ if (simstate.random.nextDouble() > super.exhaustedFraction) {
+ super.setState(State.APOPTOTIC);
+ } else {
+ super.setState(State.EXHAUSTED);
+ }
+ super.unbind();
+ this.activated = false;
+ } else {
+ // if CD8 cell is properly activated, it can be cytotoxic
+ this.lastActiveTicker = 0;
+ this.activated = true;
+ super.setState(State.CYTOTOXIC);
+ }
+ } else {
+ // If self binding, unbind
+ if (super.getBindingFlag() == AntigenFlag.BOUND_CELL_RECEPTOR) {
+ super.unbind();
+ }
+ // Check activation status. If cell has been activated before,
+ // it will proliferate. If not, it will migrate.
+ if (activated) {
+ super.setState(State.PROLIFERATIVE);
+ } else {
+ if (simstate.random.nextDouble() > super.proliferativeFraction) {
+ super.setState(State.MIGRATORY);
+ } else {
+ super.setState(State.PROLIFERATIVE);
+ }
+ }
+ }
+ }
+ }
+
+ if (super.module != null) {
+ super.module.step(simstate.random, sim);
+ }
+ }
+}
diff --git a/src/arcade/patch/agent/cell/PatchCellContainer.java b/src/arcade/patch/agent/cell/PatchCellContainer.java
index 960d5df6c..9e2242a9b 100644
--- a/src/arcade/patch/agent/cell/PatchCellContainer.java
+++ b/src/arcade/patch/agent/cell/PatchCellContainer.java
@@ -115,6 +115,10 @@ public Cell convert(
return new PatchCellCancer(this, location, parameters, links);
case "cancer_stem":
return new PatchCellCancerStem(this, location, parameters, links);
+ case "cart_cd4":
+ return new PatchCellCARTCD4(this, location, parameters, links);
+ case "cart_cd8":
+ return new PatchCellCARTCD8(this, location, parameters, links);
case "random":
return new PatchCellRandom(this, location, parameters, links);
}
diff --git a/src/arcade/patch/agent/module/PatchModuleCytotoxicity.java b/src/arcade/patch/agent/module/PatchModuleCytotoxicity.java
new file mode 100644
index 000000000..9423e69dd
--- /dev/null
+++ b/src/arcade/patch/agent/module/PatchModuleCytotoxicity.java
@@ -0,0 +1,79 @@
+package arcade.patch.agent.module;
+
+import ec.util.MersenneTwisterFast;
+import arcade.core.sim.Simulation;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.cell.PatchCell;
+import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.agent.cell.PatchCellTissue;
+import arcade.patch.agent.process.PatchProcessInflammation;
+import static arcade.patch.util.PatchEnums.Domain;
+import static arcade.patch.util.PatchEnums.State;
+
+/**
+ * Implementation of {@link Module} for killing tissue agents.
+ *
+ * {@code PatchModuleCytotoxicity} is stepped once after a CD8 CAR T-cell binds to a target
+ * tissue cell. The {@code PatchModuleCytotoxicity} determines if cell has enough granzyme to kill.
+ * If so, it kills cell and calls the reset to neutral helper to return to neutral state. If not, it
+ * waits until it has enough granzyme to kill cell.
+ */
+public class PatchModuleCytotoxicity extends PatchModule {
+ /** Target cell cytotoxic CAR T-cell is bound to. */
+ PatchCellTissue target;
+
+ /** CAR T-cell inflammation module. */
+ PatchProcessInflammation inflammation;
+
+ /** Amount of granzyme inside CAR T-cell. */
+ double granzyme;
+
+ /** Average time that T cell is bound to target [min]. */
+ private final int timeDelay;
+
+ /** Ticker to keep track of the time delay [min]. */
+ private int ticker;
+
+ /**
+ * Creates a {@code PatchActionKill} for the given {@link PatchCellCART}.
+ *
+ * @param cell the {@link PatchCell} the helper is associated with
+ */
+ public PatchModuleCytotoxicity(PatchCell cell) {
+ super(cell);
+ this.target = (PatchCellTissue) ((PatchCellCART) cell).getBoundTarget();
+ this.inflammation = (PatchProcessInflammation) cell.getProcess(Domain.INFLAMMATION);
+ this.granzyme = inflammation.getInternal("granzyme");
+
+ Parameters parameters = cell.getParameters();
+ this.timeDelay = parameters.getInt("BOUND_TIME");
+ this.ticker = 0;
+ }
+
+ @Override
+ public void step(MersenneTwisterFast random, Simulation sim) {
+ if (cell.isStopped()) {
+ return;
+ }
+
+ if (target.isStopped()) {
+ ((PatchCellCART) cell).unbind();
+ cell.setState(State.UNDEFINED);
+ return;
+ }
+
+ if (ticker == 0) {
+ if (granzyme >= 1) {
+ PatchCellTissue tissueCell = (PatchCellTissue) target;
+ tissueCell.setState(State.APOPTOTIC);
+ granzyme--;
+ inflammation.setInternal("granzyme", granzyme);
+ }
+ } else if (ticker >= timeDelay) {
+ ((PatchCellCART) cell).unbind();
+ cell.setState(State.UNDEFINED);
+ }
+
+ ticker++;
+ }
+}
diff --git a/src/arcade/patch/agent/module/PatchModuleProliferation.java b/src/arcade/patch/agent/module/PatchModuleProliferation.java
index 572a6524f..e5134bf8b 100644
--- a/src/arcade/patch/agent/module/PatchModuleProliferation.java
+++ b/src/arcade/patch/agent/module/PatchModuleProliferation.java
@@ -8,6 +8,7 @@
import arcade.core.util.MiniBox;
import arcade.core.util.Parameters;
import arcade.patch.agent.cell.PatchCell;
+import arcade.patch.agent.cell.PatchCellCART;
import arcade.patch.agent.process.PatchProcess;
import arcade.patch.env.grid.PatchGrid;
import arcade.patch.env.location.PatchLocation;
@@ -73,12 +74,20 @@ public void step(MersenneTwisterFast random, Simulation sim) {
// space in neighborhood to divide into. Otherwise, check if double
// volume has been reached, and if so, create a new cell.
if (currentHeight > maxHeight) {
- cell.setState(State.QUIESCENT);
+ if (cell instanceof PatchCellCART) {
+ cell.setState(State.PAUSED);
+ } else {
+ cell.setState(State.QUIESCENT);
+ }
} else {
PatchLocation newLocation = cell.selectBestLocation(sim, random);
if (newLocation == null) {
- cell.setState(State.QUIESCENT);
+ if (cell instanceof PatchCellCART) {
+ cell.setState(State.PAUSED);
+ } else {
+ cell.setState(State.QUIESCENT);
+ }
} else if (cell.getVolume() >= targetVolume) {
if (ticker > synthesisDuration) {
diff --git a/src/arcade/patch/agent/module/PatchModuleStimulation.java b/src/arcade/patch/agent/module/PatchModuleStimulation.java
new file mode 100644
index 000000000..d45985076
--- /dev/null
+++ b/src/arcade/patch/agent/module/PatchModuleStimulation.java
@@ -0,0 +1,72 @@
+package arcade.patch.agent.module;
+
+import ec.util.MersenneTwisterFast;
+import arcade.core.sim.Simulation;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.cell.PatchCell;
+import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.agent.cell.PatchCellTissue;
+import arcade.patch.util.PatchEnums;
+
+/**
+ * Implementation of {@link Module} for stimulatory T cell agents.
+ *
+ *
{@code PatchModuleStimulation} is stepped once after a CD4 CAR T-cell gets stimulated. The
+ * {@code PatchModuleStimulation} activates the T cell, unbinds to any target cell that the T cell
+ * is bound to, and sets the cell state back to undefined.
+ */
+public class PatchModuleStimulation extends PatchModule {
+ /** Target cell cytotoxic CAR T-cell is bound to */
+ PatchCellTissue target;
+
+ /** Time delay before calling the action [min]. */
+ private int timeDelay;
+
+ /** Ticker to keep track of the time delay [min]. */
+ private int ticker;
+
+ /** Average time that T cell is bound to target [min]. */
+ private double boundTime;
+
+ /** Range in bound time [min]. */
+ private double boundRange;
+
+ /**
+ * Creates a {@code PatchModuleStimulation} for the given {@link PatchCellCART}.
+ *
+ * @param c the {@link PatchCellCART} the helper is associated with
+ */
+ public PatchModuleStimulation(PatchCell c) {
+ super(c);
+ this.target = (PatchCellTissue) ((PatchCellCART) c).getBoundTarget();
+ Parameters parameters = c.getParameters();
+ this.boundTime = parameters.getInt("BOUND_TIME");
+ this.boundRange = parameters.getInt("BOUND_RANGE");
+ this.timeDelay = 0;
+ this.ticker = 0;
+ }
+
+ @Override
+ public void step(MersenneTwisterFast random, Simulation sim) {
+ if (cell.isStopped()) {
+ return;
+ }
+
+ if (target.isStopped()) {
+ ((PatchCellCART) cell).unbind();
+ cell.setState(PatchEnums.State.UNDEFINED);
+ return;
+ }
+
+ if (ticker == 0) {
+ this.timeDelay =
+ (int) (boundTime + Math.round((boundRange * (2 * random.nextInt() - 1))));
+ target.setState(PatchEnums.State.QUIESCENT);
+ } else if (ticker >= timeDelay) {
+ ((PatchCellCART) cell).unbind();
+ cell.setState(PatchEnums.State.UNDEFINED);
+ }
+
+ ticker++;
+ }
+}
diff --git a/src/arcade/patch/agent/process/PatchProcessInflammation.java b/src/arcade/patch/agent/process/PatchProcessInflammation.java
index 08c988d11..cefd90a79 100644
--- a/src/arcade/patch/agent/process/PatchProcessInflammation.java
+++ b/src/arcade/patch/agent/process/PatchProcessInflammation.java
@@ -124,14 +124,14 @@ public abstract class PatchProcessInflammation extends PatchProcess {
* bound and no three-chain receptors. Daughter cells split amounts of bound IL-2 and
* three-chain receptors upon dividing.
*
- * @param c the {@link PatchCellCART} the module is associated with
+ * @param cell the {@link PatchCellCART} the module is associated with
*/
- public PatchProcessInflammation(PatchCellCART c) {
- super(c);
- this.loc = c.getLocation();
- this.cell = c;
- this.pop = c.getPop();
- this.volume = c.getVolume();
+ public PatchProcessInflammation(PatchCellCART cell) {
+ super(cell);
+ this.loc = cell.getLocation();
+ this.cell = cell;
+ this.pop = cell.getPop();
+ this.volume = cell.getVolume();
this.iL2Ticker = 0;
this.activeTicker = 0;
@@ -140,6 +140,7 @@ public PatchProcessInflammation(PatchCellCART c) {
this.iL2Receptors = parameters.getDouble("inflammation/IL2_RECEPTORS");
extIL2 = 0;
+ // Initial amounts of each species, all in molecules/cell.
amts = new double[NUM_COMPONENTS];
amts[IL2_INT_TOTAL] = 0;
amts[IL2R_TOTAL] = iL2Receptors;
@@ -309,9 +310,9 @@ public void step(MersenneTwisterFast random, Simulation sim) {
public static PatchProcess make(PatchCell cell, String version) {
switch (version.toUpperCase()) {
case "CD4":
- throw new UnsupportedOperationException();
+ return new PatchProcessInflammationCD4((PatchCellCART) cell);
case "CD8":
- throw new UnsupportedOperationException();
+ return new PatchProcessInflammationCD8((PatchCellCART) cell);
default:
return null;
}
diff --git a/src/arcade/patch/agent/process/PatchProcessInflammationCD4.java b/src/arcade/patch/agent/process/PatchProcessInflammationCD4.java
new file mode 100644
index 000000000..b97cdb102
--- /dev/null
+++ b/src/arcade/patch/agent/process/PatchProcessInflammationCD4.java
@@ -0,0 +1,129 @@
+package arcade.patch.agent.process;
+
+import ec.util.MersenneTwisterFast;
+import arcade.core.agent.process.Process;
+import arcade.core.sim.Simulation;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.cell.PatchCellCART;
+
+/**
+ * Extension of {@link arcade.patch.agent.process} for CD4 CAR T-cells
+ *
+ *
{@code InflammationCD4} determines IL-2 amounts produced for stimulatory effector functions as
+ * a antigen-induced activation state.
+ */
+public class PatchProcessInflammationCD4 extends PatchProcessInflammation {
+
+ /** Rate of IL-2 production due to antigen-induced activation */
+ private final double IL2_PROD_RATE_ACTIVE;
+
+ /** Rate of IL-2 production due to IL-2 feedback */
+ private final double IL2_PROD_RATE_IL2;
+
+ /** Delay in IL-2 synthesis after antigen-induced activation */
+ private final int IL2_SYNTHESIS_DELAY;
+
+ /** Total rate of IL-2 production */
+ private double IL2ProdRate;
+
+ /** Total IL-2 produced in step */
+ private double IL2Produced;
+
+ /** Amount of IL-2 bound in past being used for current IL-2 production calculation */
+ private double priorIL2prod;
+
+ /** External IL-2 sent into environment after each step. Used for testing only */
+ private double IL2EnvTesting;
+
+ /**
+ * Creates a CD4 {@link PatchProcessInflammation} module.
+ *
+ *
IL-2 production rate parameters set.
+ *
+ * @param cell the {@link PatchCellCART} the module is associated with
+ */
+ public PatchProcessInflammationCD4(PatchCellCART cell) {
+ super(cell);
+
+ // Set parameters.
+ Parameters parameters = cell.getParameters();
+ this.IL2_PROD_RATE_IL2 = parameters.getDouble("inflammation/IL2_PROD_RATE_IL2");
+ this.IL2_PROD_RATE_ACTIVE = parameters.getDouble("inflammation/IL2_PROD_RATE_ACTIVE");
+ this.IL2_SYNTHESIS_DELAY = parameters.getInt("inflammation/IL2_SYNTHESIS_DELAY");
+ IL2ProdRate = 0;
+ }
+
+ @Override
+ public void stepProcess(MersenneTwisterFast random, Simulation sim) {
+
+ // Determine IL-2 production rate as a function of IL-2 bound.
+ int prodIndex = (iL2Ticker % boundArray.length) - IL2_SYNTHESIS_DELAY;
+ if (prodIndex < 0) {
+ prodIndex += boundArray.length;
+ }
+ priorIL2prod = boundArray[prodIndex];
+ IL2ProdRate = IL2_PROD_RATE_IL2 * (priorIL2prod / iL2Receptors);
+
+ // Add IL-2 production rate dependent on antigen-induced
+ // cell activation if cell is activated.
+ if (active && activeTicker >= IL2_SYNTHESIS_DELAY) {
+ IL2ProdRate += IL2_PROD_RATE_ACTIVE;
+ }
+
+ // Produce IL-2 to environment.
+ IL2Produced = IL2ProdRate; // [molecules], rate is already per minute
+
+ // Update environment.
+ // Take current IL2 external concentration and add the amount produced,
+ // then convert units back to molecules/cm^3.
+ double IL2Env =
+ (((extIL2 - (extIL2 * fraction - amts[IL2_EXT])) + IL2Produced)
+ * 1E12
+ / loc.getVolume());
+
+ // update IL2 env variable for testing
+ IL2EnvTesting = IL2Env;
+
+ sim.getLattice("IL-2").setValue(loc, IL2Env);
+ }
+
+ @Override
+ public void update(Process mod) {
+ PatchProcessInflammationCD4 inflammation = (PatchProcessInflammationCD4) mod;
+ double split = (this.cell.getVolume() / this.volume);
+
+ // Update daughter cell inflammation as a fraction of parent.
+ // this.volume = this.cell.getVolume();
+ this.amts[IL2RBGA] = inflammation.amts[IL2RBGA] * split;
+ this.amts[IL2_IL2RBG] = inflammation.amts[IL2_IL2RBG] * split;
+ this.amts[IL2_IL2RBGA] = inflammation.amts[IL2_IL2RBGA] * split;
+ this.amts[IL2RBG] =
+ iL2Receptors - this.amts[IL2RBGA] - this.amts[IL2_IL2RBG] - this.amts[IL2_IL2RBGA];
+ this.amts[IL2_INT_TOTAL] = this.amts[IL2_IL2RBG] + this.amts[IL2_IL2RBGA];
+ this.amts[IL2R_TOTAL] = this.amts[IL2RBG] + this.amts[IL2RBGA];
+ this.boundArray = (inflammation.boundArray).clone();
+
+ // Update parent cell with remaining fraction.
+ inflammation.amts[IL2RBGA] *= (1 - split);
+ inflammation.amts[IL2_IL2RBG] *= (1 - split);
+ inflammation.amts[IL2_IL2RBGA] *= (1 - split);
+ inflammation.amts[IL2RBG] =
+ iL2Receptors
+ - inflammation.amts[IL2RBGA]
+ - inflammation.amts[IL2_IL2RBG]
+ - inflammation.amts[IL2_IL2RBGA];
+ inflammation.amts[IL2_INT_TOTAL] =
+ inflammation.amts[IL2_IL2RBG] + inflammation.amts[IL2_IL2RBGA];
+ inflammation.amts[IL2R_TOTAL] = inflammation.amts[IL2RBG] + inflammation.amts[IL2RBGA];
+ inflammation.volume *= (1 - split);
+ }
+
+ /**
+ * Returns final value of external IL2 after stepping process. Exists for testing purposes only
+ *
+ * @return final value of external IL2
+ */
+ public double getIL2EnvTesting() {
+ return IL2EnvTesting;
+ }
+}
diff --git a/src/arcade/patch/agent/process/PatchProcessInflammationCD8.java b/src/arcade/patch/agent/process/PatchProcessInflammationCD8.java
new file mode 100644
index 000000000..b0c751fc6
--- /dev/null
+++ b/src/arcade/patch/agent/process/PatchProcessInflammationCD8.java
@@ -0,0 +1,95 @@
+package arcade.patch.agent.process;
+
+import ec.util.MersenneTwisterFast;
+import arcade.core.agent.process.Process;
+import arcade.core.sim.Simulation;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.cell.PatchCellCART;
+
+/**
+ * Extension of {@link PatchProcessInflammation} for CD8 CAR T-cells
+ *
+ *
{@code InflammationCD8} determines granzyme amounts produced for cytotoxic effector functions
+ * as a function of IL-2 bound and antigen-induced activation state.
+ */
+public class PatchProcessInflammationCD8 extends PatchProcessInflammation {
+ /** Moles of granzyme produced per moles IL-2 [mol granzyme/mol IL-2]. */
+ private static final double GRANZ_PER_IL2 = 0.005;
+
+ /** Delay in IL-2 synthesis after antigen-induced activation. */
+ private final int granzSynthesisDelay;
+
+ /** Amount of IL-2 bound in past being used for current granzyme production calculation. */
+ private double priorIL2granz;
+
+ /**
+ * Creates a CD8 {@link PatchProcessInflammation} module.
+ *
+ *
Initial amount of internal granzyme is set. Granzyme production parameters set.
+ *
+ * @param cell the {@link PatchCellCART} the module is associated with
+ */
+ public PatchProcessInflammationCD8(PatchCellCART cell) {
+ super(cell);
+
+ Parameters parameters = cell.getParameters();
+ this.granzSynthesisDelay = parameters.getInt("inflammation/GRANZ_SYNTHESIS_DELAY");
+ this.priorIL2granz = 0;
+
+ amts[GRANZYME] = 1; // [molecules]
+ names.add(GRANZYME, "granzyme");
+ }
+
+ @Override
+ public void stepProcess(MersenneTwisterFast random, Simulation sim) {
+
+ // Determine amount of granzyme production based on if cell is activated
+ // as a function of IL-2 production.
+ int granzIndex = (iL2Ticker % boundArray.length) - granzSynthesisDelay;
+ if (granzIndex < 0) {
+ granzIndex += boundArray.length;
+ }
+ priorIL2granz = boundArray[granzIndex];
+
+ if (active && activeTicker > granzSynthesisDelay) {
+ amts[GRANZYME] += GRANZ_PER_IL2 * (priorIL2granz / iL2Receptors);
+ }
+
+ // Update environment.
+ // Convert units back from molecules to molecules/cm^3.
+ double iL2Env = ((extIL2 - (extIL2 * fraction - amts[IL2_EXT])) * 1E12 / loc.getVolume());
+ sim.getLattice("IL-2").setValue(loc, iL2Env);
+ }
+
+ @Override
+ public void update(Process mod) {
+ PatchProcessInflammationCD8 inflammation = (PatchProcessInflammationCD8) mod;
+ double split = (this.cell.getVolume() / this.volume);
+
+ // Update daughter cell inflammation as a fraction of parent.
+ this.amts[IL2RBGA] = inflammation.amts[IL2RBGA] * split;
+ this.amts[IL2_IL2RBG] = inflammation.amts[IL2_IL2RBG] * split;
+ this.amts[IL2_IL2RBGA] = inflammation.amts[IL2_IL2RBGA] * split;
+ this.amts[IL2RBG] =
+ iL2Receptors - this.amts[IL2RBGA] - this.amts[IL2_IL2RBG] - this.amts[IL2_IL2RBGA];
+ this.amts[IL2_INT_TOTAL] = this.amts[IL2_IL2RBG] + this.amts[IL2_IL2RBGA];
+ this.amts[IL2R_TOTAL] = this.amts[IL2RBG] + this.amts[IL2RBGA];
+ this.amts[GRANZYME] = inflammation.amts[GRANZYME] * split;
+ this.boundArray = (inflammation.boundArray).clone();
+
+ // Update parent cell with remaining fraction.
+ inflammation.amts[IL2RBGA] *= (1 - split);
+ inflammation.amts[IL2_IL2RBG] *= (1 - split);
+ inflammation.amts[IL2_IL2RBGA] *= (1 - split);
+ inflammation.amts[IL2RBG] =
+ iL2Receptors
+ - inflammation.amts[IL2RBGA]
+ - inflammation.amts[IL2_IL2RBG]
+ - inflammation.amts[IL2_IL2RBGA];
+ inflammation.amts[IL2_INT_TOTAL] =
+ inflammation.amts[IL2_IL2RBG] + inflammation.amts[IL2_IL2RBGA];
+ inflammation.amts[IL2R_TOTAL] = inflammation.amts[IL2RBG] + inflammation.amts[IL2RBGA];
+ inflammation.amts[GRANZYME] *= (1 - split);
+ inflammation.volume *= (1 - split);
+ }
+}
diff --git a/src/arcade/patch/env/grid/PatchGrid.java b/src/arcade/patch/env/grid/PatchGrid.java
index 599171f5a..34be56bd7 100644
--- a/src/arcade/patch/env/grid/PatchGrid.java
+++ b/src/arcade/patch/env/grid/PatchGrid.java
@@ -15,7 +15,7 @@
*/
public class PatchGrid implements Grid {
/** Initial bag capacity. */
- private static final int INITIAL_CAPACITY = 6;
+ private static final int INITIAL_CAPACITY = 54;
/** Map of ID to object. */
final HashMap objects;
diff --git a/src/arcade/patch/parameter.patch.xml b/src/arcade/patch/parameter.patch.xml
index 1e369fd1c..7a6211c3d 100644
--- a/src/arcade/patch/parameter.patch.xml
+++ b/src/arcade/patch/parameter.patch.xml
@@ -43,6 +43,7 @@
+
@@ -87,6 +88,14 @@
+
+
+
+
+
+
+
+
@@ -113,6 +122,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/arcade/patch/sim/PatchSimulationHex.java b/src/arcade/patch/sim/PatchSimulationHex.java
index 6eddf28b5..0109aa872 100644
--- a/src/arcade/patch/sim/PatchSimulationHex.java
+++ b/src/arcade/patch/sim/PatchSimulationHex.java
@@ -7,6 +7,7 @@
import arcade.patch.agent.action.PatchActionConvert;
import arcade.patch.agent.action.PatchActionInsert;
import arcade.patch.agent.action.PatchActionRemove;
+import arcade.patch.agent.action.PatchActionTreat;
import arcade.patch.agent.cell.PatchCellFactory;
import arcade.patch.env.component.PatchComponentCycle;
import arcade.patch.env.component.PatchComponentDegrade;
@@ -58,6 +59,8 @@ public Action makeAction(String actionClass, MiniBox parameters) {
return new PatchActionRemove(series, parameters);
case "convert":
return new PatchActionConvert(series, parameters);
+ case "treat":
+ return new PatchActionTreat(series, parameters);
default:
return null;
}
diff --git a/src/arcade/patch/sim/PatchSimulationRect.java b/src/arcade/patch/sim/PatchSimulationRect.java
index 6623fefec..0fdfe8523 100644
--- a/src/arcade/patch/sim/PatchSimulationRect.java
+++ b/src/arcade/patch/sim/PatchSimulationRect.java
@@ -7,6 +7,7 @@
import arcade.patch.agent.action.PatchActionConvert;
import arcade.patch.agent.action.PatchActionInsert;
import arcade.patch.agent.action.PatchActionRemove;
+import arcade.patch.agent.action.PatchActionTreat;
import arcade.patch.agent.cell.PatchCellFactory;
import arcade.patch.env.component.PatchComponentCycle;
import arcade.patch.env.component.PatchComponentDegrade;
@@ -58,6 +59,8 @@ public Action makeAction(String actionClass, MiniBox parameters) {
return new PatchActionRemove(series, parameters);
case "convert":
return new PatchActionConvert(series, parameters);
+ case "treat":
+ return new PatchActionTreat(series, parameters);
default:
return null;
}
diff --git a/test/arcade/patch/agent/action/PatchActionKillTest.java b/test/arcade/patch/agent/action/PatchActionKillTest.java
new file mode 100644
index 000000000..a3698ecd8
--- /dev/null
+++ b/test/arcade/patch/agent/action/PatchActionKillTest.java
@@ -0,0 +1,77 @@
+package arcade.patch.agent.action;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import sim.engine.Schedule;
+import ec.util.MersenneTwisterFast;
+import arcade.core.sim.Series;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.agent.cell.PatchCellTissue;
+import arcade.patch.agent.module.PatchModuleApoptosis;
+import arcade.patch.agent.process.PatchProcessInflammation;
+import arcade.patch.sim.PatchSimulation;
+import arcade.patch.util.PatchEnums;
+import arcade.patch.util.PatchEnums.AntigenFlag;
+import arcade.patch.util.PatchEnums.State;
+import static org.mockito.Mockito.*;
+
+public class PatchActionKillTest {
+
+ private PatchCellCART mockCell;
+ private PatchCellTissue mockTarget;
+ private PatchProcessInflammation mockInflammation;
+ private PatchActionKill action;
+ private Schedule schedule;
+ private PatchSimulation sim;
+
+ @BeforeEach
+ public void setUp() {
+ mockCell = mock(PatchCellCART.class);
+ mockTarget = mock(PatchCellTissue.class);
+ mockInflammation = mock(PatchProcessInflammation.class);
+ MersenneTwisterFast random = new MersenneTwisterFast();
+ Series series = mock(Series.class);
+ Parameters parameters = mock(Parameters.class);
+ schedule = mock(Schedule.class);
+ sim = mock(PatchSimulation.class);
+
+ when(mockCell.getProcess(any())).thenReturn(mockInflammation);
+ when(mockInflammation.getInternal("granzyme")).thenReturn(1.0);
+
+ action = new PatchActionKill(mockCell, mockTarget, random, series, parameters);
+ }
+
+ @Test
+ public void schedule_updatesSchedule() {
+ action.schedule(schedule);
+ verify(schedule)
+ .scheduleOnce(anyDouble(), eq(PatchEnums.Ordering.ACTIONS.ordinal()), eq(action));
+ }
+
+ @Test
+ public void step_CARCellStopped_doesNotChangeCell() {
+ when(mockCell.isStopped()).thenReturn(true);
+ action.step(sim);
+ verify(mockTarget, never()).setState(any());
+ }
+
+ @Test
+ public void step_targetCellStopped_doesNotChangeCell() {
+ when(mockTarget.isStopped()).thenReturn(true);
+ action.step(sim);
+ verify(mockTarget, never()).setState(any());
+ verify(mockCell).setBindingFlag(AntigenFlag.BOUND_CELL_RECEPTOR);
+ }
+
+ @Test
+ public void step_killTargetCell_killsCellAndUsesGranzyme() {
+ when(mockCell.isStopped()).thenReturn(false);
+ when(mockTarget.isStopped()).thenReturn(false);
+ PatchModuleApoptosis mockProcess = mock(PatchModuleApoptosis.class);
+ when(mockTarget.getModule()).thenReturn(mockProcess);
+ action.step(sim);
+ verify(mockTarget).setState(State.APOPTOTIC);
+ verify(mockInflammation).setInternal("granzyme", 0.0);
+ }
+}
diff --git a/test/arcade/patch/agent/action/PatchActionResetTest.java b/test/arcade/patch/agent/action/PatchActionResetTest.java
new file mode 100644
index 000000000..c927866e3
--- /dev/null
+++ b/test/arcade/patch/agent/action/PatchActionResetTest.java
@@ -0,0 +1,112 @@
+package arcade.patch.agent.action;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import sim.engine.Schedule;
+import sim.engine.SimState;
+import ec.util.MersenneTwisterFast;
+import arcade.core.sim.Series;
+import arcade.core.util.MiniBox;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.agent.cell.PatchCellCARTCD4;
+import arcade.patch.agent.cell.PatchCellContainer;
+import arcade.patch.env.location.PatchLocation;
+import arcade.patch.util.PatchEnums;
+import arcade.patch.util.PatchEnums.AntigenFlag;
+import arcade.patch.util.PatchEnums.State;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+import static arcade.core.ARCADETestUtilities.randomDoubleBetween;
+import static arcade.core.ARCADETestUtilities.randomIntBetween;
+
+public class PatchActionResetTest {
+
+ private PatchCellCART mockCell;
+ private PatchActionReset actionReset;
+
+ @BeforeEach
+ public void setUp() {
+ MersenneTwisterFast mockRandom = mock(MersenneTwisterFast.class);
+ Series mockSeries = mock(Series.class);
+ Parameters mockParameters = spy(new Parameters(new MiniBox(), null, null));
+ PatchLocation mockLocation = mock(PatchLocation.class);
+
+ doReturn(0.0).when(mockParameters).getDouble(any(String.class));
+ doReturn(0).when(mockParameters).getInt(any(String.class));
+ when(mockParameters.getInt("BOUND_TIME")).thenReturn(10);
+ when(mockParameters.getInt("BOUND_RANGE")).thenReturn(5);
+ when(mockRandom.nextInt()).thenReturn(1);
+
+ int id = 1;
+ int parentId = 1;
+ int pop = 4;
+ int age = randomIntBetween(1, 100800);
+ int divisions = 10;
+ double volume = randomDoubleBetween(100, 200);
+ double height = randomDoubleBetween(4, 10);
+ double criticalVolume = randomDoubleBetween(100, 200);
+ double criticalHeight = randomDoubleBetween(4, 10);
+ State state = State.UNDEFINED;
+ ;
+
+ PatchCellContainer container =
+ new PatchCellContainer(
+ id,
+ parentId,
+ pop,
+ age,
+ divisions,
+ state,
+ volume,
+ height,
+ criticalVolume,
+ criticalHeight);
+
+ mockCell = spy(new PatchCellCARTCD4(container, mockLocation, mockParameters));
+
+ actionReset = new PatchActionReset(mockCell, mockRandom, mockSeries, mockParameters);
+ }
+
+ @Test
+ public void schedule_updatesSimSchedule() {
+ Schedule mockSchedule = mock(Schedule.class);
+ actionReset.schedule(mockSchedule);
+ verify(mockSchedule)
+ .scheduleOnce(
+ anyDouble(), eq(PatchEnums.Ordering.ACTIONS.ordinal()), eq(actionReset));
+ }
+
+ @Test
+ public void step_cytotoxicState_unbindsAndUncommitsCell() {
+ when(mockCell.isStopped()).thenReturn(false);
+ mockCell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN);
+ when(mockCell.getState()).thenReturn(State.CYTOTOXIC);
+
+ actionReset.step(mock(SimState.class));
+
+ verify(mockCell).setState(State.UNDEFINED);
+ assertEquals(AntigenFlag.UNBOUND, mockCell.getBindingFlag());
+ }
+
+ @Test
+ public void step_stimulatoryState_unbindsAndUncommitsCell() {
+ when(mockCell.isStopped()).thenReturn(false);
+ mockCell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN);
+ when(mockCell.getState()).thenReturn(State.STIMULATORY);
+
+ actionReset.step(mock(SimState.class));
+
+ verify(mockCell).setState(State.UNDEFINED);
+ assertEquals(AntigenFlag.UNBOUND, mockCell.getBindingFlag());
+ }
+
+ @Test
+ public void step_stoppedCell_doesNotChangeCell() {
+ when(mockCell.isStopped()).thenReturn(true);
+ mockCell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN);
+ actionReset.step(mock(SimState.class));
+ verify(mockCell, never()).setState(any(State.class));
+ assertEquals(AntigenFlag.BOUND_ANTIGEN, mockCell.getBindingFlag());
+ }
+}
diff --git a/test/arcade/patch/agent/action/PatchActionTreatTest.java b/test/arcade/patch/agent/action/PatchActionTreatTest.java
new file mode 100644
index 000000000..2f23f9615
--- /dev/null
+++ b/test/arcade/patch/agent/action/PatchActionTreatTest.java
@@ -0,0 +1,211 @@
+package arcade.patch.agent.action;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import sim.engine.Schedule;
+import sim.util.Bag;
+import ec.util.MersenneTwisterFast;
+import arcade.core.env.location.LocationContainer;
+import arcade.core.util.MiniBox;
+import arcade.patch.agent.cell.PatchCell;
+import arcade.patch.agent.cell.PatchCellContainer;
+import arcade.patch.agent.cell.PatchCellFactory;
+import arcade.patch.env.component.PatchComponentSitesSource;
+import arcade.patch.env.grid.PatchGrid;
+import arcade.patch.env.location.*;
+import arcade.patch.sim.PatchSeries;
+import arcade.patch.sim.PatchSimulation;
+import arcade.patch.util.PatchEnums;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+public class PatchActionTreatTest {
+
+ private PatchActionTreat action;
+ private PatchSimulation sim;
+ private PatchGrid gridMock;
+ private PatchCell cellMock;
+ MiniBox parameters;
+ PatchSeries series;
+
+ @BeforeEach
+ public void setUp() throws NoSuchFieldException, IllegalAccessException {
+ parameters = mock(MiniBox.class);
+ when(parameters.getInt("TIME_DELAY")).thenReturn(10);
+ when(parameters.getInt("DOSE")).thenReturn(10);
+ when(parameters.getDouble("RATIO")).thenReturn(0.5);
+ when(parameters.getDouble("MAX_DAMAGE_SEED")).thenReturn(0.1);
+ when(parameters.getDouble("MIN_RADIUS_SEED")).thenReturn(0.01);
+ when(parameters.getDouble("T_CELL_VOL_AVG")).thenReturn(175.0);
+ series = mock(PatchSeries.class);
+ MiniBox patchMock = mock(MiniBox.class);
+ series.patch = patchMock;
+ gridMock = mock(PatchGrid.class);
+ Schedule mockSchedule = mock(Schedule.class);
+ when(patchMock.get("GEOMETRY")).thenReturn("HEX");
+ sim = mock(PatchSimulation.class);
+ when(sim.getSeries()).thenReturn(series);
+ when(sim.getGrid()).thenReturn(gridMock);
+ when(sim.getSchedule()).thenReturn(mockSchedule);
+ PatchComponentSitesSource sources = mock(PatchComponentSitesSource.class);
+ double[][][] damage = new double[20][20][20];
+ boolean[][][] sitesLat = new boolean[20][20][20];
+ damage[0][0][0] = 0.05;
+ sitesLat[0][0][0] = true;
+ when(sources.getDamage()).thenReturn(damage);
+ when(sources.getSources()).thenReturn(sitesLat);
+ when(sim.getComponent("SITES")).thenReturn(sources);
+ ArrayList locations = new ArrayList<>();
+ PatchLocationContainer container = mock(PatchLocationContainer.class);
+ PatchLocation loc = mock(PatchLocation.class);
+ CoordinateXYZ coord = mock(CoordinateXYZ.class);
+ Field x = CoordinateXYZ.class.getDeclaredField("x");
+ x.setAccessible(true);
+ x.set(coord, 0);
+ Field y = CoordinateXYZ.class.getDeclaredField("y");
+ y.setAccessible(true);
+ y.set(coord, 0);
+ Field z = Coordinate.class.getDeclaredField("z");
+ z.setAccessible(true);
+ z.set(coord, 0);
+ CoordinateUVWZ c = mock(CoordinateUVWZ.class);
+ z.set(c, 0);
+ when(loc.getSubcoordinate()).thenReturn(coord);
+ when(loc.getCoordinate()).thenReturn(c);
+ when(container.convert(any(), any())).thenReturn(loc);
+ locations.add(container);
+ when(sim.getLocations()).thenReturn(locations);
+ sim.random = mock(MersenneTwisterFast.class);
+
+ PatchCellFactory factoryMock = mock(PatchCellFactory.class);
+ PatchCellContainer patchCellContainerMock = mock(PatchCellContainer.class);
+ when(factoryMock.createCellForPopulation(any(Integer.class), any(Integer.class)))
+ .thenReturn(patchCellContainerMock);
+ cellMock = mock(PatchCell.class);
+ when(patchCellContainerMock.convert(any(), any(), any())).thenReturn(cellMock);
+
+ Field factory = PatchSimulation.class.getDeclaredField("cellFactory");
+ factory.setAccessible(true);
+ factory.set(sim, factoryMock);
+ }
+
+ final Bag createPatchCellsWithVolumeAndCriticalHeight(int n, double volume, double critHeight) {
+ Bag bag = new Bag();
+ for (int i = 0; i < n; i++) {
+ PatchCell cell = mock(PatchCell.class);
+ when(cell.getVolume()).thenReturn(volume);
+ when(cell.getHeight()).thenReturn(critHeight);
+ bag.add(cell);
+ }
+ return bag;
+ }
+
+ @Test
+ public void schedule_callsScheduleOnAction()
+ throws NoSuchFieldException, IllegalAccessException {
+ action = new PatchActionTreat(series, parameters);
+
+ ArrayList populations = new ArrayList<>();
+ MiniBox populationMock = mock(MiniBox.class);
+ when(populationMock.getInt("CODE")).thenReturn(4);
+ when(populationMock.get("CLASS")).thenReturn("cart_cd4");
+ populations.add(populationMock);
+ Field pops = PatchActionTreat.class.getDeclaredField("populations");
+ pops.setAccessible(true);
+ pops.set(action, populations);
+
+ Schedule schedule = mock(Schedule.class);
+ action.schedule(schedule);
+ verify(schedule)
+ .scheduleOnce(anyDouble(), eq(PatchEnums.Ordering.ACTIONS.ordinal()), eq(action));
+ }
+
+ @Test
+ public void step_addsObjectsToSim() throws NoSuchFieldException, IllegalAccessException {
+ action = new PatchActionTreat(series, parameters);
+
+ ArrayList populations = new ArrayList<>();
+ MiniBox populationMock = mock(MiniBox.class);
+ when(populationMock.getInt("CODE")).thenReturn(4);
+ when(populationMock.get("CLASS")).thenReturn("cart_cd4");
+ populations.add(populationMock);
+ Field pops = PatchActionTreat.class.getDeclaredField("populations");
+ pops.setAccessible(true);
+ pops.set(action, populations);
+
+ action.step(sim);
+ assertFalse(action.getSiteLocs().isEmpty());
+ verify(gridMock, times(action.getSiteLocs().size() - 1)).addObject(any(), any());
+ verify(cellMock, times(action.getSiteLocs().size() - 1)).schedule(any());
+ }
+
+ @Test
+ public void step_zeroDose_doesNotAddObjects()
+ throws NoSuchFieldException, IllegalAccessException {
+ when(parameters.getInt("DOSE")).thenReturn(0);
+ action = new PatchActionTreat(series, parameters);
+
+ ArrayList populations = new ArrayList<>();
+ MiniBox populationMock = mock(MiniBox.class);
+ when(populationMock.getInt("CODE")).thenReturn(4);
+ when(populationMock.get("CLASS")).thenReturn("cart_cd4");
+ populations.add(populationMock);
+ Field pops = PatchActionTreat.class.getDeclaredField("populations");
+ pops.setAccessible(true);
+ pops.set(action, populations);
+
+ action.step(sim);
+ verify(gridMock, times(0)).addObject(any(), any());
+ verify(cellMock, times(0)).schedule(any());
+ }
+
+ @Test
+ public void checkLocationSpace_withEmptySpaces_returnsAvailable()
+ throws NoSuchFieldException, IllegalAccessException {
+ action = new PatchActionTreat(series, parameters);
+ ArrayList populations = new ArrayList<>();
+ MiniBox populationMock = mock(MiniBox.class);
+ when(populationMock.getInt("CODE")).thenReturn(4);
+ when(populationMock.get("CLASS")).thenReturn("cart_cd4");
+ populations.add(populationMock);
+ Field pops = PatchActionTreat.class.getDeclaredField("populations");
+ pops.setAccessible(true);
+ pops.set(action, populations);
+
+ PatchLocation locationMock = mock(PatchLocation.class);
+ when(locationMock.getArea()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30);
+ when(locationMock.getVolume()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30 * 8.7);
+ Bag testBag = createPatchCellsWithVolumeAndCriticalHeight(2, 10, 12.5);
+ when(gridMock.getObjectsAtLocation(locationMock)).thenReturn(testBag);
+ boolean result = action.checkLocationSpace(locationMock, gridMock);
+ assertTrue(result);
+ }
+
+ @Test
+ public void checkLocation_maxConfluency_returnsUnavailable()
+ throws NoSuchFieldException, IllegalAccessException {
+ action = new PatchActionTreat(series, parameters);
+ Field density = PatchActionTreat.class.getDeclaredField("maxConfluency");
+ density.setAccessible(true);
+ density.set(action, 1);
+
+ ArrayList populations = new ArrayList<>();
+ MiniBox populationMock = mock(MiniBox.class);
+ when(populationMock.getInt("CODE")).thenReturn(4);
+ when(populationMock.get("CLASS")).thenReturn("cart_cd4");
+ populations.add(populationMock);
+ Field pops = PatchActionTreat.class.getDeclaredField("populations");
+ pops.setAccessible(true);
+ pops.set(action, populations);
+
+ PatchLocation locationMock = mock(PatchLocation.class);
+ when(locationMock.getArea()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30);
+ when(locationMock.getVolume()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30 * 8.7);
+ Bag testBag = createPatchCellsWithVolumeAndCriticalHeight(2, 10, 12.5);
+ when(gridMock.getObjectsAtLocation(locationMock)).thenReturn(testBag);
+ boolean result = action.checkLocationSpace(locationMock, gridMock);
+ assertFalse(result);
+ }
+}
diff --git a/test/arcade/patch/agent/cell/PatchCellCARTCD4Test.java b/test/arcade/patch/agent/cell/PatchCellCARTCD4Test.java
new file mode 100644
index 000000000..2f6716b2c
--- /dev/null
+++ b/test/arcade/patch/agent/cell/PatchCellCARTCD4Test.java
@@ -0,0 +1,425 @@
+package arcade.patch.agent.cell;
+
+import java.lang.reflect.Field;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import sim.engine.Schedule;
+import sim.engine.Steppable;
+import ec.util.MersenneTwisterFast;
+import arcade.core.sim.Simulation;
+import arcade.core.util.MiniBox;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.module.PatchModule;
+import arcade.patch.agent.process.PatchProcessInflammation;
+import arcade.patch.agent.process.PatchProcessMetabolism;
+import arcade.patch.agent.process.PatchProcessSignaling;
+import arcade.patch.env.location.PatchLocation;
+import arcade.patch.sim.PatchSimulation;
+import arcade.patch.util.PatchEnums.Domain;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+import static arcade.core.ARCADETestUtilities.randomDoubleBetween;
+import static arcade.core.ARCADETestUtilities.randomIntBetween;
+import static arcade.patch.util.PatchEnums.AntigenFlag;
+import static arcade.patch.util.PatchEnums.State;
+
+public class PatchCellCARTCD4Test {
+
+ private Parameters parameters;
+ private PatchLocation location;
+ private PatchCellContainer container;
+ private PatchCellCARTCD4 cell;
+
+ @BeforeEach
+ public void setUp() throws NoSuchFieldException, IllegalAccessException {
+ parameters = spy(new Parameters(new MiniBox(), null, null));
+ location = mock(PatchLocation.class);
+
+ int id = 1;
+ int parentId = 1;
+ int pop = 1;
+ int age = randomIntBetween(1, 120950);
+ int divisions = 10;
+ double volume = randomDoubleBetween(100, 200);
+ double height = randomDoubleBetween(4, 10);
+ double criticalVolume = randomDoubleBetween(100, 200);
+ double criticalHeight = randomDoubleBetween(4, 10);
+ State state = State.UNDEFINED;
+ ;
+
+ container =
+ new PatchCellContainer(
+ id,
+ parentId,
+ pop,
+ age,
+ divisions,
+ state,
+ volume,
+ height,
+ criticalVolume,
+ criticalHeight);
+ doReturn(0.0).when(parameters).getDouble(any(String.class));
+ doReturn(0).when(parameters).getInt(any(String.class));
+ when(parameters.getDouble("HETEROGENEITY")).thenReturn(0.0);
+ when(parameters.getDouble("ENERGY_THRESHOLD")).thenReturn(1.0);
+
+ when(parameters.getDouble("NECROTIC_FRACTION"))
+ .thenReturn(randomIntBetween(40, 100) / 100.0);
+ when(parameters.getDouble("EXHAU_FRAC")).thenReturn(randomIntBetween(40, 100) / 100.0);
+ when(parameters.getDouble("SENESCENT_FRACTION"))
+ .thenReturn(randomIntBetween(40, 100) / 100.0);
+ when(parameters.getDouble("ANERGIC_FRACTION"))
+ .thenReturn(randomIntBetween(40, 100) / 100.0);
+ when(parameters.getDouble("PROLIFERATIVE_FRACTION"))
+ .thenReturn(randomIntBetween(40, 100) / 100.0);
+ when(parameters.getInt("SELF_RECEPTORS")).thenReturn(randomIntBetween(100, 200));
+ when(parameters.getDouble("SEARCH_ABILITY")).thenReturn(1.0);
+ when(parameters.getDouble("CAR_AFFINITY")).thenReturn(10 * Math.pow(10, -7));
+ when(parameters.getDouble("CAR_ALPHA")).thenReturn(3.0);
+ when(parameters.getDouble("CAR_BETA")).thenReturn(0.01);
+ when(parameters.getDouble("SELF_RECEPTOR_AFFINITY")).thenReturn(7.8E-6);
+ when(parameters.getDouble("SELF_ALPHA")).thenReturn(3.0);
+ when(parameters.getDouble("SELF_BETA")).thenReturn(0.02);
+ when(parameters.getDouble("CONTACT_FRAC")).thenReturn(7.8E-6);
+ when(parameters.getInt("MAX_ANTIGEN_BINDING")).thenReturn(10);
+ when(parameters.getInt("CARS")).thenReturn(50000);
+
+ when(parameters.getInt("APOPTOSIS_AGE")).thenReturn(120960);
+ when(parameters.getInt("MAX_DENSITY")).thenReturn(54);
+
+ cell = spy(new PatchCellCARTCD4(container, location, parameters));
+ Field apoptosisAge = PatchCell.class.getDeclaredField("apoptosisAge");
+ apoptosisAge.setAccessible(true);
+ apoptosisAge.set(cell, 120958);
+
+ Field maxDensity = PatchCell.class.getDeclaredField("maxDensity");
+ maxDensity.setAccessible(true);
+ maxDensity.set(cell, 54);
+ }
+
+ @Test
+ public void step_increasesAge() {
+ PatchSimulation sim = mock(PatchSimulation.class);
+ cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class));
+ cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class));
+ cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class));
+ PatchModule module = mock(PatchModule.class);
+ MersenneTwisterFast random = mock(MersenneTwisterFast.class);
+ doAnswer(
+ invocationOnMock -> {
+ cell.state = invocationOnMock.getArgument(0);
+ cell.module = module;
+ return null;
+ })
+ .when(cell)
+ .setState(any(State.class));
+ doReturn(new PatchCellTissue(container, location, parameters))
+ .when(cell)
+ .bindTarget(
+ any(Simulation.class),
+ any(PatchLocation.class),
+ any(MersenneTwisterFast.class));
+ sim.random = random;
+ cell.setState(State.UNDEFINED);
+ int initialAge = cell.getAge();
+ cell.step(sim);
+ assertEquals(initialAge + 1, cell.getAge());
+ }
+
+ @Test
+ public void step_whenEnergyIsLow_setsStateToApoptotic() {
+ PatchSimulation sim = mock(PatchSimulation.class);
+ cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class));
+ cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class));
+ cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class));
+ PatchModule module = mock(PatchModule.class);
+ MersenneTwisterFast random = mock(MersenneTwisterFast.class);
+ doAnswer(
+ invocationOnMock -> {
+ cell.state = invocationOnMock.getArgument(0);
+ cell.module = module;
+ return null;
+ })
+ .when(cell)
+ .setState(any(State.class));
+ doReturn(new PatchCellTissue(container, location, parameters))
+ .when(cell)
+ .bindTarget(
+ any(Simulation.class),
+ any(PatchLocation.class),
+ any(MersenneTwisterFast.class));
+ sim.random = random;
+ cell.setState(State.UNDEFINED);
+
+ cell.setEnergy(-1 * randomIntBetween(2, 5));
+ cell.step(sim);
+
+ assertEquals(State.APOPTOTIC, cell.getState());
+ assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag());
+ assertFalse(cell.getActivationStatus());
+ }
+
+ @Test
+ public void step_whenEnergyIsNegativeAndMoreThanThreshold_setsStateToStarved() {
+ PatchSimulation sim = mock(PatchSimulation.class);
+ cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class));
+ cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class));
+ cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class));
+ PatchModule module = mock(PatchModule.class);
+ MersenneTwisterFast random = mock(MersenneTwisterFast.class);
+ doAnswer(
+ invocationOnMock -> {
+ cell.state = invocationOnMock.getArgument(0);
+ cell.module = module;
+ return null;
+ })
+ .when(cell)
+ .setState(any(State.class));
+ doReturn(new PatchCellTissue(container, location, parameters))
+ .when(cell)
+ .bindTarget(
+ any(Simulation.class),
+ any(PatchLocation.class),
+ any(MersenneTwisterFast.class));
+ sim.random = random;
+ cell.setState(State.UNDEFINED);
+
+ cell.setEnergy(-0.5);
+ cell.step(sim);
+
+ assertEquals(State.STARVED, cell.getState());
+ assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag());
+ }
+
+ @Test
+ public void step_whenEnergyIsNegativeAndLessThanThreshold_setsStateToApoptotic() {
+ PatchSimulation sim = mock(PatchSimulation.class);
+ cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class));
+ cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class));
+ cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class));
+ PatchModule module = mock(PatchModule.class);
+ MersenneTwisterFast random = mock(MersenneTwisterFast.class);
+ doAnswer(
+ invocationOnMock -> {
+ cell.state = invocationOnMock.getArgument(0);
+ cell.module = module;
+ return null;
+ })
+ .when(cell)
+ .setState(any(State.class));
+ doReturn(new PatchCellTissue(container, location, parameters))
+ .when(cell)
+ .bindTarget(
+ any(Simulation.class),
+ any(PatchLocation.class),
+ any(MersenneTwisterFast.class));
+ sim.random = random;
+ cell.setState(State.UNDEFINED);
+
+ cell.setEnergy(-1.5);
+ cell.step(sim);
+
+ assertEquals(State.APOPTOTIC, cell.getState());
+ }
+
+ @Test
+ public void step_whenDivisionsAreZero_setsStateToSenescent() {
+ PatchSimulation sim = mock(PatchSimulation.class);
+ cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class));
+ cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class));
+ cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class));
+ PatchModule module = mock(PatchModule.class);
+ MersenneTwisterFast random = mock(MersenneTwisterFast.class);
+ doAnswer(
+ invocationOnMock -> {
+ cell.state = invocationOnMock.getArgument(0);
+ cell.divisions = 0;
+ cell.module = module;
+ return null;
+ })
+ .when(cell)
+ .setState(any(State.class));
+ doReturn(new PatchCellTissue(container, location, parameters))
+ .when(cell)
+ .bindTarget(
+ any(Simulation.class),
+ any(PatchLocation.class),
+ any(MersenneTwisterFast.class));
+ sim.random = random;
+ cell.setState(State.UNDEFINED);
+
+ cell.step(sim);
+
+ assertTrue(cell.getState() == State.APOPTOTIC || cell.getState() == State.SENESCENT);
+ assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag());
+ assertFalse(cell.getActivationStatus());
+ }
+
+ @Test
+ public void step_whenBoundToBothAntigenAndSelf_setsStateToAnergic() {
+ PatchSimulation sim = mock(PatchSimulation.class);
+ cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class));
+ cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class));
+ cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class));
+ PatchModule module = mock(PatchModule.class);
+ MersenneTwisterFast random = mock(MersenneTwisterFast.class);
+ doAnswer(
+ invocationOnMock -> {
+ cell.state = invocationOnMock.getArgument(0);
+ cell.module = module;
+ return null;
+ })
+ .when(cell)
+ .setState(any(State.class));
+ doReturn(new PatchCellTissue(container, location, parameters))
+ .when(cell)
+ .bindTarget(
+ any(Simulation.class),
+ any(PatchLocation.class),
+ any(MersenneTwisterFast.class));
+ sim.random = random;
+ cell.setState(State.UNDEFINED);
+
+ cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN_CELL_RECEPTOR);
+ cell.step(sim);
+
+ assertTrue(cell.getState() == State.APOPTOTIC || cell.getState() == State.ANERGIC);
+ assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag());
+ assertFalse(cell.getActivationStatus());
+ }
+
+ @Test
+ public void step_whenBoundToAntigen_setsStateToStimulatory() {
+ PatchSimulation sim = mock(PatchSimulation.class);
+ cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class));
+ cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class));
+ cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class));
+ PatchModule module = mock(PatchModule.class);
+ MersenneTwisterFast random = mock(MersenneTwisterFast.class);
+ doAnswer(
+ invocationOnMock -> {
+ cell.state = invocationOnMock.getArgument(0);
+ cell.boundCARAntigensCount = 0;
+ cell.module = module;
+ return null;
+ })
+ .when(cell)
+ .setState(any(State.class));
+ doReturn(new PatchCellTissue(container, location, parameters))
+ .when(cell)
+ .bindTarget(
+ any(Simulation.class),
+ any(PatchLocation.class),
+ any(MersenneTwisterFast.class));
+ sim.random = random;
+ cell.setState(State.UNDEFINED);
+ cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN);
+
+ Schedule schedule = mock(Schedule.class);
+ doReturn(true).when(schedule).scheduleOnce(any(Steppable.class));
+ doReturn(schedule).when(sim).getSchedule();
+
+ cell.step(sim);
+
+ assertEquals(State.STIMULATORY, cell.getState());
+ assertTrue(cell.getActivationStatus());
+ }
+
+ @Test
+ public void step_whenActivated_setsStateToProliferative() {
+ PatchSimulation sim = mock(PatchSimulation.class);
+ cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class));
+ cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class));
+ cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class));
+ PatchModule module = mock(PatchModule.class);
+ MersenneTwisterFast random = mock(MersenneTwisterFast.class);
+ doAnswer(
+ invocationOnMock -> {
+ cell.state = invocationOnMock.getArgument(0);
+ cell.activated = true;
+ cell.module = module;
+ return null;
+ })
+ .when(cell)
+ .setState(any(State.class));
+ doReturn(new PatchCellTissue(container, location, parameters))
+ .when(cell)
+ .bindTarget(
+ any(Simulation.class),
+ any(PatchLocation.class),
+ any(MersenneTwisterFast.class));
+ sim.random = random;
+ cell.setState(State.UNDEFINED);
+
+ cell.step(sim);
+
+ assertEquals(State.PROLIFERATIVE, cell.getState());
+ }
+
+ @Test
+ public void step_whenNotActivated_setsStateToMigratory() {
+ PatchSimulation sim = mock(PatchSimulation.class);
+ cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class));
+ cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class));
+ cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class));
+ PatchModule module = mock(PatchModule.class);
+ MersenneTwisterFast random = mock(MersenneTwisterFast.class);
+ doAnswer(
+ invocationOnMock -> {
+ cell.state = invocationOnMock.getArgument(0);
+ cell.activated = false;
+ cell.module = module;
+ return null;
+ })
+ .when(cell)
+ .setState(any(State.class));
+ doReturn(new PatchCellTissue(container, location, parameters))
+ .when(cell)
+ .bindTarget(
+ any(Simulation.class),
+ any(PatchLocation.class),
+ any(MersenneTwisterFast.class));
+ sim.random = random;
+ cell.setState(State.UNDEFINED);
+
+ cell.step(sim);
+
+ assertTrue(cell.getState() == State.MIGRATORY || cell.getState() == State.PROLIFERATIVE);
+ }
+
+ @Test
+ public void step_whenOverstimulated_setsStateToExhausted() {
+ PatchSimulation sim = mock(PatchSimulation.class);
+ cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class));
+ cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class));
+ cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class));
+ PatchModule module = mock(PatchModule.class);
+ MersenneTwisterFast random = mock(MersenneTwisterFast.class);
+ doAnswer(
+ invocationOnMock -> {
+ cell.state = invocationOnMock.getArgument(0);
+ cell.boundCARAntigensCount = cell.maxAntigenBinding + 1;
+ cell.module = module;
+ return null;
+ })
+ .when(cell)
+ .setState(any(State.class));
+ doReturn(new PatchCellTissue(container, location, parameters))
+ .when(cell)
+ .bindTarget(
+ any(Simulation.class),
+ any(PatchLocation.class),
+ any(MersenneTwisterFast.class));
+ sim.random = random;
+ cell.setState(State.UNDEFINED);
+ cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN);
+
+ cell.step(sim);
+
+ assertTrue(cell.getState() == State.APOPTOTIC || cell.getState() == State.EXHAUSTED);
+ assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag());
+ assertFalse(cell.getActivationStatus());
+ }
+}
diff --git a/test/arcade/patch/agent/cell/PatchCellCARTCD8Test.java b/test/arcade/patch/agent/cell/PatchCellCARTCD8Test.java
new file mode 100644
index 000000000..a28ec9bb5
--- /dev/null
+++ b/test/arcade/patch/agent/cell/PatchCellCARTCD8Test.java
@@ -0,0 +1,255 @@
+package arcade.patch.agent.cell;
+
+import java.lang.reflect.Field;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import sim.engine.Schedule;
+import sim.engine.Steppable;
+import ec.util.MersenneTwisterFast;
+import arcade.core.sim.Simulation;
+import arcade.core.util.MiniBox;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.module.PatchModule;
+import arcade.patch.agent.process.PatchProcessInflammation;
+import arcade.patch.agent.process.PatchProcessMetabolism;
+import arcade.patch.agent.process.PatchProcessSignaling;
+import arcade.patch.env.location.PatchLocation;
+import arcade.patch.sim.PatchSimulation;
+import arcade.patch.util.PatchEnums.Domain;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+import static arcade.core.ARCADETestUtilities.randomDoubleBetween;
+import static arcade.core.ARCADETestUtilities.randomIntBetween;
+import static arcade.patch.util.PatchEnums.AntigenFlag;
+import static arcade.patch.util.PatchEnums.State;
+
+public class PatchCellCARTCD8Test {
+
+ private PatchCellCARTCD8 cell;
+
+ private Parameters parameters;
+
+ private PatchLocation location;
+
+ private PatchCellContainer container;
+
+ PatchSimulation sim;
+
+ @BeforeEach
+ public final void setUp() throws NoSuchFieldException, IllegalAccessException {
+ parameters = spy(new Parameters(new MiniBox(), null, null));
+ location = mock(PatchLocation.class);
+
+ int id = 1;
+ int parentId = 1;
+ int pop = 1;
+ int age = randomIntBetween(1, 120950);
+ int divisions = 0;
+ double volume = randomDoubleBetween(100, 200);
+ double height = randomDoubleBetween(4, 10);
+ double criticalVolume = randomDoubleBetween(100, 200);
+ double criticalHeight = randomDoubleBetween(4, 10);
+ State state = State.UNDEFINED;
+
+ container =
+ new PatchCellContainer(
+ id,
+ parentId,
+ pop,
+ age,
+ divisions,
+ state,
+ volume,
+ height,
+ criticalVolume,
+ criticalHeight);
+
+ doReturn(0.0).when(parameters).getDouble(any(String.class));
+ doReturn(0).when(parameters).getInt(any(String.class));
+ when(parameters.getDouble("HETEROGENEITY")).thenReturn(0.0);
+ when(parameters.getDouble("ENERGY_THRESHOLD")).thenReturn(1.0);
+ when(parameters.getDouble("NECROTIC_FRACTION")).thenReturn(0.5);
+ when(parameters.getDouble("EXHAUSTED_FRAC")).thenReturn(0.5);
+ when(parameters.getDouble("SENESCENT_FRACTION")).thenReturn(0.5);
+ when(parameters.getDouble("ANERGIC_FRACTION")).thenReturn(0.5);
+ when(parameters.getDouble("PROLIFERATIVE_FRACTION")).thenReturn(0.5);
+ when(parameters.getInt("SELF_RECEPTORS")).thenReturn(randomIntBetween(100, 200));
+ when(parameters.getDouble("SEARCH_ABILITY")).thenReturn(1.0);
+ when(parameters.getDouble("CAR_AFFINITY")).thenReturn(10 * Math.pow(10, -7));
+ when(parameters.getDouble("CAR_ALPHA")).thenReturn(3.0);
+ when(parameters.getDouble("CAR_BETA")).thenReturn(0.01);
+ when(parameters.getDouble("SELF_RECEPTOR_AFFINITY")).thenReturn(7.8E-6);
+ when(parameters.getDouble("SELF_ALPHA")).thenReturn(3.0);
+ when(parameters.getDouble("SELF_BETA")).thenReturn(0.02);
+ when(parameters.getDouble("CONTACT_FRAC")).thenReturn(7.8E-6);
+ when(parameters.getInt("MAX_ANTIGEN_BINDING")).thenReturn(10);
+ when(parameters.getInt("CARS")).thenReturn(50000);
+ when(parameters.getDouble("APOPTOSIS_AGE")).thenReturn(120960.0);
+ when(parameters.getInt("MAX_DENSITY")).thenReturn(54);
+ when(parameters.getInt("DIVISION_POTENTIAL")).thenReturn(10);
+
+ cell = spy(new PatchCellCARTCD8(container, location, parameters));
+
+ sim = mock(PatchSimulation.class);
+ cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class));
+ cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class));
+ cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class));
+ PatchModule module = mock(PatchModule.class);
+ MersenneTwisterFast random = mock(MersenneTwisterFast.class);
+ doAnswer(
+ invocationOnMock -> {
+ cell.state = invocationOnMock.getArgument(0);
+ cell.module = module;
+ return null;
+ })
+ .when(cell)
+ .setState(any(State.class));
+ doReturn(new PatchCellTissue(container, location, parameters))
+ .when(cell)
+ .bindTarget(
+ any(Simulation.class),
+ any(PatchLocation.class),
+ any(MersenneTwisterFast.class));
+ when(random.nextDouble()).thenReturn(0.49);
+ sim.random = random;
+ cell.setState(State.UNDEFINED);
+ }
+
+ @Test
+ public void step_called_increasesAge() {
+ int initialAge = cell.getAge();
+
+ cell.step(sim);
+
+ assertEquals(initialAge + 1, cell.getAge());
+ }
+
+ @Test
+ public void step_whenEnergyIsLow_setsStateToApoptotic() {
+ cell.setEnergy(-1 * randomIntBetween(2, 5));
+
+ cell.step(sim);
+
+ assertEquals(State.APOPTOTIC, cell.getState());
+ assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag());
+ assertFalse(cell.getActivationStatus());
+ }
+
+ @Test
+ public void step_whenEnergyIsNegativeAndMoreThanThreshold_setsStateToStarved() {
+ cell.setEnergy(-0.5);
+
+ cell.step(sim);
+
+ assertEquals(State.STARVED, cell.getState());
+ assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag());
+ }
+
+ @Test
+ public void step_whenEnergyIsNegativeAndMoreThanThreshold_setsStateToApoptotic() {
+ cell.setEnergy(-1.5);
+
+ cell.step(sim);
+
+ assertEquals(State.APOPTOTIC, cell.getState());
+ }
+
+ @Test
+ public void step_whenDivisionPotentialMet_setsStateToApoptotic()
+ throws NoSuchFieldException, IllegalAccessException {
+ Field div = PatchCell.class.getDeclaredField("divisions");
+ div.setAccessible(true);
+ div.set(cell, cell.divisionPotential);
+ when(sim.random.nextDouble()).thenReturn(0.51);
+
+ cell.step(sim);
+
+ assertTrue(cell.getState() == State.APOPTOTIC);
+ assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag());
+ assertFalse(cell.getActivationStatus());
+ }
+
+ @Test
+ public void step_whenDivisionPotentialMet_setsStateToSenescent()
+ throws NoSuchFieldException, IllegalAccessException {
+ Field div = PatchCell.class.getDeclaredField("divisions");
+ div.setAccessible(true);
+ div.set(cell, cell.divisionPotential);
+ when(sim.random.nextDouble()).thenReturn(0.49);
+
+ cell.step(sim);
+
+ assertTrue(cell.getState() == State.SENESCENT);
+ assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag());
+ assertFalse(cell.getActivationStatus());
+ }
+
+ @Test
+ public void step_whenBoundToBothAntigenAndSelf_setsStateToAnergic() {
+ cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN_CELL_RECEPTOR);
+
+ cell.step(sim);
+
+ assertTrue(cell.getState() == State.ANERGIC);
+ assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag());
+ assertFalse(cell.getActivationStatus());
+ }
+
+ @Test
+ public void step_whenBoundToAntigen_setsStateToCytotoxic()
+ throws NoSuchFieldException, IllegalAccessException {
+ Field boundAntigens = PatchCellCART.class.getDeclaredField("boundCARAntigensCount");
+ boundAntigens.setAccessible(true);
+ boundAntigens.set(cell, 0);
+ cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN);
+ Schedule schedule = mock(Schedule.class);
+ doReturn(true).when(schedule).scheduleOnce(any(Steppable.class));
+ doReturn(schedule).when(sim).getSchedule();
+
+ cell.step(sim);
+
+ verify(cell, times(1)).setState(State.CYTOTOXIC);
+ }
+
+ @Test
+ public void step_whenActivated_setsStateToProliferative()
+ throws NoSuchFieldException, IllegalAccessException {
+ Field active = PatchCellCART.class.getDeclaredField("activated");
+ active.setAccessible(true);
+ active.set(cell, true);
+
+ cell.step(sim);
+
+ assertEquals(State.PROLIFERATIVE, cell.getState());
+ }
+
+ @Test
+ public void step_whenNotActivated_setsStateToMigratory()
+ throws NoSuchFieldException, IllegalAccessException {
+ when(sim.random.nextDouble()).thenReturn(0.51);
+ Field active = PatchCellCART.class.getDeclaredField("activated");
+ active.setAccessible(true);
+ active.set(cell, false);
+
+ cell.step(sim);
+
+ assertTrue(cell.getState() == State.MIGRATORY);
+ }
+
+ @Test
+ public void step_whenOverstimulated_setsStateToExhausted()
+ throws NoSuchFieldException, IllegalAccessException {
+ when(sim.random.nextDouble()).thenReturn(0.49);
+ Field boundAntigens = PatchCellCART.class.getDeclaredField("boundCARAntigensCount");
+ boundAntigens.setAccessible(true);
+ boundAntigens.set(cell, cell.maxAntigenBinding + 1);
+ cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN);
+
+ cell.step(sim);
+
+ assertTrue(cell.getState() == State.EXHAUSTED);
+ assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag());
+ assertFalse(cell.getActivationStatus());
+ }
+}
diff --git a/test/arcade/patch/agent/cell/PatchCellCARTTest.java b/test/arcade/patch/agent/cell/PatchCellCARTTest.java
index 09afbb5df..241e65ab9 100644
--- a/test/arcade/patch/agent/cell/PatchCellCARTTest.java
+++ b/test/arcade/patch/agent/cell/PatchCellCARTTest.java
@@ -199,4 +199,31 @@ public void getActivationStatus_called_returnsStatus()
activation.set(patchCellCART, true);
assertTrue(patchCellCART.getActivationStatus());
}
+
+ @Test
+ public void getBoundTarget_called_returnsTarget()
+ throws NoSuchFieldException, IllegalAccessException {
+ Field target = PatchCellCART.class.getDeclaredField("boundTarget");
+ target.setAccessible(true);
+ target.set(patchCellCART, tissueCell);
+
+ PatchCell targetCell = patchCellCART.getBoundTarget();
+
+ assertEquals(targetCell, tissueCell);
+ }
+
+ @Test
+ public void unbind_called_setsStateAndTarget()
+ throws NoSuchFieldException, IllegalAccessException {
+ patchCellCART.setBindingFlag(PatchEnums.AntigenFlag.BOUND_ANTIGEN);
+ Field target = PatchCellCART.class.getDeclaredField("boundTarget");
+ target.setAccessible(true);
+ target.set(patchCellCART, tissueCell);
+
+ patchCellCART.unbind();
+
+ PatchCell targetCell = patchCellCART.getBoundTarget();
+ assertNull(targetCell);
+ assertEquals(PatchEnums.AntigenFlag.UNBOUND, patchCellCART.getBindingFlag());
+ }
}
diff --git a/test/arcade/patch/agent/module/PatchModuleCytotoxicityTest.java b/test/arcade/patch/agent/module/PatchModuleCytotoxicityTest.java
new file mode 100644
index 000000000..1c18e0a04
--- /dev/null
+++ b/test/arcade/patch/agent/module/PatchModuleCytotoxicityTest.java
@@ -0,0 +1,75 @@
+package arcade.patch.agent.module;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import ec.util.MersenneTwisterFast;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.agent.cell.PatchCellTissue;
+import arcade.patch.agent.process.PatchProcessInflammation;
+import arcade.patch.sim.PatchSimulation;
+import arcade.patch.util.PatchEnums.State;
+import static org.mockito.Mockito.*;
+
+public class PatchModuleCytotoxicityTest {
+
+ private PatchCellCART mockCell;
+
+ private PatchCellTissue mockTarget;
+
+ private PatchProcessInflammation mockInflammation;
+
+ private PatchModuleCytotoxicity action;
+
+ private PatchSimulation sim;
+
+ private MersenneTwisterFast randomMock;
+
+ @BeforeEach
+ public final void setUp() {
+ mockCell = mock(PatchCellCART.class);
+ mockTarget = mock(PatchCellTissue.class);
+ mockInflammation = mock(PatchProcessInflammation.class);
+ sim = mock(PatchSimulation.class);
+ randomMock = mock(MersenneTwisterFast.class);
+ Parameters parameters = mock(Parameters.class);
+ doReturn(0.0).when(parameters).getDouble(any(String.class));
+ doReturn(0).when(parameters).getInt(any(String.class));
+
+ when(mockCell.getParameters()).thenReturn(parameters);
+ when(mockCell.getProcess(any())).thenReturn(mockInflammation);
+ when(mockInflammation.getInternal("granzyme")).thenReturn(1.0);
+ when(mockCell.getBoundTarget()).thenReturn(mockTarget);
+
+ action = new PatchModuleCytotoxicity(mockCell);
+ }
+
+ @Test
+ public void step_CARCellStopped_doesNotChangeCell() {
+ when(mockCell.isStopped()).thenReturn(true);
+ action.step(randomMock, sim);
+
+ verify(mockTarget, never()).setState(any());
+ }
+
+ @Test
+ public void step_targetCellStopped_doesNotChangeCell() {
+ when(mockTarget.isStopped()).thenReturn(true);
+ action.step(randomMock, sim);
+
+ verify(mockTarget, never()).setState(any());
+ verify(mockCell).unbind();
+ }
+
+ @Test
+ public void step_killTargetCell_killsCellAndUsesGranzyme() {
+ when(mockCell.isStopped()).thenReturn(false);
+ when(mockTarget.isStopped()).thenReturn(false);
+ PatchModuleApoptosis mockProcess = mock(PatchModuleApoptosis.class);
+ when(mockTarget.getModule()).thenReturn(mockProcess);
+ action.step(randomMock, sim);
+
+ verify(mockTarget).setState(State.APOPTOTIC);
+ verify(mockInflammation).setInternal("granzyme", 0.0);
+ }
+}
diff --git a/test/arcade/patch/agent/process/PatchProcessInflammationCD4Test.java b/test/arcade/patch/agent/process/PatchProcessInflammationCD4Test.java
new file mode 100644
index 000000000..7e910de3b
--- /dev/null
+++ b/test/arcade/patch/agent/process/PatchProcessInflammationCD4Test.java
@@ -0,0 +1,279 @@
+package arcade.patch.agent.process;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import ec.util.MersenneTwisterFast;
+import arcade.core.sim.Simulation;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.env.lattice.PatchLattice;
+import arcade.patch.env.location.PatchLocation;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+import static arcade.core.ARCADETestUtilities.randomDoubleBetween;
+
+public class PatchProcessInflammationCD4Test {
+
+ private PatchProcessInflammationCD4 inflammation;
+ private PatchCellCART mockCell;
+ private PatchLattice mockLattice;
+ private Parameters mockParameters;
+ private Simulation mockSim;
+ private MersenneTwisterFast mockRandom;
+ private double cellVolume;
+ private PatchLocation mockLocation;
+
+ @BeforeEach
+ public void setUp() {
+ mockCell = Mockito.mock(PatchCellCART.class);
+ mockParameters = Mockito.mock(Parameters.class);
+ mockSim = Mockito.mock(Simulation.class);
+ mockRandom = Mockito.mock(MersenneTwisterFast.class);
+ mockLocation = mock(PatchLocation.class);
+ mockLattice = mock(PatchLattice.class);
+
+ Mockito.when(mockCell.getParameters()).thenReturn(mockParameters);
+ cellVolume = randomDoubleBetween(165, 180);
+ when(mockCell.getVolume()).thenReturn(cellVolume);
+ when(mockCell.getLocation()).thenReturn(mockLocation);
+ when(mockLocation.getVolume()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30 * 8.7);
+ when(mockParameters.getDouble(anyString())).thenReturn(1.0);
+ when(mockParameters.getInt(anyString())).thenReturn(1);
+
+ when(mockSim.getLattice(anyString())).thenReturn(mockLattice);
+ doNothing().when(mockLattice).setValue(any(PatchLocation.class), anyDouble());
+ }
+
+ @Test
+ public void constructor_setsParameters() throws NoSuchFieldException, IllegalAccessException {
+ inflammation = new PatchProcessInflammationCD4(mockCell);
+ assertNotNull(inflammation);
+
+ Field il2ProdRate = PatchProcessInflammationCD4.class.getDeclaredField("IL2ProdRate");
+ il2ProdRate.setAccessible(true);
+ assertEquals(0.0, il2ProdRate.get(inflammation));
+
+ Field il2ProdRateIL2 =
+ PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_IL2");
+ il2ProdRateIL2.setAccessible(true);
+ assertEquals(1.0, il2ProdRateIL2.get(inflammation));
+
+ Field il2ProdRateActive =
+ PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_ACTIVE");
+ il2ProdRateActive.setAccessible(true);
+ assertEquals(1.0, il2ProdRateActive.get(inflammation));
+
+ Field il2SynthesisDelay =
+ PatchProcessInflammationCD4.class.getDeclaredField("IL2_SYNTHESIS_DELAY");
+ il2SynthesisDelay.setAccessible(true);
+ assertEquals(1, il2SynthesisDelay.get(inflammation));
+ }
+
+ @Test
+ public void stepProcess_updatesEnvironment()
+ throws NoSuchFieldException, IllegalAccessException {
+ inflammation = new PatchProcessInflammationCD4(mockCell);
+
+ inflammation.active = true;
+ inflammation.activeTicker = 10;
+ inflammation.iL2Ticker = 10;
+ inflammation.boundArray = new double[180];
+ Arrays.fill(inflammation.boundArray, 10000);
+
+ Field il2ProdRateIL2 =
+ PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_IL2");
+ il2ProdRateIL2.setAccessible(true);
+ il2ProdRateIL2.set(inflammation, 0.05);
+
+ Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors");
+ receptors.setAccessible(true);
+ receptors.set(inflammation, 5000);
+
+ Field fraction = PatchProcessInflammation.class.getDeclaredField("fraction");
+ fraction.setAccessible(true);
+ fraction.set(inflammation, 1.0);
+
+ inflammation.stepProcess(mockRandom, mockSim);
+
+ // check that patch lattice set value is called
+ verify(mockLattice, times(1)).setValue(any(PatchLocation.class), anyDouble());
+
+ // check that IL2 produced is calculated correctly
+ Field il2Produced = PatchProcessInflammationCD4.class.getDeclaredField("IL2Produced");
+ il2Produced.setAccessible(true);
+ assertEquals(1.1, il2Produced.get(inflammation));
+
+ // check that extIL2 is calculated correctly
+ double expectedIL2 =
+ (1.1 + inflammation.amts[PatchProcessInflammationCD4.IL2_EXT])
+ * 1E12
+ / mockLocation.getVolume();
+ assertEquals(expectedIL2, inflammation.getIL2EnvTesting());
+ }
+
+ @Test
+ public void stepProcess_whenActive_returnsHigherRate()
+ throws NoSuchFieldException, IllegalAccessException {
+ inflammation = new PatchProcessInflammationCD4(mockCell);
+
+ inflammation.active = true;
+ inflammation.activeTicker = 10;
+ inflammation.iL2Ticker = 10;
+ inflammation.boundArray = new double[180];
+ Arrays.fill(inflammation.boundArray, 10000);
+
+ Field il2ProdRateIL2 =
+ PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_IL2");
+ il2ProdRateIL2.setAccessible(true);
+ il2ProdRateIL2.set(inflammation, 0.05);
+
+ Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors");
+ receptors.setAccessible(true);
+ receptors.set(inflammation, 5000);
+
+ Field fraction = PatchProcessInflammation.class.getDeclaredField("fraction");
+ fraction.setAccessible(true);
+ fraction.set(inflammation, 1.0);
+
+ Field activeIl2Rate =
+ PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_ACTIVE");
+ activeIl2Rate.setAccessible(true);
+ activeIl2Rate.set(inflammation, 2.5);
+
+ inflammation.stepProcess(mockRandom, mockSim);
+
+ Field il2ProdRate = PatchProcessInflammationCD4.class.getDeclaredField("IL2ProdRate");
+ il2ProdRate.setAccessible(true);
+ assertEquals(2.6, il2ProdRate.get(inflammation));
+ }
+
+ @Test
+ public void stepProcess_whenInactive_returnsDefaultRate()
+ throws NoSuchFieldException, IllegalAccessException {
+ inflammation = new PatchProcessInflammationCD4(mockCell);
+
+ inflammation.active = false;
+ inflammation.activeTicker = 10;
+ inflammation.iL2Ticker = 10;
+ inflammation.boundArray = new double[180];
+ Arrays.fill(inflammation.boundArray, 10000);
+
+ Field il2ProdRateIL2 =
+ PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_IL2");
+ il2ProdRateIL2.setAccessible(true);
+ il2ProdRateIL2.set(inflammation, 0.05);
+
+ Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors");
+ receptors.setAccessible(true);
+ receptors.set(inflammation, 5000);
+
+ Field fraction = PatchProcessInflammation.class.getDeclaredField("fraction");
+ fraction.setAccessible(true);
+ fraction.set(inflammation, 1.0);
+
+ inflammation.stepProcess(mockRandom, mockSim);
+
+ Field il2ProdRate = PatchProcessInflammationCD4.class.getDeclaredField("IL2ProdRate");
+ il2ProdRate.setAccessible(true);
+ assertEquals(0.1, il2ProdRate.get(inflammation));
+ }
+
+ @Test
+ public void stepProcess_activeTickerLessThanDelay_usesDefaultRate()
+ throws NoSuchFieldException, IllegalAccessException {
+ inflammation = new PatchProcessInflammationCD4(mockCell);
+
+ inflammation.active = true;
+ inflammation.activeTicker = 10;
+ inflammation.iL2Ticker = 10;
+ inflammation.boundArray = new double[180];
+ Arrays.fill(inflammation.boundArray, 10000);
+
+ Field il2ProdRateIL2 =
+ PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_IL2");
+ il2ProdRateIL2.setAccessible(true);
+ il2ProdRateIL2.set(inflammation, 0.05);
+
+ Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors");
+ receptors.setAccessible(true);
+ receptors.set(inflammation, 5000);
+
+ Field fraction = PatchProcessInflammation.class.getDeclaredField("fraction");
+ fraction.setAccessible(true);
+ fraction.set(inflammation, 1.0);
+
+ Field delay = PatchProcessInflammationCD4.class.getDeclaredField("IL2_SYNTHESIS_DELAY");
+ delay.setAccessible(true);
+ delay.set(inflammation, 15);
+
+ inflammation.stepProcess(mockRandom, mockSim);
+
+ Field il2ProdRate = PatchProcessInflammationCD4.class.getDeclaredField("IL2ProdRate");
+ il2ProdRate.setAccessible(true);
+ assertEquals(0.1, il2ProdRate.get(inflammation));
+ }
+
+ @Test
+ public void stepProcess_withZeroIL2ProdRate_returnsZero()
+ throws NoSuchFieldException, IllegalAccessException {
+ when(mockParameters.getDouble("inflammation/IL2_PROD_RATE_IL2")).thenReturn(0.0);
+ when(mockParameters.getDouble("inflammation/IL2_PROD_RATE_ACTIVE")).thenReturn(0.0);
+ inflammation = new PatchProcessInflammationCD4(mockCell);
+
+ inflammation.active = true;
+ inflammation.activeTicker = 10;
+ inflammation.iL2Ticker = 10;
+ inflammation.boundArray = new double[180];
+ Arrays.fill(inflammation.boundArray, 10000);
+
+ Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors");
+ receptors.setAccessible(true);
+ receptors.set(inflammation, 5000);
+
+ Field fraction = PatchProcessInflammation.class.getDeclaredField("fraction");
+ fraction.setAccessible(true);
+ fraction.set(inflammation, 1.0);
+
+ inflammation.stepProcess(mockRandom, mockSim);
+
+ Field il2ProdRate = PatchProcessInflammationCD4.class.getDeclaredField("IL2ProdRate");
+ il2ProdRate.setAccessible(true);
+ assertEquals(0.0, il2ProdRate.get(inflammation));
+
+ Field il2Produced = PatchProcessInflammationCD4.class.getDeclaredField("IL2Produced");
+ il2Produced.setAccessible(true);
+ assertEquals(0.0, il2Produced.get(inflammation));
+ }
+
+ @Test
+ public void update_evenSplit_splitsEvenly() {
+ inflammation = new PatchProcessInflammationCD4(mockCell);
+ PatchProcessInflammationCD4 parentProcess = new PatchProcessInflammationCD4(mockCell);
+ parentProcess.amts[PatchProcessInflammationCD4.IL2RBGA] = 100;
+ when(mockCell.getVolume()).thenReturn(cellVolume / 2);
+
+ inflammation.update(parentProcess);
+
+ assertEquals(50, inflammation.amts[PatchProcessInflammationCD4.IL2RBGA]);
+ assertEquals(50, parentProcess.amts[PatchProcessInflammationCD4.IL2RBGA]);
+ }
+
+ @Test
+ public void update_withZeroVolume_splitsUnevenly() {
+ inflammation = new PatchProcessInflammationCD4(mockCell);
+ PatchProcessInflammationCD4 parentProcess = new PatchProcessInflammationCD4(mockCell);
+ parentProcess.amts[PatchProcessInflammationCD4.IL2RBGA] = 100;
+ when(mockCell.getVolume()).thenReturn(0.0);
+
+ inflammation.update(parentProcess);
+
+ assertEquals(0.0, inflammation.amts[PatchProcessInflammationCD8.IL2RBGA]);
+ assertEquals(0.0, inflammation.amts[PatchProcessInflammationCD8.IL2_IL2RBG]);
+ assertEquals(0.0, inflammation.amts[PatchProcessInflammationCD8.IL2_IL2RBGA]);
+
+ assertEquals(100, parentProcess.amts[PatchProcessInflammationCD4.IL2RBGA]);
+ }
+}
diff --git a/test/arcade/patch/agent/process/PatchProcessInflammationCD8Test.java b/test/arcade/patch/agent/process/PatchProcessInflammationCD8Test.java
new file mode 100644
index 000000000..a6f7b4332
--- /dev/null
+++ b/test/arcade/patch/agent/process/PatchProcessInflammationCD8Test.java
@@ -0,0 +1,155 @@
+package arcade.patch.agent.process;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import ec.util.MersenneTwisterFast;
+import arcade.core.sim.Simulation;
+import arcade.core.util.Parameters;
+import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.env.lattice.PatchLattice;
+import arcade.patch.env.location.PatchLocation;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+import static arcade.core.ARCADETestUtilities.randomDoubleBetween;
+
+public class PatchProcessInflammationCD8Test {
+
+ private PatchProcessInflammationCD8 inflammation;
+
+ private PatchCellCART mockCell;
+
+ private Parameters mockParameters;
+
+ private Simulation mockSimulation;
+
+ private MersenneTwisterFast mockRandom;
+
+ private double cellVolume;
+
+ @BeforeEach
+ public final void setUp() {
+ mockCell = Mockito.mock(PatchCellCART.class);
+ mockParameters = Mockito.mock(Parameters.class);
+ mockSimulation = Mockito.mock(Simulation.class);
+ mockRandom = Mockito.mock(MersenneTwisterFast.class);
+ PatchLocation mockLocation = mock(PatchLocation.class);
+ PatchLattice mockLattice = mock(PatchLattice.class);
+
+ Mockito.when(mockCell.getParameters()).thenReturn(mockParameters);
+ cellVolume = randomDoubleBetween(165, 180);
+ when(mockCell.getVolume()).thenReturn(cellVolume);
+ when(mockCell.getLocation()).thenReturn(mockLocation);
+ when(mockLocation.getVolume()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30 * 8.7);
+ when(mockParameters.getDouble(anyString())).thenReturn(1.0);
+ when(mockParameters.getInt(anyString())).thenReturn(1);
+
+ when(mockSimulation.getLattice(anyString())).thenReturn(mockLattice);
+ doNothing().when(mockLattice).setValue(any(PatchLocation.class), anyDouble());
+ }
+
+ @Test
+ public void constructor_called_setsParameters()
+ throws NoSuchFieldException, IllegalAccessException {
+ inflammation = new PatchProcessInflammationCD8(mockCell);
+ assertNotNull(inflammation);
+
+ assertEquals(1, inflammation.amts[PatchProcessInflammationCD8.GRANZYME]);
+
+ Field prior = PatchProcessInflammationCD8.class.getDeclaredField("priorIL2granz");
+ prior.setAccessible(true);
+ assertEquals(0.0, prior.get(inflammation));
+
+ Field delay = PatchProcessInflammationCD8.class.getDeclaredField("granzSynthesisDelay");
+ delay.setAccessible(true);
+ assertEquals(1, delay.get(inflammation));
+ }
+
+ @Test
+ public void stepProcess_called_updatesEnvironment()
+ throws NoSuchFieldException, IllegalAccessException {
+ inflammation = new PatchProcessInflammationCD8(mockCell);
+ inflammation.active = true;
+ inflammation.activeTicker = 10;
+ inflammation.iL2Ticker = 10;
+ inflammation.boundArray = new double[180];
+ Arrays.fill(inflammation.boundArray, 10000);
+
+ Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors");
+ receptors.setAccessible(true);
+ receptors.set(inflammation, 5000);
+
+ inflammation.stepProcess(mockRandom, mockSimulation);
+
+ assertTrue(inflammation.amts[PatchProcessInflammationCD8.GRANZYME] > 1);
+ }
+
+ @Test
+ public void stepProcess_whenInactive_returnsDefaultRate()
+ throws NoSuchFieldException, IllegalAccessException {
+ inflammation = new PatchProcessInflammationCD8(mockCell);
+ inflammation.active = false;
+ inflammation.activeTicker = 10;
+ inflammation.iL2Ticker = 10;
+ inflammation.boundArray = new double[180];
+ Arrays.fill(inflammation.boundArray, 10000);
+
+ Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors");
+ receptors.setAccessible(true);
+ receptors.set(inflammation, 5000);
+
+ inflammation.stepProcess(mockRandom, mockSimulation);
+
+ assertEquals(1, inflammation.amts[PatchProcessInflammationCD8.GRANZYME]);
+ }
+
+ @Test
+ public void stepProcess_activeTickerLessThanDelay_usesDefaultRate()
+ throws NoSuchFieldException, IllegalAccessException {
+ Mockito.when(mockParameters.getInt("inflammation/GRANZ_SYNTHESIS_DELAY")).thenReturn(5);
+ inflammation = new PatchProcessInflammationCD8(mockCell);
+ inflammation.active = true;
+ inflammation.activeTicker = 3;
+ inflammation.iL2Ticker = 10;
+ inflammation.boundArray = new double[180];
+ Arrays.fill(inflammation.boundArray, 10000);
+
+ Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors");
+ receptors.setAccessible(true);
+ receptors.set(inflammation, 5000);
+
+ inflammation.stepProcess(mockRandom, mockSimulation);
+
+ assertEquals(1, inflammation.amts[PatchProcessInflammationCD8.GRANZYME]);
+ }
+
+ @Test
+ public void update_evenSplit_splitsEvenly() {
+ inflammation = new PatchProcessInflammationCD8(mockCell);
+ PatchProcessInflammationCD8 parentProcess = new PatchProcessInflammationCD8(mockCell);
+ parentProcess.amts[PatchProcessInflammationCD8.GRANZYME] = 100;
+ when(mockCell.getVolume()).thenReturn(cellVolume / 2);
+
+ inflammation.update(parentProcess);
+
+ assertEquals(50, inflammation.amts[PatchProcessInflammationCD8.GRANZYME]);
+ assertEquals(50, parentProcess.amts[PatchProcessInflammationCD8.GRANZYME]);
+ }
+
+ @Test
+ public void update_withZeroVolume_splitsUnevenly() {
+ inflammation = new PatchProcessInflammationCD8(mockCell);
+ PatchProcessInflammationCD8 parentProcess = new PatchProcessInflammationCD8(mockCell);
+ parentProcess.amts[PatchProcessInflammationCD8.GRANZYME] = 100;
+ when(mockCell.getVolume()).thenReturn(0.0);
+
+ inflammation.update(parentProcess);
+
+ assertEquals(0, inflammation.amts[PatchProcessInflammationCD8.GRANZYME]);
+ assertEquals(100, parentProcess.amts[PatchProcessInflammationCD8.GRANZYME]);
+ }
+}