diff --git a/src/arcade/patch/agent/cell/PatchCell.java b/src/arcade/patch/agent/cell/PatchCell.java index d117ef807..eafe30af0 100644 --- a/src/arcade/patch/agent/cell/PatchCell.java +++ b/src/arcade/patch/agent/cell/PatchCell.java @@ -19,6 +19,7 @@ import arcade.patch.agent.module.PatchModuleApoptosis; import arcade.patch.agent.module.PatchModuleMigration; import arcade.patch.agent.module.PatchModuleProliferation; +import arcade.patch.agent.process.PatchProcessChemotherapy; import arcade.patch.agent.process.PatchProcessMetabolism; import arcade.patch.agent.process.PatchProcessSignaling; import arcade.patch.env.grid.PatchGrid; @@ -341,6 +342,8 @@ public Process makeProcess(ProcessDomain domain, String version) { return PatchProcessMetabolism.make(this, version); case SIGNALING: return PatchProcessSignaling.make(this, version); + case CHEMOTHERAPY: + return PatchProcessChemotherapy.make(this, version); case UNDEFINED: default: return null; @@ -396,6 +399,10 @@ public void step(SimState simstate) { } } + if (processes.get(Domain.CHEMOTHERAPY) != null) { + processes.get(Domain.CHEMOTHERAPY).step(simstate.random, sim); + } + // Step the module for the cell state. if (module != null) { module.step(simstate.random, sim); diff --git a/src/arcade/patch/agent/process/PatchProcessChemotherapy.java b/src/arcade/patch/agent/process/PatchProcessChemotherapy.java new file mode 100644 index 000000000..39dccd630 --- /dev/null +++ b/src/arcade/patch/agent/process/PatchProcessChemotherapy.java @@ -0,0 +1,128 @@ +package arcade.patch.agent.process; + +import sim.util.Bag; +import ec.util.MersenneTwisterFast; +import arcade.core.sim.Simulation; +import arcade.core.util.MiniBox; +import arcade.patch.agent.cell.PatchCell; +import arcade.patch.env.grid.PatchGrid; + +/** + * Implementation of {@link Process} for cell chemotherapy. + * + *

The {@code PatchProcessChemotherapy} process: + * + *

