diff --git a/src/arcade/patch/agent/action/PatchActionKill.java b/src/arcade/patch/agent/action/PatchActionKill.java new file mode 100644 index 000000000..a0119a7dd --- /dev/null +++ b/src/arcade/patch/agent/action/PatchActionKill.java @@ -0,0 +1,100 @@ +package arcade.patch.agent.action; + +import sim.engine.Schedule; +import sim.engine.SimState; +import ec.util.MersenneTwisterFast; +import arcade.core.agent.action.Action; +import arcade.core.sim.Series; +import arcade.core.sim.Simulation; +import arcade.core.util.Parameters; +import arcade.patch.agent.cell.PatchCellCART; +import arcade.patch.agent.cell.PatchCellTissue; +import arcade.patch.agent.process.PatchProcessInflammation; +import arcade.patch.util.PatchEnums.AntigenFlag; +import arcade.patch.util.PatchEnums.Domain; +import arcade.patch.util.PatchEnums.Ordering; +import arcade.patch.util.PatchEnums.State; + +/** + * Implementation of {@link Action} for killing tissue agents. + * + *

{@code PatchActionKill} is stepped once after a CD8 CAR T-cell binds to a target tissue cell. + * The {@code PatchActionKill} determines if cell has enough granzyme to kill. If so, it kills cell + * and calls the reset to neutral helper to return to neutral state. If not, it waits until it has + * enough granzyme to kill cell. + */ +public class PatchActionKill implements Action { + + /** Target cell cytotoxic CAR T-cell is bound to */ + PatchCellTissue target; + + /** CAR T-cell inflammation module */ + PatchProcessInflammation inflammation; + + /** Amount of granzyme inside CAR T-cell */ + double granzyme; + + /** CAR T-cell that the module is linked to */ + PatchCellCART c; + + /** Time delay before calling the action [min]. */ + private final int timeDelay; + + /** + * Creates a {@code PatchActionKill} for the given {@link PatchCellCART}. + * + * @param c the {@link PatchCellCART} the helper is associated with + * @param target the {@link PatchCellTissue} the CAR T-cell is bound to + */ + public PatchActionKill( + PatchCellCART c, + PatchCellTissue target, + MersenneTwisterFast random, + Series series, + Parameters parameters) { + this.c = c; + this.target = target; + this.inflammation = (PatchProcessInflammation) c.getProcess(Domain.INFLAMMATION); + this.granzyme = inflammation.getInternal("granzyme"); + timeDelay = 0; + } + + @Override + public void schedule(Schedule schedule) { + schedule.scheduleOnce(schedule.getTime() + timeDelay, Ordering.ACTIONS.ordinal(), this); + } + + @Override + public void register(Simulation sim, String population) {} + + @Override + public void step(SimState state) { + Simulation sim = (Simulation) state; + + // If current CAR T-cell is stopped, stop helper. + if (c.isStopped()) { + return; + } + + // If bound target cell is stopped, stop helper. + if (target.isStopped()) { + if (c.getBindingFlag() == AntigenFlag.BOUND_ANTIGEN) { + c.setBindingFlag(AntigenFlag.UNBOUND); + } else { + c.setBindingFlag(AntigenFlag.BOUND_CELL_RECEPTOR); + } + return; + } + + if (granzyme >= 1) { + + // Kill bound target cell. + PatchCellTissue tissueCell = (PatchCellTissue) target; + tissueCell.setState(State.APOPTOTIC); + + // Use up some granzyme in the process. + granzyme--; + inflammation.setInternal("granzyme", granzyme); + } + } +} diff --git a/src/arcade/patch/agent/action/PatchActionReset.java b/src/arcade/patch/agent/action/PatchActionReset.java new file mode 100644 index 000000000..78ffa48c9 --- /dev/null +++ b/src/arcade/patch/agent/action/PatchActionReset.java @@ -0,0 +1,67 @@ +package arcade.patch.agent.action; + +import sim.engine.Schedule; +import sim.engine.SimState; +import ec.util.MersenneTwisterFast; +import arcade.core.agent.action.Action; +import arcade.core.sim.Series; +import arcade.core.sim.Simulation; +import arcade.core.util.Parameters; +import arcade.patch.agent.cell.PatchCellCART; +import arcade.patch.agent.process.PatchProcessInflammation; +import arcade.patch.util.PatchEnums.AntigenFlag; +import arcade.patch.util.PatchEnums.Ordering; +import arcade.patch.util.PatchEnums.State; + +/** + * Implementation of {@link Action} for resetting T cell agents. + * + *

{@code PatchActionReset} is stepped once after a CD8 CAR T-cell binds to a target tissue cell, + * or after a CD4 CAR T-cell gets stimulated. The {@code PatchReset} unbinds to any target cell that + * the T cell is bound to, and sets the cell state back to undefined. + */ +public class PatchActionReset implements Action { + + /** CAR T-cell inflammation module */ + PatchProcessInflammation inflammation; + + /** CAR T-cell that the module is linked to */ + PatchCellCART c; + + /** Time delay before calling the action [min]. */ + private final int timeDelay; + + /** + * Creates a {@code PatchActionReset} for the given {@link PatchCellCART}. + * + * @param c the {@link PatchCellCART} the helper is associated with + */ + public PatchActionReset( + PatchCellCART c, MersenneTwisterFast random, Series series, Parameters parameters) { + this.c = c; + double boundTime = parameters.getInt("BOUND_TIME"); + double boundRange = parameters.getInt("BOUND_RANGE"); + timeDelay = (int) (boundTime + Math.round((boundRange * (2 * random.nextInt() - 1)))); + } + + @Override + public void schedule(Schedule schedule) { + schedule.scheduleOnce(schedule.getTime() + timeDelay, Ordering.ACTIONS.ordinal(), this); + } + + @Override + public void register(Simulation sim, String population) {} + + @Override + public void step(SimState state) { + // If current CAR T-cell is stopped, stop helper. + if (c.isStopped()) { + return; + } + + if (c.getState() == State.CYTOTOXIC || c.getState() == State.STIMULATORY) { + c.setBindingFlag(AntigenFlag.UNBOUND); + c.setState(State.UNDEFINED); + } + } +} diff --git a/src/arcade/patch/agent/action/PatchActionTreat.java b/src/arcade/patch/agent/action/PatchActionTreat.java new file mode 100644 index 000000000..071d1942a --- /dev/null +++ b/src/arcade/patch/agent/action/PatchActionTreat.java @@ -0,0 +1,368 @@ +package arcade.patch.agent.action; + +import java.util.ArrayList; +import java.util.Objects; +import sim.engine.Schedule; +import sim.engine.SimState; +import sim.util.Bag; +import arcade.core.agent.action.Action; +import arcade.core.env.location.Location; +import arcade.core.env.location.LocationContainer; +import arcade.core.sim.Series; +import arcade.core.sim.Simulation; +import arcade.core.util.Graph; +import arcade.core.util.MiniBox; +import arcade.core.util.Utilities; +import arcade.patch.agent.cell.PatchCell; +import arcade.patch.agent.cell.PatchCellCART; +import arcade.patch.agent.cell.PatchCellContainer; +import arcade.patch.agent.cell.PatchCellTissue; +import arcade.patch.env.component.PatchComponentSites; +import arcade.patch.env.component.PatchComponentSitesGraph; +import arcade.patch.env.component.PatchComponentSitesGraph.SiteEdge; +import arcade.patch.env.component.PatchComponentSitesGraphRect; +import arcade.patch.env.component.PatchComponentSitesGraphTri; +import arcade.patch.env.component.PatchComponentSitesPattern; +import arcade.patch.env.component.PatchComponentSitesSource; +import arcade.patch.env.grid.PatchGrid; +import arcade.patch.env.location.Coordinate; +import arcade.patch.env.location.CoordinateXYZ; +import arcade.patch.env.location.PatchLocation; +import arcade.patch.env.location.PatchLocationContainer; +import arcade.patch.sim.PatchSeries; +import arcade.patch.sim.PatchSimulation; +import arcade.patch.util.PatchEnums.Ordering; + +/** + * Implementation of {@link Action} for removing cell agents. + * + *

The action is stepped once after {@code TIME_DELAY}. The {@code TreatAction} will add CAR + * T-cell agents of specified dose and ratio next to source points or vasculature. + */ +public class PatchActionTreat implements Action { + /** Serialization version identifier */ + private static final long serialVersionUID = 0; + + /** Delay before calling the helper (in minutes) */ + private final int delay; + + /** Total number of CAR T-cells to treat with */ + private final int dose; + + /** List of fraction of each population to treat with. CD4 to CD8 ratio */ + private final double treatFrac; + + /** Maximum damage value at which T-cells can spawn next to in source or pattern source */ + private double max_damage; + + /** Minimum radius value at which T- cells can spawn next to in graph source */ + private final double min_damage_radius; + + /** Number of agent positions per lattice site */ + private int latPositions; + + /** Coordinate system used for simulation */ + private final String coord; + + /** List of populations. */ + private final ArrayList populations; + + /** parameters */ + MiniBox parameters; + + /** Maximum confluency of cells in any location */ + final int maxConfluency; + + /** location of available places to insert T cells. For testing purposes only */ + private ArrayList siteLocations; + + /** + * Creates an {@code Action} to add agents after a delay. + * + * @param series the simulation series + * @param parameters the component parameters dictionary + */ + public PatchActionTreat(Series series, MiniBox parameters) { + // Set loaded parameters. + this.delay = parameters.getInt("TIME_DELAY"); + this.dose = parameters.getInt("DOSE"); + this.treatFrac = parameters.getDouble("RATIO"); + this.max_damage = parameters.getDouble("MAX_DAMAGE_SEED"); + this.min_damage_radius = parameters.getDouble("MIN_RADIUS_SEED"); + this.maxConfluency = 54; + this.parameters = parameters; + + this.coord = + ((PatchSeries) series).patch.get("GEOMETRY").equalsIgnoreCase("HEX") + ? "Hex" + : "Rect"; + if (coord.equals("Hex")) { + latPositions = 9; + } + if (coord.equals("Rect")) { + latPositions = 16; + } + + // Initialize population register. + populations = new ArrayList<>(); + } + + @Override + public void schedule(Schedule schedule) { + schedule.scheduleOnce(delay, Ordering.ACTIONS.ordinal(), this); + } + + @Override + public void register(Simulation sim, String population) { + populations.add(sim.getSeries().populations.get(population)); + } + + /** + * Steps the helper to insert cells of the treatment population(s). + * + * @param simstate the MASON simulation state + */ + public void step(SimState simstate) { + + PatchSimulation sim = (PatchSimulation) simstate; + String type = "null"; + PatchGrid grid = (PatchGrid) sim.getGrid(); + PatchComponentSites comp = (PatchComponentSites) sim.getComponent("SITES"); + + ArrayList locs = sim.getLocations(); + + ArrayList siteLocs0 = new ArrayList(); + ArrayList siteLocs1 = new ArrayList(); + ArrayList siteLocs2 = new ArrayList(); + ArrayList siteLocs3 = new ArrayList(); + ArrayList siteLocs = new ArrayList(); + + // Determine type of sites component implemented. + if (comp instanceof PatchComponentSitesSource) { + type = "source"; + } else if (comp instanceof PatchComponentSitesPattern) { + type = "pattern"; + } else if (comp instanceof PatchComponentSitesGraph) { + type = "graph"; + } + + // Find sites without specified level of damage based on component type. + switch (type) { + case "source": + case "pattern": + double[][][] damage; + boolean[][][] sitesLat; + + if (type.equals("source")) { + damage = ((PatchComponentSitesSource) comp).getDamage(); + sitesLat = ((PatchComponentSitesSource) comp).getSources(); + } else { + damage = ((PatchComponentSitesPattern) comp).getDamage(); + sitesLat = ((PatchComponentSitesPattern) comp).getPatterns(); + } + + // Iterate through list of locations and remove locations not next to a site. + for (LocationContainer l : locs) { + PatchLocationContainer contain = (PatchLocationContainer) l; + PatchLocation loc = + (PatchLocation) + contain.convert( + sim.locationFactory, + sim.cellFactory.createCellForPopulation( + 0, populations.get(0).getInt("CODE"))); + CoordinateXYZ coord = (CoordinateXYZ) loc.getSubcoordinate(); + int z = coord.z; + if (sitesLat[z][coord.x][coord.y] + && damage[z][coord.x][coord.y] <= this.max_damage) { + addCellsIntoList(grid, loc, siteLocs0, siteLocs1, siteLocs2, siteLocs3); + } + } + break; + + case "graph": + Graph G = ((PatchComponentSitesGraph) comp).getGraph(); + Bag allEdges = new Bag(G.getAllEdges()); + PatchComponentSitesGraph graphSites = (PatchComponentSitesGraph) comp; + + for (Object edgeObj : allEdges) { + SiteEdge edge = (SiteEdge) edgeObj; + Bag allEdgeLocs = new Bag(); + if (Objects.equals(coord, "Hex")) { + allEdgeLocs.add( + ((PatchComponentSitesGraphTri) graphSites) + .getSpan(edge.getFrom(), edge.getTo())); + } else { + allEdgeLocs.add( + ((PatchComponentSitesGraphRect) graphSites) + .getSpan(edge.getFrom(), edge.getTo())); + } + + for (Object locObj : allEdgeLocs) { + Location loc = (Location) locObj; + if (locs.contains(loc)) { + if (edge.getRadius() >= min_damage_radius) { + addCellsIntoList( + grid, loc, siteLocs0, siteLocs1, siteLocs2, siteLocs3); + } + } + } + } + break; + } + + Utilities.shuffleList(siteLocs3, sim.random); + Utilities.shuffleList(siteLocs2, sim.random); + Utilities.shuffleList(siteLocs1, sim.random); + Utilities.shuffleList(siteLocs0, sim.random); + siteLocs.addAll(siteLocs3); + siteLocs.addAll(siteLocs2); + siteLocs.addAll(siteLocs1); + siteLocs.addAll(siteLocs0); + siteLocations = (ArrayList) siteLocs.clone(); + insert(siteLocs, simstate); + } + + private void addCellsIntoList( + PatchGrid grid, + Location loc, + ArrayList siteLocs0, + ArrayList siteLocs1, + ArrayList siteLocs2, + ArrayList siteLocs3) { + Bag bag = new Bag(grid.getObjectsAtLocation(loc)); + int numAgents = bag.numObjs; + + if (numAgents == 0) { + for (int p = 0; p < latPositions; p++) { + siteLocs0.add(loc); + } + } else if (numAgents == 1) { + for (int p = 0; p < latPositions; p++) { + siteLocs1.add(loc); + } + } else if (numAgents == 2) { + for (int p = 0; p < latPositions; p++) { + siteLocs2.add(loc); + } + } else { + for (int p = 0; p < latPositions; p++) { + siteLocs3.add(loc); + } + } + // Remove break statement if more than one per hex can appear + // with break statement, each location can only be added to list once + // without it, places with more vasc sites get added more times to list + // break; + } + + private void insert(ArrayList coordinates, SimState simstate) { + PatchSimulation sim = (PatchSimulation) simstate; + PatchGrid grid = (PatchGrid) sim.getGrid(); + Utilities.shuffleList(coordinates, sim.random); + + int cd4Code = 0; + int cd8Code = 0; + + for (MiniBox population : populations) { + String className = population.get("CLASS"); + if (className.equals("cart_cd4")) { + cd4Code = population.getInt("CODE"); + } + if (className.equals("cart_cd8")) { + cd8Code = population.getInt("CODE"); + } + } + + for (int i = 0; i < dose; i++) { + + int id = sim.getID(); + + int pop = cd4Code; + + if (sim.random.nextDouble() > treatFrac) { + pop = cd8Code; + } + + PatchLocation loc = ((PatchLocation) coordinates.remove(0)); + Coordinate coord = loc.getCoordinate(); + + // find available location space + while (!coordinates.isEmpty() && !checkLocationSpace(loc, grid)) { + loc = (PatchLocation) coordinates.remove(0); + } + + if (coordinates.isEmpty()) { + break; + } + + PatchLocationContainer locationContainer = new PatchLocationContainer(id, coord); + PatchCellContainer cellContainer = sim.cellFactory.createCellForPopulation(id, pop); + Location location = locationContainer.convert(sim.locationFactory, cellContainer); + PatchCell cell = + (PatchCell) cellContainer.convert(sim.cellFactory, location, sim.random); + grid.addObject(cell, location); + cell.schedule(sim.getSchedule()); + } + } + + protected boolean checkLocationSpace(Location loc, PatchGrid grid) { + boolean available; + int locMax = this.maxConfluency; + double locVolume = ((PatchLocation) loc).getVolume(); + double locArea = ((PatchLocation) loc).getArea(); + + Bag bag = new Bag(grid.getObjectsAtLocation(loc)); + int n = bag.numObjs; // number of agents in location + + if (n == 0) { + available = true; + } // no cells in location + else if (n >= locMax) { + available = false; + } // location already full + else { + available = true; + double totalVol = PatchCell.calculateTotalVolume(bag); + double currentHeight = totalVol / locArea; + + if (totalVol > locVolume) { + available = false; + } + + for (Object cellObj : bag) { + PatchCell cell = (PatchCell) cellObj; + if (cell instanceof PatchCellCART) { + totalVol = + PatchCell.calculateTotalVolume(bag) + + parameters.getDouble("T_CELL_VOL_AVG"); + currentHeight = totalVol / locArea; + } + if (cell instanceof PatchCellTissue) { + if (currentHeight > cell.getCriticalHeight()) { + available = false; + } + } + } + } + + return available; + } + + /** + * Returns registered populations for the action. Exists for testing purposes only + * + * @return registered populations for the action + */ + public ArrayList getPopulations() { + return populations; + } + + /** + * Returns locations of sites available for insertion. Exists for testing purposes only + * + * @return Returns locations of sites available for insertion + */ + public ArrayList getSiteLocs() { + return siteLocations; + } +} diff --git a/src/arcade/patch/agent/cell/PatchCell.java b/src/arcade/patch/agent/cell/PatchCell.java index b495215ae..1e6e71cac 100644 --- a/src/arcade/patch/agent/cell/PatchCell.java +++ b/src/arcade/patch/agent/cell/PatchCell.java @@ -18,11 +18,13 @@ import arcade.core.util.MiniBox; import arcade.core.util.Parameters; import arcade.patch.agent.module.PatchModuleApoptosis; +import arcade.patch.agent.module.PatchModuleCytotoxicity; import arcade.patch.agent.module.PatchModuleMigration; import arcade.patch.agent.module.PatchModuleNecrosis; import arcade.patch.agent.module.PatchModuleProliferation; import arcade.patch.agent.module.PatchModuleQuiescence; import arcade.patch.agent.module.PatchModuleSenescence; +import arcade.patch.agent.module.PatchModuleStimulation; import arcade.patch.agent.process.PatchProcessInflammation; import arcade.patch.agent.process.PatchProcessMetabolism; import arcade.patch.agent.process.PatchProcessSignaling; @@ -331,6 +333,27 @@ public boolean isStopped() { return isStopped; } + /** + * Makes the specified {@link Process} object. + * + * @param domain the process domain + * @param version the process version + * @return the process instance + */ + public Process makeProcess(ProcessDomain domain, String version) { + switch ((Domain) domain) { + case METABOLISM: + return PatchProcessMetabolism.make(this, version); + case SIGNALING: + return PatchProcessSignaling.make(this, version); + case INFLAMMATION: + return PatchProcessInflammation.make(this, version); + case UNDEFINED: + default: + return null; + } + } + @Override public void setState(CellState state) { this.state = state; @@ -355,37 +378,18 @@ public void setState(CellState state) { case SENESCENT: module = new PatchModuleSenescence(this); break; - case CYTOTOXIC: - throw new UnsupportedOperationException(); case STIMULATORY: - throw new UnsupportedOperationException(); + module = new PatchModuleStimulation(this); + break; + case CYTOTOXIC: + module = new PatchModuleCytotoxicity(this); + break; default: module = null; break; } } - /** - * Makes the specified {@link Process} object. - * - * @param domain the process domain - * @param version the process version - * @return the process instance - */ - public Process makeProcess(ProcessDomain domain, String version) { - switch ((Domain) domain) { - case METABOLISM: - return PatchProcessMetabolism.make(this, version); - case SIGNALING: - return PatchProcessSignaling.make(this, version); - case INFLAMMATION: - return PatchProcessInflammation.make(this, version); - case UNDEFINED: - default: - return null; - } - } - @Override public void schedule(Schedule schedule) { stopper = schedule.scheduleRepeating(this, Ordering.CELLS.ordinal(), 1); diff --git a/src/arcade/patch/agent/cell/PatchCellCART.java b/src/arcade/patch/agent/cell/PatchCellCART.java index 8f8cba716..25cc71835 100644 --- a/src/arcade/patch/agent/cell/PatchCellCART.java +++ b/src/arcade/patch/agent/cell/PatchCellCART.java @@ -48,6 +48,10 @@ * parameter classes have support for loading in distributions to reflect heterogeneity. */ public abstract class PatchCellCART extends PatchCell { + + /** Constant for amount of minutes in a day. */ + public static final int MINUTES_IN_DAY = 1440; + /** Cell activation flag. */ protected boolean activated; @@ -108,6 +112,9 @@ public abstract class PatchCellCART extends PatchCell { /** Fraction of proliferative cells that become apoptotic. */ protected final double proliferativeFraction; + /** Target cell that current T cell is bound to. */ + protected PatchCell boundTarget; + /** * Creates a {@code PatchCellCART} agent. * * @@ -155,6 +162,7 @@ public PatchCellCART( boundSelfAntigensCount = 0; lastActiveTicker = 0; activated = true; + boundTarget = null; // Set loaded parameters. exhaustedFraction = parameters.getDouble("EXHAUSTED_FRAC"); @@ -395,4 +403,19 @@ private double computeAffinity(double affinity, PatchLocation loc) { private void updateSelfReceptors() { selfReceptors += (int) ((double) selfReceptorsStart * (0.95 + Math.random() / 10)); } + + /** + * Returns bound cell. + * + * @return the bound cell + */ + public PatchCell getBoundTarget() { + return this.boundTarget; + } + + /** Sets binding flag to unbound and binding target to null. */ + public void unbind() { + super.setBindingFlag(AntigenFlag.UNBOUND); + this.boundTarget = null; + } } diff --git a/src/arcade/patch/agent/cell/PatchCellCARTCD4.java b/src/arcade/patch/agent/cell/PatchCellCARTCD4.java new file mode 100644 index 000000000..fb39f96a8 --- /dev/null +++ b/src/arcade/patch/agent/cell/PatchCellCARTCD4.java @@ -0,0 +1,173 @@ +package arcade.patch.agent.cell; + +import sim.engine.SimState; +import ec.util.MersenneTwisterFast; +import arcade.core.agent.cell.CellState; +import arcade.core.env.location.Location; +import arcade.core.sim.Simulation; +import arcade.core.util.GrabBag; +import arcade.core.util.Parameters; +import arcade.patch.util.PatchEnums.AntigenFlag; +import arcade.patch.util.PatchEnums.Domain; +import arcade.patch.util.PatchEnums.State; + +public class PatchCellCARTCD4 extends PatchCellCART { + + /** + * Creates a T cell {@code PatchCellCARTCD4} agent. * + * + * @param container the cell container + * @param location the {@link Location} of the cell + * @param parameters the dictionary of parameters + */ + public PatchCellCARTCD4( + PatchCellContainer container, Location location, Parameters parameters) { + this(container, location, parameters, null); + } + + /** + * Creates a T cell {@code PatchCellCARTCD4} agent. * + * + * @param container the cell container + * @param location the {@link Location} of the cell + * @param parameters the dictionary of parameters + * @param links the map of population links + */ + public PatchCellCARTCD4( + PatchCellContainer container, Location location, Parameters parameters, GrabBag links) { + super(container, location, parameters, links); + } + + @Override + public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFast random) { + divisions++; + return new PatchCellContainer( + newID, + id, + pop, + age, + divisions, + newState, + volume, + height, + criticalVolume, + criticalHeight); + } + + @Override + public void step(SimState simstate) { + Simulation sim = (Simulation) simstate; + + // Increase age of cell. + super.age++; + + if (state != State.APOPTOTIC && age > apoptosisAge) { + setState(State.APOPTOTIC); + super.setBindingFlag(AntigenFlag.UNBOUND); + this.activated = false; + } + + // Increase time since last active ticker + super.lastActiveTicker++; + if (super.lastActiveTicker != 0 && super.lastActiveTicker % MINUTES_IN_DAY == 0) { + if (super.boundCARAntigensCount != 0) super.boundCARAntigensCount--; + } + if (super.lastActiveTicker / MINUTES_IN_DAY >= 7) super.activated = false; + + // Step metabolism process. + super.processes.get(Domain.METABOLISM).step(simstate.random, sim); + + // Check energy status. If cell has less energy than threshold, it will + // apoptose. If overall energy is negative, then cell enters quiescence. + if (state != State.APOPTOTIC) { + if (super.energy < super.energyThreshold) { + + super.setState(State.APOPTOTIC); + super.unbind(); + this.activated = false; + } else if (state != State.ANERGIC + && state != State.SENESCENT + && state != State.EXHAUSTED + && state != State.STARVED + && energy < 0) { + + super.setState(State.STARVED); + super.unbind(); + } else if (state == State.STARVED && energy >= 0) { + super.setState(State.UNDEFINED); + } + } + + // Step inflammation process. + super.processes.get(Domain.INFLAMMATION).step(simstate.random, sim); + + // Change state from undefined. + if (super.state == State.UNDEFINED || super.state == State.PAUSED) { + if (divisions == 0) { + if (simstate.random.nextDouble() > super.senescentFraction) { + super.setState(State.APOPTOTIC); + } else { + super.setState(State.SENESCENT); + } + super.unbind(); + this.activated = false; + } else { + // Cell attempts to bind to a target + PatchCellTissue target = + super.bindTarget(sim, location, new MersenneTwisterFast(simstate.seed())); + super.boundTarget = target; + // If cell is bound to both antigen and self it will become anergic. + if (super.getBindingFlag() == AntigenFlag.BOUND_ANTIGEN_CELL_RECEPTOR) { + if (simstate.random.nextDouble() > super.anergicFraction) { + super.setState(State.APOPTOTIC); + } else { + super.setState(State.ANERGIC); + } + super.unbind(); + this.activated = false; + } else if (super.getBindingFlag() == AntigenFlag.BOUND_ANTIGEN) { + // If cell is only bound to target antigen, the cell + // can potentially become properly activated. + + // Check overstimulation. If cell has bound to + // target antigens too many times, becomes exhausted. + if (boundCARAntigensCount > maxAntigenBinding) { + if (simstate.random.nextDouble() > super.exhaustedFraction) { + super.setState(State.APOPTOTIC); + } else { + super.setState(State.EXHAUSTED); + } + super.unbind(); + this.activated = false; + } else { + // if CD4 cell is properly activated, it can stimulate + super.setState(State.STIMULATORY); + this.lastActiveTicker = 0; + this.activated = true; + } + } else { + // If self binding, unbind + if (super.getBindingFlag() == AntigenFlag.BOUND_CELL_RECEPTOR) { + super.unbind(); + } + // Check activation status. If cell has been activated before, + // it will proliferate. If not, it will migrate. + if (activated) { + super.setState(State.PROLIFERATIVE); + } else { + if (simstate.random.nextDouble() > super.proliferativeFraction) { + super.setState(State.MIGRATORY); + } else { + super.setState(State.PROLIFERATIVE); + } + } + } + } + } + + // Step the module for the cell state. + if (super.module != null) { + super.module.step(simstate.random, sim); + } + } +} diff --git a/src/arcade/patch/agent/cell/PatchCellCARTCD8.java b/src/arcade/patch/agent/cell/PatchCellCARTCD8.java new file mode 100644 index 000000000..708bd8682 --- /dev/null +++ b/src/arcade/patch/agent/cell/PatchCellCARTCD8.java @@ -0,0 +1,170 @@ +package arcade.patch.agent.cell; + +import sim.engine.SimState; +import ec.util.MersenneTwisterFast; +import arcade.core.agent.cell.CellState; +import arcade.core.env.location.Location; +import arcade.core.sim.Simulation; +import arcade.core.util.GrabBag; +import arcade.core.util.Parameters; +import arcade.patch.util.PatchEnums.AntigenFlag; +import arcade.patch.util.PatchEnums.Domain; +import arcade.patch.util.PatchEnums.State; + +/** Extension of {@link PatchCellCART} for CD8 CART-cells with selected module versions. */ +public class PatchCellCARTCD8 extends PatchCellCART { + /** + * Creates a T cell {@code PatchCellCARTCD8} agent. * + * + * @param container the cell container + * @param location the {@link Location} of the cell + * @param parameters the dictionary of parameters + */ + public PatchCellCARTCD8( + PatchCellContainer container, Location location, Parameters parameters) { + this(container, location, parameters, null); + } + + /** + * Creates a T cell {@code PatchCellCARTCD8} agent. * + * + * @param container the cell container + * @param location the {@link Location} of the cell + * @param parameters the dictionary of parameters + * @param links the map of population links + */ + public PatchCellCARTCD8( + PatchCellContainer container, Location location, Parameters parameters, GrabBag links) { + super(container, location, parameters, links); + } + + @Override + public PatchCellContainer make(int newID, CellState newState, MersenneTwisterFast random) { + divisions++; + return new PatchCellContainer( + newID, + id, + pop, + age, + divisions, + newState, + volume, + height, + criticalVolume, + criticalHeight); + } + + @Override + public void step(SimState simstate) { + Simulation sim = (Simulation) simstate; + + super.age++; + + if (state != State.APOPTOTIC && age > apoptosisAge) { + setState(State.APOPTOTIC); + super.unbind(); + this.activated = false; + } + + super.lastActiveTicker++; + if (super.lastActiveTicker != 0 && super.lastActiveTicker % MINUTES_IN_DAY == 0) { + if (super.boundCARAntigensCount != 0) { + super.boundCARAntigensCount--; + } + } + if (super.lastActiveTicker / MINUTES_IN_DAY >= 7) { + super.activated = false; + } + + super.processes.get(Domain.METABOLISM).step(simstate.random, sim); + + // Check energy status. If cell has less energy than threshold, it will + // apoptose. If overall energy is negative, then cell enters quiescence. + if (state != State.APOPTOTIC) { + if (super.energy < super.energyThreshold) { + + super.setState(State.APOPTOTIC); + super.unbind(); + this.activated = false; + } else if (state != State.ANERGIC + && state != State.SENESCENT + && state != State.EXHAUSTED + && state != State.STARVED + && energy < 0) { + + super.setState(State.STARVED); + super.unbind(); + } else if (state == State.STARVED && energy >= 0) { + super.setState(State.UNDEFINED); + } + } + + super.processes.get(Domain.INFLAMMATION).step(simstate.random, sim); + + if (super.state == State.UNDEFINED || super.state == State.PAUSED) { + if (divisions == divisionPotential) { + if (simstate.random.nextDouble() > super.senescentFraction) { + super.setState(State.APOPTOTIC); + } else { + super.setState(State.SENESCENT); + } + super.unbind(); + this.activated = false; + } else { + PatchCellTissue target = super.bindTarget(sim, location, simstate.random); + super.boundTarget = target; + + // If cell is bound to both antigen and self it will become anergic. + if (super.getBindingFlag() == AntigenFlag.BOUND_ANTIGEN_CELL_RECEPTOR) { + if (simstate.random.nextDouble() > super.anergicFraction) { + super.setState(State.APOPTOTIC); + } else { + super.setState(State.ANERGIC); + } + super.unbind(); + this.activated = false; + } else if (super.getBindingFlag() == AntigenFlag.BOUND_ANTIGEN) { + // If cell is only bound to target antigen, the cell + // can potentially become properly activated. + + // Check overstimulation. If cell has bound to + // target antigens too many times, becomes exhausted. + if (boundCARAntigensCount > maxAntigenBinding) { + if (simstate.random.nextDouble() > super.exhaustedFraction) { + super.setState(State.APOPTOTIC); + } else { + super.setState(State.EXHAUSTED); + } + super.unbind(); + this.activated = false; + } else { + // if CD8 cell is properly activated, it can be cytotoxic + this.lastActiveTicker = 0; + this.activated = true; + super.setState(State.CYTOTOXIC); + } + } else { + // If self binding, unbind + if (super.getBindingFlag() == AntigenFlag.BOUND_CELL_RECEPTOR) { + super.unbind(); + } + // Check activation status. If cell has been activated before, + // it will proliferate. If not, it will migrate. + if (activated) { + super.setState(State.PROLIFERATIVE); + } else { + if (simstate.random.nextDouble() > super.proliferativeFraction) { + super.setState(State.MIGRATORY); + } else { + super.setState(State.PROLIFERATIVE); + } + } + } + } + } + + if (super.module != null) { + super.module.step(simstate.random, sim); + } + } +} diff --git a/src/arcade/patch/agent/cell/PatchCellContainer.java b/src/arcade/patch/agent/cell/PatchCellContainer.java index 960d5df6c..9e2242a9b 100644 --- a/src/arcade/patch/agent/cell/PatchCellContainer.java +++ b/src/arcade/patch/agent/cell/PatchCellContainer.java @@ -115,6 +115,10 @@ public Cell convert( return new PatchCellCancer(this, location, parameters, links); case "cancer_stem": return new PatchCellCancerStem(this, location, parameters, links); + case "cart_cd4": + return new PatchCellCARTCD4(this, location, parameters, links); + case "cart_cd8": + return new PatchCellCARTCD8(this, location, parameters, links); case "random": return new PatchCellRandom(this, location, parameters, links); } diff --git a/src/arcade/patch/agent/module/PatchModuleCytotoxicity.java b/src/arcade/patch/agent/module/PatchModuleCytotoxicity.java new file mode 100644 index 000000000..9423e69dd --- /dev/null +++ b/src/arcade/patch/agent/module/PatchModuleCytotoxicity.java @@ -0,0 +1,79 @@ +package arcade.patch.agent.module; + +import ec.util.MersenneTwisterFast; +import arcade.core.sim.Simulation; +import arcade.core.util.Parameters; +import arcade.patch.agent.cell.PatchCell; +import arcade.patch.agent.cell.PatchCellCART; +import arcade.patch.agent.cell.PatchCellTissue; +import arcade.patch.agent.process.PatchProcessInflammation; +import static arcade.patch.util.PatchEnums.Domain; +import static arcade.patch.util.PatchEnums.State; + +/** + * Implementation of {@link Module} for killing tissue agents. + * + *

{@code PatchModuleCytotoxicity} is stepped once after a CD8 CAR T-cell binds to a target + * tissue cell. The {@code PatchModuleCytotoxicity} determines if cell has enough granzyme to kill. + * If so, it kills cell and calls the reset to neutral helper to return to neutral state. If not, it + * waits until it has enough granzyme to kill cell. + */ +public class PatchModuleCytotoxicity extends PatchModule { + /** Target cell cytotoxic CAR T-cell is bound to. */ + PatchCellTissue target; + + /** CAR T-cell inflammation module. */ + PatchProcessInflammation inflammation; + + /** Amount of granzyme inside CAR T-cell. */ + double granzyme; + + /** Average time that T cell is bound to target [min]. */ + private final int timeDelay; + + /** Ticker to keep track of the time delay [min]. */ + private int ticker; + + /** + * Creates a {@code PatchActionKill} for the given {@link PatchCellCART}. + * + * @param cell the {@link PatchCell} the helper is associated with + */ + public PatchModuleCytotoxicity(PatchCell cell) { + super(cell); + this.target = (PatchCellTissue) ((PatchCellCART) cell).getBoundTarget(); + this.inflammation = (PatchProcessInflammation) cell.getProcess(Domain.INFLAMMATION); + this.granzyme = inflammation.getInternal("granzyme"); + + Parameters parameters = cell.getParameters(); + this.timeDelay = parameters.getInt("BOUND_TIME"); + this.ticker = 0; + } + + @Override + public void step(MersenneTwisterFast random, Simulation sim) { + if (cell.isStopped()) { + return; + } + + if (target.isStopped()) { + ((PatchCellCART) cell).unbind(); + cell.setState(State.UNDEFINED); + return; + } + + if (ticker == 0) { + if (granzyme >= 1) { + PatchCellTissue tissueCell = (PatchCellTissue) target; + tissueCell.setState(State.APOPTOTIC); + granzyme--; + inflammation.setInternal("granzyme", granzyme); + } + } else if (ticker >= timeDelay) { + ((PatchCellCART) cell).unbind(); + cell.setState(State.UNDEFINED); + } + + ticker++; + } +} diff --git a/src/arcade/patch/agent/module/PatchModuleProliferation.java b/src/arcade/patch/agent/module/PatchModuleProliferation.java index 572a6524f..e5134bf8b 100644 --- a/src/arcade/patch/agent/module/PatchModuleProliferation.java +++ b/src/arcade/patch/agent/module/PatchModuleProliferation.java @@ -8,6 +8,7 @@ import arcade.core.util.MiniBox; import arcade.core.util.Parameters; import arcade.patch.agent.cell.PatchCell; +import arcade.patch.agent.cell.PatchCellCART; import arcade.patch.agent.process.PatchProcess; import arcade.patch.env.grid.PatchGrid; import arcade.patch.env.location.PatchLocation; @@ -73,12 +74,20 @@ public void step(MersenneTwisterFast random, Simulation sim) { // space in neighborhood to divide into. Otherwise, check if double // volume has been reached, and if so, create a new cell. if (currentHeight > maxHeight) { - cell.setState(State.QUIESCENT); + if (cell instanceof PatchCellCART) { + cell.setState(State.PAUSED); + } else { + cell.setState(State.QUIESCENT); + } } else { PatchLocation newLocation = cell.selectBestLocation(sim, random); if (newLocation == null) { - cell.setState(State.QUIESCENT); + if (cell instanceof PatchCellCART) { + cell.setState(State.PAUSED); + } else { + cell.setState(State.QUIESCENT); + } } else if (cell.getVolume() >= targetVolume) { if (ticker > synthesisDuration) { diff --git a/src/arcade/patch/agent/module/PatchModuleStimulation.java b/src/arcade/patch/agent/module/PatchModuleStimulation.java new file mode 100644 index 000000000..d45985076 --- /dev/null +++ b/src/arcade/patch/agent/module/PatchModuleStimulation.java @@ -0,0 +1,72 @@ +package arcade.patch.agent.module; + +import ec.util.MersenneTwisterFast; +import arcade.core.sim.Simulation; +import arcade.core.util.Parameters; +import arcade.patch.agent.cell.PatchCell; +import arcade.patch.agent.cell.PatchCellCART; +import arcade.patch.agent.cell.PatchCellTissue; +import arcade.patch.util.PatchEnums; + +/** + * Implementation of {@link Module} for stimulatory T cell agents. + * + *

{@code PatchModuleStimulation} is stepped once after a CD4 CAR T-cell gets stimulated. The + * {@code PatchModuleStimulation} activates the T cell, unbinds to any target cell that the T cell + * is bound to, and sets the cell state back to undefined. + */ +public class PatchModuleStimulation extends PatchModule { + /** Target cell cytotoxic CAR T-cell is bound to */ + PatchCellTissue target; + + /** Time delay before calling the action [min]. */ + private int timeDelay; + + /** Ticker to keep track of the time delay [min]. */ + private int ticker; + + /** Average time that T cell is bound to target [min]. */ + private double boundTime; + + /** Range in bound time [min]. */ + private double boundRange; + + /** + * Creates a {@code PatchModuleStimulation} for the given {@link PatchCellCART}. + * + * @param c the {@link PatchCellCART} the helper is associated with + */ + public PatchModuleStimulation(PatchCell c) { + super(c); + this.target = (PatchCellTissue) ((PatchCellCART) c).getBoundTarget(); + Parameters parameters = c.getParameters(); + this.boundTime = parameters.getInt("BOUND_TIME"); + this.boundRange = parameters.getInt("BOUND_RANGE"); + this.timeDelay = 0; + this.ticker = 0; + } + + @Override + public void step(MersenneTwisterFast random, Simulation sim) { + if (cell.isStopped()) { + return; + } + + if (target.isStopped()) { + ((PatchCellCART) cell).unbind(); + cell.setState(PatchEnums.State.UNDEFINED); + return; + } + + if (ticker == 0) { + this.timeDelay = + (int) (boundTime + Math.round((boundRange * (2 * random.nextInt() - 1)))); + target.setState(PatchEnums.State.QUIESCENT); + } else if (ticker >= timeDelay) { + ((PatchCellCART) cell).unbind(); + cell.setState(PatchEnums.State.UNDEFINED); + } + + ticker++; + } +} diff --git a/src/arcade/patch/agent/process/PatchProcessInflammation.java b/src/arcade/patch/agent/process/PatchProcessInflammation.java index 08c988d11..cefd90a79 100644 --- a/src/arcade/patch/agent/process/PatchProcessInflammation.java +++ b/src/arcade/patch/agent/process/PatchProcessInflammation.java @@ -124,14 +124,14 @@ public abstract class PatchProcessInflammation extends PatchProcess { * bound and no three-chain receptors. Daughter cells split amounts of bound IL-2 and * three-chain receptors upon dividing. * - * @param c the {@link PatchCellCART} the module is associated with + * @param cell the {@link PatchCellCART} the module is associated with */ - public PatchProcessInflammation(PatchCellCART c) { - super(c); - this.loc = c.getLocation(); - this.cell = c; - this.pop = c.getPop(); - this.volume = c.getVolume(); + public PatchProcessInflammation(PatchCellCART cell) { + super(cell); + this.loc = cell.getLocation(); + this.cell = cell; + this.pop = cell.getPop(); + this.volume = cell.getVolume(); this.iL2Ticker = 0; this.activeTicker = 0; @@ -140,6 +140,7 @@ public PatchProcessInflammation(PatchCellCART c) { this.iL2Receptors = parameters.getDouble("inflammation/IL2_RECEPTORS"); extIL2 = 0; + // Initial amounts of each species, all in molecules/cell. amts = new double[NUM_COMPONENTS]; amts[IL2_INT_TOTAL] = 0; amts[IL2R_TOTAL] = iL2Receptors; @@ -309,9 +310,9 @@ public void step(MersenneTwisterFast random, Simulation sim) { public static PatchProcess make(PatchCell cell, String version) { switch (version.toUpperCase()) { case "CD4": - throw new UnsupportedOperationException(); + return new PatchProcessInflammationCD4((PatchCellCART) cell); case "CD8": - throw new UnsupportedOperationException(); + return new PatchProcessInflammationCD8((PatchCellCART) cell); default: return null; } diff --git a/src/arcade/patch/agent/process/PatchProcessInflammationCD4.java b/src/arcade/patch/agent/process/PatchProcessInflammationCD4.java new file mode 100644 index 000000000..b97cdb102 --- /dev/null +++ b/src/arcade/patch/agent/process/PatchProcessInflammationCD4.java @@ -0,0 +1,129 @@ +package arcade.patch.agent.process; + +import ec.util.MersenneTwisterFast; +import arcade.core.agent.process.Process; +import arcade.core.sim.Simulation; +import arcade.core.util.Parameters; +import arcade.patch.agent.cell.PatchCellCART; + +/** + * Extension of {@link arcade.patch.agent.process} for CD4 CAR T-cells + * + *

{@code InflammationCD4} determines IL-2 amounts produced for stimulatory effector functions as + * a antigen-induced activation state. + */ +public class PatchProcessInflammationCD4 extends PatchProcessInflammation { + + /** Rate of IL-2 production due to antigen-induced activation */ + private final double IL2_PROD_RATE_ACTIVE; + + /** Rate of IL-2 production due to IL-2 feedback */ + private final double IL2_PROD_RATE_IL2; + + /** Delay in IL-2 synthesis after antigen-induced activation */ + private final int IL2_SYNTHESIS_DELAY; + + /** Total rate of IL-2 production */ + private double IL2ProdRate; + + /** Total IL-2 produced in step */ + private double IL2Produced; + + /** Amount of IL-2 bound in past being used for current IL-2 production calculation */ + private double priorIL2prod; + + /** External IL-2 sent into environment after each step. Used for testing only */ + private double IL2EnvTesting; + + /** + * Creates a CD4 {@link PatchProcessInflammation} module. + * + *

IL-2 production rate parameters set. + * + * @param cell the {@link PatchCellCART} the module is associated with + */ + public PatchProcessInflammationCD4(PatchCellCART cell) { + super(cell); + + // Set parameters. + Parameters parameters = cell.getParameters(); + this.IL2_PROD_RATE_IL2 = parameters.getDouble("inflammation/IL2_PROD_RATE_IL2"); + this.IL2_PROD_RATE_ACTIVE = parameters.getDouble("inflammation/IL2_PROD_RATE_ACTIVE"); + this.IL2_SYNTHESIS_DELAY = parameters.getInt("inflammation/IL2_SYNTHESIS_DELAY"); + IL2ProdRate = 0; + } + + @Override + public void stepProcess(MersenneTwisterFast random, Simulation sim) { + + // Determine IL-2 production rate as a function of IL-2 bound. + int prodIndex = (iL2Ticker % boundArray.length) - IL2_SYNTHESIS_DELAY; + if (prodIndex < 0) { + prodIndex += boundArray.length; + } + priorIL2prod = boundArray[prodIndex]; + IL2ProdRate = IL2_PROD_RATE_IL2 * (priorIL2prod / iL2Receptors); + + // Add IL-2 production rate dependent on antigen-induced + // cell activation if cell is activated. + if (active && activeTicker >= IL2_SYNTHESIS_DELAY) { + IL2ProdRate += IL2_PROD_RATE_ACTIVE; + } + + // Produce IL-2 to environment. + IL2Produced = IL2ProdRate; // [molecules], rate is already per minute + + // Update environment. + // Take current IL2 external concentration and add the amount produced, + // then convert units back to molecules/cm^3. + double IL2Env = + (((extIL2 - (extIL2 * fraction - amts[IL2_EXT])) + IL2Produced) + * 1E12 + / loc.getVolume()); + + // update IL2 env variable for testing + IL2EnvTesting = IL2Env; + + sim.getLattice("IL-2").setValue(loc, IL2Env); + } + + @Override + public void update(Process mod) { + PatchProcessInflammationCD4 inflammation = (PatchProcessInflammationCD4) mod; + double split = (this.cell.getVolume() / this.volume); + + // Update daughter cell inflammation as a fraction of parent. + // this.volume = this.cell.getVolume(); + this.amts[IL2RBGA] = inflammation.amts[IL2RBGA] * split; + this.amts[IL2_IL2RBG] = inflammation.amts[IL2_IL2RBG] * split; + this.amts[IL2_IL2RBGA] = inflammation.amts[IL2_IL2RBGA] * split; + this.amts[IL2RBG] = + iL2Receptors - this.amts[IL2RBGA] - this.amts[IL2_IL2RBG] - this.amts[IL2_IL2RBGA]; + this.amts[IL2_INT_TOTAL] = this.amts[IL2_IL2RBG] + this.amts[IL2_IL2RBGA]; + this.amts[IL2R_TOTAL] = this.amts[IL2RBG] + this.amts[IL2RBGA]; + this.boundArray = (inflammation.boundArray).clone(); + + // Update parent cell with remaining fraction. + inflammation.amts[IL2RBGA] *= (1 - split); + inflammation.amts[IL2_IL2RBG] *= (1 - split); + inflammation.amts[IL2_IL2RBGA] *= (1 - split); + inflammation.amts[IL2RBG] = + iL2Receptors + - inflammation.amts[IL2RBGA] + - inflammation.amts[IL2_IL2RBG] + - inflammation.amts[IL2_IL2RBGA]; + inflammation.amts[IL2_INT_TOTAL] = + inflammation.amts[IL2_IL2RBG] + inflammation.amts[IL2_IL2RBGA]; + inflammation.amts[IL2R_TOTAL] = inflammation.amts[IL2RBG] + inflammation.amts[IL2RBGA]; + inflammation.volume *= (1 - split); + } + + /** + * Returns final value of external IL2 after stepping process. Exists for testing purposes only + * + * @return final value of external IL2 + */ + public double getIL2EnvTesting() { + return IL2EnvTesting; + } +} diff --git a/src/arcade/patch/agent/process/PatchProcessInflammationCD8.java b/src/arcade/patch/agent/process/PatchProcessInflammationCD8.java new file mode 100644 index 000000000..b0c751fc6 --- /dev/null +++ b/src/arcade/patch/agent/process/PatchProcessInflammationCD8.java @@ -0,0 +1,95 @@ +package arcade.patch.agent.process; + +import ec.util.MersenneTwisterFast; +import arcade.core.agent.process.Process; +import arcade.core.sim.Simulation; +import arcade.core.util.Parameters; +import arcade.patch.agent.cell.PatchCellCART; + +/** + * Extension of {@link PatchProcessInflammation} for CD8 CAR T-cells + * + *

{@code InflammationCD8} determines granzyme amounts produced for cytotoxic effector functions + * as a function of IL-2 bound and antigen-induced activation state. + */ +public class PatchProcessInflammationCD8 extends PatchProcessInflammation { + /** Moles of granzyme produced per moles IL-2 [mol granzyme/mol IL-2]. */ + private static final double GRANZ_PER_IL2 = 0.005; + + /** Delay in IL-2 synthesis after antigen-induced activation. */ + private final int granzSynthesisDelay; + + /** Amount of IL-2 bound in past being used for current granzyme production calculation. */ + private double priorIL2granz; + + /** + * Creates a CD8 {@link PatchProcessInflammation} module. + * + *

Initial amount of internal granzyme is set. Granzyme production parameters set. + * + * @param cell the {@link PatchCellCART} the module is associated with + */ + public PatchProcessInflammationCD8(PatchCellCART cell) { + super(cell); + + Parameters parameters = cell.getParameters(); + this.granzSynthesisDelay = parameters.getInt("inflammation/GRANZ_SYNTHESIS_DELAY"); + this.priorIL2granz = 0; + + amts[GRANZYME] = 1; // [molecules] + names.add(GRANZYME, "granzyme"); + } + + @Override + public void stepProcess(MersenneTwisterFast random, Simulation sim) { + + // Determine amount of granzyme production based on if cell is activated + // as a function of IL-2 production. + int granzIndex = (iL2Ticker % boundArray.length) - granzSynthesisDelay; + if (granzIndex < 0) { + granzIndex += boundArray.length; + } + priorIL2granz = boundArray[granzIndex]; + + if (active && activeTicker > granzSynthesisDelay) { + amts[GRANZYME] += GRANZ_PER_IL2 * (priorIL2granz / iL2Receptors); + } + + // Update environment. + // Convert units back from molecules to molecules/cm^3. + double iL2Env = ((extIL2 - (extIL2 * fraction - amts[IL2_EXT])) * 1E12 / loc.getVolume()); + sim.getLattice("IL-2").setValue(loc, iL2Env); + } + + @Override + public void update(Process mod) { + PatchProcessInflammationCD8 inflammation = (PatchProcessInflammationCD8) mod; + double split = (this.cell.getVolume() / this.volume); + + // Update daughter cell inflammation as a fraction of parent. + this.amts[IL2RBGA] = inflammation.amts[IL2RBGA] * split; + this.amts[IL2_IL2RBG] = inflammation.amts[IL2_IL2RBG] * split; + this.amts[IL2_IL2RBGA] = inflammation.amts[IL2_IL2RBGA] * split; + this.amts[IL2RBG] = + iL2Receptors - this.amts[IL2RBGA] - this.amts[IL2_IL2RBG] - this.amts[IL2_IL2RBGA]; + this.amts[IL2_INT_TOTAL] = this.amts[IL2_IL2RBG] + this.amts[IL2_IL2RBGA]; + this.amts[IL2R_TOTAL] = this.amts[IL2RBG] + this.amts[IL2RBGA]; + this.amts[GRANZYME] = inflammation.amts[GRANZYME] * split; + this.boundArray = (inflammation.boundArray).clone(); + + // Update parent cell with remaining fraction. + inflammation.amts[IL2RBGA] *= (1 - split); + inflammation.amts[IL2_IL2RBG] *= (1 - split); + inflammation.amts[IL2_IL2RBGA] *= (1 - split); + inflammation.amts[IL2RBG] = + iL2Receptors + - inflammation.amts[IL2RBGA] + - inflammation.amts[IL2_IL2RBG] + - inflammation.amts[IL2_IL2RBGA]; + inflammation.amts[IL2_INT_TOTAL] = + inflammation.amts[IL2_IL2RBG] + inflammation.amts[IL2_IL2RBGA]; + inflammation.amts[IL2R_TOTAL] = inflammation.amts[IL2RBG] + inflammation.amts[IL2RBGA]; + inflammation.amts[GRANZYME] *= (1 - split); + inflammation.volume *= (1 - split); + } +} diff --git a/src/arcade/patch/env/grid/PatchGrid.java b/src/arcade/patch/env/grid/PatchGrid.java index 599171f5a..34be56bd7 100644 --- a/src/arcade/patch/env/grid/PatchGrid.java +++ b/src/arcade/patch/env/grid/PatchGrid.java @@ -15,7 +15,7 @@ */ public class PatchGrid implements Grid { /** Initial bag capacity. */ - private static final int INITIAL_CAPACITY = 6; + private static final int INITIAL_CAPACITY = 54; /** Map of ID to object. */ final HashMap objects; diff --git a/src/arcade/patch/parameter.patch.xml b/src/arcade/patch/parameter.patch.xml index 1e369fd1c..7a6211c3d 100644 --- a/src/arcade/patch/parameter.patch.xml +++ b/src/arcade/patch/parameter.patch.xml @@ -43,6 +43,7 @@ + @@ -87,6 +88,14 @@ + + + + + + + + @@ -113,6 +122,16 @@ + + + + + + + + + + diff --git a/src/arcade/patch/sim/PatchSimulationHex.java b/src/arcade/patch/sim/PatchSimulationHex.java index 6eddf28b5..0109aa872 100644 --- a/src/arcade/patch/sim/PatchSimulationHex.java +++ b/src/arcade/patch/sim/PatchSimulationHex.java @@ -7,6 +7,7 @@ import arcade.patch.agent.action.PatchActionConvert; import arcade.patch.agent.action.PatchActionInsert; import arcade.patch.agent.action.PatchActionRemove; +import arcade.patch.agent.action.PatchActionTreat; import arcade.patch.agent.cell.PatchCellFactory; import arcade.patch.env.component.PatchComponentCycle; import arcade.patch.env.component.PatchComponentDegrade; @@ -58,6 +59,8 @@ public Action makeAction(String actionClass, MiniBox parameters) { return new PatchActionRemove(series, parameters); case "convert": return new PatchActionConvert(series, parameters); + case "treat": + return new PatchActionTreat(series, parameters); default: return null; } diff --git a/src/arcade/patch/sim/PatchSimulationRect.java b/src/arcade/patch/sim/PatchSimulationRect.java index 6623fefec..0fdfe8523 100644 --- a/src/arcade/patch/sim/PatchSimulationRect.java +++ b/src/arcade/patch/sim/PatchSimulationRect.java @@ -7,6 +7,7 @@ import arcade.patch.agent.action.PatchActionConvert; import arcade.patch.agent.action.PatchActionInsert; import arcade.patch.agent.action.PatchActionRemove; +import arcade.patch.agent.action.PatchActionTreat; import arcade.patch.agent.cell.PatchCellFactory; import arcade.patch.env.component.PatchComponentCycle; import arcade.patch.env.component.PatchComponentDegrade; @@ -58,6 +59,8 @@ public Action makeAction(String actionClass, MiniBox parameters) { return new PatchActionRemove(series, parameters); case "convert": return new PatchActionConvert(series, parameters); + case "treat": + return new PatchActionTreat(series, parameters); default: return null; } diff --git a/test/arcade/patch/agent/action/PatchActionKillTest.java b/test/arcade/patch/agent/action/PatchActionKillTest.java new file mode 100644 index 000000000..a3698ecd8 --- /dev/null +++ b/test/arcade/patch/agent/action/PatchActionKillTest.java @@ -0,0 +1,77 @@ +package arcade.patch.agent.action; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import sim.engine.Schedule; +import ec.util.MersenneTwisterFast; +import arcade.core.sim.Series; +import arcade.core.util.Parameters; +import arcade.patch.agent.cell.PatchCellCART; +import arcade.patch.agent.cell.PatchCellTissue; +import arcade.patch.agent.module.PatchModuleApoptosis; +import arcade.patch.agent.process.PatchProcessInflammation; +import arcade.patch.sim.PatchSimulation; +import arcade.patch.util.PatchEnums; +import arcade.patch.util.PatchEnums.AntigenFlag; +import arcade.patch.util.PatchEnums.State; +import static org.mockito.Mockito.*; + +public class PatchActionKillTest { + + private PatchCellCART mockCell; + private PatchCellTissue mockTarget; + private PatchProcessInflammation mockInflammation; + private PatchActionKill action; + private Schedule schedule; + private PatchSimulation sim; + + @BeforeEach + public void setUp() { + mockCell = mock(PatchCellCART.class); + mockTarget = mock(PatchCellTissue.class); + mockInflammation = mock(PatchProcessInflammation.class); + MersenneTwisterFast random = new MersenneTwisterFast(); + Series series = mock(Series.class); + Parameters parameters = mock(Parameters.class); + schedule = mock(Schedule.class); + sim = mock(PatchSimulation.class); + + when(mockCell.getProcess(any())).thenReturn(mockInflammation); + when(mockInflammation.getInternal("granzyme")).thenReturn(1.0); + + action = new PatchActionKill(mockCell, mockTarget, random, series, parameters); + } + + @Test + public void schedule_updatesSchedule() { + action.schedule(schedule); + verify(schedule) + .scheduleOnce(anyDouble(), eq(PatchEnums.Ordering.ACTIONS.ordinal()), eq(action)); + } + + @Test + public void step_CARCellStopped_doesNotChangeCell() { + when(mockCell.isStopped()).thenReturn(true); + action.step(sim); + verify(mockTarget, never()).setState(any()); + } + + @Test + public void step_targetCellStopped_doesNotChangeCell() { + when(mockTarget.isStopped()).thenReturn(true); + action.step(sim); + verify(mockTarget, never()).setState(any()); + verify(mockCell).setBindingFlag(AntigenFlag.BOUND_CELL_RECEPTOR); + } + + @Test + public void step_killTargetCell_killsCellAndUsesGranzyme() { + when(mockCell.isStopped()).thenReturn(false); + when(mockTarget.isStopped()).thenReturn(false); + PatchModuleApoptosis mockProcess = mock(PatchModuleApoptosis.class); + when(mockTarget.getModule()).thenReturn(mockProcess); + action.step(sim); + verify(mockTarget).setState(State.APOPTOTIC); + verify(mockInflammation).setInternal("granzyme", 0.0); + } +} diff --git a/test/arcade/patch/agent/action/PatchActionResetTest.java b/test/arcade/patch/agent/action/PatchActionResetTest.java new file mode 100644 index 000000000..c927866e3 --- /dev/null +++ b/test/arcade/patch/agent/action/PatchActionResetTest.java @@ -0,0 +1,112 @@ +package arcade.patch.agent.action; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import sim.engine.Schedule; +import sim.engine.SimState; +import ec.util.MersenneTwisterFast; +import arcade.core.sim.Series; +import arcade.core.util.MiniBox; +import arcade.core.util.Parameters; +import arcade.patch.agent.cell.PatchCellCART; +import arcade.patch.agent.cell.PatchCellCARTCD4; +import arcade.patch.agent.cell.PatchCellContainer; +import arcade.patch.env.location.PatchLocation; +import arcade.patch.util.PatchEnums; +import arcade.patch.util.PatchEnums.AntigenFlag; +import arcade.patch.util.PatchEnums.State; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static arcade.core.ARCADETestUtilities.randomDoubleBetween; +import static arcade.core.ARCADETestUtilities.randomIntBetween; + +public class PatchActionResetTest { + + private PatchCellCART mockCell; + private PatchActionReset actionReset; + + @BeforeEach + public void setUp() { + MersenneTwisterFast mockRandom = mock(MersenneTwisterFast.class); + Series mockSeries = mock(Series.class); + Parameters mockParameters = spy(new Parameters(new MiniBox(), null, null)); + PatchLocation mockLocation = mock(PatchLocation.class); + + doReturn(0.0).when(mockParameters).getDouble(any(String.class)); + doReturn(0).when(mockParameters).getInt(any(String.class)); + when(mockParameters.getInt("BOUND_TIME")).thenReturn(10); + when(mockParameters.getInt("BOUND_RANGE")).thenReturn(5); + when(mockRandom.nextInt()).thenReturn(1); + + int id = 1; + int parentId = 1; + int pop = 4; + int age = randomIntBetween(1, 100800); + int divisions = 10; + double volume = randomDoubleBetween(100, 200); + double height = randomDoubleBetween(4, 10); + double criticalVolume = randomDoubleBetween(100, 200); + double criticalHeight = randomDoubleBetween(4, 10); + State state = State.UNDEFINED; + ; + + PatchCellContainer container = + new PatchCellContainer( + id, + parentId, + pop, + age, + divisions, + state, + volume, + height, + criticalVolume, + criticalHeight); + + mockCell = spy(new PatchCellCARTCD4(container, mockLocation, mockParameters)); + + actionReset = new PatchActionReset(mockCell, mockRandom, mockSeries, mockParameters); + } + + @Test + public void schedule_updatesSimSchedule() { + Schedule mockSchedule = mock(Schedule.class); + actionReset.schedule(mockSchedule); + verify(mockSchedule) + .scheduleOnce( + anyDouble(), eq(PatchEnums.Ordering.ACTIONS.ordinal()), eq(actionReset)); + } + + @Test + public void step_cytotoxicState_unbindsAndUncommitsCell() { + when(mockCell.isStopped()).thenReturn(false); + mockCell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN); + when(mockCell.getState()).thenReturn(State.CYTOTOXIC); + + actionReset.step(mock(SimState.class)); + + verify(mockCell).setState(State.UNDEFINED); + assertEquals(AntigenFlag.UNBOUND, mockCell.getBindingFlag()); + } + + @Test + public void step_stimulatoryState_unbindsAndUncommitsCell() { + when(mockCell.isStopped()).thenReturn(false); + mockCell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN); + when(mockCell.getState()).thenReturn(State.STIMULATORY); + + actionReset.step(mock(SimState.class)); + + verify(mockCell).setState(State.UNDEFINED); + assertEquals(AntigenFlag.UNBOUND, mockCell.getBindingFlag()); + } + + @Test + public void step_stoppedCell_doesNotChangeCell() { + when(mockCell.isStopped()).thenReturn(true); + mockCell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN); + actionReset.step(mock(SimState.class)); + verify(mockCell, never()).setState(any(State.class)); + assertEquals(AntigenFlag.BOUND_ANTIGEN, mockCell.getBindingFlag()); + } +} diff --git a/test/arcade/patch/agent/action/PatchActionTreatTest.java b/test/arcade/patch/agent/action/PatchActionTreatTest.java new file mode 100644 index 000000000..2f23f9615 --- /dev/null +++ b/test/arcade/patch/agent/action/PatchActionTreatTest.java @@ -0,0 +1,211 @@ +package arcade.patch.agent.action; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import sim.engine.Schedule; +import sim.util.Bag; +import ec.util.MersenneTwisterFast; +import arcade.core.env.location.LocationContainer; +import arcade.core.util.MiniBox; +import arcade.patch.agent.cell.PatchCell; +import arcade.patch.agent.cell.PatchCellContainer; +import arcade.patch.agent.cell.PatchCellFactory; +import arcade.patch.env.component.PatchComponentSitesSource; +import arcade.patch.env.grid.PatchGrid; +import arcade.patch.env.location.*; +import arcade.patch.sim.PatchSeries; +import arcade.patch.sim.PatchSimulation; +import arcade.patch.util.PatchEnums; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class PatchActionTreatTest { + + private PatchActionTreat action; + private PatchSimulation sim; + private PatchGrid gridMock; + private PatchCell cellMock; + MiniBox parameters; + PatchSeries series; + + @BeforeEach + public void setUp() throws NoSuchFieldException, IllegalAccessException { + parameters = mock(MiniBox.class); + when(parameters.getInt("TIME_DELAY")).thenReturn(10); + when(parameters.getInt("DOSE")).thenReturn(10); + when(parameters.getDouble("RATIO")).thenReturn(0.5); + when(parameters.getDouble("MAX_DAMAGE_SEED")).thenReturn(0.1); + when(parameters.getDouble("MIN_RADIUS_SEED")).thenReturn(0.01); + when(parameters.getDouble("T_CELL_VOL_AVG")).thenReturn(175.0); + series = mock(PatchSeries.class); + MiniBox patchMock = mock(MiniBox.class); + series.patch = patchMock; + gridMock = mock(PatchGrid.class); + Schedule mockSchedule = mock(Schedule.class); + when(patchMock.get("GEOMETRY")).thenReturn("HEX"); + sim = mock(PatchSimulation.class); + when(sim.getSeries()).thenReturn(series); + when(sim.getGrid()).thenReturn(gridMock); + when(sim.getSchedule()).thenReturn(mockSchedule); + PatchComponentSitesSource sources = mock(PatchComponentSitesSource.class); + double[][][] damage = new double[20][20][20]; + boolean[][][] sitesLat = new boolean[20][20][20]; + damage[0][0][0] = 0.05; + sitesLat[0][0][0] = true; + when(sources.getDamage()).thenReturn(damage); + when(sources.getSources()).thenReturn(sitesLat); + when(sim.getComponent("SITES")).thenReturn(sources); + ArrayList locations = new ArrayList<>(); + PatchLocationContainer container = mock(PatchLocationContainer.class); + PatchLocation loc = mock(PatchLocation.class); + CoordinateXYZ coord = mock(CoordinateXYZ.class); + Field x = CoordinateXYZ.class.getDeclaredField("x"); + x.setAccessible(true); + x.set(coord, 0); + Field y = CoordinateXYZ.class.getDeclaredField("y"); + y.setAccessible(true); + y.set(coord, 0); + Field z = Coordinate.class.getDeclaredField("z"); + z.setAccessible(true); + z.set(coord, 0); + CoordinateUVWZ c = mock(CoordinateUVWZ.class); + z.set(c, 0); + when(loc.getSubcoordinate()).thenReturn(coord); + when(loc.getCoordinate()).thenReturn(c); + when(container.convert(any(), any())).thenReturn(loc); + locations.add(container); + when(sim.getLocations()).thenReturn(locations); + sim.random = mock(MersenneTwisterFast.class); + + PatchCellFactory factoryMock = mock(PatchCellFactory.class); + PatchCellContainer patchCellContainerMock = mock(PatchCellContainer.class); + when(factoryMock.createCellForPopulation(any(Integer.class), any(Integer.class))) + .thenReturn(patchCellContainerMock); + cellMock = mock(PatchCell.class); + when(patchCellContainerMock.convert(any(), any(), any())).thenReturn(cellMock); + + Field factory = PatchSimulation.class.getDeclaredField("cellFactory"); + factory.setAccessible(true); + factory.set(sim, factoryMock); + } + + final Bag createPatchCellsWithVolumeAndCriticalHeight(int n, double volume, double critHeight) { + Bag bag = new Bag(); + for (int i = 0; i < n; i++) { + PatchCell cell = mock(PatchCell.class); + when(cell.getVolume()).thenReturn(volume); + when(cell.getHeight()).thenReturn(critHeight); + bag.add(cell); + } + return bag; + } + + @Test + public void schedule_callsScheduleOnAction() + throws NoSuchFieldException, IllegalAccessException { + action = new PatchActionTreat(series, parameters); + + ArrayList populations = new ArrayList<>(); + MiniBox populationMock = mock(MiniBox.class); + when(populationMock.getInt("CODE")).thenReturn(4); + when(populationMock.get("CLASS")).thenReturn("cart_cd4"); + populations.add(populationMock); + Field pops = PatchActionTreat.class.getDeclaredField("populations"); + pops.setAccessible(true); + pops.set(action, populations); + + Schedule schedule = mock(Schedule.class); + action.schedule(schedule); + verify(schedule) + .scheduleOnce(anyDouble(), eq(PatchEnums.Ordering.ACTIONS.ordinal()), eq(action)); + } + + @Test + public void step_addsObjectsToSim() throws NoSuchFieldException, IllegalAccessException { + action = new PatchActionTreat(series, parameters); + + ArrayList populations = new ArrayList<>(); + MiniBox populationMock = mock(MiniBox.class); + when(populationMock.getInt("CODE")).thenReturn(4); + when(populationMock.get("CLASS")).thenReturn("cart_cd4"); + populations.add(populationMock); + Field pops = PatchActionTreat.class.getDeclaredField("populations"); + pops.setAccessible(true); + pops.set(action, populations); + + action.step(sim); + assertFalse(action.getSiteLocs().isEmpty()); + verify(gridMock, times(action.getSiteLocs().size() - 1)).addObject(any(), any()); + verify(cellMock, times(action.getSiteLocs().size() - 1)).schedule(any()); + } + + @Test + public void step_zeroDose_doesNotAddObjects() + throws NoSuchFieldException, IllegalAccessException { + when(parameters.getInt("DOSE")).thenReturn(0); + action = new PatchActionTreat(series, parameters); + + ArrayList populations = new ArrayList<>(); + MiniBox populationMock = mock(MiniBox.class); + when(populationMock.getInt("CODE")).thenReturn(4); + when(populationMock.get("CLASS")).thenReturn("cart_cd4"); + populations.add(populationMock); + Field pops = PatchActionTreat.class.getDeclaredField("populations"); + pops.setAccessible(true); + pops.set(action, populations); + + action.step(sim); + verify(gridMock, times(0)).addObject(any(), any()); + verify(cellMock, times(0)).schedule(any()); + } + + @Test + public void checkLocationSpace_withEmptySpaces_returnsAvailable() + throws NoSuchFieldException, IllegalAccessException { + action = new PatchActionTreat(series, parameters); + ArrayList populations = new ArrayList<>(); + MiniBox populationMock = mock(MiniBox.class); + when(populationMock.getInt("CODE")).thenReturn(4); + when(populationMock.get("CLASS")).thenReturn("cart_cd4"); + populations.add(populationMock); + Field pops = PatchActionTreat.class.getDeclaredField("populations"); + pops.setAccessible(true); + pops.set(action, populations); + + PatchLocation locationMock = mock(PatchLocation.class); + when(locationMock.getArea()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30); + when(locationMock.getVolume()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30 * 8.7); + Bag testBag = createPatchCellsWithVolumeAndCriticalHeight(2, 10, 12.5); + when(gridMock.getObjectsAtLocation(locationMock)).thenReturn(testBag); + boolean result = action.checkLocationSpace(locationMock, gridMock); + assertTrue(result); + } + + @Test + public void checkLocation_maxConfluency_returnsUnavailable() + throws NoSuchFieldException, IllegalAccessException { + action = new PatchActionTreat(series, parameters); + Field density = PatchActionTreat.class.getDeclaredField("maxConfluency"); + density.setAccessible(true); + density.set(action, 1); + + ArrayList populations = new ArrayList<>(); + MiniBox populationMock = mock(MiniBox.class); + when(populationMock.getInt("CODE")).thenReturn(4); + when(populationMock.get("CLASS")).thenReturn("cart_cd4"); + populations.add(populationMock); + Field pops = PatchActionTreat.class.getDeclaredField("populations"); + pops.setAccessible(true); + pops.set(action, populations); + + PatchLocation locationMock = mock(PatchLocation.class); + when(locationMock.getArea()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30); + when(locationMock.getVolume()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30 * 8.7); + Bag testBag = createPatchCellsWithVolumeAndCriticalHeight(2, 10, 12.5); + when(gridMock.getObjectsAtLocation(locationMock)).thenReturn(testBag); + boolean result = action.checkLocationSpace(locationMock, gridMock); + assertFalse(result); + } +} diff --git a/test/arcade/patch/agent/cell/PatchCellCARTCD4Test.java b/test/arcade/patch/agent/cell/PatchCellCARTCD4Test.java new file mode 100644 index 000000000..2f6716b2c --- /dev/null +++ b/test/arcade/patch/agent/cell/PatchCellCARTCD4Test.java @@ -0,0 +1,425 @@ +package arcade.patch.agent.cell; + +import java.lang.reflect.Field; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import sim.engine.Schedule; +import sim.engine.Steppable; +import ec.util.MersenneTwisterFast; +import arcade.core.sim.Simulation; +import arcade.core.util.MiniBox; +import arcade.core.util.Parameters; +import arcade.patch.agent.module.PatchModule; +import arcade.patch.agent.process.PatchProcessInflammation; +import arcade.patch.agent.process.PatchProcessMetabolism; +import arcade.patch.agent.process.PatchProcessSignaling; +import arcade.patch.env.location.PatchLocation; +import arcade.patch.sim.PatchSimulation; +import arcade.patch.util.PatchEnums.Domain; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static arcade.core.ARCADETestUtilities.randomDoubleBetween; +import static arcade.core.ARCADETestUtilities.randomIntBetween; +import static arcade.patch.util.PatchEnums.AntigenFlag; +import static arcade.patch.util.PatchEnums.State; + +public class PatchCellCARTCD4Test { + + private Parameters parameters; + private PatchLocation location; + private PatchCellContainer container; + private PatchCellCARTCD4 cell; + + @BeforeEach + public void setUp() throws NoSuchFieldException, IllegalAccessException { + parameters = spy(new Parameters(new MiniBox(), null, null)); + location = mock(PatchLocation.class); + + int id = 1; + int parentId = 1; + int pop = 1; + int age = randomIntBetween(1, 120950); + int divisions = 10; + double volume = randomDoubleBetween(100, 200); + double height = randomDoubleBetween(4, 10); + double criticalVolume = randomDoubleBetween(100, 200); + double criticalHeight = randomDoubleBetween(4, 10); + State state = State.UNDEFINED; + ; + + container = + new PatchCellContainer( + id, + parentId, + pop, + age, + divisions, + state, + volume, + height, + criticalVolume, + criticalHeight); + doReturn(0.0).when(parameters).getDouble(any(String.class)); + doReturn(0).when(parameters).getInt(any(String.class)); + when(parameters.getDouble("HETEROGENEITY")).thenReturn(0.0); + when(parameters.getDouble("ENERGY_THRESHOLD")).thenReturn(1.0); + + when(parameters.getDouble("NECROTIC_FRACTION")) + .thenReturn(randomIntBetween(40, 100) / 100.0); + when(parameters.getDouble("EXHAU_FRAC")).thenReturn(randomIntBetween(40, 100) / 100.0); + when(parameters.getDouble("SENESCENT_FRACTION")) + .thenReturn(randomIntBetween(40, 100) / 100.0); + when(parameters.getDouble("ANERGIC_FRACTION")) + .thenReturn(randomIntBetween(40, 100) / 100.0); + when(parameters.getDouble("PROLIFERATIVE_FRACTION")) + .thenReturn(randomIntBetween(40, 100) / 100.0); + when(parameters.getInt("SELF_RECEPTORS")).thenReturn(randomIntBetween(100, 200)); + when(parameters.getDouble("SEARCH_ABILITY")).thenReturn(1.0); + when(parameters.getDouble("CAR_AFFINITY")).thenReturn(10 * Math.pow(10, -7)); + when(parameters.getDouble("CAR_ALPHA")).thenReturn(3.0); + when(parameters.getDouble("CAR_BETA")).thenReturn(0.01); + when(parameters.getDouble("SELF_RECEPTOR_AFFINITY")).thenReturn(7.8E-6); + when(parameters.getDouble("SELF_ALPHA")).thenReturn(3.0); + when(parameters.getDouble("SELF_BETA")).thenReturn(0.02); + when(parameters.getDouble("CONTACT_FRAC")).thenReturn(7.8E-6); + when(parameters.getInt("MAX_ANTIGEN_BINDING")).thenReturn(10); + when(parameters.getInt("CARS")).thenReturn(50000); + + when(parameters.getInt("APOPTOSIS_AGE")).thenReturn(120960); + when(parameters.getInt("MAX_DENSITY")).thenReturn(54); + + cell = spy(new PatchCellCARTCD4(container, location, parameters)); + Field apoptosisAge = PatchCell.class.getDeclaredField("apoptosisAge"); + apoptosisAge.setAccessible(true); + apoptosisAge.set(cell, 120958); + + Field maxDensity = PatchCell.class.getDeclaredField("maxDensity"); + maxDensity.setAccessible(true); + maxDensity.set(cell, 54); + } + + @Test + public void step_increasesAge() { + PatchSimulation sim = mock(PatchSimulation.class); + cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class)); + cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class)); + cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class)); + PatchModule module = mock(PatchModule.class); + MersenneTwisterFast random = mock(MersenneTwisterFast.class); + doAnswer( + invocationOnMock -> { + cell.state = invocationOnMock.getArgument(0); + cell.module = module; + return null; + }) + .when(cell) + .setState(any(State.class)); + doReturn(new PatchCellTissue(container, location, parameters)) + .when(cell) + .bindTarget( + any(Simulation.class), + any(PatchLocation.class), + any(MersenneTwisterFast.class)); + sim.random = random; + cell.setState(State.UNDEFINED); + int initialAge = cell.getAge(); + cell.step(sim); + assertEquals(initialAge + 1, cell.getAge()); + } + + @Test + public void step_whenEnergyIsLow_setsStateToApoptotic() { + PatchSimulation sim = mock(PatchSimulation.class); + cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class)); + cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class)); + cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class)); + PatchModule module = mock(PatchModule.class); + MersenneTwisterFast random = mock(MersenneTwisterFast.class); + doAnswer( + invocationOnMock -> { + cell.state = invocationOnMock.getArgument(0); + cell.module = module; + return null; + }) + .when(cell) + .setState(any(State.class)); + doReturn(new PatchCellTissue(container, location, parameters)) + .when(cell) + .bindTarget( + any(Simulation.class), + any(PatchLocation.class), + any(MersenneTwisterFast.class)); + sim.random = random; + cell.setState(State.UNDEFINED); + + cell.setEnergy(-1 * randomIntBetween(2, 5)); + cell.step(sim); + + assertEquals(State.APOPTOTIC, cell.getState()); + assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag()); + assertFalse(cell.getActivationStatus()); + } + + @Test + public void step_whenEnergyIsNegativeAndMoreThanThreshold_setsStateToStarved() { + PatchSimulation sim = mock(PatchSimulation.class); + cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class)); + cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class)); + cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class)); + PatchModule module = mock(PatchModule.class); + MersenneTwisterFast random = mock(MersenneTwisterFast.class); + doAnswer( + invocationOnMock -> { + cell.state = invocationOnMock.getArgument(0); + cell.module = module; + return null; + }) + .when(cell) + .setState(any(State.class)); + doReturn(new PatchCellTissue(container, location, parameters)) + .when(cell) + .bindTarget( + any(Simulation.class), + any(PatchLocation.class), + any(MersenneTwisterFast.class)); + sim.random = random; + cell.setState(State.UNDEFINED); + + cell.setEnergy(-0.5); + cell.step(sim); + + assertEquals(State.STARVED, cell.getState()); + assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag()); + } + + @Test + public void step_whenEnergyIsNegativeAndLessThanThreshold_setsStateToApoptotic() { + PatchSimulation sim = mock(PatchSimulation.class); + cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class)); + cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class)); + cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class)); + PatchModule module = mock(PatchModule.class); + MersenneTwisterFast random = mock(MersenneTwisterFast.class); + doAnswer( + invocationOnMock -> { + cell.state = invocationOnMock.getArgument(0); + cell.module = module; + return null; + }) + .when(cell) + .setState(any(State.class)); + doReturn(new PatchCellTissue(container, location, parameters)) + .when(cell) + .bindTarget( + any(Simulation.class), + any(PatchLocation.class), + any(MersenneTwisterFast.class)); + sim.random = random; + cell.setState(State.UNDEFINED); + + cell.setEnergy(-1.5); + cell.step(sim); + + assertEquals(State.APOPTOTIC, cell.getState()); + } + + @Test + public void step_whenDivisionsAreZero_setsStateToSenescent() { + PatchSimulation sim = mock(PatchSimulation.class); + cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class)); + cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class)); + cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class)); + PatchModule module = mock(PatchModule.class); + MersenneTwisterFast random = mock(MersenneTwisterFast.class); + doAnswer( + invocationOnMock -> { + cell.state = invocationOnMock.getArgument(0); + cell.divisions = 0; + cell.module = module; + return null; + }) + .when(cell) + .setState(any(State.class)); + doReturn(new PatchCellTissue(container, location, parameters)) + .when(cell) + .bindTarget( + any(Simulation.class), + any(PatchLocation.class), + any(MersenneTwisterFast.class)); + sim.random = random; + cell.setState(State.UNDEFINED); + + cell.step(sim); + + assertTrue(cell.getState() == State.APOPTOTIC || cell.getState() == State.SENESCENT); + assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag()); + assertFalse(cell.getActivationStatus()); + } + + @Test + public void step_whenBoundToBothAntigenAndSelf_setsStateToAnergic() { + PatchSimulation sim = mock(PatchSimulation.class); + cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class)); + cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class)); + cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class)); + PatchModule module = mock(PatchModule.class); + MersenneTwisterFast random = mock(MersenneTwisterFast.class); + doAnswer( + invocationOnMock -> { + cell.state = invocationOnMock.getArgument(0); + cell.module = module; + return null; + }) + .when(cell) + .setState(any(State.class)); + doReturn(new PatchCellTissue(container, location, parameters)) + .when(cell) + .bindTarget( + any(Simulation.class), + any(PatchLocation.class), + any(MersenneTwisterFast.class)); + sim.random = random; + cell.setState(State.UNDEFINED); + + cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN_CELL_RECEPTOR); + cell.step(sim); + + assertTrue(cell.getState() == State.APOPTOTIC || cell.getState() == State.ANERGIC); + assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag()); + assertFalse(cell.getActivationStatus()); + } + + @Test + public void step_whenBoundToAntigen_setsStateToStimulatory() { + PatchSimulation sim = mock(PatchSimulation.class); + cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class)); + cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class)); + cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class)); + PatchModule module = mock(PatchModule.class); + MersenneTwisterFast random = mock(MersenneTwisterFast.class); + doAnswer( + invocationOnMock -> { + cell.state = invocationOnMock.getArgument(0); + cell.boundCARAntigensCount = 0; + cell.module = module; + return null; + }) + .when(cell) + .setState(any(State.class)); + doReturn(new PatchCellTissue(container, location, parameters)) + .when(cell) + .bindTarget( + any(Simulation.class), + any(PatchLocation.class), + any(MersenneTwisterFast.class)); + sim.random = random; + cell.setState(State.UNDEFINED); + cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN); + + Schedule schedule = mock(Schedule.class); + doReturn(true).when(schedule).scheduleOnce(any(Steppable.class)); + doReturn(schedule).when(sim).getSchedule(); + + cell.step(sim); + + assertEquals(State.STIMULATORY, cell.getState()); + assertTrue(cell.getActivationStatus()); + } + + @Test + public void step_whenActivated_setsStateToProliferative() { + PatchSimulation sim = mock(PatchSimulation.class); + cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class)); + cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class)); + cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class)); + PatchModule module = mock(PatchModule.class); + MersenneTwisterFast random = mock(MersenneTwisterFast.class); + doAnswer( + invocationOnMock -> { + cell.state = invocationOnMock.getArgument(0); + cell.activated = true; + cell.module = module; + return null; + }) + .when(cell) + .setState(any(State.class)); + doReturn(new PatchCellTissue(container, location, parameters)) + .when(cell) + .bindTarget( + any(Simulation.class), + any(PatchLocation.class), + any(MersenneTwisterFast.class)); + sim.random = random; + cell.setState(State.UNDEFINED); + + cell.step(sim); + + assertEquals(State.PROLIFERATIVE, cell.getState()); + } + + @Test + public void step_whenNotActivated_setsStateToMigratory() { + PatchSimulation sim = mock(PatchSimulation.class); + cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class)); + cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class)); + cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class)); + PatchModule module = mock(PatchModule.class); + MersenneTwisterFast random = mock(MersenneTwisterFast.class); + doAnswer( + invocationOnMock -> { + cell.state = invocationOnMock.getArgument(0); + cell.activated = false; + cell.module = module; + return null; + }) + .when(cell) + .setState(any(State.class)); + doReturn(new PatchCellTissue(container, location, parameters)) + .when(cell) + .bindTarget( + any(Simulation.class), + any(PatchLocation.class), + any(MersenneTwisterFast.class)); + sim.random = random; + cell.setState(State.UNDEFINED); + + cell.step(sim); + + assertTrue(cell.getState() == State.MIGRATORY || cell.getState() == State.PROLIFERATIVE); + } + + @Test + public void step_whenOverstimulated_setsStateToExhausted() { + PatchSimulation sim = mock(PatchSimulation.class); + cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class)); + cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class)); + cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class)); + PatchModule module = mock(PatchModule.class); + MersenneTwisterFast random = mock(MersenneTwisterFast.class); + doAnswer( + invocationOnMock -> { + cell.state = invocationOnMock.getArgument(0); + cell.boundCARAntigensCount = cell.maxAntigenBinding + 1; + cell.module = module; + return null; + }) + .when(cell) + .setState(any(State.class)); + doReturn(new PatchCellTissue(container, location, parameters)) + .when(cell) + .bindTarget( + any(Simulation.class), + any(PatchLocation.class), + any(MersenneTwisterFast.class)); + sim.random = random; + cell.setState(State.UNDEFINED); + cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN); + + cell.step(sim); + + assertTrue(cell.getState() == State.APOPTOTIC || cell.getState() == State.EXHAUSTED); + assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag()); + assertFalse(cell.getActivationStatus()); + } +} diff --git a/test/arcade/patch/agent/cell/PatchCellCARTCD8Test.java b/test/arcade/patch/agent/cell/PatchCellCARTCD8Test.java new file mode 100644 index 000000000..a28ec9bb5 --- /dev/null +++ b/test/arcade/patch/agent/cell/PatchCellCARTCD8Test.java @@ -0,0 +1,255 @@ +package arcade.patch.agent.cell; + +import java.lang.reflect.Field; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import sim.engine.Schedule; +import sim.engine.Steppable; +import ec.util.MersenneTwisterFast; +import arcade.core.sim.Simulation; +import arcade.core.util.MiniBox; +import arcade.core.util.Parameters; +import arcade.patch.agent.module.PatchModule; +import arcade.patch.agent.process.PatchProcessInflammation; +import arcade.patch.agent.process.PatchProcessMetabolism; +import arcade.patch.agent.process.PatchProcessSignaling; +import arcade.patch.env.location.PatchLocation; +import arcade.patch.sim.PatchSimulation; +import arcade.patch.util.PatchEnums.Domain; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static arcade.core.ARCADETestUtilities.randomDoubleBetween; +import static arcade.core.ARCADETestUtilities.randomIntBetween; +import static arcade.patch.util.PatchEnums.AntigenFlag; +import static arcade.patch.util.PatchEnums.State; + +public class PatchCellCARTCD8Test { + + private PatchCellCARTCD8 cell; + + private Parameters parameters; + + private PatchLocation location; + + private PatchCellContainer container; + + PatchSimulation sim; + + @BeforeEach + public final void setUp() throws NoSuchFieldException, IllegalAccessException { + parameters = spy(new Parameters(new MiniBox(), null, null)); + location = mock(PatchLocation.class); + + int id = 1; + int parentId = 1; + int pop = 1; + int age = randomIntBetween(1, 120950); + int divisions = 0; + double volume = randomDoubleBetween(100, 200); + double height = randomDoubleBetween(4, 10); + double criticalVolume = randomDoubleBetween(100, 200); + double criticalHeight = randomDoubleBetween(4, 10); + State state = State.UNDEFINED; + + container = + new PatchCellContainer( + id, + parentId, + pop, + age, + divisions, + state, + volume, + height, + criticalVolume, + criticalHeight); + + doReturn(0.0).when(parameters).getDouble(any(String.class)); + doReturn(0).when(parameters).getInt(any(String.class)); + when(parameters.getDouble("HETEROGENEITY")).thenReturn(0.0); + when(parameters.getDouble("ENERGY_THRESHOLD")).thenReturn(1.0); + when(parameters.getDouble("NECROTIC_FRACTION")).thenReturn(0.5); + when(parameters.getDouble("EXHAUSTED_FRAC")).thenReturn(0.5); + when(parameters.getDouble("SENESCENT_FRACTION")).thenReturn(0.5); + when(parameters.getDouble("ANERGIC_FRACTION")).thenReturn(0.5); + when(parameters.getDouble("PROLIFERATIVE_FRACTION")).thenReturn(0.5); + when(parameters.getInt("SELF_RECEPTORS")).thenReturn(randomIntBetween(100, 200)); + when(parameters.getDouble("SEARCH_ABILITY")).thenReturn(1.0); + when(parameters.getDouble("CAR_AFFINITY")).thenReturn(10 * Math.pow(10, -7)); + when(parameters.getDouble("CAR_ALPHA")).thenReturn(3.0); + when(parameters.getDouble("CAR_BETA")).thenReturn(0.01); + when(parameters.getDouble("SELF_RECEPTOR_AFFINITY")).thenReturn(7.8E-6); + when(parameters.getDouble("SELF_ALPHA")).thenReturn(3.0); + when(parameters.getDouble("SELF_BETA")).thenReturn(0.02); + when(parameters.getDouble("CONTACT_FRAC")).thenReturn(7.8E-6); + when(parameters.getInt("MAX_ANTIGEN_BINDING")).thenReturn(10); + when(parameters.getInt("CARS")).thenReturn(50000); + when(parameters.getDouble("APOPTOSIS_AGE")).thenReturn(120960.0); + when(parameters.getInt("MAX_DENSITY")).thenReturn(54); + when(parameters.getInt("DIVISION_POTENTIAL")).thenReturn(10); + + cell = spy(new PatchCellCARTCD8(container, location, parameters)); + + sim = mock(PatchSimulation.class); + cell.processes.put(Domain.METABOLISM, mock(PatchProcessMetabolism.class)); + cell.processes.put(Domain.SIGNALING, mock(PatchProcessSignaling.class)); + cell.processes.put(Domain.INFLAMMATION, mock(PatchProcessInflammation.class)); + PatchModule module = mock(PatchModule.class); + MersenneTwisterFast random = mock(MersenneTwisterFast.class); + doAnswer( + invocationOnMock -> { + cell.state = invocationOnMock.getArgument(0); + cell.module = module; + return null; + }) + .when(cell) + .setState(any(State.class)); + doReturn(new PatchCellTissue(container, location, parameters)) + .when(cell) + .bindTarget( + any(Simulation.class), + any(PatchLocation.class), + any(MersenneTwisterFast.class)); + when(random.nextDouble()).thenReturn(0.49); + sim.random = random; + cell.setState(State.UNDEFINED); + } + + @Test + public void step_called_increasesAge() { + int initialAge = cell.getAge(); + + cell.step(sim); + + assertEquals(initialAge + 1, cell.getAge()); + } + + @Test + public void step_whenEnergyIsLow_setsStateToApoptotic() { + cell.setEnergy(-1 * randomIntBetween(2, 5)); + + cell.step(sim); + + assertEquals(State.APOPTOTIC, cell.getState()); + assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag()); + assertFalse(cell.getActivationStatus()); + } + + @Test + public void step_whenEnergyIsNegativeAndMoreThanThreshold_setsStateToStarved() { + cell.setEnergy(-0.5); + + cell.step(sim); + + assertEquals(State.STARVED, cell.getState()); + assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag()); + } + + @Test + public void step_whenEnergyIsNegativeAndMoreThanThreshold_setsStateToApoptotic() { + cell.setEnergy(-1.5); + + cell.step(sim); + + assertEquals(State.APOPTOTIC, cell.getState()); + } + + @Test + public void step_whenDivisionPotentialMet_setsStateToApoptotic() + throws NoSuchFieldException, IllegalAccessException { + Field div = PatchCell.class.getDeclaredField("divisions"); + div.setAccessible(true); + div.set(cell, cell.divisionPotential); + when(sim.random.nextDouble()).thenReturn(0.51); + + cell.step(sim); + + assertTrue(cell.getState() == State.APOPTOTIC); + assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag()); + assertFalse(cell.getActivationStatus()); + } + + @Test + public void step_whenDivisionPotentialMet_setsStateToSenescent() + throws NoSuchFieldException, IllegalAccessException { + Field div = PatchCell.class.getDeclaredField("divisions"); + div.setAccessible(true); + div.set(cell, cell.divisionPotential); + when(sim.random.nextDouble()).thenReturn(0.49); + + cell.step(sim); + + assertTrue(cell.getState() == State.SENESCENT); + assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag()); + assertFalse(cell.getActivationStatus()); + } + + @Test + public void step_whenBoundToBothAntigenAndSelf_setsStateToAnergic() { + cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN_CELL_RECEPTOR); + + cell.step(sim); + + assertTrue(cell.getState() == State.ANERGIC); + assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag()); + assertFalse(cell.getActivationStatus()); + } + + @Test + public void step_whenBoundToAntigen_setsStateToCytotoxic() + throws NoSuchFieldException, IllegalAccessException { + Field boundAntigens = PatchCellCART.class.getDeclaredField("boundCARAntigensCount"); + boundAntigens.setAccessible(true); + boundAntigens.set(cell, 0); + cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN); + Schedule schedule = mock(Schedule.class); + doReturn(true).when(schedule).scheduleOnce(any(Steppable.class)); + doReturn(schedule).when(sim).getSchedule(); + + cell.step(sim); + + verify(cell, times(1)).setState(State.CYTOTOXIC); + } + + @Test + public void step_whenActivated_setsStateToProliferative() + throws NoSuchFieldException, IllegalAccessException { + Field active = PatchCellCART.class.getDeclaredField("activated"); + active.setAccessible(true); + active.set(cell, true); + + cell.step(sim); + + assertEquals(State.PROLIFERATIVE, cell.getState()); + } + + @Test + public void step_whenNotActivated_setsStateToMigratory() + throws NoSuchFieldException, IllegalAccessException { + when(sim.random.nextDouble()).thenReturn(0.51); + Field active = PatchCellCART.class.getDeclaredField("activated"); + active.setAccessible(true); + active.set(cell, false); + + cell.step(sim); + + assertTrue(cell.getState() == State.MIGRATORY); + } + + @Test + public void step_whenOverstimulated_setsStateToExhausted() + throws NoSuchFieldException, IllegalAccessException { + when(sim.random.nextDouble()).thenReturn(0.49); + Field boundAntigens = PatchCellCART.class.getDeclaredField("boundCARAntigensCount"); + boundAntigens.setAccessible(true); + boundAntigens.set(cell, cell.maxAntigenBinding + 1); + cell.setBindingFlag(AntigenFlag.BOUND_ANTIGEN); + + cell.step(sim); + + assertTrue(cell.getState() == State.EXHAUSTED); + assertEquals(AntigenFlag.UNBOUND, cell.getBindingFlag()); + assertFalse(cell.getActivationStatus()); + } +} diff --git a/test/arcade/patch/agent/cell/PatchCellCARTTest.java b/test/arcade/patch/agent/cell/PatchCellCARTTest.java index 09afbb5df..241e65ab9 100644 --- a/test/arcade/patch/agent/cell/PatchCellCARTTest.java +++ b/test/arcade/patch/agent/cell/PatchCellCARTTest.java @@ -199,4 +199,31 @@ public void getActivationStatus_called_returnsStatus() activation.set(patchCellCART, true); assertTrue(patchCellCART.getActivationStatus()); } + + @Test + public void getBoundTarget_called_returnsTarget() + throws NoSuchFieldException, IllegalAccessException { + Field target = PatchCellCART.class.getDeclaredField("boundTarget"); + target.setAccessible(true); + target.set(patchCellCART, tissueCell); + + PatchCell targetCell = patchCellCART.getBoundTarget(); + + assertEquals(targetCell, tissueCell); + } + + @Test + public void unbind_called_setsStateAndTarget() + throws NoSuchFieldException, IllegalAccessException { + patchCellCART.setBindingFlag(PatchEnums.AntigenFlag.BOUND_ANTIGEN); + Field target = PatchCellCART.class.getDeclaredField("boundTarget"); + target.setAccessible(true); + target.set(patchCellCART, tissueCell); + + patchCellCART.unbind(); + + PatchCell targetCell = patchCellCART.getBoundTarget(); + assertNull(targetCell); + assertEquals(PatchEnums.AntigenFlag.UNBOUND, patchCellCART.getBindingFlag()); + } } diff --git a/test/arcade/patch/agent/module/PatchModuleCytotoxicityTest.java b/test/arcade/patch/agent/module/PatchModuleCytotoxicityTest.java new file mode 100644 index 000000000..1c18e0a04 --- /dev/null +++ b/test/arcade/patch/agent/module/PatchModuleCytotoxicityTest.java @@ -0,0 +1,75 @@ +package arcade.patch.agent.module; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ec.util.MersenneTwisterFast; +import arcade.core.util.Parameters; +import arcade.patch.agent.cell.PatchCellCART; +import arcade.patch.agent.cell.PatchCellTissue; +import arcade.patch.agent.process.PatchProcessInflammation; +import arcade.patch.sim.PatchSimulation; +import arcade.patch.util.PatchEnums.State; +import static org.mockito.Mockito.*; + +public class PatchModuleCytotoxicityTest { + + private PatchCellCART mockCell; + + private PatchCellTissue mockTarget; + + private PatchProcessInflammation mockInflammation; + + private PatchModuleCytotoxicity action; + + private PatchSimulation sim; + + private MersenneTwisterFast randomMock; + + @BeforeEach + public final void setUp() { + mockCell = mock(PatchCellCART.class); + mockTarget = mock(PatchCellTissue.class); + mockInflammation = mock(PatchProcessInflammation.class); + sim = mock(PatchSimulation.class); + randomMock = mock(MersenneTwisterFast.class); + Parameters parameters = mock(Parameters.class); + doReturn(0.0).when(parameters).getDouble(any(String.class)); + doReturn(0).when(parameters).getInt(any(String.class)); + + when(mockCell.getParameters()).thenReturn(parameters); + when(mockCell.getProcess(any())).thenReturn(mockInflammation); + when(mockInflammation.getInternal("granzyme")).thenReturn(1.0); + when(mockCell.getBoundTarget()).thenReturn(mockTarget); + + action = new PatchModuleCytotoxicity(mockCell); + } + + @Test + public void step_CARCellStopped_doesNotChangeCell() { + when(mockCell.isStopped()).thenReturn(true); + action.step(randomMock, sim); + + verify(mockTarget, never()).setState(any()); + } + + @Test + public void step_targetCellStopped_doesNotChangeCell() { + when(mockTarget.isStopped()).thenReturn(true); + action.step(randomMock, sim); + + verify(mockTarget, never()).setState(any()); + verify(mockCell).unbind(); + } + + @Test + public void step_killTargetCell_killsCellAndUsesGranzyme() { + when(mockCell.isStopped()).thenReturn(false); + when(mockTarget.isStopped()).thenReturn(false); + PatchModuleApoptosis mockProcess = mock(PatchModuleApoptosis.class); + when(mockTarget.getModule()).thenReturn(mockProcess); + action.step(randomMock, sim); + + verify(mockTarget).setState(State.APOPTOTIC); + verify(mockInflammation).setInternal("granzyme", 0.0); + } +} diff --git a/test/arcade/patch/agent/process/PatchProcessInflammationCD4Test.java b/test/arcade/patch/agent/process/PatchProcessInflammationCD4Test.java new file mode 100644 index 000000000..7e910de3b --- /dev/null +++ b/test/arcade/patch/agent/process/PatchProcessInflammationCD4Test.java @@ -0,0 +1,279 @@ +package arcade.patch.agent.process; + +import java.lang.reflect.Field; +import java.util.Arrays; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import ec.util.MersenneTwisterFast; +import arcade.core.sim.Simulation; +import arcade.core.util.Parameters; +import arcade.patch.agent.cell.PatchCellCART; +import arcade.patch.env.lattice.PatchLattice; +import arcade.patch.env.location.PatchLocation; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static arcade.core.ARCADETestUtilities.randomDoubleBetween; + +public class PatchProcessInflammationCD4Test { + + private PatchProcessInflammationCD4 inflammation; + private PatchCellCART mockCell; + private PatchLattice mockLattice; + private Parameters mockParameters; + private Simulation mockSim; + private MersenneTwisterFast mockRandom; + private double cellVolume; + private PatchLocation mockLocation; + + @BeforeEach + public void setUp() { + mockCell = Mockito.mock(PatchCellCART.class); + mockParameters = Mockito.mock(Parameters.class); + mockSim = Mockito.mock(Simulation.class); + mockRandom = Mockito.mock(MersenneTwisterFast.class); + mockLocation = mock(PatchLocation.class); + mockLattice = mock(PatchLattice.class); + + Mockito.when(mockCell.getParameters()).thenReturn(mockParameters); + cellVolume = randomDoubleBetween(165, 180); + when(mockCell.getVolume()).thenReturn(cellVolume); + when(mockCell.getLocation()).thenReturn(mockLocation); + when(mockLocation.getVolume()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30 * 8.7); + when(mockParameters.getDouble(anyString())).thenReturn(1.0); + when(mockParameters.getInt(anyString())).thenReturn(1); + + when(mockSim.getLattice(anyString())).thenReturn(mockLattice); + doNothing().when(mockLattice).setValue(any(PatchLocation.class), anyDouble()); + } + + @Test + public void constructor_setsParameters() throws NoSuchFieldException, IllegalAccessException { + inflammation = new PatchProcessInflammationCD4(mockCell); + assertNotNull(inflammation); + + Field il2ProdRate = PatchProcessInflammationCD4.class.getDeclaredField("IL2ProdRate"); + il2ProdRate.setAccessible(true); + assertEquals(0.0, il2ProdRate.get(inflammation)); + + Field il2ProdRateIL2 = + PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_IL2"); + il2ProdRateIL2.setAccessible(true); + assertEquals(1.0, il2ProdRateIL2.get(inflammation)); + + Field il2ProdRateActive = + PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_ACTIVE"); + il2ProdRateActive.setAccessible(true); + assertEquals(1.0, il2ProdRateActive.get(inflammation)); + + Field il2SynthesisDelay = + PatchProcessInflammationCD4.class.getDeclaredField("IL2_SYNTHESIS_DELAY"); + il2SynthesisDelay.setAccessible(true); + assertEquals(1, il2SynthesisDelay.get(inflammation)); + } + + @Test + public void stepProcess_updatesEnvironment() + throws NoSuchFieldException, IllegalAccessException { + inflammation = new PatchProcessInflammationCD4(mockCell); + + inflammation.active = true; + inflammation.activeTicker = 10; + inflammation.iL2Ticker = 10; + inflammation.boundArray = new double[180]; + Arrays.fill(inflammation.boundArray, 10000); + + Field il2ProdRateIL2 = + PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_IL2"); + il2ProdRateIL2.setAccessible(true); + il2ProdRateIL2.set(inflammation, 0.05); + + Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors"); + receptors.setAccessible(true); + receptors.set(inflammation, 5000); + + Field fraction = PatchProcessInflammation.class.getDeclaredField("fraction"); + fraction.setAccessible(true); + fraction.set(inflammation, 1.0); + + inflammation.stepProcess(mockRandom, mockSim); + + // check that patch lattice set value is called + verify(mockLattice, times(1)).setValue(any(PatchLocation.class), anyDouble()); + + // check that IL2 produced is calculated correctly + Field il2Produced = PatchProcessInflammationCD4.class.getDeclaredField("IL2Produced"); + il2Produced.setAccessible(true); + assertEquals(1.1, il2Produced.get(inflammation)); + + // check that extIL2 is calculated correctly + double expectedIL2 = + (1.1 + inflammation.amts[PatchProcessInflammationCD4.IL2_EXT]) + * 1E12 + / mockLocation.getVolume(); + assertEquals(expectedIL2, inflammation.getIL2EnvTesting()); + } + + @Test + public void stepProcess_whenActive_returnsHigherRate() + throws NoSuchFieldException, IllegalAccessException { + inflammation = new PatchProcessInflammationCD4(mockCell); + + inflammation.active = true; + inflammation.activeTicker = 10; + inflammation.iL2Ticker = 10; + inflammation.boundArray = new double[180]; + Arrays.fill(inflammation.boundArray, 10000); + + Field il2ProdRateIL2 = + PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_IL2"); + il2ProdRateIL2.setAccessible(true); + il2ProdRateIL2.set(inflammation, 0.05); + + Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors"); + receptors.setAccessible(true); + receptors.set(inflammation, 5000); + + Field fraction = PatchProcessInflammation.class.getDeclaredField("fraction"); + fraction.setAccessible(true); + fraction.set(inflammation, 1.0); + + Field activeIl2Rate = + PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_ACTIVE"); + activeIl2Rate.setAccessible(true); + activeIl2Rate.set(inflammation, 2.5); + + inflammation.stepProcess(mockRandom, mockSim); + + Field il2ProdRate = PatchProcessInflammationCD4.class.getDeclaredField("IL2ProdRate"); + il2ProdRate.setAccessible(true); + assertEquals(2.6, il2ProdRate.get(inflammation)); + } + + @Test + public void stepProcess_whenInactive_returnsDefaultRate() + throws NoSuchFieldException, IllegalAccessException { + inflammation = new PatchProcessInflammationCD4(mockCell); + + inflammation.active = false; + inflammation.activeTicker = 10; + inflammation.iL2Ticker = 10; + inflammation.boundArray = new double[180]; + Arrays.fill(inflammation.boundArray, 10000); + + Field il2ProdRateIL2 = + PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_IL2"); + il2ProdRateIL2.setAccessible(true); + il2ProdRateIL2.set(inflammation, 0.05); + + Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors"); + receptors.setAccessible(true); + receptors.set(inflammation, 5000); + + Field fraction = PatchProcessInflammation.class.getDeclaredField("fraction"); + fraction.setAccessible(true); + fraction.set(inflammation, 1.0); + + inflammation.stepProcess(mockRandom, mockSim); + + Field il2ProdRate = PatchProcessInflammationCD4.class.getDeclaredField("IL2ProdRate"); + il2ProdRate.setAccessible(true); + assertEquals(0.1, il2ProdRate.get(inflammation)); + } + + @Test + public void stepProcess_activeTickerLessThanDelay_usesDefaultRate() + throws NoSuchFieldException, IllegalAccessException { + inflammation = new PatchProcessInflammationCD4(mockCell); + + inflammation.active = true; + inflammation.activeTicker = 10; + inflammation.iL2Ticker = 10; + inflammation.boundArray = new double[180]; + Arrays.fill(inflammation.boundArray, 10000); + + Field il2ProdRateIL2 = + PatchProcessInflammationCD4.class.getDeclaredField("IL2_PROD_RATE_IL2"); + il2ProdRateIL2.setAccessible(true); + il2ProdRateIL2.set(inflammation, 0.05); + + Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors"); + receptors.setAccessible(true); + receptors.set(inflammation, 5000); + + Field fraction = PatchProcessInflammation.class.getDeclaredField("fraction"); + fraction.setAccessible(true); + fraction.set(inflammation, 1.0); + + Field delay = PatchProcessInflammationCD4.class.getDeclaredField("IL2_SYNTHESIS_DELAY"); + delay.setAccessible(true); + delay.set(inflammation, 15); + + inflammation.stepProcess(mockRandom, mockSim); + + Field il2ProdRate = PatchProcessInflammationCD4.class.getDeclaredField("IL2ProdRate"); + il2ProdRate.setAccessible(true); + assertEquals(0.1, il2ProdRate.get(inflammation)); + } + + @Test + public void stepProcess_withZeroIL2ProdRate_returnsZero() + throws NoSuchFieldException, IllegalAccessException { + when(mockParameters.getDouble("inflammation/IL2_PROD_RATE_IL2")).thenReturn(0.0); + when(mockParameters.getDouble("inflammation/IL2_PROD_RATE_ACTIVE")).thenReturn(0.0); + inflammation = new PatchProcessInflammationCD4(mockCell); + + inflammation.active = true; + inflammation.activeTicker = 10; + inflammation.iL2Ticker = 10; + inflammation.boundArray = new double[180]; + Arrays.fill(inflammation.boundArray, 10000); + + Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors"); + receptors.setAccessible(true); + receptors.set(inflammation, 5000); + + Field fraction = PatchProcessInflammation.class.getDeclaredField("fraction"); + fraction.setAccessible(true); + fraction.set(inflammation, 1.0); + + inflammation.stepProcess(mockRandom, mockSim); + + Field il2ProdRate = PatchProcessInflammationCD4.class.getDeclaredField("IL2ProdRate"); + il2ProdRate.setAccessible(true); + assertEquals(0.0, il2ProdRate.get(inflammation)); + + Field il2Produced = PatchProcessInflammationCD4.class.getDeclaredField("IL2Produced"); + il2Produced.setAccessible(true); + assertEquals(0.0, il2Produced.get(inflammation)); + } + + @Test + public void update_evenSplit_splitsEvenly() { + inflammation = new PatchProcessInflammationCD4(mockCell); + PatchProcessInflammationCD4 parentProcess = new PatchProcessInflammationCD4(mockCell); + parentProcess.amts[PatchProcessInflammationCD4.IL2RBGA] = 100; + when(mockCell.getVolume()).thenReturn(cellVolume / 2); + + inflammation.update(parentProcess); + + assertEquals(50, inflammation.amts[PatchProcessInflammationCD4.IL2RBGA]); + assertEquals(50, parentProcess.amts[PatchProcessInflammationCD4.IL2RBGA]); + } + + @Test + public void update_withZeroVolume_splitsUnevenly() { + inflammation = new PatchProcessInflammationCD4(mockCell); + PatchProcessInflammationCD4 parentProcess = new PatchProcessInflammationCD4(mockCell); + parentProcess.amts[PatchProcessInflammationCD4.IL2RBGA] = 100; + when(mockCell.getVolume()).thenReturn(0.0); + + inflammation.update(parentProcess); + + assertEquals(0.0, inflammation.amts[PatchProcessInflammationCD8.IL2RBGA]); + assertEquals(0.0, inflammation.amts[PatchProcessInflammationCD8.IL2_IL2RBG]); + assertEquals(0.0, inflammation.amts[PatchProcessInflammationCD8.IL2_IL2RBGA]); + + assertEquals(100, parentProcess.amts[PatchProcessInflammationCD4.IL2RBGA]); + } +} diff --git a/test/arcade/patch/agent/process/PatchProcessInflammationCD8Test.java b/test/arcade/patch/agent/process/PatchProcessInflammationCD8Test.java new file mode 100644 index 000000000..a6f7b4332 --- /dev/null +++ b/test/arcade/patch/agent/process/PatchProcessInflammationCD8Test.java @@ -0,0 +1,155 @@ +package arcade.patch.agent.process; + +import java.lang.reflect.Field; +import java.util.Arrays; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import ec.util.MersenneTwisterFast; +import arcade.core.sim.Simulation; +import arcade.core.util.Parameters; +import arcade.patch.agent.cell.PatchCellCART; +import arcade.patch.env.lattice.PatchLattice; +import arcade.patch.env.location.PatchLocation; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; +import static arcade.core.ARCADETestUtilities.randomDoubleBetween; + +public class PatchProcessInflammationCD8Test { + + private PatchProcessInflammationCD8 inflammation; + + private PatchCellCART mockCell; + + private Parameters mockParameters; + + private Simulation mockSimulation; + + private MersenneTwisterFast mockRandom; + + private double cellVolume; + + @BeforeEach + public final void setUp() { + mockCell = Mockito.mock(PatchCellCART.class); + mockParameters = Mockito.mock(Parameters.class); + mockSimulation = Mockito.mock(Simulation.class); + mockRandom = Mockito.mock(MersenneTwisterFast.class); + PatchLocation mockLocation = mock(PatchLocation.class); + PatchLattice mockLattice = mock(PatchLattice.class); + + Mockito.when(mockCell.getParameters()).thenReturn(mockParameters); + cellVolume = randomDoubleBetween(165, 180); + when(mockCell.getVolume()).thenReturn(cellVolume); + when(mockCell.getLocation()).thenReturn(mockLocation); + when(mockLocation.getVolume()).thenReturn(3.0 / 2.0 / Math.sqrt(3.0) * 30 * 30 * 8.7); + when(mockParameters.getDouble(anyString())).thenReturn(1.0); + when(mockParameters.getInt(anyString())).thenReturn(1); + + when(mockSimulation.getLattice(anyString())).thenReturn(mockLattice); + doNothing().when(mockLattice).setValue(any(PatchLocation.class), anyDouble()); + } + + @Test + public void constructor_called_setsParameters() + throws NoSuchFieldException, IllegalAccessException { + inflammation = new PatchProcessInflammationCD8(mockCell); + assertNotNull(inflammation); + + assertEquals(1, inflammation.amts[PatchProcessInflammationCD8.GRANZYME]); + + Field prior = PatchProcessInflammationCD8.class.getDeclaredField("priorIL2granz"); + prior.setAccessible(true); + assertEquals(0.0, prior.get(inflammation)); + + Field delay = PatchProcessInflammationCD8.class.getDeclaredField("granzSynthesisDelay"); + delay.setAccessible(true); + assertEquals(1, delay.get(inflammation)); + } + + @Test + public void stepProcess_called_updatesEnvironment() + throws NoSuchFieldException, IllegalAccessException { + inflammation = new PatchProcessInflammationCD8(mockCell); + inflammation.active = true; + inflammation.activeTicker = 10; + inflammation.iL2Ticker = 10; + inflammation.boundArray = new double[180]; + Arrays.fill(inflammation.boundArray, 10000); + + Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors"); + receptors.setAccessible(true); + receptors.set(inflammation, 5000); + + inflammation.stepProcess(mockRandom, mockSimulation); + + assertTrue(inflammation.amts[PatchProcessInflammationCD8.GRANZYME] > 1); + } + + @Test + public void stepProcess_whenInactive_returnsDefaultRate() + throws NoSuchFieldException, IllegalAccessException { + inflammation = new PatchProcessInflammationCD8(mockCell); + inflammation.active = false; + inflammation.activeTicker = 10; + inflammation.iL2Ticker = 10; + inflammation.boundArray = new double[180]; + Arrays.fill(inflammation.boundArray, 10000); + + Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors"); + receptors.setAccessible(true); + receptors.set(inflammation, 5000); + + inflammation.stepProcess(mockRandom, mockSimulation); + + assertEquals(1, inflammation.amts[PatchProcessInflammationCD8.GRANZYME]); + } + + @Test + public void stepProcess_activeTickerLessThanDelay_usesDefaultRate() + throws NoSuchFieldException, IllegalAccessException { + Mockito.when(mockParameters.getInt("inflammation/GRANZ_SYNTHESIS_DELAY")).thenReturn(5); + inflammation = new PatchProcessInflammationCD8(mockCell); + inflammation.active = true; + inflammation.activeTicker = 3; + inflammation.iL2Ticker = 10; + inflammation.boundArray = new double[180]; + Arrays.fill(inflammation.boundArray, 10000); + + Field receptors = PatchProcessInflammation.class.getDeclaredField("iL2Receptors"); + receptors.setAccessible(true); + receptors.set(inflammation, 5000); + + inflammation.stepProcess(mockRandom, mockSimulation); + + assertEquals(1, inflammation.amts[PatchProcessInflammationCD8.GRANZYME]); + } + + @Test + public void update_evenSplit_splitsEvenly() { + inflammation = new PatchProcessInflammationCD8(mockCell); + PatchProcessInflammationCD8 parentProcess = new PatchProcessInflammationCD8(mockCell); + parentProcess.amts[PatchProcessInflammationCD8.GRANZYME] = 100; + when(mockCell.getVolume()).thenReturn(cellVolume / 2); + + inflammation.update(parentProcess); + + assertEquals(50, inflammation.amts[PatchProcessInflammationCD8.GRANZYME]); + assertEquals(50, parentProcess.amts[PatchProcessInflammationCD8.GRANZYME]); + } + + @Test + public void update_withZeroVolume_splitsUnevenly() { + inflammation = new PatchProcessInflammationCD8(mockCell); + PatchProcessInflammationCD8 parentProcess = new PatchProcessInflammationCD8(mockCell); + parentProcess.amts[PatchProcessInflammationCD8.GRANZYME] = 100; + when(mockCell.getVolume()).thenReturn(0.0); + + inflammation.update(parentProcess); + + assertEquals(0, inflammation.amts[PatchProcessInflammationCD8.GRANZYME]); + assertEquals(100, parentProcess.amts[PatchProcessInflammationCD8.GRANZYME]); + } +}