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
139148Real3 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
189372real_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
232415void 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