diff --git a/src/arcade/patch/agent/action/PatchActionTreat.java b/src/arcade/patch/agent/action/PatchActionTreat.java
new file mode 100644
index 000000000..fc418a77c
--- /dev/null
+++ b/src/arcade/patch/agent/action/PatchActionTreat.java
@@ -0,0 +1,378 @@
+package arcade.patch.agent.action;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import sim.engine.Schedule;
+import sim.engine.SimState;
+import sim.util.Bag;
+import arcade.core.agent.action.Action;
+import arcade.core.env.location.Location;
+import arcade.core.env.location.LocationContainer;
+import arcade.core.sim.Series;
+import arcade.core.sim.Simulation;
+import arcade.core.util.Graph;
+import arcade.core.util.MiniBox;
+import arcade.core.util.Utilities;
+import arcade.patch.agent.cell.PatchCell;
+import arcade.patch.agent.cell.PatchCellCART;
+import arcade.patch.agent.cell.PatchCellContainer;
+import arcade.patch.agent.cell.PatchCellTissue;
+import arcade.patch.env.component.PatchComponentSites;
+import arcade.patch.env.component.PatchComponentSitesGraph;
+import arcade.patch.env.component.PatchComponentSitesGraph.SiteEdge;
+import arcade.patch.env.component.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.Immune;
+import arcade.patch.util.PatchEnums.Ordering;
+
+/**
+ * Implementation of {@link Action} for inserting T-cell agents.
+ *
+ *
The action is stepped once after {@code TIME_DELAY}. The {@code TreatAction} will add CAR
+ * T-cell agents of specified dose next to source points or vasculature.
+ */
+public class PatchActionTreat implements Action {
+
+ /** Delay before calling the helper (in minutes). */
+ private final int delay;
+
+ /** Total number of CAR T-cells to treat with. */
+ private final int dose;
+
+ /** Maximum damage value at which T-cells can spawn next to in source or pattern source. */
+ private final double maxDamage;
+
+ /** Minimum radius value at which T-cells can spawn next to in graph source. */
+ private final double minDamageRadius;
+
+ /** Number of agent positions per lattice site. */
+ private int latPositions;
+
+ /** Coordinate system used for simulation. */
+ private final String coord;
+
+ /** List of populations. */
+ private final ArrayList populations;
+
+ /** parameters. */
+ MiniBox parameters;
+
+ /** Maximum confluency of cells in any location. */
+ int maxConfluency;
+
+ /**
+ * Creates an {@code Action} to add agents after a delay.
+ *
+ * @param series the simulation series
+ * @param parameters the component parameters dictionary
+ */
+ public PatchActionTreat(Series series, MiniBox parameters) {
+ this.delay = parameters.getInt("TIME_DELAY");
+ this.dose = parameters.getInt("DOSE");
+ this.maxDamage = parameters.getDouble("MAX_DAMAGE_SEED");
+ this.minDamageRadius = parameters.getDouble("MIN_RADIUS_SEED");
+ this.parameters = parameters;
+ this.coord =
+ ((PatchSeries) series).patch.get("GEOMETRY").equalsIgnoreCase("HEX")
+ ? "Hex"
+ : "Rect";
+
+ populations = new ArrayList<>();
+ }
+
+ @Override
+ public void schedule(Schedule schedule) {
+ schedule.scheduleOnce(delay, Ordering.ACTIONS.ordinal(), this);
+ }
+
+ @Override
+ public void register(Simulation sim, String population) {
+ populations.add(sim.getSeries().populations.get(population));
+ }
+
+ /**
+ * Steps the action to insert cells of the treatment population(s).
+ *
+ * @param simstate the MASON simulation state
+ */
+ public void step(SimState simstate) {
+ PatchSimulation sim = (PatchSimulation) simstate;
+ String type = "null";
+ PatchGrid grid = (PatchGrid) sim.getGrid();
+ PatchComponentSites comp = (PatchComponentSites) sim.getComponent("SITES");
+
+ // Get a sample location to determine the number of subpositions
+ PatchLocation sampleLoc = (PatchLocation) sim.getAllLocations().iterator().next();
+ latPositions = (int) (-3.5 * sampleLoc.getSubcoordinates().size() + 30);
+
+ // Determine type of sites component implemented.
+ if (comp instanceof PatchComponentSitesSource) {
+ type = "source";
+ } else if (comp instanceof PatchComponentSitesPattern) {
+ type = "pattern";
+ } else if (comp instanceof PatchComponentSitesGraph) {
+ type = "graph";
+ }
+
+ Set immuneCells =
+ Arrays.stream(Immune.values()).map(Enum::name).collect(Collectors.toSet());
+
+ for (MiniBox population : populations) {
+ String className = population.get("CLASS").toUpperCase();
+
+ if (!immuneCells.contains(className)) {
+ throw new IllegalArgumentException(
+ "Population "
+ + population.get("CLASS")
+ + " is not an immune cell and cannot be treated.");
+ }
+
+ maxConfluency = population.getInt("MAX_DENSITY");
+
+ int pop = population.getInt("CODE");
+
+ ArrayList locs = sim.getLocations();
+ ArrayList siteLocs = new ArrayList();
+
+ // Find sites without specified level of damage based on component type.
+ findLocations(comp, type, locs, siteLocs, sim);
+ Utilities.shuffleList(siteLocs, sim.random);
+ // sort locations in descending order from highest to lowest density
+ siteLocs.sort(Comparator.comparingInt(l -> -computeDensity(grid, l)));
+ insert(siteLocs, simstate, pop);
+ }
+ }
+
+ /**
+ * Helper method to find possible locations to insert T-cells.
+ *
+ * @param comp the component
+ * @param type the type of component (source, pattern, or graph)
+ * @param locs the locations to check
+ * @param siteLocs the locations that meet the criteria
+ * @param sim the simulation instance
+ * @throws IllegalArgumentException if the component type is invalid
+ */
+ private void findLocations(
+ PatchComponentSites comp,
+ String type,
+ ArrayList locs,
+ ArrayList siteLocs,
+ PatchSimulation sim) {
+ if (type.equals("graph")) {
+ findGraphSites(comp, locs, siteLocs);
+ } else if (type.equals("source") || type.equals("pattern")) {
+ double[][][] damage;
+ boolean[][][] sitesLat;
+
+ if (type.equals("source")) {
+ damage = ((PatchComponentSitesSource) comp).getDamage();
+ sitesLat = ((PatchComponentSitesSource) comp).getSources();
+ } else {
+ damage = ((PatchComponentSitesPattern) comp).getDamage();
+ sitesLat = ((PatchComponentSitesPattern) comp).getPatterns();
+ }
+ pruneSite(locs, sim, damage, sitesLat, siteLocs);
+ } else {
+ throw new IllegalArgumentException(
+ "Invalid component type: "
+ + type
+ + ". Must be of type source, pattern, or graph.");
+ }
+ }
+
+ /**
+ * Helper method to check if radius is wide enough for T-cells to pass through.
+ *
+ * @param comp the component
+ * @param locs the locations to check
+ * @param siteLocs the locations that meet the criteria
+ */
+ private void findGraphSites(
+ PatchComponentSites comp,
+ ArrayList locs,
+ ArrayList siteLocs) {
+ Graph graph = ((PatchComponentSitesGraph) comp).getGraph();
+ Bag allEdges = new Bag(graph.getAllEdges());
+ PatchComponentSitesGraph graphSites = (PatchComponentSitesGraph) comp;
+
+ Set coordinateSet =
+ locs.stream()
+ .map(container -> ((PatchLocationContainer) container).coordinate)
+ .collect(Collectors.toSet());
+
+ 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 (coordinateSet.contains(((PatchLocation) loc).getCoordinate())) {
+ if (edge.getRadius() >= minDamageRadius) {
+ for (int p = 0; p < latPositions; p++) {
+ siteLocs.add(loc);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method to remove locations that are not next to a site or have too much damage for
+ * T-cells to pass through.
+ *
+ * @param locs the locations to check
+ * @param sim the simuation instance
+ * @param damage the damage array for sites
+ * @param sitesLat the lattice array for sites
+ * @param siteLocs the locations that meet the criteria
+ */
+ public void pruneSite(
+ ArrayList locs,
+ PatchSimulation sim,
+ double[][][] damage,
+ boolean[][][] sitesLat,
+ ArrayList siteLocs) {
+ for (LocationContainer l : locs) {
+ PatchLocationContainer contain = (PatchLocationContainer) l;
+ PatchLocation loc =
+ (PatchLocation)
+ contain.convert(
+ sim.locationFactory,
+ sim.cellFactory.createCellForPopulation(
+ 0, populations.get(0).getInt("CODE")));
+ CoordinateXYZ coordinate = (CoordinateXYZ) loc.getSubcoordinate();
+ int z = coordinate.z;
+ if (sitesLat[z][coordinate.x][coordinate.y]
+ && damage[z][coordinate.x][coordinate.y] <= this.maxDamage) {
+ for (int p = 0; p < latPositions; p++) {
+ siteLocs.add(loc);
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method to sort locations.
+ *
+ * @param grid the simulation grid
+ * @param loc the current location being looked
+ * @return the density of agents at the location
+ */
+ private int computeDensity(PatchGrid grid, Location loc) {
+ Bag bag = new Bag(grid.getObjectsAtLocation(loc));
+ int numAgents = bag.numObjs;
+ return numAgents;
+ }
+
+ /**
+ * Helper method to add cells into the grid.
+ *
+ * @param coordinates the locations to insert the cells
+ * @param simstate the simulation state
+ * @param pop the population code for the cells
+ */
+ private void insert(ArrayList coordinates, SimState simstate, int pop) {
+ PatchSimulation sim = (PatchSimulation) simstate;
+ PatchGrid grid = (PatchGrid) sim.getGrid();
+ Utilities.shuffleList(coordinates, sim.random);
+
+ for (int i = 0; i < dose; i++) {
+ int id = sim.getID();
+
+ PatchLocation loc = ((PatchLocation) coordinates.remove(0));
+
+ while (!coordinates.isEmpty() && !checkLocationSpace(loc, grid)) {
+ loc = ((PatchLocation) coordinates.remove(0));
+ }
+
+ if (coordinates.isEmpty()) {
+ break;
+ }
+
+ Coordinate coordinate = loc.getCoordinate();
+ PatchLocationContainer locationContainer = new PatchLocationContainer(id, coordinate);
+ PatchCellContainer cellContainer = sim.cellFactory.createCellForPopulation(id, pop);
+
+ Location location = locationContainer.convert(sim.locationFactory, cellContainer);
+ PatchCell cell =
+ (PatchCell) cellContainer.convert(sim.cellFactory, location, sim.random);
+
+ grid.addObject(cell, location);
+ cell.schedule(sim.getSchedule());
+ }
+ }
+
+ /**
+ * Helper method to check if location is available.
+ *
+ * @param grid the simulation grid
+ * @param loc the current location being looked at
+ * @return boolean indicating if location is free
+ */
+ protected boolean checkLocationSpace(Location loc, PatchGrid grid) {
+ boolean available;
+ int locMax = this.maxConfluency;
+ double locVolume = ((PatchLocation) loc).getVolume();
+ double locArea = ((PatchLocation) loc).getArea();
+
+ Bag bag = new Bag(grid.getObjectsAtLocation(loc));
+ int n = bag.numObjs; // number of agents in location
+
+ if (n == 0) {
+ // no cells in location
+ available = true;
+ } else if (n >= locMax) {
+ // location already full
+ available = false;
+ } else {
+ available = true;
+ double totalVol = PatchCell.calculateTotalVolume(bag);
+ double currentHeight = totalVol / locArea;
+
+ if (totalVol > locVolume) {
+ available = false;
+ }
+
+ for (Object cellObj : bag) {
+ PatchCell cell = (PatchCell) cellObj;
+ if (cell instanceof PatchCellCART) {
+ totalVol =
+ PatchCell.calculateTotalVolume(bag)
+ + parameters.getDouble("T_CELL_VOL_AVG");
+ currentHeight = totalVol / locArea;
+ }
+ if (cell instanceof PatchCellTissue) {
+ if (currentHeight > cell.getCriticalHeight()) {
+ available = false;
+ }
+ }
+ }
+ }
+
+ return available;
+ }
+}
diff --git a/src/arcade/patch/parameter.patch.xml b/src/arcade/patch/parameter.patch.xml
index 40824712c..2b0ef1bd3 100644
--- a/src/arcade/patch/parameter.patch.xml
+++ b/src/arcade/patch/parameter.patch.xml
@@ -125,6 +125,12 @@
+
+
+
+
+
+
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/src/arcade/patch/util/PatchEnums.java b/src/arcade/patch/util/PatchEnums.java
index 02cfbc500..fe84172f0 100644
--- a/src/arcade/patch/util/PatchEnums.java
+++ b/src/arcade/patch/util/PatchEnums.java
@@ -200,6 +200,28 @@ public static AntigenFlag random(MersenneTwisterFast rng) {
}
}
+ /** Cell types for immune cell classification. */
+ public enum Immune {
+ /** Code for cd4 T cell. */
+ CART_CD4,
+ /** Code for cd8 T cell. */
+ CART_CD8,
+ /** Code for macrophage cell. */
+ MACROPHAGE,
+ /** Code for killer cd8 cell. */
+ KILLER;
+
+ /**
+ * Randomly selects a {@code Immune} cell type.
+ *
+ * @param rng the random number generator
+ * @return a random {@code Immune} cell type
+ */
+ public static Immune random(MersenneTwisterFast rng) {
+ return values()[rng.nextInt(values().length - 1) + 1];
+ }
+ }
+
/** Operation category codes for patch simulations. */
public enum Category implements OperationCategory {
/** Code for undefined category. */
diff --git a/test/arcade/patch/agent/action/PatchActionTreatTest.java b/test/arcade/patch/agent/action/PatchActionTreatTest.java
new file mode 100644
index 000000000..75f91c629
--- /dev/null
+++ b/test/arcade/patch/agent/action/PatchActionTreatTest.java
@@ -0,0 +1,261 @@
+package arcade.patch.agent.action;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+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.Location;
+import arcade.core.env.location.LocationContainer;
+import arcade.core.sim.Series;
+import arcade.core.sim.Simulation;
+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.CoordinateUVWZ;
+import arcade.patch.env.location.CoordinateXYZ;
+import arcade.patch.env.location.PatchLocation;
+import arcade.patch.env.location.PatchLocationContainer;
+import arcade.patch.env.location.PatchLocationHex;
+import arcade.patch.sim.PatchSeries;
+import arcade.patch.sim.PatchSimulation;
+import arcade.patch.util.PatchEnums;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+public class PatchActionTreatTest {
+
+ private PatchActionTreat action;
+
+ private PatchSimulation sim;
+
+ private PatchGrid gridMock;
+
+ private PatchCell cellMock;
+
+ private PatchLocation locationMock;
+
+ MiniBox parameters;
+
+ PatchSeries series;
+
+ @BeforeEach
+ public final void setUp()
+ throws NoSuchFieldException,
+ SecurityException,
+ IllegalArgumentException,
+ IllegalAccessException {
+ // set up mocks
+ parameters = mock(MiniBox.class);
+ when(parameters.getInt(anyString())).thenReturn(1);
+ when(parameters.getDouble(anyString())).thenReturn(1.0);
+
+ series = mock(PatchSeries.class);
+ MiniBox patchMock = mock(MiniBox.class);
+ series.patch = patchMock;
+ gridMock = mock(PatchGrid.class);
+ cellMock = mock(PatchCell.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;
+ damage[1][0][0] = 0.05;
+ sitesLat[1][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);
+ PatchLocationContainer container2 = mock(PatchLocationContainer.class);
+ PatchLocation loc = mock(PatchLocation.class);
+ locationMock = loc;
+ PatchLocation loc2 = mock(PatchLocation.class);
+ CoordinateXYZ coord = new CoordinateXYZ(0, 0, 0);
+ CoordinateUVWZ c = new CoordinateUVWZ(0, 0, 0, 0);
+ when(loc.getSubcoordinate()).thenReturn(coord);
+ when(loc.getCoordinate()).thenReturn(c);
+ Bag mockBag1 = new Bag();
+ when(container.convert(any(), any())).thenReturn(loc);
+ CoordinateXYZ coord2 = new CoordinateXYZ(0, 0, 1);
+ CoordinateUVWZ c2 = new CoordinateUVWZ(0, 0, 0, 1);
+ when(loc2.getSubcoordinate()).thenReturn(coord2);
+ when(loc2.getCoordinate()).thenReturn(c2);
+ when(container2.convert(any(), any())).thenReturn(loc2);
+ Bag mockBag2 = new Bag();
+ mockBag2.add(cellMock);
+ locations.add(container);
+ locations.add(container2);
+ when(gridMock.getObjectsAtLocation(loc)).thenReturn(mockBag1);
+ when(gridMock.getObjectsAtLocation(loc2)).thenReturn(mockBag2);
+ when(sim.getLocations()).thenReturn(locations);
+ Set newLocations = new java.util.HashSet<>();
+ newLocations.add(new PatchLocationHex(c2));
+ when(sim.getAllLocations()).thenReturn(newLocations);
+ 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);
+ 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;
+ }
+
+ private static class TestPatchActionTreat extends PatchActionTreat {
+
+ private final ArrayList testPopulations;
+
+ TestPatchActionTreat(Series series, MiniBox parameters, ArrayList populations) {
+ super(series, parameters);
+ this.testPopulations = populations;
+
+ HashMap populationsMap = new HashMap<>();
+ for (MiniBox pop : populations) {
+ String className = pop.get("CLASS").toString();
+ populationsMap.put(className, pop);
+ }
+ series.populations = populationsMap;
+ for (MiniBox pop : populations) {
+ Simulation mockSim = mock(Simulation.class);
+ when(mockSim.getSeries()).thenReturn(series);
+ String className = pop.get("CLASS").toString();
+ register(mockSim, className);
+ }
+ }
+ }
+
+ @Test
+ public void schedule_called_schedulesOnAction() {
+ 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);
+
+ action = new TestPatchActionTreat(series, parameters, populations);
+
+ Schedule schedule = mock(Schedule.class);
+ action.schedule(schedule);
+
+ verify(schedule)
+ .scheduleOnce(anyDouble(), eq(PatchEnums.Ordering.ACTIONS.ordinal()), eq(action));
+ }
+
+ @Test
+ public void step_called_addsObjectsToSim() {
+ ArrayList populations = new ArrayList<>();
+ MiniBox populationMock1 = new MiniBox();
+ populationMock1.put("CLASS", "cart_cd4");
+ populationMock1.put("CODE", 4);
+ MiniBox populationMock2 = new MiniBox();
+ populationMock2.put("CLASS", "cart_cd8");
+ populationMock2.put("CODE", 8);
+ populations.add(populationMock1);
+ populations.add(populationMock2);
+
+ action = new TestPatchActionTreat(series, parameters, populations);
+
+ action.step(sim);
+
+ verify(gridMock, atLeastOnce()).addObject(any(), any());
+ verify(cellMock, atLeastOnce()).schedule(any());
+ }
+
+ @Test
+ public void step_zeroDose_doesNotAddObjects() {
+ when(parameters.getInt("DOSE")).thenReturn(0);
+
+ ArrayList populations = new ArrayList<>();
+ MiniBox populationMock1 = new MiniBox();
+ populationMock1.put("CLASS", "cart_cd4");
+ populationMock1.put("CODE", 4);
+ MiniBox populationMock2 = new MiniBox();
+ populationMock2.put("CLASS", "cart_cd8");
+ populationMock2.put("CODE", 8);
+ populations.add(populationMock1);
+ populations.add(populationMock2);
+
+ action = new TestPatchActionTreat(series, parameters, populations);
+
+ action.step(sim);
+
+ verify(gridMock, times(0)).addObject(any(), any());
+ verify(cellMock, times(0)).schedule(any());
+ }
+
+ @Test
+ public void checkLocationSpace_withEmptySpaces_addsObject() {
+ when(parameters.getDouble("T_CELL_VOL_AVG")).thenReturn(175.0);
+
+ ArrayList populations = new ArrayList<>();
+ MiniBox populationMock = mock(MiniBox.class);
+ when(populationMock.getInt("CODE")).thenReturn(4);
+ when(populationMock.get("CLASS")).thenReturn("cart_cd4");
+ when(populationMock.getInt("MAX_DENSITY")).thenReturn(54);
+ when(populationMock.getDouble("T_CELL_VOL_AVG")).thenReturn(175.0);
+ populations.add(populationMock);
+ 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);
+
+ action = new TestPatchActionTreat(series, parameters, populations);
+
+ action.step(sim);
+
+ verify(gridMock, times(1)).addObject(any(), any());
+ verify(cellMock, times(1)).schedule(any());
+ }
+
+ @Test
+ public void checkLocation_maxConfluency_doesNotAddObject() {
+ when(parameters.getDouble("T_CELL_VOL_AVG")).thenReturn(175.0);
+
+ ArrayList populations = new ArrayList<>();
+ MiniBox populationMock = mock(MiniBox.class);
+ when(populationMock.getInt("CODE")).thenReturn(4);
+ when(populationMock.get("CLASS")).thenReturn("cart_cd4");
+ when(populationMock.getInt("MAX_DENSITY")).thenReturn(1);
+ populations.add(populationMock);
+ 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);
+
+ action = new TestPatchActionTreat(series, parameters, populations);
+
+ action.step(sim);
+
+ verify(gridMock, times(0)).addObject(any(), any());
+ verify(cellMock, times(0)).schedule(any());
+ }
+}