Skip to content

Commit 6601f3e

Browse files
authored
Model with CAR-T cells (#7)
All work I have done untill the midterm: tumor replicated from the nature paper including forces and diffusion modules
1 parent 414d1d5 commit 6601f3e

14 files changed

+687
-146
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,7 @@ doc/latex
121121
*.dat
122122

123123
# Draft code for comparing models
124-
draft_code_my_own_analysis/
124+
draft_code_my_own_analysis/
125+
126+
# Temporary files
127+
cout.txt

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
cmake_minimum_required(VERSION 3.19.3)
2-
project(cart_tumor)
2+
project(CARTopiaX)
33

44

55
# BioDynaMo curretly uses the C++17 standard.

bdm.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,12 @@ additional_data_members = [
1010
{name = "cell_type", function = "GetTypeAsInt"}
1111
]
1212

13+
[[visualize_agent]]
14+
name = "CarTCell"
15+
additional_data_members = ["diameter_","volume_"]
16+
1317
[[visualize_diffusion]]
1418
name = "oxygen"
19+
20+
[[visualize_diffusion]]
21+
name = "immunostimulatory_factor"

src/cart_cell.cc

Lines changed: 207 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,17 @@
2424
#include "tumor_cell.h"
2525
#include "utils_aux.h"
2626
#include "core/agent/agent.h"
27+
#include "core/agent/agent_pointer.h"
2728
#include "core/agent/new_agent_event.h"
2829
#include "core/container/math_array.h"
2930
#include "core/diffusion/diffusion_grid.h"
3031
#include "core/functor.h"
3132
#include "core/interaction_force.h"
3233
#include "core/real_t.h"
3334
#include "core/resource_manager.h"
35+
#include "core/simulation.h"
3436
#include "core/util/log.h"
37+
#include "core/util/random.h"
3538
#include <algorithm>
3639
#include <cmath>
3740
#include <cstdint>
@@ -47,6 +50,12 @@ CarTCell::CarTCell(const Real3& position) {
4750
oxygen_dgrid_ = rm.GetDiffusionGrid("oxygen");
4851
immunostimulatory_factor_dgrid_ =
4952
rm.GetDiffusionGrid("immunostimulatory_factor");
53+
54+
SetCurrentLiveTime(kAverageMaximumTimeUntillApoptosisCart);
55+
// Add Consumption and Secretion
56+
// Set default oxygen consumption rate
57+
SetOxygenConsumptionRate(kDefaultOxygenConsumptionCarT);
58+
// Compute constants for all ConsumptionSecretion of Oxygen
5059
ComputeConstantsConsumptionSecretion();
5160
}
5261

@@ -138,6 +147,8 @@ void CarTCell::ChangeVolumeExponentialRelaxationEquation(
138147
// compute Displacement
139148
Real3 CarTCell::CalculateDisplacement(const InteractionForce* force,
140149
real_t squared_radius, real_t /*dt*/) {
150+
Simulation* sim = Simulation::GetActive();
151+
141152
// real_t h = dt;
142153
Real3 movement_at_next_step{0, 0, 0};
143154
// this should be chaged in a future version of BioDynaMo in order to have a
@@ -148,33 +159,114 @@ Real3 CarTCell::CalculateDisplacement(const InteractionForce* force,
148159

149160
Real3 translation_velocity_on_point_mass{0, 0, 0};
150161

151-
// We check for every neighbor object if they touch us, i.e. push us
152-
// away and agreagate the velocities
162+
//--------------------------------------------
163+
// CAR-T self motility (in case of migration)
164+
//--------------------------------------------
165+
Real3 current_position = GetPosition();
166+
ExecutionContext* ctxt = sim->GetExecutionContext();
167+
Random* rng = sim->GetRandom();
168+
Real3 motility;
169+
if (DoesCellMove()) {
170+
// compute motility
171+
if (rng->Uniform(0.0, 1.0) < kMotilityProbability) {
172+
// random direction as unitary vector
173+
const Real3 random_direction = GenerateRandomDirection();
174+
Real3 direction_to_immunostimulatory_factor;
175+
// returns normalized gradient towards the immunostimulatory factor source
176+
immunostimulatory_factor_dgrid_->GetGradient(
177+
current_position, &direction_to_immunostimulatory_factor, true);
178+
// motility = bias * direction_to_immunostimulatory_factor +
179+
// (1-bias)*random_direction
180+
motility = kMigrationBiasCart * direction_to_immunostimulatory_factor +
181+
kMigrationOneMinusBiasCart * random_direction;
182+
const real_t motility_norm_squared = motility[0] * motility[0] +
183+
motility[1] * motility[1] +
184+
motility[2] * motility[2];
185+
// Convert to unit direction
186+
if (motility_norm_squared > 0) {
187+
motility.Normalize();
188+
}
189+
// Scale by migration speed and add to the velocity
190+
translation_velocity_on_point_mass += motility * kMigrationSpeedCart;
191+
}
192+
}
193+
194+
//--------------------------------------------
195+
// CAR-T killing or victim cell escaping
196+
//--------------------------------------------
197+
// If cell is not apoptotic
198+
if (state_ == CarTCellState::kAlive) {
199+
// if it is attached to tumor cell
200+
if (attached_to_tumor_cell_) {
201+
// try to kill the cancer cell and in case of failure see if it manages to
202+
// scape the order needs to be this one because it should try to kill
203+
// before seeing if it scapes
204+
if (TryToInduceApoptosis(attached_cell_ptr_, rng) ||
205+
rng->Uniform(0.0, 1.0) < kProbabilityEscape) {
206+
// the cancer cell is detached
207+
attached_cell_ptr_->SetAttachedToCart(false);
208+
// empty ID
209+
attached_cell_ptr_ = nullptr;
210+
attached_to_tumor_cell_ = false;
211+
}
212+
}
153213

154-
uint64_t non_zero_neighbor_forces = 0;
155-
if (!IsStatic()) {
156-
auto* ctxt = Simulation::GetActive()->GetExecutionContext();
157-
auto calculate_neighbor_forces =
214+
//--------------------------------------------
215+
// CAR-T adhesion to victim cell
216+
//--------------------------------------------
217+
// Compute forces between the cells and check for a new attachment
218+
auto calculate_forces_and_elastic_displacement =
158219
L2F([&](Agent* neighbor, real_t /*squared_distance*/) {
159-
auto neighbor_force = force->Calculate(this, neighbor);
160-
if (neighbor_force[0] != 0 || neighbor_force[1] != 0 ||
161-
neighbor_force[2] != 0) {
162-
non_zero_neighbor_forces++;
163-
translation_velocity_on_point_mass[0] += neighbor_force[0];
164-
translation_velocity_on_point_mass[1] += neighbor_force[1];
165-
translation_velocity_on_point_mass[2] += neighbor_force[2];
220+
// Adhesion repulsion forces between cells
221+
// We check for every neighbor object if they touch us, i.e. push us
222+
// away and aggregate the velocities
223+
Real4 neighbor_force = force->Calculate(this, neighbor);
224+
translation_velocity_on_point_mass[0] += neighbor_force[0];
225+
translation_velocity_on_point_mass[1] += neighbor_force[1];
226+
translation_velocity_on_point_mass[2] += neighbor_force[2];
227+
228+
// CAR-T adhesion to new victim cell
229+
Real3 displac = neighbor->GetPosition() - current_position;
230+
231+
if (auto* cancer_cell = dynamic_cast<TumorCell*>(neighbor)) {
232+
// movement towards the tumor cells
233+
const real_t sq_norm_displac = displac[0] * displac[0] +
234+
displac[1] * displac[1] +
235+
displac[2] * displac[2];
236+
237+
// The cart moves towards the tumor cell only if they are not
238+
// touching already If they are too close the only force affecting
239+
// is the adhesion force to avoid CAR-T non-stop pushing tumor
240+
// cells. In case of being closer than
241+
// gKMaxSquaredDistanceCartMovingTowardsTumorCell there is a
242+
// probability kProbabilityPushing for the CAR-T to keep pushing the
243+
// tumor cell
244+
if (sq_norm_displac >
245+
gKMaxSquaredDistanceCartMovingTowardsTumorCell) {
246+
translation_velocity_on_point_mass[0] +=
247+
displac[0] * kElasticConstantCart;
248+
translation_velocity_on_point_mass[1] +=
249+
displac[1] * kElasticConstantCart;
250+
translation_velocity_on_point_mass[2] +=
251+
displac[2] * kElasticConstantCart;
252+
}
253+
254+
// If the CAR-T has not succeeded in attaching to a tumor cell yet,
255+
// it tries again
256+
if (!attached_to_tumor_cell_) {
257+
TryToGetAttachedTo(cancer_cell, sq_norm_displac, rng);
258+
}
166259
}
167260
});
168-
ctxt->ForEachNeighbor(calculate_neighbor_forces, *this, squared_radius);
169-
170-
if (non_zero_neighbor_forces > 1) {
171-
SetStaticnessNextTimestep(false);
172-
}
261+
ctxt->ForEachNeighbor(calculate_forces_and_elastic_displacement, *this,
262+
squared_radius);
173263
}
174264

265+
//--------------------------------------------
175266
// Two step Adams-Bashforth approximation of the time derivative for position
176267
// position(t + dt) ≈ position(t) + dt * [ 1.5 * velocity(t) - 0.5 *
177268
// velocity(t - dt) ]
269+
//--------------------------------------------
178270
movement_at_next_step +=
179271
translation_velocity_on_point_mass * kDnew + older_velocity_ * kDold;
180272

@@ -184,6 +276,97 @@ Real3 CarTCell::CalculateDisplacement(const InteractionForce* force,
184276
return movement_at_next_step;
185277
}
186278

279+
// Try to get attached to a tumor cell
280+
void CarTCell::TryToGetAttachedTo(TumorCell* victim, real_t squared_distance,
281+
Random* rng) {
282+
// If the tumor cell is not already attached to a CAR-T cell, is not dead and
283+
// is not too far away.
284+
if (!victim->IsAttachedToCart() && !victim->IsDead() &&
285+
squared_distance < kSquaredMaxAdhesionDistanceCart) {
286+
// factor of how high is the oncoprotein level of the cancer cell
287+
real_t oncoprotein_scale_factor =
288+
(victim->GetOncoproteinLevel() - kOncoproteinLimit) /
289+
kOncoproteinDifference;
290+
// Clamp scale_factor to be in [0,1]
291+
if (oncoprotein_scale_factor > 1.0) {
292+
oncoprotein_scale_factor = 1.0;
293+
}
294+
// If oncoprotein level is lower than the limit the cancer cell does not get
295+
// detected
296+
if (oncoprotein_scale_factor <= 0.0) {
297+
// oncoprotein_scale_factor = 0.0; the probability is going to be 0 so
298+
// return the function is the most efficient
299+
return;
300+
}
301+
302+
// factor of how far the cancer cell is
303+
real_t distance_scale_factor =
304+
(kMaxAdhesionDistanceCart - std::sqrt(squared_distance)) /
305+
kDifferenceCartAdhesionDistances;
306+
// Clamp scale_factor to be in [0,1]. We already checked that it is > 0
307+
// because squared_distance < kSquaredMaxAdhesionDistanceCart
308+
if (distance_scale_factor > 1.0) {
309+
distance_scale_factor = 1.0;
310+
}
311+
312+
// It tries to attach the CAR-T cell to the tumor cell with probability
313+
// kAdhesionRateCart * oncoprotein_scale_factor * distance_scale_factor *
314+
// kDtMechanics
315+
if (rng->Uniform(0.0, 1.0) < kAdhesionRateCart * oncoprotein_scale_factor *
316+
distance_scale_factor * kDtMechanics) {
317+
// avoid race condition. Only one cell can be attached to the tumor cell.
318+
#pragma omp critical
319+
{
320+
// We need to check again if the victim is not attached to a CAR-T cell
321+
// yet. This could be made more efficiently with a semaphore for each
322+
// cancer cell
323+
if (!victim->IsAttachedToCart()) {
324+
attached_to_tumor_cell_ = true;
325+
attached_cell_ptr_ = victim->GetAgentPtr<TumorCell>();
326+
victim->SetAttachedToCart(true);
327+
}
328+
}
329+
}
330+
}
331+
}
332+
333+
// Try to induce apoptosis
334+
bool CarTCell::TryToInduceApoptosis(bdm::AgentPointer<TumorCell> attached_cell,
335+
Random* rng) const {
336+
// If there is no attached cell (this should not happen)
337+
338+
if (!attached_to_tumor_cell_) {
339+
return false;
340+
}
341+
342+
// factor of how high is the oncoprotein levelof the cancer cell
343+
real_t scale_factor =
344+
(attached_cell->GetOncoproteinLevel() - kOncoproteinLimit) /
345+
kOncoproteinDifference;
346+
// Clamp scale_factor to be in [0,1]
347+
if (scale_factor > 1.0) {
348+
scale_factor = 1.0;
349+
}
350+
// If oncoprotein level is lower than the limit the cancer cell does not
351+
// become apoptotic
352+
if (scale_factor < 0.0) {
353+
// scale_factor = 0.0; the probability is going to be 0 so return the
354+
// function is the most efficient
355+
return false;
356+
}
357+
// CAR-T success of killing probability: aggressive cancer cells (high
358+
// oncoprotein level) are more likely to be killed
359+
const bool succeeded =
360+
rng->Uniform(0.0, 1.0) < kKillRateCart * scale_factor * kDtMechanics;
361+
362+
// The CAR-T has succeeded to induce apoptosis on the Cancer Cell
363+
if (succeeded) {
364+
attached_cell->StartApoptosis();
365+
}
366+
367+
return succeeded;
368+
}
369+
187370
// Compute new oxygen or immunostimulatory factor concentration after
188371
// consumption/ secretion
189372
real_t CarTCell::ConsumeSecreteSubstance(int substance_id,
@@ -194,7 +377,7 @@ real_t CarTCell::ConsumeSecreteSubstance(int substance_id,
194377
res = (old_concentration + constant1_oxygen_) / constant2_oxygen_;
195378
} else if (substance_id ==
196379
immunostimulatory_factor_dgrid_->GetContinuumId()) {
197-
// This point should never be reached
380+
// CAR-T do not change immunostimulatory factor levels
198381
res = old_concentration;
199382
} else {
200383
throw std::invalid_argument("Unknown substance id: " +
@@ -230,7 +413,7 @@ void CarTCell::ComputeConstantsConsumptionSecretion() {
230413

231414
/// Main behavior executed at each simulation step
232415
void StateControlCart::Run(Agent* agent) {
233-
auto* sim = Simulation::GetActive();
416+
Simulation* sim = Simulation::GetActive();
234417
// Run only every kDtCycle minutes, fmod does not work with the type
235418
// returned by GetSimulatedTime()
236419
if (sim->GetScheduler()->GetSimulatedSteps() % kStepsPerCycle != 0) {
@@ -241,7 +424,7 @@ void StateControlCart::Run(Agent* agent) {
241424
switch (cell->GetState()) {
242425
case CarTCellState::kAlive: {
243426
// the cell is growing to real_t its size before mitosis
244-
// Probability of death= 1/CurrentLiveTime, division by 0
427+
// Probability of death= 1/CurrentLiveTime,avoiding division by 0
245428
if (sim->GetRandom()->Uniform(1.0) <
246429
kDtCycle / std::max(cell->GetCurrentLiveTime(), kEpsilon)) {
247430
// the cell Dies
@@ -260,8 +443,8 @@ void StateControlCart::Run(Agent* agent) {
260443
cell->ComputeConstantsConsumptionSecretion();
261444
// Detach from tumor cell if it was attached
262445
if (cell->IsAttachedToTumorCell()) {
263-
cell->GetAttachedCell()->SetAttachedToCart(false);
264-
cell->SetAttachedCell(nullptr);
446+
cell->GetAttachedCellPointer()->SetAttachedToCart(false);
447+
cell->SetAttachedCellPointer(nullptr);
265448
cell->SetAttachedToTumorCell(false);
266449
}
267450
} else {
@@ -284,7 +467,7 @@ void StateControlCart::Run(Agent* agent) {
284467
// duration transition)
285468
if (kTimeApoptosis < cell->GetTimerState()) {
286469
// remove the cell from the simulation
287-
auto* ctxt = sim->GetExecutionContext();
470+
ExecutionContext* ctxt = sim->GetExecutionContext();
288471
ctxt->RemoveAgent(agent->GetUid());
289472
}
290473
break;

0 commit comments

Comments
 (0)