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: + * + *

+ * + * @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 { * *

* * @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> events; + /** Location factory instance for the simulation. */ public final PatchLocationFactory locationFactory; @@ -77,6 +82,7 @@ public PatchSimulation(long seed, Series series) { super(seed); this.series = (PatchSeries) series; this.seed = (int) seed - Series.SEED_OFFSET; + this.events = new ArrayList<>(); this.locationFactory = makeLocationFactory(); this.cellFactory = makeCellFactory(); @@ -103,6 +109,25 @@ public final int getID() { return ++id; } + /** + * Log an event to the simulation's event list. + * + * @param eventType the type of event + * @param eventData the event data + */ + public void logEvent(Map event) { + events.add(event); + } + + /** + * Get the list of events logged during the simulation. + * + * @return the list of events + */ + public List> getEvents() { + return new ArrayList<>(events); + } + @Override public final ArrayList getCells() { ArrayList cellContainers = new ArrayList<>(); diff --git a/src/arcade/patch/sim/PatchSimulationHex.java b/src/arcade/patch/sim/PatchSimulationHex.java index 6eddf28b5..237e7b9fc 100644 --- a/src/arcade/patch/sim/PatchSimulationHex.java +++ b/src/arcade/patch/sim/PatchSimulationHex.java @@ -7,6 +7,8 @@ import arcade.patch.agent.action.PatchActionConvert; import arcade.patch.agent.action.PatchActionInsert; import arcade.patch.agent.action.PatchActionRemove; +import arcade.patch.agent.action.PatchActionReplace; +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 +60,10 @@ public Action makeAction(String actionClass, MiniBox parameters) { return new PatchActionRemove(series, parameters); case "convert": return new PatchActionConvert(series, parameters); + case "replace": + return new PatchActionReplace(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..7e516a473 100644 --- a/src/arcade/patch/sim/PatchSimulationRect.java +++ b/src/arcade/patch/sim/PatchSimulationRect.java @@ -7,6 +7,8 @@ import arcade.patch.agent.action.PatchActionConvert; import arcade.patch.agent.action.PatchActionInsert; import arcade.patch.agent.action.PatchActionRemove; +import arcade.patch.agent.action.PatchActionReplace; +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 +60,10 @@ public Action makeAction(String actionClass, MiniBox parameters) { return new PatchActionRemove(series, parameters); case "convert": return new PatchActionConvert(series, parameters); + case "replace": + return new PatchActionReplace(series, parameters); + case "treat": + return new PatchActionTreat(series, parameters); default: return null; } diff --git a/src/arcade/patch/sim/output/PatchOutputDeserializer.java b/src/arcade/patch/sim/output/PatchOutputDeserializer.java index 80c1c73f2..da136db75 100644 --- a/src/arcade/patch/sim/output/PatchOutputDeserializer.java +++ b/src/arcade/patch/sim/output/PatchOutputDeserializer.java @@ -10,6 +10,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; +import sim.util.Bag; import arcade.core.agent.cell.CellContainer; import arcade.core.env.location.LocationContainer; import arcade.core.sim.output.OutputDeserializer; @@ -67,7 +68,10 @@ public PatchCellContainer deserialize( int divisions = jsonObject.get("divisions").getAsInt(); double volume = jsonObject.get("volume").getAsDouble(); double height = jsonObject.get("height").getAsDouble(); - + Bag cycles = new Bag(); + for (JsonElement cycle : jsonObject.get("cycles").getAsJsonArray()) { + cycles.add(cycle.getAsDouble()); + } State state = State.valueOf(jsonObject.get("state").getAsString()); JsonArray criticals = jsonObject.get("criticals").getAsJsonArray(); @@ -84,7 +88,8 @@ public PatchCellContainer deserialize( volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cycles); } } diff --git a/src/arcade/patch/sim/output/PatchOutputSaver.java b/src/arcade/patch/sim/output/PatchOutputSaver.java index 0a3447d8f..aa2a02722 100644 --- a/src/arcade/patch/sim/output/PatchOutputSaver.java +++ b/src/arcade/patch/sim/output/PatchOutputSaver.java @@ -1,5 +1,7 @@ package arcade.patch.sim.output; +import java.util.List; +import java.util.Map; import com.google.gson.Gson; import arcade.core.env.component.Component; import arcade.core.sim.Series; @@ -17,6 +19,9 @@ public final class PatchOutputSaver extends OutputSaver { /** {@code true} to save lattices, {@code false} otherwise. */ public boolean saveLattice; + /** {@code true} to save events, {@code false} otherwise. */ + public boolean saveEvents; + /** * Creates an {@code PatchOutputSaver} for the series. * @@ -26,6 +31,22 @@ public PatchOutputSaver(Series series) { super(series); } + /** + * Save the collection of events to a JSON. + * + * @param tick the simulation tick + */ + public void saveEventsToFile(int tick) { + if (saveEvents) { + List> events = ((PatchSimulation) sim).getEvents(); + if (!events.isEmpty()) { + String json = gson.toJson(events); + String path = prefix + String.format("_%06d.EVENTS.json", tick); + write(path, format(json, FORMAT_ELEMENTS)); + } + } + } + @Override protected Gson makeGSON() { return PatchOutputSerializer.makeGSON(); @@ -70,5 +91,8 @@ public void save(int tick) { if (saveLattice) { saveLayers(tick); } + if (saveEvents) { + saveEventsToFile(tick); + } } } diff --git a/src/arcade/patch/sim/output/PatchOutputSerializer.java b/src/arcade/patch/sim/output/PatchOutputSerializer.java index cdfa02efa..042005a52 100644 --- a/src/arcade/patch/sim/output/PatchOutputSerializer.java +++ b/src/arcade/patch/sim/output/PatchOutputSerializer.java @@ -10,6 +10,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; +import sim.util.Bag; import arcade.core.agent.cell.CellContainer; import arcade.core.env.location.Location; import arcade.core.env.location.LocationContainer; @@ -156,7 +157,12 @@ public JsonElement serialize( json.add("criticals", criticals); // TODO: add cycles - + Bag cycles = src.cycles; + JsonArray jsonCycles = new JsonArray(); + for (Object cycle : cycles) { + jsonCycles.add(cycle.toString()); + } + json.add("cycles", jsonCycles); return json; } } diff --git a/src/arcade/patch/util/PatchEnums.java b/src/arcade/patch/util/PatchEnums.java index 02cfbc500..92947a3e3 100644 --- a/src/arcade/patch/util/PatchEnums.java +++ b/src/arcade/patch/util/PatchEnums.java @@ -200,6 +200,71 @@ public static AntigenFlag random(MersenneTwisterFast rng) { } } + /** Combinatorial circuit types. */ + public enum LogicalCARs { + /** Code for inducible synNotch. */ + INDUCIBLE_SYNNOTCH, + + /** Code for inducible inflammation. */ + INDUCIBLE_INFLAMMATION, + + /** Code for inhibitory receptor. */ + INHIBITORY_RECEPTOR, + + /** Code for inhibitory inflammation. */ + INHIBITORY_INFLAMMATION; + + /** + * Randomly selects a {@code LogicalCARs}. + * + * @param rng the random number generator + * @return a random {@code LogicalCARs} + */ + public static LogicalCARs random(MersenneTwisterFast rng) { + return values()[rng.nextInt(values().length - 1) + 1]; + } + } + + /** Cell types for immune cell classification. */ + public enum Immune { + /** Code for cd4 T cell. */ + CART_CD4, + + /** Code for cd8 T cell. */ + CART_CD8, + + /** Code for combined cd4/cd8 T cell. */ + COMBINED, + + /** Code for macrophage cell. */ + MACROPHAGE, + + /** Code for killer cd8 cell. */ + KILLER, + + /** Code for inducible synNotch. */ + INDUCIBLE_SYNNOTCH, + + /** Code for inducible inflammation. */ + INDUCIBLE_INFLAMMATION, + + /** Code for inhibitory receptor. */ + INHIBITORY_RECEPTOR, + + /** Code for inhibitory inflammation. */ + INHIBITORY_INFLAMMATION; + + /** + * Randomly selects a {@code Immune} cell type. + * + * @param rng the random number generator + * @return a random {@code Immune} cell type + */ + public static Immune random(MersenneTwisterFast rng) { + return values()[rng.nextInt(values().length - 1) + 1]; + } + } + /** Operation category codes for patch simulations. */ public enum Category implements OperationCategory { /** Code for undefined category. */ diff --git a/test/arcade/patch/agent/cell/PatchCellCARTCD4Test.java b/test/arcade/patch/agent/cell/PatchCellCARTCD4Test.java index 275892652..c98739fec 100644 --- a/test/arcade/patch/agent/cell/PatchCellCARTCD4Test.java +++ b/test/arcade/patch/agent/cell/PatchCellCARTCD4Test.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import sim.engine.Schedule; import sim.engine.Steppable; +import sim.util.Bag; import ec.util.MersenneTwisterFast; import arcade.core.sim.Simulation; import arcade.core.util.MiniBox; @@ -50,6 +51,7 @@ public final void setUp() { double height = randomDoubleBetween(4, 10); double criticalVolume = randomDoubleBetween(100, 200); double criticalHeight = randomDoubleBetween(4, 10); + Bag cycles = new Bag(); State state = State.UNDEFINED; container = @@ -63,7 +65,8 @@ public final void setUp() { volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cycles); doReturn(1.0).when(parametersMock).getDouble(any(String.class)); doReturn(1).when(parametersMock).getInt(any(String.class)); diff --git a/test/arcade/patch/agent/cell/PatchCellCARTCD8Test.java b/test/arcade/patch/agent/cell/PatchCellCARTCD8Test.java index a28ec9bb5..f13b88e77 100644 --- a/test/arcade/patch/agent/cell/PatchCellCARTCD8Test.java +++ b/test/arcade/patch/agent/cell/PatchCellCARTCD8Test.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import sim.engine.Schedule; import sim.engine.Steppable; +import sim.util.Bag; import ec.util.MersenneTwisterFast; import arcade.core.sim.Simulation; import arcade.core.util.MiniBox; @@ -15,7 +16,9 @@ import arcade.patch.agent.process.PatchProcessSignaling; import arcade.patch.env.location.PatchLocation; import arcade.patch.sim.PatchSimulation; +import arcade.patch.util.PatchEnums.AntigenFlag; import arcade.patch.util.PatchEnums.Domain; +import arcade.patch.util.PatchEnums.State; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -51,6 +54,7 @@ public final void setUp() throws NoSuchFieldException, IllegalAccessException { double criticalVolume = randomDoubleBetween(100, 200); double criticalHeight = randomDoubleBetween(4, 10); State state = State.UNDEFINED; + Bag cycles = new Bag(); container = new PatchCellContainer( @@ -63,7 +67,8 @@ public final void setUp() throws NoSuchFieldException, IllegalAccessException { volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cycles); doReturn(0.0).when(parameters).getDouble(any(String.class)); doReturn(0).when(parameters).getInt(any(String.class)); diff --git a/test/arcade/patch/agent/cell/PatchCellCARTTest.java b/test/arcade/patch/agent/cell/PatchCellCARTTest.java index 241e65ab9..d27d7c477 100644 --- a/test/arcade/patch/agent/cell/PatchCellCARTTest.java +++ b/test/arcade/patch/agent/cell/PatchCellCARTTest.java @@ -44,6 +44,8 @@ public class PatchCellCARTTest { private static Bag bag; + static Bag cycles = new Bag(); + static class PatchCellMock extends PatchCellCART { PatchCellMock(PatchCellContainer container, Location location, Parameters parameters) { super(container, location, parameters, null); @@ -61,7 +63,8 @@ public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFas volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cycles); } @Override @@ -98,7 +101,8 @@ public final void setUp() throws NoSuchFieldException, IllegalAccessException { volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cycles); when(parameters.getDouble("ENERGY_THRESHOLD")).thenReturn(1.0); when(parameters.getDouble("NECROTIC_FRACTION")) diff --git a/test/arcade/patch/agent/cell/PatchCellCancerStemTest.java b/test/arcade/patch/agent/cell/PatchCellCancerStemTest.java index a87bc1566..ffa549f52 100644 --- a/test/arcade/patch/agent/cell/PatchCellCancerStemTest.java +++ b/test/arcade/patch/agent/cell/PatchCellCancerStemTest.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import sim.util.Bag; import ec.util.MersenneTwisterFast; import arcade.core.util.GrabBag; import arcade.core.util.MiniBox; @@ -60,6 +61,8 @@ public class PatchCellCancerStemTest { static State cellState = State.QUIESCENT; + static Bag cellCycles = new Bag(); + @BeforeAll public static void setupMocks() { simMock = mock(PatchSimulation.class); @@ -99,7 +102,8 @@ public void step_calledWhenAgeGreaterThanApoptosisAge_doesNotSetApoptoticState() cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cellCycles); PatchCell cell = spy(new PatchCellCancerStem(container, locationMock, parametersMock)); cell.module = module; cell.processes.put(Domain.METABOLISM, metabolismMock); @@ -149,7 +153,8 @@ public void make_called_createsContainer() { volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cellCycles); PatchCellCancerStem cell = new PatchCellCancerStem(cellContainer, locationMock, parametersMock, links); @@ -198,7 +203,8 @@ public void make_calledWithLinks_createsContainer() { volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cellCycles); PatchCellCancerStem cell = new PatchCellCancerStem(cellContainer, locationMock, parametersMock, links); diff --git a/test/arcade/patch/agent/cell/PatchCellCancerTest.java b/test/arcade/patch/agent/cell/PatchCellCancerTest.java index f7f80df21..36c2e111b 100644 --- a/test/arcade/patch/agent/cell/PatchCellCancerTest.java +++ b/test/arcade/patch/agent/cell/PatchCellCancerTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import sim.util.Bag; import ec.util.MersenneTwisterFast; import arcade.core.util.GrabBag; import arcade.core.util.MiniBox; @@ -56,6 +57,8 @@ public class PatchCellCancerTest { static State cellState = State.QUIESCENT; + static Bag cellCycles = new Bag(); + @BeforeAll public static void setupMocks() { simMock = mock(PatchSimulation.class); @@ -99,7 +102,8 @@ public void make_called_createsContainer() { volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cellCycles); PatchCellCancer cell = new PatchCellCancer(cellContainer, locationMock, parametersMock, links); @@ -148,7 +152,8 @@ public void make_calledWithLinks_createsContainer() { volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cellCycles); PatchCellCancer cell = new PatchCellCancer(cellContainer, locationMock, parametersMock, links); diff --git a/test/arcade/patch/agent/cell/PatchCellRandomTest.java b/test/arcade/patch/agent/cell/PatchCellRandomTest.java index 7f71b8feb..2112db81f 100644 --- a/test/arcade/patch/agent/cell/PatchCellRandomTest.java +++ b/test/arcade/patch/agent/cell/PatchCellRandomTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import sim.util.Bag; import ec.util.MersenneTwisterFast; import arcade.core.util.GrabBag; import arcade.core.util.MiniBox; @@ -43,6 +44,8 @@ public class PatchCellRandomTest { static State cellState = State.QUIESCENT; + static Bag cellCycles = new Bag(); + static PatchCellContainer baseContainer = new PatchCellContainer( cellID, @@ -54,7 +57,8 @@ public class PatchCellRandomTest { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cellCycles); @BeforeAll public static void setupMocks() { @@ -84,7 +88,8 @@ public void make_calledNoLinks_createsContainer() { volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cellCycles); PatchCellRandom cell = new PatchCellRandom(cellContainer, locationMock, parametersMock, null); @@ -130,7 +135,8 @@ public void make_calledWithLinks_createsContainer() { volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cellCycles); PatchCellRandom cell = new PatchCellRandom(cellContainer, locationMock, parametersMock, links); diff --git a/test/arcade/patch/agent/cell/PatchCellTest.java b/test/arcade/patch/agent/cell/PatchCellTest.java index 63ccb0367..3faade153 100644 --- a/test/arcade/patch/agent/cell/PatchCellTest.java +++ b/test/arcade/patch/agent/cell/PatchCellTest.java @@ -58,6 +58,8 @@ public class PatchCellTest { static State cellState = State.QUIESCENT; + static Bag cellCycles = new Bag(); + static PatchCellContainer baseContainer = new PatchCellContainer( cellID, @@ -69,7 +71,8 @@ public class PatchCellTest { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cellCycles); static class PatchCellMock extends PatchCellTissue { PatchCellMock(PatchCellContainer container, Location location, Parameters parameters) { @@ -88,7 +91,8 @@ public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFas volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cycles); } } @@ -251,7 +255,7 @@ public void setState_proliferation_createsCell() { Schedule scheduleMock = mock(Schedule.class); doReturn(0.0).when(parametersMock).getDouble(any(String.class)); doReturn(0).when(parametersMock).getInt(any(String.class)); - doReturn(1).when(parametersMock).getInt("proliferation/SYNTHESIS_DURATION"); + doReturn(1).when(parametersMock).getInt("SYNTHESIS_DURATION"); doReturn(cellID + 1).when(simMock).getID(); doReturn(scheduleMock).when(simMock).getSchedule(); doReturn(null).when(scheduleMock).scheduleRepeating(anyInt(), anyInt(), any()); @@ -275,7 +279,8 @@ public void setState_proliferation_createsCell() { volume, cellHeight, critVolume, - critHeight); + critHeight, + cellCycles); PatchCellContainer daughterContainer = new PatchCellContainer( @@ -288,7 +293,8 @@ public void setState_proliferation_createsCell() { volume, cellHeight, critVolume, - critHeight); + critHeight, + cellCycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); cell.setEnergy(200.); @@ -344,7 +350,8 @@ public void setState_proliferationWithNoFreeLocation_setQuiescent() { volume, cellHeight, critVolume, - critHeight); + critHeight, + cellCycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); Bag locationBag = new Bag(); @@ -379,7 +386,8 @@ public void setState_proliferativeWhenHeightExceedsCriticalHeight_setQuiescent() volume, cellHeight, cellCriticalVolume, - critHeight); + critHeight, + cellCycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); Bag locationBag = new Bag(); @@ -449,7 +457,8 @@ final Bag createPatchCellsWithVolumeAndCriticalHeight(int n, double volume, doub volume, cellHeight, cellCriticalVolume, - critHeight); + critHeight, + cellCycles); PatchCell cell = new PatchCellMock(container, locationMock, parametersMock); bag.add(cell); } @@ -612,7 +621,8 @@ public void findFreeLocations_whenOnlyOneNeighborIsFree_returnsCurrentAndOpenLoc cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cellCycles); PatchCell cell = new PatchCellMock(container, locationMock, parametersMock); Bag currentBag = new Bag(); @@ -664,7 +674,8 @@ public void findFreeLocations_whenOnlyOneNeighborIsFree_returnsCurrentAndOpenLoc cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cellCycles); PatchCell cell = new PatchCellMock(container, locationMock, parametersMock); Bag currentBag = new Bag(); diff --git a/test/arcade/patch/agent/cell/PatchCellTissueTest.java b/test/arcade/patch/agent/cell/PatchCellTissueTest.java index 006acccdf..20a11bfcf 100644 --- a/test/arcade/patch/agent/cell/PatchCellTissueTest.java +++ b/test/arcade/patch/agent/cell/PatchCellTissueTest.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import sim.util.Bag; import ec.util.MersenneTwisterFast; import arcade.core.agent.cell.CellState; import arcade.core.env.location.*; @@ -58,6 +59,8 @@ public class PatchCellTissueTest { static double cellCriticalHeight = randomDoubleBetween(10, 100); + static Bag cycles = new Bag(); + static class PatchCellMock extends PatchCellTissue { PatchCellMock(PatchCellContainer container, Location location, Parameters parameters) { super(container, location, parameters, null); @@ -75,7 +78,8 @@ public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFas volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cycles); } } @@ -117,7 +121,8 @@ public void step_calledWhenAgeGreaterThanApoptosisAge_setsApoptoticState() { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); cell.processes.put(Domain.METABOLISM, metabolismMock); cell.processes.put(Domain.SIGNALING, signalingMock); @@ -160,7 +165,8 @@ public void step_calledWhenAgeLessThanApoptosisAge_doesNotSetState() { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); cell.processes.put(Domain.METABOLISM, metabolismMock); cell.processes.put(Domain.SIGNALING, signalingMock); @@ -199,7 +205,8 @@ public void step_calledApoptoticStateAndApoptoticAge_doesNotResetState() { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); cell.processes.put(Domain.METABOLISM, metabolismMock); cell.processes.put(Domain.SIGNALING, signalingMock); @@ -244,7 +251,8 @@ public void step_nutrientStarved_setNecroticStateWithProbability() { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); cell.processes.put(Domain.METABOLISM, metabolismMock); cell.processes.put(Domain.SIGNALING, signalingMock); @@ -291,7 +299,8 @@ public void step_nutrientStarved_setApototicStateWithProbability() { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); cell.processes.put(Domain.METABOLISM, metabolismMock); cell.processes.put(Domain.SIGNALING, signalingMock); @@ -336,7 +345,8 @@ public void step_energyDeficitDoesNotExceedThreshold_setQuiescent() { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); cell.processes.put(Domain.METABOLISM, metabolismMock); cell.processes.put(Domain.SIGNALING, signalingMock); @@ -377,7 +387,8 @@ public void step_undefinedCellWithMigratoryFlag_setsMigratoryState() { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); cell.processes.put(Domain.METABOLISM, metabolismMock); cell.processes.put(Domain.SIGNALING, signalingMock); @@ -417,7 +428,8 @@ public void step_undefinedCellWithProliferativeFlag_setsProliferativeState() { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); cell.processes.put(Domain.METABOLISM, metabolismMock); cell.processes.put(Domain.SIGNALING, signalingMock); @@ -462,7 +474,8 @@ public void step_undefinedCellWithProliferativeFlag_setsProliferativeState() { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); cell.processes.put(Domain.METABOLISM, metabolismMock); cell.processes.put(Domain.SIGNALING, signalingMock); @@ -507,7 +520,8 @@ public void step_undefinedCellWithProliferativeFlag_setsProliferativeState() { cellVolume, cellHeight, cellCriticalVolume, - cellCriticalHeight); + cellCriticalHeight, + cycles); PatchCell cell = spy(new PatchCellMock(container, locationMock, parametersMock)); cell.processes.put(Domain.METABOLISM, metabolismMock); cell.processes.put(Domain.SIGNALING, signalingMock); @@ -556,7 +570,8 @@ public void make_calledWithoutLinks_createsContainer() { volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cycles); PatchCellTissue cell = new PatchCellTissue(cellContainer, locationMock, parametersMock, links); @@ -605,7 +620,8 @@ public void make_calledWithLinks_createsContainer() { volume, height, criticalVolume, - criticalHeight); + criticalHeight, + cycles); PatchCellTissue cell = new PatchCellTissue(cellContainer, locationMock, parametersMock, links); diff --git a/v2.4 b/v2.4 new file mode 160000 index 000000000..b8adced65 --- /dev/null +++ b/v2.4 @@ -0,0 +1 @@ +Subproject commit b8adced65b0bdf90e1a989e91b3e7c65b5ae88a2