+ */ +public abstract class PatchProcessChemotherapy extends PatchProcess { + /** Threshold at which cells undergo apoptosis. */ + protected final double chemotherapyThreshold; + + /** Constant drug uptake rate [fmol drug/um^2 cell/min/M drug]. */ + protected final double drugUptakeRate; + + /** Rate at which drugs are removed from the cell. */ + protected final double drugRemovalRate; + + /** Rate at which drugs decay in the cell. */ + protected final double drugDecayRate; + + /** Volume of cell [um3]. */ + double volume; + + /** Volume fraction. */ + protected double f; + + /** Internal amount of the drug. */ + protected double intAmt; + + /** External amout of the drug. */ + protected double extAmt; + + /** Uptake of the drug in the current step. */ + protected double uptakeAmt; + + /** Whether the cell was killed by chemotherapy. */ + protected boolean wasChemo; + + /** + * Creates a chemotherapy {@code Process} for the given {@link PatchCell}. + * + * @param cell the {@link PatchCell} the process is associated with + */ + PatchProcessChemotherapy(PatchCell cell) { + super(cell); + + // Initialize process + volume = cell.getVolume(); + + // Set loaded parameters. + MiniBox parameters = cell.getParameters(); + chemotherapyThreshold = parameters.getDouble("chemotherapy/CHEMOTHERAPY_THRESHOLD"); + drugUptakeRate = parameters.getDouble("chemotherapy/CONSTANT_DRUG_UPTAKE_RATE"); + drugRemovalRate = parameters.getDouble("chemotherapy/DRUG_REMOVAL_RATE"); + drugDecayRate = parameters.getDouble("chemotherapy/DRUG_DECAY_RATE"); + + // Initial internal concentrations. + extAmt = 0.0; + uptakeAmt = 0.0; + intAmt = 0.0; + } + + /** + * Steps the chemotherapy process. + * + * @param random the random number generator + * @param sim the simulation instance + */ + abstract void stepProcess(MersenneTwisterFast random, Simulation sim); + + /** + * Gets the external drug concentrations from the environment. + * + * @param sim the simulation instance + */ + private void updateExternal(Simulation sim) { + extAmt = sim.getLattice("DRUG").getAverageValue(location) * location.getVolume(); + extAmt *= (1.0 - drugDecayRate); + } + + @Override + public void step(MersenneTwisterFast random, Simulation sim) { + // Calculate fraction of volume occupied by cell. + Bag bag = ((PatchGrid) sim.getGrid()).getObjectsAtLocation(location); + double totalVolume = PatchCell.calculateTotalVolume(bag); + f = volume / totalVolume; + + // Get external drug concentration. + updateExternal(sim); + + // Calculate drug uptake and internal concentration. + stepProcess(random, sim); + + // Update environment for the drug. + sim.getLattice("DRUG").updateValue(location, 1.0 - uptakeAmt / extAmt); + } + + /** + * Creates a {@code PatchProcessChemotherapy} for the given version. + * + * @param cell the {@link PatchCell} the process is associated with + * @param version the process version + * @return the process instance + */ + public static PatchProcess make(PatchCell cell, String version) { + switch (version.toUpperCase()) { + case "SIMPLE": + return new PatchProcessChemotherapySimple(cell); + default: + return null; + } + } +} diff --git a/src/arcade/patch/agent/process/PatchProcessChemotherapySimple.java b/src/arcade/patch/agent/process/PatchProcessChemotherapySimple.java new file mode 100644 index 000000000..13b90ed4e --- /dev/null +++ b/src/arcade/patch/agent/process/PatchProcessChemotherapySimple.java @@ -0,0 +1,75 @@ +package arcade.patch.agent.process; + +import ec.util.MersenneTwisterFast; +import arcade.core.agent.process.Process; +import arcade.core.sim.Simulation; +import arcade.patch.agent.cell.PatchCell; +import static arcade.patch.util.PatchEnums.State; + +/** + * Extension of {@link PatchProcessChemotherapy} for simple chemotherapy. + * + *

{@code PatchProcessChemotherapySimple} assumes a constant drug uptake rate and a threshold for + * apoptosis. + */ +public class PatchProcessChemotherapySimple extends PatchProcessChemotherapy { + /** + * Creates a simple chemotherapy {@code Process} for the given {@link PatchCell}. + * + *

Loaded parameters include: + * + *

+ * + * @param cell the {@link PatchCell} the process is associated with + */ + public PatchProcessChemotherapySimple(PatchCell cell) { + super(cell); + } + + @Override + public void stepProcess(MersenneTwisterFast random, Simulation sim) { + double drugInt = intAmt; + double drugExt = extAmt; + + // Calculate drug uptake rate based on concentration gradient. + double area = location.getArea() * f; + double surfaceArea = area * 2 + (volume / area) * location.getPerimeter(f); + double drugGrad = (extAmt / location.getVolume()) - (drugInt / volume); + drugGrad *= drugGrad < 1E-10 ? 0 : 1; + double drugUptake = drugUptakeRate * drugGrad * surfaceArea; + drugInt += drugUptake; + + // If drug concentration exceeds kill threshold kill cells with probability based on drug + // concentration. + if (cell.getState() == State.PROLIFERATIVE && drugInt > chemotherapyThreshold) { + double oxygen = sim.getLattice("OXYGEN").getAverageValue(location); + double p = Math.pow(oxygen, 2) / (Math.pow(oxygen, 2) + Math.pow(drugInt, 2)); + + // TODO: Update probability + if (random.nextDouble() < p) { + cell.setState(State.APOPTOTIC); + wasChemo = true; + } + } + intAmt = Math.exp(-drugRemovalRate) * drugInt; + // intAmt = drugInt; + uptakeAmt = drugUptake; + } + + @Override + public void update(Process process) { + PatchProcessChemotherapySimple chemotherapy = (PatchProcessChemotherapySimple) process; + double split = this.cell.getVolume() / this.volume; + + // Update this process as split of given process. + this.volume = this.cell.getVolume(); + this.intAmt = chemotherapy.intAmt * split; + + chemotherapy.volume = chemotherapy.cell.getVolume(); + chemotherapy.intAmt *= (1 - split); + } +} diff --git a/src/arcade/patch/env/component/PatchComponentDegrade.java b/src/arcade/patch/env/component/PatchComponentDegrade.java index 35da75184..d18956dbb 100644 --- a/src/arcade/patch/env/component/PatchComponentDegrade.java +++ b/src/arcade/patch/env/component/PatchComponentDegrade.java @@ -75,7 +75,6 @@ public void schedule(Schedule schedule) { @Override public void register(Simulation sim, String layer) { Component component = sim.getComponent(layer); - if (!(component instanceof PatchComponentSitesGraph)) { return; } diff --git a/src/arcade/patch/env/component/PatchComponentDose.java b/src/arcade/patch/env/component/PatchComponentDose.java new file mode 100644 index 000000000..6660c8032 --- /dev/null +++ b/src/arcade/patch/env/component/PatchComponentDose.java @@ -0,0 +1,122 @@ +package arcade.patch.env.component; + +import java.util.ArrayList; +import sim.engine.Schedule; +import sim.engine.SimState; +import arcade.core.env.component.Component; +import arcade.core.env.lattice.Lattice; +import arcade.core.env.operation.Operation; +import arcade.core.sim.Series; +import arcade.core.sim.Simulation; +import arcade.core.util.MiniBox; +import arcade.patch.env.operation.PatchOperationGenerator; +import arcade.patch.sim.PatchSeries; +import static arcade.patch.env.component.PatchComponentSites.SiteLayer; +import static arcade.patch.util.PatchEnums.Category; +import static arcade.patch.util.PatchEnums.Ordering; + +public class PatchComponentDose implements Component { + private final ArrayList layers; + private final int latticeHeight; + private final int latticeLength; + private final int latticeWidth; + private final double mediaAmount; + private final double mediaVolume; + private final double latticePatchVolume; + private final double latticePatchArea; + private final double doseStart; + private final double doseDuration; + private final double doseInterval; + private final double doseEnd; + + public PatchComponentDose(Series series, MiniBox parameters) { + layers = new ArrayList<>(); + + latticeLength = series.length; + latticeWidth = series.width; + latticeHeight = series.height; + + // Set loaded parameters. + mediaAmount = parameters.getDouble("MEDIA_AMOUNT"); + doseStart = parameters.getDouble("DOSE_START"); + doseDuration = parameters.getDouble("DOSE_DURATION"); + doseInterval = parameters.getDouble("DOSE_INTERVAL"); + doseEnd = parameters.getDouble("DOSE_END"); + + // Set patch parameters. + MiniBox patch = ((PatchSeries) series).patch; + latticePatchVolume = patch.getDouble("LATTICE_VOLUME"); + latticePatchArea = patch.getDouble("LATTICE_AREA"); + + mediaVolume = latticePatchArea * latticeLength * latticeWidth * mediaAmount; + } + + protected static class DoseLayer { + final String name; + final double[][][] current; + final SiteLayer siteLayer; + final double initialConcentration; + double currentAmount; + + DoseLayer(String name, SiteLayer siteLayer, PatchOperationGenerator generator) { + this.name = name; + this.siteLayer = siteLayer; + current = generator.latticeCurrent; + initialConcentration = generator.concentration; + currentAmount = 0; + } + } + + @Override + public void schedule(Schedule schedule) { + schedule.scheduleRepeating(doseStart, Ordering.FIRST_COMPONENT.ordinal(), this); + } + + @Override + public void register(Simulation sim, String layer) { + String[] layerSplit = layer.split(":"); + Lattice lattice = sim.getLattice(layerSplit[1]); + Operation generator = lattice.getOperation(Category.GENERATOR); + Component component = sim.getComponent(layerSplit[0]); + + if (!(component instanceof PatchComponentSitesSource)) { + return; + } + + PatchComponentSitesSource sites = (PatchComponentSitesSource) component; + SiteLayer siteLayer = + sites.layers.stream() + .filter(sl -> sl.name.equalsIgnoreCase(layerSplit[1])) + .findFirst() + .orElse(null); + + if (siteLayer != null) { + DoseLayer doseLayer = + new DoseLayer(layer, siteLayer, (PatchOperationGenerator) generator); + layers.add(doseLayer); + } + } + + @Override + public void step(SimState simstate) { + double tick = simstate.schedule.getTime(); + + double doseAmount = 400000; + for (DoseLayer layer : layers) { + if (tick > doseEnd) { + layer.currentAmount = 0; + layer.siteLayer.concentration = 0; + return; + } + + if (tick >= doseStart && (tick - doseStart) % doseInterval < doseDuration) { + layer.currentAmount += doseAmount; + } else { + layer.currentAmount = 0; + } + + layer.siteLayer.concentration = layer.currentAmount / mediaVolume; + + } + } +} diff --git a/src/arcade/patch/env/component/PatchComponentPulse.java b/src/arcade/patch/env/component/PatchComponentPulse.java index f8bb5a3f7..b1ab7de1f 100644 --- a/src/arcade/patch/env/component/PatchComponentPulse.java +++ b/src/arcade/patch/env/component/PatchComponentPulse.java @@ -137,7 +137,6 @@ public void register(Simulation sim, String layer) { Lattice lattice = sim.getLattice(layerSplit[1]); Operation generator = lattice.getOperation(Category.GENERATOR); Component component = sim.getComponent(layerSplit[0]); - if (!(component instanceof PatchComponentSitesSource)) { return; } @@ -174,10 +173,15 @@ public void step(SimState simstate) { } } + System.out.println(delta); + System.out.println(layer.currentAmount); // Update available concentrations. layer.currentAmount = Math.max(0, layer.currentAmount - delta); - layer.siteLayer.concentration = layer.currentAmount / mediaVolume; + System.out.println(layer.currentAmount); + System.out.println(layer.siteLayer.concentration); + layer.siteLayer.concentration = layer.currentAmount / mediaVolume; + System.out.println(layer.siteLayer.concentration); // Pulse returns concentration to initial value. if (tick % pulseInterval == 0) { layer.currentAmount = layer.initialConcentration * mediaVolume; diff --git a/src/arcade/patch/parameter.patch.xml b/src/arcade/patch/parameter.patch.xml index 6653e9cc3..dfde736b8 100644 --- a/src/arcade/patch/parameter.patch.xml +++ b/src/arcade/patch/parameter.patch.xml @@ -55,6 +55,13 @@ + + + + + + + @@ -103,9 +110,16 @@ - + + + + + + + + diff --git a/src/arcade/patch/sim/PatchSimulationHex.java b/src/arcade/patch/sim/PatchSimulationHex.java index 6eddf28b5..5fc677618 100644 --- a/src/arcade/patch/sim/PatchSimulationHex.java +++ b/src/arcade/patch/sim/PatchSimulationHex.java @@ -10,6 +10,7 @@ import arcade.patch.agent.cell.PatchCellFactory; import arcade.patch.env.component.PatchComponentCycle; import arcade.patch.env.component.PatchComponentDegrade; +import arcade.patch.env.component.PatchComponentDose; import arcade.patch.env.component.PatchComponentPulse; import arcade.patch.env.component.PatchComponentRemodel; import arcade.patch.env.component.PatchComponentSitesGraphTri; @@ -82,6 +83,8 @@ public Component makeComponent(String componentClass, MiniBox parameters) { return new PatchComponentDegrade(series, parameters); case "remodel": return new PatchComponentRemodel(series, parameters); + case "dose": + return new PatchComponentDose(series, parameters); default: return null; } diff --git a/src/arcade/patch/sim/PatchSimulationRect.java b/src/arcade/patch/sim/PatchSimulationRect.java index 6623fefec..b196a3cba 100644 --- a/src/arcade/patch/sim/PatchSimulationRect.java +++ b/src/arcade/patch/sim/PatchSimulationRect.java @@ -10,6 +10,7 @@ import arcade.patch.agent.cell.PatchCellFactory; import arcade.patch.env.component.PatchComponentCycle; import arcade.patch.env.component.PatchComponentDegrade; +import arcade.patch.env.component.PatchComponentDose; import arcade.patch.env.component.PatchComponentPulse; import arcade.patch.env.component.PatchComponentRemodel; import arcade.patch.env.component.PatchComponentSitesGraphRect; @@ -82,6 +83,8 @@ public Component makeComponent(String componentClass, MiniBox parameters) { return new PatchComponentDegrade(series, parameters); case "remodel": return new PatchComponentRemodel(series, parameters); + case "dose": + return new PatchComponentDose(series, parameters); default: return null; } diff --git a/src/arcade/patch/util/PatchEnums.java b/src/arcade/patch/util/PatchEnums.java index 12a31655f..f6df7907b 100644 --- a/src/arcade/patch/util/PatchEnums.java +++ b/src/arcade/patch/util/PatchEnums.java @@ -112,7 +112,10 @@ public enum Domain implements ProcessDomain { METABOLISM, /** Code for signaling domain. */ - SIGNALING; + SIGNALING, + + /** Code for chemotherapy domain. */ + CHEMOTHERAPY; /** * Randomly selects a {@code Domain}.