diff --git a/src/arcade/patch/PatchARCADE.java b/src/arcade/patch/PatchARCADE.java
index 115823a6b..1759df94b 100644
--- a/src/arcade/patch/PatchARCADE.java
+++ b/src/arcade/patch/PatchARCADE.java
@@ -34,6 +34,7 @@ public OutputSaver getSaver(Series series) {
PatchOutputSaver saver = new PatchOutputSaver(series);
saver.saveGraph = settings.contains("SAVE_GRAPH");
saver.saveLattice = settings.contains("SAVE_LAYERS");
+ saver.saveEvents = settings.contains("SAVE_EVENTS");
return saver;
}
}
diff --git a/src/arcade/patch/agent/action/PatchActionConvert.java b/src/arcade/patch/agent/action/PatchActionConvert.java
index 1c33b50d6..72777e81f 100644
--- a/src/arcade/patch/agent/action/PatchActionConvert.java
+++ b/src/arcade/patch/agent/action/PatchActionConvert.java
@@ -87,7 +87,8 @@ public void step(SimState simstate) {
oldCell.getVolume(),
oldCell.getHeight(),
oldCell.getCriticalVolume(),
- oldCell.getCriticalHeight());
+ oldCell.getCriticalHeight(),
+ oldCell.getCycles());
PatchCell newCell =
(PatchCell) cellContainer.convert(sim.cellFactory, location, sim.random);
grid.addObject(newCell, location);
diff --git a/src/arcade/patch/agent/action/PatchActionReplace.java b/src/arcade/patch/agent/action/PatchActionReplace.java
new file mode 100644
index 000000000..b50d54db5
--- /dev/null
+++ b/src/arcade/patch/agent/action/PatchActionReplace.java
@@ -0,0 +1,150 @@
+package arcade.patch.agent.action;
+
+import java.util.ArrayList;
+import java.util.logging.Logger;
+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.sim.Series;
+import arcade.core.sim.Simulation;
+import arcade.core.util.MiniBox;
+import arcade.core.util.Utilities;
+import arcade.patch.agent.cell.PatchCell;
+import arcade.patch.agent.cell.PatchCellContainer;
+import arcade.patch.env.grid.PatchGrid;
+import arcade.patch.env.location.Coordinate;
+import arcade.patch.env.location.PatchLocationContainer;
+import arcade.patch.sim.PatchSeries;
+import arcade.patch.sim.PatchSimulation;
+import static arcade.patch.util.PatchEnums.Ordering;
+
+/**
+ * Implementation of {@link Action} for inserting cell agents.
+ *
+ *
The action is stepped once after {@code TIME_DELAY}. The action will insert a mixture of
+ * {@code INSERT_NUMBER} cells from each of the registered populations into locations within the
+ * specified radius {@code INSERT_RADIUS} from the center of the simulation.
+ */
+public class PatchActionReplace implements Action {
+ private static final Logger LOGGER = Logger.getLogger(PatchActionReplace.class.getName());
+
+ /** Time delay before calling the action [min]. */
+ private final int timeDelay;
+
+ /** Grid radius that cells are inserted into. */
+ private final int insertRadius;
+
+ /** Grid depth that cells are inserted into. */
+ private final int insertDepth;
+
+ /** Number of cells to insert from each population. */
+ private final int insertNumber;
+
+ /** List of populations. */
+ private final ArrayList populations;
+
+ /**
+ * Creates a {@link Action} for removing cell agents.
+ *
+ * Loaded parameters include:
+ *
+ *
+ * - {@code TIME_DELAY} = time delay before calling the action
+ *
- {@code INSERT_RADIUS} = grid radius that cells are inserted into
+ *
- {@code INSERT_NUMBER} = number of cells to insert from each population
+ *
+ *
+ * @param series the simulation series
+ * @param parameters the component parameters dictionary
+ */
+ public PatchActionReplace(Series series, MiniBox parameters) {
+ int maxRadius = ((PatchSeries) series).radius;
+
+ // Set loaded parameters.
+ timeDelay = parameters.getInt("TIME_DELAY");
+ insertRadius = Math.min(maxRadius, parameters.getInt("INSERT_RADIUS"));
+ insertDepth = ((PatchSeries) series).depth;
+ insertNumber = parameters.getInt("INSERT_NUMBER");
+
+ // Initialize population register.
+ populations = new ArrayList<>();
+ LOGGER.info(
+ "Action Replace: "
+ + parameters.getInt("TIME_DELAY")
+ + " "
+ + insertRadius
+ + " "
+ + insertNumber);
+ }
+
+ @Override
+ public void schedule(Schedule schedule) {
+ schedule.scheduleOnce(timeDelay, Ordering.ACTIONS.ordinal(), this);
+ }
+
+ @Override
+ public void register(Simulation sim, String population) {
+ populations.add(sim.getSeries().populations.get(population));
+ }
+
+ @Override
+ public void step(SimState simstate) {
+ PatchSimulation sim = (PatchSimulation) simstate;
+ PatchGrid grid = (PatchGrid) sim.getGrid();
+
+ // Select valid coordinates to insert into and shuffle.
+ ArrayList coordinates =
+ sim.locationFactory.getCoordinates(insertRadius, insertDepth);
+ Utilities.shuffleList(coordinates, sim.random);
+
+ // Add cells from each population into insertion area.
+ for (MiniBox population : populations) {
+ int id = sim.getID();
+ int pop = population.getInt("CODE");
+
+ for (int i = 0; i < insertNumber; i++) {
+ if (coordinates.isEmpty()) {
+ break;
+ }
+
+ Coordinate coord = coordinates.remove(0);
+ PatchLocationContainer locationContainer = new PatchLocationContainer(id, coord);
+ PatchCellContainer tempContainer = sim.cellFactory.createCellForPopulation(id, pop);
+ Location tempLocation =
+ locationContainer.convert(sim.locationFactory, tempContainer);
+
+ Bag bag = (Bag) grid.getObjectAt(tempLocation.hashCode());
+
+ if (bag == null) {
+ continue;
+ }
+
+ // Select old cell and remove from simulation.
+ PatchCell oldCell = (PatchCell) bag.get(0);
+ Location location = oldCell.getLocation();
+ grid.removeObject(oldCell, oldCell.getLocation());
+ oldCell.stop();
+ // Create new cell and add to simulation.
+ PatchCellContainer cellContainer =
+ new PatchCellContainer(
+ oldCell.getID(),
+ oldCell.getParent(),
+ pop,
+ oldCell.getAge(),
+ oldCell.getDivisions(),
+ oldCell.getState(),
+ oldCell.getVolume(),
+ oldCell.getHeight(),
+ oldCell.getCriticalVolume(),
+ oldCell.getCriticalHeight(),
+ oldCell.getCycles());
+ PatchCell newCell =
+ (PatchCell) cellContainer.convert(sim.cellFactory, location, sim.random);
+ grid.addObject(newCell, location);
+ newCell.schedule(sim.getSchedule());
+ }
+ }
+ }
+}
diff --git a/src/arcade/patch/agent/action/PatchActionTreat.java b/src/arcade/patch/agent/action/PatchActionTreat.java
new file mode 100644
index 000000000..e16a2326d
--- /dev/null
+++ b/src/arcade/patch/agent/action/PatchActionTreat.java
@@ -0,0 +1,397 @@
+package arcade.patch.agent.action;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Set;
+import java.util.stream.Collectors;
+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.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.Immune;
+import arcade.patch.util.PatchEnums.Ordering;
+
+/**
+ * Implementation of {@link Action} for inserting T-cell agents.
+ *
+ * The action is stepped once after {@code TIME_DELAY}. The {@code TreatAction} will add CAR
+ * T-cell agents of specified dose next to source points or vasculature.
+ */
+public class PatchActionTreat implements Action {
+
+ /** Delay before calling the helper (in minutes). */
+ private final int delay;
+
+ /** Total number of CAR T-cells to treat with. */
+ private final int dose;
+
+ /** Maximum damage value at which T-cells can spawn next to in source or pattern source. */
+ private final double maxDamage;
+
+ /** Minimum radius value at which T-cells can spawn next to in graph source. */
+ private final double minDamageRadius;
+
+ /** 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. */
+ int maxConfluency;
+
+ /**
+ * 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) {
+ this.delay = parameters.getInt("TIME_DELAY");
+ this.dose = parameters.getInt("DOSE");
+ this.maxDamage = parameters.getDouble("MAX_DAMAGE_SEED");
+ this.minDamageRadius = parameters.getDouble("MIN_RADIUS_SEED");
+ this.parameters = parameters;
+ this.coord =
+ ((PatchSeries) series).patch.get("GEOMETRY").equalsIgnoreCase("HEX")
+ ? "Hex"
+ : "Rect";
+ if (this.coord.equals("Hex")) {
+ this.latPositions = 9;
+ } else {
+ this.latPositions = 16;
+ }
+
+ 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 action 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");
+
+ // 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";
+ }
+
+ Set immuneCells =
+ Arrays.stream(Immune.values()).map(Enum::name).collect(Collectors.toSet());
+
+ for (MiniBox population : populations) {
+ String className = population.get("CLASS").toUpperCase();
+
+ if (!immuneCells.contains(className)) {
+ throw new IllegalArgumentException(
+ "Population "
+ + population.get("CLASS")
+ + " is not an immune cell and cannot be treated.");
+ }
+
+ maxConfluency = population.getInt("MAX_DENSITY");
+ maxConfluency = (maxConfluency >= 0 ? maxConfluency : Integer.MAX_VALUE);
+
+ int pop = population.getInt("CODE");
+
+ ArrayList locs = sim.getLocations();
+ ArrayList siteLocs = new ArrayList();
+
+ // Find sites without specified level of damage based on component type.
+ findLocations(comp, type, locs, siteLocs, sim);
+ Utilities.shuffleList(siteLocs, sim.random);
+ // sort locations in descending order from highest to lowest density
+ siteLocs.sort(Comparator.comparingInt(l -> -computeDensity(grid, l)));
+ insert(siteLocs, simstate, pop);
+ }
+ }
+
+ /**
+ * Helper method to find possible locations to insert T-cells.
+ *
+ * @param comp the component
+ * @param type the type of component (source, pattern, or graph)
+ * @param locs the locations to check
+ * @param siteLocs the locations that meet the criteria
+ * @param sim the simulation instance
+ * @throws IllegalArgumentException if the component type is invalid
+ */
+ private void findLocations(
+ PatchComponentSites comp,
+ String type,
+ ArrayList locs,
+ ArrayList siteLocs,
+ PatchSimulation sim) {
+ if (type.equals("graph")) {
+ findGraphSites(comp, locs, siteLocs, sim);
+ } else if (type.equals("source") || type.equals("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();
+ }
+ pruneSite(locs, sim, damage, sitesLat, siteLocs);
+ } else {
+ throw new IllegalArgumentException(
+ "Invalid component type: "
+ + type
+ + ". Must be of type source, pattern, or graph.");
+ }
+ }
+
+ /**
+ * Helper method to check if radius is wide enough for T-cells to pass through.
+ *
+ * @param comp the component
+ * @param locs the locations to check
+ * @param siteLocs the locations that meet the criteria
+ * @param sim the simulation instance
+ */
+ private void findGraphSites(
+ PatchComponentSites comp,
+ ArrayList locs,
+ ArrayList siteLocs,
+ PatchSimulation sim) {
+ PatchComponentSitesGraph graphSites = (PatchComponentSitesGraph) comp;
+ Graph graph = graphSites.getGraph();
+ Bag allEdges = new Bag(graph.getAllEdges());
+
+ Set coordinateSet =
+ locs.stream()
+ .map(container -> ((PatchLocationContainer) container).coordinate)
+ .collect(Collectors.toSet());
+
+ for (Object edgeObj : allEdges) {
+ SiteEdge edge = (SiteEdge) edgeObj;
+ ArrayList spans = new ArrayList<>();
+ ArrayList spanLocs = new ArrayList<>();
+
+ // if (Objects.equals(coord, "Hex")) {
+ // spans = (
+ // ((PatchComponentSitesGraphTri) graphSites)
+ // .getSpan(edge.getFrom(), edge.getTo()));
+ // for (CoordinateXYZ span : spans) {
+ // spanLocs.add(((PatchComponentSitesGraphTri) graphSites).getLocation(span));
+ // }
+ // } else {
+ // spans = (
+ // ((PatchComponentSitesGraphRect) graphSites)
+ // .getSpan(edge.getFrom(), edge.getTo()));
+ // for (CoordinateXYZ span : spans) {
+ // spanLocs.add(((PatchComponentSitesGraphRect) graphSites).getLocation(span));
+ // }
+ // }
+
+ spans = graphSites.getSpan(edge.getFrom(), edge.getTo());
+ for (CoordinateXYZ span : spans) {
+ Location newLoc = graphSites.getLocation(span);
+ if (newLoc != null) {
+ spanLocs.add(newLoc);
+ }
+ }
+
+ for (Location loc : spanLocs) {
+ if (coordinateSet.contains(((PatchLocation) loc).getCoordinate())) {
+ if (edge.getRadius() >= minDamageRadius) {
+ for (int p = 0; p < latPositions; p++) {
+ siteLocs.add(loc);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method to remove locations that are not next to a site or have too much damage for
+ * T-cells to pass through.
+ *
+ * @param locs the locations to check
+ * @param sim the simuation instance
+ * @param damage the damage array for sites
+ * @param sitesLat the lattice array for sites
+ * @param siteLocs the locations that meet the criteria
+ */
+ public void pruneSite(
+ ArrayList locs,
+ PatchSimulation sim,
+ double[][][] damage,
+ boolean[][][] sitesLat,
+ ArrayList siteLocs) {
+ 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 coordinate = (CoordinateXYZ) loc.getSubcoordinate();
+ int z = coordinate.z;
+ if (sitesLat[z][coordinate.x][coordinate.y]
+ && damage[z][coordinate.x][coordinate.y] <= this.maxDamage) {
+ for (int p = 0; p < latPositions; p++) {
+ siteLocs.add(loc);
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method to sort locations.
+ *
+ * @param grid the simulation grid
+ * @param loc the current location being looked
+ * @return the density of agents at the location
+ */
+ private int computeDensity(PatchGrid grid, Location loc) {
+ Bag bag = new Bag(grid.getObjectsAtLocation(loc));
+ int numAgents = bag.numObjs;
+ return numAgents;
+ }
+
+ /**
+ * Helper method to add cells into the grid.
+ *
+ * @param coordinates the locations to insert the cells
+ * @param simstate the simulation state
+ * @param pop the population code for the cells
+ */
+ private void insert(ArrayList coordinates, SimState simstate, int pop) {
+ PatchSimulation sim = (PatchSimulation) simstate;
+ PatchGrid grid = (PatchGrid) sim.getGrid();
+ Utilities.shuffleList(coordinates, sim.random);
+
+ for (int i = 0; i < dose; i++) {
+ int id = sim.getID();
+
+ if (coordinates.isEmpty()) {
+ break;
+ }
+
+ PatchLocation loc = ((PatchLocation) coordinates.remove(0));
+
+ while (!coordinates.isEmpty() && !checkLocationSpace(loc, grid)) {
+ loc = ((PatchLocation) coordinates.remove(0));
+ }
+
+ if (coordinates.isEmpty()) {
+ break;
+ }
+
+ Coordinate coordinate = loc.getCoordinate();
+ PatchLocationContainer locationContainer = new PatchLocationContainer(id, coordinate);
+ 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());
+ }
+ }
+
+ /**
+ * Helper method to check if location is available.
+ *
+ * @param grid the simulation grid
+ * @param loc the current location being looked at
+ * @return boolean indicating if location is free
+ */
+ 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) {
+ // no cells in location
+ available = true;
+ } else if (n >= locMax) {
+ // location already full
+ available = false;
+ } 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) + cell.getVolume();
+ currentHeight = totalVol / locArea;
+ }
+ if (cell instanceof PatchCellTissue) {
+ if (currentHeight > cell.getCriticalHeight()) {
+ available = false;
+ }
+ }
+ }
+ }
+
+ return available;
+ }
+}
diff --git a/src/arcade/patch/agent/cell/PatchCell.java b/src/arcade/patch/agent/cell/PatchCell.java
index 4a84bb520..7ecb4b733 100644
--- a/src/arcade/patch/agent/cell/PatchCell.java
+++ b/src/arcade/patch/agent/cell/PatchCell.java
@@ -32,6 +32,10 @@
import arcade.patch.env.grid.PatchGrid;
import arcade.patch.env.location.PatchLocation;
import arcade.patch.util.PatchEnums;
+import arcade.patch.util.PatchEnums.Domain;
+import arcade.patch.util.PatchEnums.Flag;
+import arcade.patch.util.PatchEnums.Ordering;
+import arcade.patch.util.PatchEnums.State;
import static arcade.patch.util.PatchEnums.Domain;
import static arcade.patch.util.PatchEnums.Flag;
import static arcade.patch.util.PatchEnums.Ordering;
@@ -123,12 +127,12 @@ public abstract class PatchCell implements Cell {
/** Cell parameters. */
final Parameters parameters;
+ /** List of cell cycle lengths (in minutes). */
+ public final Bag cycles = new Bag();
+
/** Cell population links. */
final GrabBag links;
- /** List of cell cycle lengths (in minutes). */
- private final Bag cycles = new Bag();
-
/** If cell is stopped in the simulation. */
private boolean isStopped;
@@ -410,7 +414,8 @@ public CellContainer convert() {
volume,
height,
criticalVolume,
- criticalHeight);
+ criticalHeight,
+ cycles);
}
/**
diff --git a/src/arcade/patch/agent/cell/PatchCellCART.java b/src/arcade/patch/agent/cell/PatchCellCART.java
index 25cc71835..0f34a3856 100644
--- a/src/arcade/patch/agent/cell/PatchCellCART.java
+++ b/src/arcade/patch/agent/cell/PatchCellCART.java
@@ -9,6 +9,8 @@
import arcade.core.util.Parameters;
import arcade.patch.env.grid.PatchGrid;
import arcade.patch.env.location.PatchLocation;
+import arcade.patch.util.PatchEnums.AntigenFlag;
+import arcade.patch.util.PatchEnums.State;
import static arcade.patch.util.PatchEnums.AntigenFlag;
import static arcade.patch.util.PatchEnums.State;
@@ -56,16 +58,16 @@ public abstract class PatchCellCART extends PatchCell {
protected boolean activated;
/** number of current PDL-1 receptors on CART cell. */
- protected int selfReceptors;
+ public int selfReceptors;
/** initial number of PDL-1 receptors on CART cell. */
protected int selfReceptorsStart;
/** number of bound CAR antigens. */
- protected int boundCARAntigensCount;
+ public int boundCARAntigensCount;
/** number of bound PDL-1 antigens. */
- protected int boundSelfAntigensCount;
+ public int boundSelfAntigensCount;
/** number of neighbors that T cell is able to search through. */
protected final double searchAbility;
@@ -95,7 +97,10 @@ public abstract class PatchCellCART extends PatchCell {
protected final int maxAntigenBinding;
/** number of CARs on T cell surface. */
- protected final int cars;
+ protected int cars;
+
+ /** number of starting CARs on T cell surface. */
+ protected int startCars;
/** simulation time since T cell was last activated. */
protected int lastActiveTicker;
@@ -115,6 +120,9 @@ public abstract class PatchCellCART extends PatchCell {
/** Target cell that current T cell is bound to. */
protected PatchCell boundTarget;
+ /** maximum number of CAR receptors on a T cell. */
+ static final int MAX_CARS = 50000;
+
/**
* Creates a {@code PatchCellCART} agent. *
*
@@ -160,8 +168,8 @@ public PatchCellCART(
// initialized non-loaded parameters
boundCARAntigensCount = 0;
boundSelfAntigensCount = 0;
- lastActiveTicker = 0;
- activated = true;
+ // lastActiveTicker = 0;
+ activated = false;
boundTarget = null;
// Set loaded parameters.
@@ -180,6 +188,7 @@ public PatchCellCART(
selfBeta = parameters.getDouble("SELF_BETA");
contactFraction = parameters.getDouble("CONTACT_FRAC");
maxAntigenBinding = parameters.getInt("MAX_ANTIGEN_BINDING");
+ startCars = MAX_CARS;
cars = parameters.getInt("CARS");
}
@@ -202,7 +211,7 @@ public PatchCellTissue bindTarget(
double kDSelf = computeAffinity(selfReceptorAffinity, loc);
PatchGrid grid = (PatchGrid) sim.getGrid();
- Bag allAgents = grabAllTissueNeighbors(grid, loc);
+ Bag allAgents = getAllTissueNeighbors(grid, loc);
allAgents.remove(this);
allAgents.shuffle(random);
int neighbors = allAgents.size();
@@ -220,7 +229,8 @@ public PatchCellTissue bindTarget(
double selfTargets = tissueCell.getSelfAntigens();
double probabilityCAR =
- computeProbability(cARAntigens, kDCAR, cars, 5000, carAlpha, carBeta);
+ computeProbability(
+ cARAntigens, kDCAR, cars, startCars, carAlpha, carBeta);
double probabilitySelf =
computeProbability(
selfTargets,
@@ -259,21 +269,6 @@ public boolean getActivationStatus() {
return this.activated;
}
- /**
- * Adds only tissue cells to the provided bag.
- *
- * @param tissueAgents the bag to add tissue cells into
- * @param possibleAgents the bag of possible agents to check for tissue cells
- */
- private void grabTissueAgents(Bag tissueAgents, Bag possibleAgents) {
- for (Object agent : possibleAgents) {
- Cell cell = (Cell) agent;
- if (cell instanceof PatchCellTissue) {
- tissueAgents.add(cell);
- }
- }
- }
-
/**
* Computes the binding probability for the receptor with the given parameters.
*
@@ -285,7 +280,7 @@ private void grabTissueAgents(Bag tissueAgents, Bag possibleAgents) {
* @param beta fudge factor for receptor binding
* @return the binding probability for the receptor
*/
- private double computeProbability(
+ protected double computeProbability(
double antigens,
double kD,
int currentReceptors,
@@ -331,12 +326,27 @@ private PatchCellTissue bindToCARAndSelfAntigen(PatchCellTissue tissueCell) {
* @param tissueCell the target cell to bind to
* @return the target tissue cell to bind to
*/
- private PatchCellTissue bindToSelfAntigen(PatchCellTissue tissueCell) {
+ protected PatchCellTissue bindToSelfAntigen(PatchCellTissue tissueCell) {
super.setBindingFlag(AntigenFlag.BOUND_CELL_RECEPTOR);
boundSelfAntigensCount++;
return tissueCell;
}
+ /**
+ * Adds only tissue cells to the provided bag. Helper method for bindTarget.
+ *
+ * @param tissueAgents the bag to add tissue cells into
+ * @param possibleAgents the bag of possible agents to check for tissue cells
+ */
+ public void getTissueAgents(Bag tissueAgents, Bag possibleAgents) {
+ for (Object agent : possibleAgents) {
+ Cell cell = (Cell) agent;
+ if (cell instanceof PatchCellTissue) {
+ tissueAgents.add(cell);
+ }
+ }
+ }
+
/**
* Returns all tissue cells in neighborhood and current location.
*
@@ -344,12 +354,12 @@ private PatchCellTissue bindToSelfAntigen(PatchCellTissue tissueCell) {
* @param loc current location of the cell
* @return bag of all tissue cells in neighborhood and current location
*/
- private Bag grabAllTissueNeighbors(PatchGrid grid, PatchLocation loc) {
+ protected Bag getAllTissueNeighbors(PatchGrid grid, PatchLocation loc) {
Bag neighbors = new Bag();
- grabTissueAgents(neighbors, grid.getObjectsAtLocation(loc));
+ getTissueAgents(neighbors, grid.getObjectsAtLocation(loc));
for (Location neighborLocation : loc.getNeighbors()) {
Bag bag = new Bag(grid.getObjectsAtLocation(neighborLocation));
- grabTissueAgents(neighbors, bag);
+ getTissueAgents(neighbors, bag);
}
return neighbors;
@@ -373,8 +383,15 @@ private double calculateMichaelisMenten(
int startReceptors,
double alpha,
double beta) {
+
+ double correctedStartReceptors = startReceptors;
+
+ if (startReceptors == 0) {
+ correctedStartReceptors = 1;
+ }
+
return (targets * contactFraction / (affinity * beta + targets * contactFraction))
- * (currentReceptors / startReceptors)
+ * (currentReceptors / correctedStartReceptors)
* alpha;
}
@@ -395,7 +412,7 @@ private double applySigmoid(double bindingCoefficient) {
* @param loc the current location of the cell
* @return the affinity per receptor molecule
*/
- private double computeAffinity(double affinity, PatchLocation loc) {
+ protected double computeAffinity(double affinity, PatchLocation loc) {
return affinity * (loc.getVolume() * 1e-15 * 6.022E23);
}
@@ -418,4 +435,27 @@ public void unbind() {
super.setBindingFlag(AntigenFlag.UNBOUND);
this.boundTarget = null;
}
+
+ /**
+ * Sets the cell activation status.
+ *
+ * @param activated the activation status to set
+ */
+ public void setActivationStatus(boolean activated) {
+ this.activated = activated;
+ }
+
+ /**
+ * Sets the car amount.
+ *
+ * @param cars the car amount to set
+ */
+ public void setCars(int cars) {
+ this.cars = cars;
+ }
+
+ /** Gets the car amount. */
+ public int getCars() {
+ return cars;
+ }
}
diff --git a/src/arcade/patch/agent/cell/PatchCellCARTCD4.java b/src/arcade/patch/agent/cell/PatchCellCARTCD4.java
index 609be4934..3090fdb36 100644
--- a/src/arcade/patch/agent/cell/PatchCellCARTCD4.java
+++ b/src/arcade/patch/agent/cell/PatchCellCARTCD4.java
@@ -52,7 +52,8 @@ public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFas
volume,
height,
criticalVolume,
- criticalHeight);
+ criticalHeight,
+ cycles);
}
@Override
diff --git a/src/arcade/patch/agent/cell/PatchCellCARTCD8.java b/src/arcade/patch/agent/cell/PatchCellCARTCD8.java
index d7f66c9a0..6d19517b7 100644
--- a/src/arcade/patch/agent/cell/PatchCellCARTCD8.java
+++ b/src/arcade/patch/agent/cell/PatchCellCARTCD8.java
@@ -52,7 +52,8 @@ public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFas
volume,
height,
criticalVolume,
- criticalHeight);
+ criticalHeight,
+ cycles);
}
@Override
diff --git a/src/arcade/patch/agent/cell/PatchCellCARTCombined.java b/src/arcade/patch/agent/cell/PatchCellCARTCombined.java
new file mode 100644
index 000000000..b551ebb98
--- /dev/null
+++ b/src/arcade/patch/agent/cell/PatchCellCARTCombined.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;
+
+/** Extension of {@link PatchCellCART} for CD8 CART-cells with selected module versions. */
+public class PatchCellCARTCombined 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 PatchCellCARTCombined(
+ 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 PatchCellCARTCombined(
+ 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,
+ cycles);
+ }
+
+ @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,s
+ // 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/PatchCellCARTCombinedCombinatorial.java b/src/arcade/patch/agent/cell/PatchCellCARTCombinedCombinatorial.java
new file mode 100644
index 000000000..4f978dddd
--- /dev/null
+++ b/src/arcade/patch/agent/cell/PatchCellCARTCombinedCombinatorial.java
@@ -0,0 +1,202 @@
+package arcade.patch.agent.cell;
+
+import java.util.logging.Logger;
+import sim.engine.SimState;
+import sim.util.Bag;
+import sim.util.distribution.Poisson;
+import ec.util.MersenneTwisterFast;
+import arcade.core.env.location.Location;
+import arcade.core.sim.Simulation;
+import arcade.core.util.GrabBag;
+import arcade.core.util.Parameters;
+import arcade.patch.env.grid.PatchGrid;
+
+/**
+ * Abstract class of {@link PatchCellCART} for combined CD4/CD8 combinatorial CART-cells with
+ * selected module versions.
+ */
+public abstract class PatchCellCARTCombinedCombinatorial extends PatchCellCARTCombined {
+
+ /** Logger for this class. */
+ private static final Logger LOGGER =
+ Logger.getLogger(PatchCellCARTCombinedCombinatorial.class.getName());
+
+ /** Number of bound synnotchs required to trigger activation/inactivation. */
+ protected final double synNotchThreshold;
+
+ /** synnotch receptor-antigen binding rate. */
+ protected final double bindingConstant;
+
+ /** synnotch receptor-antigen unbinding rate. */
+ protected final double unbindingConstant;
+
+ /** car receptor degradation rate. */
+ protected final double carDegradationConstant;
+
+ /** Number of synnotch receptors on this cell. */
+ public int synnotchs;
+
+ /** Number of bound synnotch receptors on this cell. */
+ public int boundSynNotch;
+
+ /** poisson distribution. */
+ PatchCellCARTCombinedInducible.PoissonFactory poissonFactory;
+
+ /** Target cell that is bound. */
+ protected PatchCellTissue boundCell;
+
+ /** basal CAR receptor expression rate. */
+ protected final double basalCARGenerationRate;
+
+ /** time step for tau stepping. */
+ private static final int TAU = 60;
+
+ /**
+ * Creates a T cell {@code PatchCellCARTCombinedCombinatorial} agent. *
+ *
+ * @param container the cell container
+ * @param location the {@link Location} of the cell
+ * @param parameters the dictionary of parameters
+ */
+ public PatchCellCARTCombinedCombinatorial(
+ PatchCellContainer container, Location location, Parameters parameters) {
+ this(container, location, parameters, null);
+ }
+
+ /**
+ * Creates a T cell {@code PatchCellCARTCombinedCombinatorial} 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 PatchCellCARTCombinedCombinatorial(
+ PatchCellContainer container, Location location, Parameters parameters, GrabBag links) {
+ super(container, location, parameters, links);
+ bindingConstant = parameters.getDouble("K_SYNNOTCH_ON");
+ unbindingConstant = parameters.getDouble("K_SYNNOTCH_OFF");
+ carDegradationConstant = parameters.getDouble("K_CAR_DEGRADE");
+ synnotchs = parameters.getInt("SYNNOTCHS");
+ synNotchThreshold = parameters.getDouble("SYNNOTCH_THRESHOLD") * synnotchs;
+ basalCARGenerationRate = parameters.getDouble("K_CAR_GENERATION");
+ boundSynNotch = 0;
+ poissonFactory = Poisson::new;
+ }
+
+ /**
+ * Binds to target cell in neighborhood. *
+ *
+ * @param simstate the simulation state
+ */
+ protected void checkForBinding(SimState simstate) {
+ Simulation sim = (Simulation) simstate;
+ PatchGrid grid = (PatchGrid) sim.getGrid();
+
+ Bag allAgents = new Bag();
+ getTissueAgents(allAgents, grid.getObjectsAtLocation(location));
+ for (Location neighborLocation : location.getNeighbors()) {
+ Bag bag = new Bag(grid.getObjectsAtLocation(neighborLocation));
+ getTissueAgents(allAgents, bag);
+ }
+
+ if (allAgents.size() > 0) {
+ PatchCellTissue randomCell =
+ (PatchCellTissue) allAgents.get(simstate.random.nextInt(allAgents.size()));
+ if (randomCell.getSynNotchAntigens() > 0) {
+ boundCell = randomCell;
+ }
+ }
+ }
+
+ /**
+ * Calculates the number of binding and unbinding events for the synnotch receptor . *
+ *
+ * @param random the random object
+ * @param sim the simulation instance
+ */
+ protected void calculateCARS(MersenneTwisterFast random, Simulation sim) {
+ // T cell cannot bind if no synnotch receptors are available
+ if (synnotchs == 0) {
+ if (boundSynNotch != 0) {
+ boundSynNotch = 0;
+ }
+ return;
+ }
+
+ // unbound synnotch should never be negative
+ int unboundSynNotch = synnotchs - boundSynNotch;
+ unboundSynNotch = Math.max(0, unboundSynNotch);
+
+ double expectedBindingEvents =
+ bindingConstant
+ / (volume * 6.0221415e23 * 1e-15)
+ * unboundSynNotch
+ * boundCell.getSynNotchAntigens()
+ * contactFraction
+ * TAU;
+
+ // binding events should not exceed available unbound receptors
+ expectedBindingEvents = Math.min(expectedBindingEvents, unboundSynNotch);
+
+ int bindingEvents = poissonFactory.createPoisson(expectedBindingEvents, random).nextInt();
+ double expectedUnbindingEvents = unbindingConstant * boundSynNotch * TAU;
+
+ expectedUnbindingEvents = Math.min(expectedUnbindingEvents, boundSynNotch);
+
+ // unbinding events should not exceed available bound receptors
+ int unbindingEvents =
+ poissonFactory.createPoisson(expectedUnbindingEvents, random).nextInt();
+
+ boundSynNotch += bindingEvents;
+ boundSynNotch -= unbindingEvents;
+
+ // bound synnotch should never be negative
+ boundSynNotch = Math.max(0, boundSynNotch);
+ boundCell.updateSynNotchAntigens(unbindingEvents, bindingEvents);
+
+ // synnotch receptors become unavailable after binding
+ synnotchs = Math.max(0, synnotchs - bindingEvents);
+ }
+
+ /** A {@code PoissonFactory} object instantiates Poisson distributions. */
+ interface PoissonFactory {
+ /**
+ * Creates instance of Poisson.
+ *
+ * @param lambda the Poisson distribution lambda
+ * @param random the random number generator
+ * @return a Poisson distribution instance
+ */
+ Poisson createPoisson(double lambda, MersenneTwisterFast random);
+ }
+
+ /**
+ * returns the poisson distribution.
+ *
+ * @param lambda the Poisson distribution lambda
+ * @param random the random number generator
+ * @return a Poisson distribution instance
+ */
+ public double callPoisson(double lambda, MersenneTwisterFast random) {
+ return poissonFactory.createPoisson(lambda, random).nextInt();
+ }
+
+ /** resets bound target cell and unbinds from it. */
+ public void resetBoundCell() {
+ if (boundCell != null) {
+ boundCell.updateSynNotchAntigens(boundSynNotch, 0);
+ boundCell = null;
+ }
+ boundSynNotch = 0;
+ }
+
+ /**
+ * returns the synnotch threshold.
+ *
+ * @return the synnotch threshold
+ */
+ public double getSynNotchThreshold() {
+ return synNotchThreshold;
+ }
+}
diff --git a/src/arcade/patch/agent/cell/PatchCellCARTCombinedInducible.java b/src/arcade/patch/agent/cell/PatchCellCARTCombinedInducible.java
new file mode 100644
index 000000000..4bf1b8370
--- /dev/null
+++ b/src/arcade/patch/agent/cell/PatchCellCARTCombinedInducible.java
@@ -0,0 +1,244 @@
+package arcade.patch.agent.cell;
+
+import java.util.logging.Logger;
+import sim.engine.SimState;
+import ec.util.MersenneTwisterFast;
+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;
+import arcade.patch.util.PatchEnums.LogicalCARs;
+
+/** Extension of {@link PatchCellCARTCombinedCombinatorial} for synnotch circuit. */
+public class PatchCellCARTCombinedInducible extends PatchCellCARTCombinedCombinatorial {
+
+ /** Logger for this class. */
+ private static final Logger LOGGER =
+ Logger.getLogger(PatchCellCARTCombinedInducible.class.getName());
+
+ /** Type of combinatorial circuit. */
+ private final LogicalCARs type;
+
+ /** time step for tau stepping. */
+ private static final int TAU = 60;
+
+ /**
+ * Creates a tissue {@code PatchCellCARTCombinedInducible} agent. *
+ *
+ * @param location the {@link Location} of the cell
+ * @param container the cell container
+ * @param parameters the dictionary of parameters
+ */
+ public PatchCellCARTCombinedInducible(
+ PatchCellContainer container, Location location, Parameters parameters) {
+ this(container, location, parameters, null);
+ }
+
+ /**
+ * Creates a T cell {@code PatchCellCARTCombinedInducible} 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 PatchCellCARTCombinedInducible(
+ PatchCellContainer container, Location location, Parameters parameters, GrabBag links) {
+ this(container, location, parameters, links, null);
+ }
+
+ /**
+ * Creates a T cell {@code PatchCellCARTCombinedInducible} 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
+ * @param type the type of combinatorial circuit
+ */
+ public PatchCellCARTCombinedInducible(
+ PatchCellContainer container,
+ Location location,
+ Parameters parameters,
+ GrabBag links,
+ LogicalCARs type) {
+ super(container, location, parameters, links);
+ this.type = type;
+ if (this.type.equals(LogicalCARs.INDUCIBLE_SYNNOTCH)) {
+ // for receptor based circuits, receptor binding depends on initial receptors
+ this.startCars = cars;
+ }
+ }
+
+ @Override
+ public void step(SimState simstate) {
+ Simulation sim = (Simulation) simstate;
+
+ if (super.boundCell == null) {
+ super.checkForBinding(simstate);
+ } else {
+ calculateCARS(simstate.random, sim);
+ }
+
+ if (type.equals(LogicalCARs.INDUCIBLE_SYNNOTCH)) {
+ super.step(simstate);
+ } else if (type.equals(LogicalCARs.INDUCIBLE_INFLAMMATION)) {
+ inflammationStep(simstate);
+ }
+ }
+
+ @Override
+ protected void calculateCARS(MersenneTwisterFast random, Simulation sim) {
+ super.calculateCARS(random, sim);
+ if (type.equals(LogicalCARs.INDUCIBLE_SYNNOTCH)) {
+ synNotchCARCalculation();
+ } else if (type.equals(LogicalCARs.INDUCIBLE_INFLAMMATION)) {
+ inflammationActivation();
+ }
+ }
+
+ /** Calculates the number of cars produced for synnotch circuit. * */
+ protected void synNotchCARCalculation() {
+ double n = 4.4;
+ int newCars =
+ (int)
+ (MAX_CARS
+ / (1
+ + Math.pow(synNotchThreshold, n)
+ / Math.pow(boundSynNotch, n)));
+ cars = Math.max((int) (cars - (carDegradationConstant * cars * TAU)), newCars);
+ }
+
+ /** Calculates the number of cars produced for inflammation circuit. * */
+ protected void inflammationActivation() {
+ cars =
+ Math.max(
+ (int)
+ (cars
+ + (basalCARGenerationRate * TAU)
+ - (carDegradationConstant * cars * TAU)),
+ 0);
+ if (boundSynNotch >= synNotchThreshold) {
+ this.lastActiveTicker = 0;
+ this.activated = true;
+ }
+ }
+
+ /**
+ * Steps through T-cell rules using inflammation circuit. *
+ *
+ * @param simstate the current simulation state
+ */
+ protected void inflammationStep(SimState simstate) {
+ Simulation sim = (Simulation) simstate;
+
+ super.age++;
+
+ if (state != PatchEnums.State.APOPTOTIC && age > apoptosisAge) {
+ setState(PatchEnums.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(PatchEnums.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 != PatchEnums.State.APOPTOTIC) {
+ if (super.energy < super.energyThreshold) {
+
+ super.setState(PatchEnums.State.APOPTOTIC);
+ super.unbind();
+ this.activated = false;
+ } else if (state != PatchEnums.State.ANERGIC
+ && state != PatchEnums.State.SENESCENT
+ && state != PatchEnums.State.EXHAUSTED
+ && state != PatchEnums.State.STARVED
+ && energy < 0) {
+
+ super.setState(PatchEnums.State.STARVED);
+ super.unbind();
+ } else if (state == PatchEnums.State.STARVED && energy >= 0) {
+ super.setState(PatchEnums.State.UNDEFINED);
+ }
+ }
+
+ super.processes.get(PatchEnums.Domain.INFLAMMATION).step(simstate.random, sim);
+
+ if (super.state == PatchEnums.State.UNDEFINED || super.state == PatchEnums.State.PAUSED) {
+ if (divisions == divisionPotential) {
+ if (simstate.random.nextDouble() > super.senescentFraction) {
+ super.setState(PatchEnums.State.APOPTOTIC);
+ } else {
+ super.setState(PatchEnums.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() == PatchEnums.AntigenFlag.BOUND_ANTIGEN_CELL_RECEPTOR) {
+ if (simstate.random.nextDouble() > super.anergicFraction) {
+ super.setState(PatchEnums.State.APOPTOTIC);
+ } else {
+ super.setState(PatchEnums.State.ANERGIC);
+ }
+ super.unbind();
+ this.activated = false;
+ } else if (super.getBindingFlag() == PatchEnums.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(PatchEnums.State.APOPTOTIC);
+ } else {
+ super.setState(PatchEnums.State.EXHAUSTED);
+ }
+ super.unbind();
+ this.activated = false;
+ } else {
+ // if CD8 cell is properly activated, it can be cytotoxic
+ super.setState(PatchEnums.State.CYTOTOXIC);
+ }
+ } else {
+ // If self binding, unbind
+ if (super.getBindingFlag() == PatchEnums.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(PatchEnums.State.PROLIFERATIVE);
+ } else {
+ if (simstate.random.nextDouble() > super.proliferativeFraction) {
+ super.setState(PatchEnums.State.MIGRATORY);
+ } else {
+ super.setState(PatchEnums.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/PatchCellCARTCombinedInhibitory.java b/src/arcade/patch/agent/cell/PatchCellCARTCombinedInhibitory.java
new file mode 100644
index 000000000..aba48f780
--- /dev/null
+++ b/src/arcade/patch/agent/cell/PatchCellCARTCombinedInhibitory.java
@@ -0,0 +1,342 @@
+package arcade.patch.agent.cell;
+
+import java.util.logging.Logger;
+import sim.engine.SimState;
+import sim.util.Bag;
+import ec.util.MersenneTwisterFast;
+import arcade.core.agent.cell.Cell;
+import arcade.core.env.location.Location;
+import arcade.core.sim.Simulation;
+import arcade.core.util.GrabBag;
+import arcade.core.util.Parameters;
+import arcade.patch.env.grid.PatchGrid;
+import arcade.patch.env.location.PatchLocation;
+import arcade.patch.util.PatchEnums.AntigenFlag;
+import arcade.patch.util.PatchEnums.Domain;
+import arcade.patch.util.PatchEnums.LogicalCARs;
+import arcade.patch.util.PatchEnums.State;
+
+/** Extension of {@link PatchCellCARTCombinedCombinatorial} for iCAR synnotch circuit. */
+public class PatchCellCARTCombinedInhibitory extends PatchCellCARTCombinedCombinatorial {
+
+ /** Logger for this class. */
+ private static final Logger LOGGER =
+ Logger.getLogger(PatchCellCARTCombinedInhibitory.class.getName());
+
+ /** Type of combinatorial circuit. */
+ private final LogicalCARs type;
+
+ /** time step for tau stepping. */
+ private static final int TAU = 60;
+
+ /** Tracker for internal proteasome levels. */
+ private double proteasome;
+
+ /** Current proteasome production rate. */
+ private double proteasomeProdRate;
+
+ /** Proteasome threshold for CAR activation. */
+ private double proteasomeActivationThreshold;
+
+ /** Basal rate of proteasome decay. */
+ private static final double BASAL_PROTEASOME_DECAY_RATE = 0.1;
+
+ /** Synnotch binding status. */
+ public boolean boundPD1 = false;
+
+ /** Initial synnotch receptors. */
+ private int initialSynnotchReceptors;
+
+ /** Whether or not inhibitory mechanism is initiated. */
+ private boolean pdel;
+
+ /** icar_affinity. */
+ private double icarReceptorAffinity;
+
+ /** max number of iCAR receptors on iCAR surface. */
+ static final int MAX_SYNNOTCHS = 20000;
+
+ /**
+ * Creates a tissue {@code PatchCellCARTCombinedInhibitory} agent. *
+ *
+ * @param location the {@link Location} of the cell
+ * @param container the cell container
+ * @param parameters the dictionary of parameters
+ */
+ public PatchCellCARTCombinedInhibitory(
+ PatchCellContainer container, Location location, Parameters parameters) {
+ this(container, location, parameters, null);
+ }
+
+ /**
+ * Creates a T cell {@code PatchCellCombinedInhibitory} 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 PatchCellCARTCombinedInhibitory(
+ PatchCellContainer container, Location location, Parameters parameters, GrabBag links) {
+ this(container, location, parameters, links, null);
+ }
+
+ /**
+ * Creates a T cell {@code PatchCellCARTCombinedInhibitory} 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
+ * @param type the type of combinatorial circuit
+ */
+ public PatchCellCARTCombinedInhibitory(
+ PatchCellContainer container,
+ Location location,
+ Parameters parameters,
+ GrabBag links,
+ LogicalCARs type) {
+ super(container, location, parameters, links);
+ this.type = type;
+ proteasome = 0;
+ proteasomeProdRate =
+ parameters.getInt("PROTEASOME_PRODUCTION_RATIO") * BASAL_PROTEASOME_DECAY_RATE;
+ double maxProteasome = parameters.getInt("PROTEASOME_PRODUCTION_RATIO") * synnotchs;
+ proteasomeActivationThreshold = parameters.getDouble("SYNNOTCH_THRESHOLD") * maxProteasome;
+ initialSynnotchReceptors = parameters.getInt("SYNNOTCHS");
+ pdel = parameters.getInt("PDEL") > 0 ? true : false;
+ icarReceptorAffinity = parameters.getDouble("ICAR_AFFINITY");
+ // for receptor based circuits, receptor binding depends on initial receptors
+ if (this.type.equals(LogicalCARs.INHIBITORY_RECEPTOR)) {
+ this.startCars = cars;
+ } else {
+ // for inflammation based circuits, receptor binding depends on max receptors
+ this.initialSynnotchReceptors = MAX_SYNNOTCHS;
+ }
+ }
+
+ @Override
+ public void step(SimState simstate) {
+ Simulation sim = (Simulation) simstate;
+ if (type.equals(LogicalCARs.INHIBITORY_INFLAMMATION)) {
+ // primary receptor binding
+ if (!pdel) {
+ bindTargetPD1(sim, location, simstate.random);
+ }
+ stepInflammation(simstate);
+ } else {
+ if (super.boundCell == null) {
+ super.checkForBinding(simstate);
+ } else {
+ calculateCARS(simstate.random, sim);
+ }
+ super.step(simstate);
+ }
+ }
+
+ public void bindTargetPD1(Simulation sim, PatchLocation loc, MersenneTwisterFast random) {
+
+ double kDSelf = computeAffinity(icarReceptorAffinity, loc);
+ PatchGrid grid = (PatchGrid) sim.getGrid();
+
+ Bag allAgents = getAllTissueNeighbors(grid, loc);
+ allAgents.remove(this);
+ allAgents.shuffle(random);
+ int neighbors = allAgents.size();
+
+ if (neighbors == 0) {
+ super.setBindingFlag(AntigenFlag.UNBOUND);
+ return;
+ } else {
+ int maxSearch = (int) Math.min(neighbors, searchAbility);
+ for (int i = 0; i < maxSearch; i++) {
+ Cell cell = (Cell) allAgents.get(i);
+ if (cell.getState() != State.APOPTOTIC && cell.getState() != State.NECROTIC) {
+ PatchCellTissue tissueCell = (PatchCellTissue) cell;
+ double selfTargets = tissueCell.getSynNotchAntigens();
+
+ double probabilitySelf =
+ computeProbability(
+ selfTargets,
+ kDSelf,
+ synnotchs,
+ initialSynnotchReceptors,
+ selfAlpha,
+ selfBeta);
+
+ double randomSelf = random.nextDouble();
+
+ if (probabilitySelf >= randomSelf) {
+ boundPD1 = true;
+ boundSelfAntigensCount++;
+ synnotchs--;
+ } else {
+ boundPD1 = false;
+ }
+ }
+ }
+ }
+ }
+
+ public void stepInflammation(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 {
+ // CAR receptor binding
+ 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
+ || (boundPD1 && super.getBindingFlag() == AntigenFlag.BOUND_ANTIGEN)) {
+ 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
+ || boundSelfAntigensCount > 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,s
+ // 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);
+ }
+ }
+
+ @Override
+ protected void calculateCARS(MersenneTwisterFast random, Simulation sim) {
+ super.calculateCARS(random, sim);
+
+ if (type.equals(LogicalCARs.INHIBITORY_RECEPTOR)) {
+ receptorCars();
+ } else if (type.equals(LogicalCARs.INHIBITORY_INFLAMMATION)) {
+ inflammationCars();
+ }
+ }
+
+ /** Calculates the number of cars produced for receptor circuit. * */
+ protected void receptorCars() {
+ proteasome = proteasomeProdRate * boundSynNotch - BASAL_PROTEASOME_DECAY_RATE * proteasome;
+ double n = 4.4;
+ int removeCARs =
+ (int)
+ (MAX_CARS
+ / (1
+ + Math.pow(proteasome, n)
+ / Math.pow(proteasomeActivationThreshold, n)));
+ cars = Math.min(cars + (int) (basalCARGenerationRate * TAU), removeCARs);
+ }
+
+ /** Calculates T-cell activation caused by inflammation circuit. * */
+ protected void inflammationCars() {
+ cars =
+ Math.max(
+ (int)
+ (cars
+ + (basalCARGenerationRate * TAU)
+ - (carDegradationConstant * cars * TAU)),
+ 0);
+ }
+
+ /** Calculates the proportion of inhibition given amount of SynNotch bound * */
+ public double getInflammationInhibition() {
+ double n = 4.4;
+ return (1 / (1 + Math.pow(boundSynNotch, n) / Math.pow(synNotchThreshold, n)));
+ }
+
+ /** Calculates the proportion of inhibition given amount of SynNotch bound * */
+ public boolean isInhibited() {
+ return (synNotchThreshold <= boundSynNotch);
+ }
+}
diff --git a/src/arcade/patch/agent/cell/PatchCellCancer.java b/src/arcade/patch/agent/cell/PatchCellCancer.java
index 01d505229..0b4c002d0 100644
--- a/src/arcade/patch/agent/cell/PatchCellCancer.java
+++ b/src/arcade/patch/agent/cell/PatchCellCancer.java
@@ -71,7 +71,8 @@ public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFas
volume,
height,
criticalVolume,
- criticalHeight);
+ criticalHeight,
+ cycles);
}
/**
diff --git a/src/arcade/patch/agent/cell/PatchCellCancerStem.java b/src/arcade/patch/agent/cell/PatchCellCancerStem.java
index b1701504c..a6a30948a 100644
--- a/src/arcade/patch/agent/cell/PatchCellCancerStem.java
+++ b/src/arcade/patch/agent/cell/PatchCellCancerStem.java
@@ -65,6 +65,7 @@ public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFas
volume,
height,
criticalVolume,
- criticalHeight);
+ criticalHeight,
+ cycles);
}
}
diff --git a/src/arcade/patch/agent/cell/PatchCellContainer.java b/src/arcade/patch/agent/cell/PatchCellContainer.java
index 3a942c388..ec7308062 100644
--- a/src/arcade/patch/agent/cell/PatchCellContainer.java
+++ b/src/arcade/patch/agent/cell/PatchCellContainer.java
@@ -1,5 +1,6 @@
package arcade.patch.agent.cell;
+import sim.util.Bag;
import ec.util.MersenneTwisterFast;
import arcade.core.agent.cell.Cell;
import arcade.core.agent.cell.CellContainer;
@@ -9,6 +10,7 @@
import arcade.core.util.GrabBag;
import arcade.core.util.MiniBox;
import arcade.core.util.Parameters;
+import arcade.patch.util.PatchEnums.LogicalCARs;
/**
* Implementation of {@link CellContainer} for {@link PatchCell} agents.
@@ -47,6 +49,9 @@ public final class PatchCellContainer implements CellContainer {
/** Critical cell height [um]. */
public final double criticalHeight;
+ /** Cell cycles. */
+ public final Bag cycles;
+
/**
* Creates a {@code PatchCellContainer} instance.
*
@@ -60,6 +65,7 @@ public final class PatchCellContainer implements CellContainer {
* @param height the cell height
* @param criticalVolume the critical volume
* @param criticalHeight the critical height
+ * @param cycles the cell cycles
*/
public PatchCellContainer(
int id,
@@ -71,7 +77,8 @@ public PatchCellContainer(
double volume,
double height,
double criticalVolume,
- double criticalHeight) {
+ double criticalHeight,
+ Bag cycles) {
this.id = id;
this.parent = parent;
this.pop = pop;
@@ -82,6 +89,7 @@ public PatchCellContainer(
this.height = height;
this.criticalVolume = criticalVolume;
this.criticalHeight = criticalHeight;
+ this.cycles = cycles;
}
@Override
@@ -119,6 +127,20 @@ public Cell convert(
return new PatchCellCARTCD8(this, location, parameters, links);
case "cart_cd4":
return new PatchCellCARTCD4(this, location, parameters, links);
+ case "combined":
+ return new PatchCellCARTCombined(this, location, parameters, links);
+ case "inducible_synnotch":
+ return new PatchCellCARTCombinedInducible(
+ this, location, parameters, links, LogicalCARs.INDUCIBLE_SYNNOTCH);
+ case "inhibitory_receptor":
+ return new PatchCellCARTCombinedInhibitory(
+ this, location, parameters, links, LogicalCARs.INHIBITORY_RECEPTOR);
+ case "inducible_inflammation":
+ return new PatchCellCARTCombinedInducible(
+ this, location, parameters, links, LogicalCARs.INDUCIBLE_INFLAMMATION);
+ case "inhibitory_inflammation":
+ return new PatchCellCARTCombinedInhibitory(
+ this, location, parameters, links, LogicalCARs.INHIBITORY_INFLAMMATION);
case "random":
return new PatchCellRandom(this, location, parameters, links);
}
diff --git a/src/arcade/patch/agent/cell/PatchCellFactory.java b/src/arcade/patch/agent/cell/PatchCellFactory.java
index 0492e0801..6ce07920a 100644
--- a/src/arcade/patch/agent/cell/PatchCellFactory.java
+++ b/src/arcade/patch/agent/cell/PatchCellFactory.java
@@ -6,6 +6,7 @@
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
+import sim.util.Bag;
import ec.util.MersenneTwisterFast;
import arcade.core.agent.cell.*;
import arcade.core.sim.Series;
@@ -13,6 +14,8 @@
import arcade.core.util.MiniBox;
import arcade.core.util.Parameters;
import arcade.patch.sim.PatchSeries;
+import arcade.patch.util.PatchEnums.Domain;
+import arcade.patch.util.PatchEnums.State;
import static arcade.core.util.MiniBox.TAG_SEPARATOR;
import static arcade.patch.util.PatchEnums.Domain;
import static arcade.patch.util.PatchEnums.State;
@@ -153,14 +156,27 @@ public PatchCellContainer createCellForPopulation(int id, int pop) {
MiniBox population = popToParameters.get(pop);
Parameters parameters = new Parameters(population, null, random);
- double compression = parameters.getDouble("COMPRESSION_TOLERANCE");
+ double compression =
+ parameters.getDouble("COMPRESSION_TOLERANCE") >= 0
+ ? parameters.getDouble("COMPRESSION_TOLERANCE")
+ : Double.MAX_VALUE;
double volume = parameters.getDouble("CELL_VOLUME");
double height = parameters.getDouble("CELL_HEIGHT");
int age = parameters.getInt("CELL_AGE");
-
+ Bag cycles = new Bag();
return new PatchCellContainer(
- id, 0, pop, age, 0, State.UNDEFINED, volume, height, volume, height + compression);
+ id,
+ 0,
+ pop,
+ age,
+ 0,
+ State.UNDEFINED,
+ volume,
+ height,
+ volume,
+ height + compression,
+ cycles);
}
/**
diff --git a/src/arcade/patch/agent/cell/PatchCellRandom.java b/src/arcade/patch/agent/cell/PatchCellRandom.java
index 19a31a035..3db18519d 100644
--- a/src/arcade/patch/agent/cell/PatchCellRandom.java
+++ b/src/arcade/patch/agent/cell/PatchCellRandom.java
@@ -56,7 +56,8 @@ public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFas
volume,
height,
criticalVolume,
- criticalHeight);
+ criticalHeight,
+ cycles);
}
@Override
diff --git a/src/arcade/patch/agent/cell/PatchCellTissue.java b/src/arcade/patch/agent/cell/PatchCellTissue.java
index 352260b28..09a978fbf 100644
--- a/src/arcade/patch/agent/cell/PatchCellTissue.java
+++ b/src/arcade/patch/agent/cell/PatchCellTissue.java
@@ -42,6 +42,9 @@ public class PatchCellTissue extends PatchCell {
/** Cell surface PDL1 count. */
private final int selfTargets;
+ /** Cell surface SynNotch antigen count */
+ private int synNotchAntigens;
+
/**
* Creates a tissue {@code PatchCell} agent.
*
@@ -66,6 +69,7 @@ public PatchCellTissue(
super(container, location, parameters, links);
carAntigens = parameters.getInt("CAR_ANTIGENS");
selfTargets = parameters.getInt("SELF_TARGETS");
+ synNotchAntigens = parameters.getInt("SYNNOTCH_ANTIGENS");
}
@Override
@@ -82,12 +86,17 @@ public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFas
volume,
height,
criticalVolume,
- criticalHeight);
+ criticalHeight,
+ cycles);
}
@Override
public void step(SimState simstate) {
Simulation sim = (Simulation) simstate;
+
+ if (sim.getSchedule().getTime() >= 2500) {
+ int a = 0;
+ }
// Increase age of cell.
age++;
@@ -153,4 +162,18 @@ public int getCarAntigens() {
public int getSelfAntigens() {
return selfTargets;
}
+
+ /**
+ * Returns the number of synnotch antigens on this cell.
+ *
+ * @return the number of self receptor antigens on this cell.
+ */
+ public int getSynNotchAntigens() {
+ return synNotchAntigens;
+ }
+
+ public void updateSynNotchAntigens(int add, int subtract) {
+ synNotchAntigens += add;
+ synNotchAntigens -= subtract;
+ }
}
diff --git a/src/arcade/patch/agent/module/PatchModuleApoptosis.java b/src/arcade/patch/agent/module/PatchModuleApoptosis.java
index 34518d0c8..3c46bf578 100644
--- a/src/arcade/patch/agent/module/PatchModuleApoptosis.java
+++ b/src/arcade/patch/agent/module/PatchModuleApoptosis.java
@@ -8,6 +8,7 @@
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.env.grid.PatchGrid;
import static arcade.patch.util.PatchEnums.State;
@@ -47,17 +48,20 @@ public PatchModuleApoptosis(PatchCell cell) {
@Override
public void step(MersenneTwisterFast random, Simulation sim) {
if (ticker > deathDuration) {
- // Induce one neighboring quiescent cell to proliferate.
- ArrayList neighborhood = location.getNeighbors();
- neighborhood.add(location);
- Bag bag = ((PatchGrid) sim.getGrid()).getObjectsAtLocations(neighborhood);
+ // CART cells do not induce neighboring tissue cells to proliferate.
+ if (!(cell instanceof PatchCellCART)) {
+ // Induce one neighboring quiescent cell to proliferate.
+ ArrayList neighborhood = location.getNeighbors();
+ neighborhood.add(location);
+ Bag bag = ((PatchGrid) sim.getGrid()).getObjectsAtLocations(neighborhood);
- bag.shuffle(random);
- for (Object obj : bag) {
- Cell neighbor = (Cell) obj;
- if (neighbor.getState() == State.QUIESCENT) {
- neighbor.setState(State.PROLIFERATIVE);
- break;
+ bag.shuffle(random);
+ for (Object obj : bag) {
+ Cell neighbor = (Cell) obj;
+ if (neighbor.getState() == State.QUIESCENT) {
+ neighbor.setState(State.PROLIFERATIVE);
+ break;
+ }
}
}
diff --git a/src/arcade/patch/agent/module/PatchModuleCytotoxicity.java b/src/arcade/patch/agent/module/PatchModuleCytotoxicity.java
index f302193a2..7f4997e25 100644
--- a/src/arcade/patch/agent/module/PatchModuleCytotoxicity.java
+++ b/src/arcade/patch/agent/module/PatchModuleCytotoxicity.java
@@ -1,5 +1,7 @@
package arcade.patch.agent.module;
+import java.util.HashMap;
+import java.util.Map;
import ec.util.MersenneTwisterFast;
import arcade.core.sim.Simulation;
import arcade.core.util.Parameters;
@@ -7,6 +9,10 @@
import arcade.patch.agent.cell.PatchCellCART;
import arcade.patch.agent.cell.PatchCellTissue;
import arcade.patch.agent.process.PatchProcessInflammation;
+import arcade.patch.env.location.PatchLocation;
+import arcade.patch.sim.PatchSimulation;
+import arcade.patch.util.PatchEnums.Domain;
+import arcade.patch.util.PatchEnums.State;
import static arcade.patch.util.PatchEnums.Domain;
import static arcade.patch.util.PatchEnums.State;
@@ -68,6 +74,18 @@ public void step(MersenneTwisterFast random, Simulation sim) {
tissueCell.setState(State.APOPTOTIC);
granzyme--;
inflammation.setInternal("granzyme", granzyme);
+
+ // Log cytotoxicity event
+ PatchSimulation patchSim = (PatchSimulation) sim;
+ Map eventData = new HashMap<>();
+ eventData.put("t-cell-id", cell.getID());
+ eventData.put("tissue-cell-id", target.getID());
+ eventData.put("tissue-cell-type", target.getPop());
+ eventData.put("type", "lysis");
+ eventData.put("timestamp", (int) ((PatchSimulation) sim).getSchedule().getTime());
+ eventData.put(
+ "tissue-location", ((PatchLocation) target.getLocation()).getCoordinate());
+ patchSim.logEvent(eventData);
}
}
diff --git a/src/arcade/patch/agent/module/PatchModuleMigration.java b/src/arcade/patch/agent/module/PatchModuleMigration.java
index e17987aff..99d91529a 100644
--- a/src/arcade/patch/agent/module/PatchModuleMigration.java
+++ b/src/arcade/patch/agent/module/PatchModuleMigration.java
@@ -25,6 +25,9 @@ public class PatchModuleMigration extends PatchModule {
/** Time required for cell migration [min]. */
private final double movementDuration;
+ /** Boolean indicating if cell is on a matrigel. */
+ private final double matrigel;
+
/**
* Creates a migration {@link PatchModule} for the given cell.
*
@@ -32,6 +35,7 @@ public class PatchModuleMigration extends PatchModule {
*
*
* - {@code MIGRATION_RATE} = cell migration rate
+ *
- {@code MATRIGEL} = boolean indicating if cell is on a matrigel
*
*
* @param cell the {@link PatchCell} the module is associated with
@@ -42,6 +46,7 @@ public PatchModuleMigration(PatchCell cell) {
// Set loaded parameters.
Parameters parameters = cell.getParameters();
migrationRate = parameters.getDouble("migration/MIGRATION_RATE");
+ matrigel = parameters.getDouble("migration/MATRIGEL");
movementDuration = Math.round(location.getCoordinateSize() / migrationRate);
}
@@ -49,7 +54,7 @@ public PatchModuleMigration(PatchCell cell) {
public void step(MersenneTwisterFast random, Simulation sim) {
if (ticker > movementDuration) {
PatchLocation newLocation = cell.selectBestLocation(sim, random);
- if (newLocation == null) {
+ if (matrigel > 0 || newLocation == null) {
if (cell instanceof PatchCellCART) {
cell.setState(State.PAUSED);
} else {
diff --git a/src/arcade/patch/agent/module/PatchModuleProliferation.java b/src/arcade/patch/agent/module/PatchModuleProliferation.java
index e5134bf8b..ec9232d58 100644
--- a/src/arcade/patch/agent/module/PatchModuleProliferation.java
+++ b/src/arcade/patch/agent/module/PatchModuleProliferation.java
@@ -9,6 +9,7 @@
import arcade.core.util.Parameters;
import arcade.patch.agent.cell.PatchCell;
import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.agent.cell.PatchCellCARTCombinedCombinatorial;
import arcade.patch.agent.process.PatchProcess;
import arcade.patch.env.grid.PatchGrid;
import arcade.patch.env.location.PatchLocation;
@@ -59,7 +60,7 @@ public PatchModuleProliferation(PatchCell cell) {
duration = 0;
// Load parameters.
Parameters parameters = cell.getParameters();
- synthesisDuration = parameters.getInt("proliferation/SYNTHESIS_DURATION");
+ synthesisDuration = parameters.getInt("SYNTHESIS_DURATION");
}
@Override
@@ -90,7 +91,6 @@ public void step(MersenneTwisterFast random, Simulation sim) {
}
} else if (cell.getVolume() >= targetVolume) {
if (ticker > synthesisDuration) {
-
cell.addCycle(duration);
// Reset current cell.
cell.setState(State.UNDEFINED);
@@ -106,6 +106,22 @@ public void step(MersenneTwisterFast random, Simulation sim) {
newLocation,
random,
newParameters);
+ if (cell instanceof PatchCellCART) {
+ ((PatchCellCART) newCell)
+ .setActivationStatus(((PatchCellCART) cell).getActivationStatus());
+ ((PatchCellCART) newCell).boundSelfAntigensCount =
+ ((PatchCellCART) cell).boundSelfAntigensCount;
+ ((PatchCellCART) newCell).selfReceptors =
+ ((PatchCellCART) cell).selfReceptors;
+ ((PatchCellCART) newCell).boundCARAntigensCount =
+ ((PatchCellCART) cell).boundCARAntigensCount;
+ if (cell instanceof PatchCellCARTCombinedCombinatorial) {
+ ((PatchCellCARTCombinedCombinatorial) newCell).boundSynNotch =
+ ((PatchCellCARTCombinedCombinatorial) cell).boundSynNotch;
+ ((PatchCellCARTCombinedCombinatorial) newCell)
+ .setCars(((PatchCellCARTCombinedCombinatorial) cell).getCars());
+ }
+ }
sim.getGrid().addObject(newCell, newLocation);
newCell.schedule(sim.getSchedule());
diff --git a/src/arcade/patch/agent/module/PatchModuleStimulation.java b/src/arcade/patch/agent/module/PatchModuleStimulation.java
index 48f961dcb..8f4c3d0cb 100644
--- a/src/arcade/patch/agent/module/PatchModuleStimulation.java
+++ b/src/arcade/patch/agent/module/PatchModuleStimulation.java
@@ -9,11 +9,9 @@
import arcade.patch.util.PatchEnums.State;
/**
- * 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.
+ * Implementation of {@link Module} for stimulatory T cell agents. {@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 thgt CAR T-cell is bound to. */
diff --git a/src/arcade/patch/agent/process/PatchProcessInflammation.java b/src/arcade/patch/agent/process/PatchProcessInflammation.java
index 29675a561..1c8a37efc 100644
--- a/src/arcade/patch/agent/process/PatchProcessInflammation.java
+++ b/src/arcade/patch/agent/process/PatchProcessInflammation.java
@@ -4,6 +4,7 @@
import java.util.ArrayList;
import java.util.List;
import ec.util.MersenneTwisterFast;
+import arcade.core.agent.process.Process;
import arcade.core.env.location.Location;
import arcade.core.sim.Simulation;
import arcade.core.util.Parameters;
@@ -19,7 +20,7 @@
*
*
The {@code Inflammation} module represents an 8-component signaling network.
*/
-public abstract class PatchProcessInflammation extends PatchProcess {
+public class PatchProcessInflammation extends PatchProcess {
/** Number of components in signaling network. */
protected static final int NUM_COMPONENTS = 8;
@@ -117,6 +118,30 @@ public abstract class PatchProcessInflammation extends PatchProcess {
/** Total 2-complex receptors. */
protected final double iL2Receptors;
+ /** 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;
+
+ /** Rate of IL-2 production due to antigen-induced activation [molecules IL-2/cell/min]. */
+ private final double iL2ProdRateActive = 293.27;
+
+ /** Rate of IL-2 production due to IL-2 feedback [molecules IL-2/cell/min]. */
+ private final double iL2ProdRateMaxFeedback = 16.62;
+
+ /** Delay in IL-2 synthesis after antigen-induced activation. */
+ private final int iL2SynthesisDelay;
+
+ /** Total rate of IL-2 production. */
+ private double iL2ProdRate;
+
+ /** Amount of IL-2 bound in past being used for current IL-2 production calculation. */
+ private double priorIL2prod;
+
/**
* Creates an {@code Inflammation} module for the given {@link PatchCellCART}.
*
@@ -138,6 +163,11 @@ public PatchProcessInflammation(PatchCellCART cell) {
Parameters parameters = cell.getParameters();
this.shellThickness = parameters.getDouble("inflammation/SHELL_THICKNESS");
this.iL2Receptors = parameters.getDouble("inflammation/IL2_RECEPTORS");
+ this.iL2SynthesisDelay = parameters.getInt("inflammation/IL2_SYNTHESIS_DELAY");
+ this.granzSynthesisDelay = parameters.getInt("inflammation/GRANZ_SYNTHESIS_DELAY");
+
+ priorIL2granz = 0;
+ iL2ProdRate = 0;
extIL2 = 0;
amts = new double[NUM_COMPONENTS];
@@ -147,6 +177,7 @@ public PatchProcessInflammation(PatchCellCART cell) {
amts[IL2RBGA] = 0;
amts[IL2_IL2RBG] = 0;
amts[IL2_IL2RBGA] = 0;
+ amts[GRANZYME] = 1; // [molecules]
names = new ArrayList();
names.add(IL2_INT_TOTAL, "IL-2");
@@ -156,6 +187,7 @@ public PatchProcessInflammation(PatchCellCART cell) {
names.add(IL2RBGA, "IL2R_three_chain_complex");
names.add(IL2_IL2RBG, "IL-2_IL2R_two_chain_complex");
names.add(IL2_IL2RBGA, "IL-2_IL2R_three_chain_complex");
+ names.add(GRANZYME, "granzyme");
// Initialize prior IL2 array.
this.boundArray = new double[180];
@@ -252,7 +284,80 @@ public void setInternal(String key, double val) {
* @param random the random number generator
* @param sim the simulation instance
*/
- abstract void stepProcess(MersenneTwisterFast random, Simulation sim);
+ void stepProcess(MersenneTwisterFast random, Simulation sim) {
+ stepCD4(sim);
+ stepCD8(sim);
+ double iL2Env =
+ (((extIL2 - (extIL2 * fraction - amts[IL2_EXT])) + iL2ProdRate)
+ * 1E12
+ / loc.getVolume());
+
+ sim.getLattice("IL-2").setValue(loc, iL2Env);
+ }
+
+ void stepCD4(Simulation sim) {
+
+ // Determine IL-2 production rate as a function of IL-2 bound.
+ int prodIndex = (iL2Ticker % boundArray.length) - iL2SynthesisDelay;
+ if (prodIndex < 0) {
+ prodIndex += boundArray.length;
+ }
+ priorIL2prod = boundArray[prodIndex];
+ iL2ProdRate = iL2ProdRateMaxFeedback * (priorIL2prod / iL2Receptors);
+
+ // Add IL-2 production rate dependent on antigen-induced
+ // cell activation if cell is activated.
+ if (active && activeTicker >= iL2SynthesisDelay) {
+ iL2ProdRate += iL2ProdRateActive;
+ }
+ }
+
+ void stepCD8(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);
+ }
+ }
+
+ @Override
+ public void update(Process process) {
+ PatchProcessInflammation inflammation = (PatchProcessInflammation) process;
+ 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);
+ }
/**
* Gets the external amounts of IL-2.
@@ -313,7 +418,7 @@ public static PatchProcess make(PatchCell cell, String version) {
case "CD8":
return new PatchProcessInflammationCD8((PatchCellCART) cell);
default:
- return null;
+ return new PatchProcessInflammation((PatchCellCART) cell);
}
}
}
diff --git a/src/arcade/patch/command.patch.xml b/src/arcade/patch/command.patch.xml
index 4902b6289..2fda14ff3 100644
--- a/src/arcade/patch/command.patch.xml
+++ b/src/arcade/patch/command.patch.xml
@@ -1,4 +1,5 @@
+
diff --git a/src/arcade/patch/env/component/PatchComponentDegrade.java b/src/arcade/patch/env/component/PatchComponentDegrade.java
index 35da75184..eaa2ec574 100644
--- a/src/arcade/patch/env/component/PatchComponentDegrade.java
+++ b/src/arcade/patch/env/component/PatchComponentDegrade.java
@@ -102,6 +102,9 @@ public void step(SimState state) {
// Get agents at locations.
locations.remove(null);
+ if (!locations.isEmpty()) {
+ continue;
+ }
Bag agents = grid.getObjectsAtLocations(new ArrayList<>(locations));
// If any agents are cancerous, then degrade the wall.
diff --git a/src/arcade/patch/env/component/PatchComponentSitesGraph.java b/src/arcade/patch/env/component/PatchComponentSitesGraph.java
index 771147dd6..8d21fabcf 100644
--- a/src/arcade/patch/env/component/PatchComponentSitesGraph.java
+++ b/src/arcade/patch/env/component/PatchComponentSitesGraph.java
@@ -130,7 +130,7 @@ public Graph getGraph() {
* @param to the node the edge extends to
* @return the list of span coordinates
*/
- abstract ArrayList getSpan(SiteNode from, SiteNode to);
+ public abstract ArrayList getSpan(SiteNode from, SiteNode to);
/**
* Checks if given coordinates are within the environment to add to list.
@@ -152,7 +152,7 @@ void checkSite(ArrayList s, int x, int y, int z) {
* @param span the span coordinate
* @return a location object
*/
- abstract Location getLocation(CoordinateXYZ span);
+ public abstract Location getLocation(CoordinateXYZ span);
/**
* Initializes graph for representing sites.
diff --git a/src/arcade/patch/env/location/PatchLocationContainer.java b/src/arcade/patch/env/location/PatchLocationContainer.java
index 26524ab76..c47d1c534 100644
--- a/src/arcade/patch/env/location/PatchLocationContainer.java
+++ b/src/arcade/patch/env/location/PatchLocationContainer.java
@@ -40,7 +40,6 @@ public Location convert(LocationFactory factory, CellContainer cell) {
} else {
location = new PatchLocationHex((CoordinateUVWZ) coordinate);
}
-
return location;
}
}
diff --git a/src/arcade/patch/parameter.patch.xml b/src/arcade/patch/parameter.patch.xml
index 40824712c..bb86f29d1 100644
--- a/src/arcade/patch/parameter.patch.xml
+++ b/src/arcade/patch/parameter.patch.xml
@@ -25,6 +25,7 @@
+
@@ -44,11 +45,25 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -118,6 +133,11 @@
+
+
+
+
+
@@ -125,6 +145,13 @@
+
+
+
+
+
+
+
diff --git a/src/arcade/patch/sim/PatchSimulation.java b/src/arcade/patch/sim/PatchSimulation.java
index a98b60158..e4eb702eb 100644
--- a/src/arcade/patch/sim/PatchSimulation.java
+++ b/src/arcade/patch/sim/PatchSimulation.java
@@ -4,6 +4,8 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import com.google.gson.reflect.TypeToken;
import sim.engine.Schedule;
@@ -55,6 +57,9 @@ public abstract class PatchSimulation extends SimState implements Simulation {
/** Cell ID tracker. */
int id;
+ /** List to store events throughout the simulation. */
+ private final List