Cell Death

This guide covers suggested apoptosis, necrosis, and phagocytosis.

Death by Apoptosis

For each cell you want to kill, set its targetVolume to 0. The cell will begin to shrink over time. You can make this happen faster by increasing lambdaVolume. Once the cell’s volume is small enough, you should delete it with self.delete_cell(cell).

Example step 1: This example simulates apoptosis for B cells. In your main Steppable, write:

def step(self, mcs):
    for b_cell, common_surface_area in self.get_cell_neighbor_data_list(tfh_cell):
        if b_cell and b_cell.type == self.CENTROCYTE:
            b_cell.targetVolume = 0 #Starts apoptosis
            b_cell.lambdaVolume = 1000 #Optional: make the shrinkage happen very fast

Example step 2: Add this to your Main Python Script file. We use a separate Steppable to check for cell death only periodically (that is, every 100 MCS instead of every 1 step). This is __optional__, but it may provide a slight boost to performance.

from GerminalCenterMigrationSteppables import DeathSteppable

CompuCellSetup.register_steppable(steppable=DeathSteppable(frequency=100))

Example step 3: Add a new Steppable to your Main Python Script file.

class DeathSteppable(SteppableBasePy):
    def __init__(self, frequency=1):
        SteppableBasePy.__init__(self, frequency)

    def step(self, mcs):
        cells_to_delete = []
        for cell in self.cell_list:
            if cell.volume < 2: #Replace '2' with any arbitrary small number
                cells_to_delete.append(cell)

        for cell in cells_to_delete:
            self.delete_cell(cell)

Note

It’s important to use move all cells to delete to a new list, cells_to_delete, so that a separate Python loop will make the calls to self.delete_cell(cell). This ensures that the first loop does not lose its place as it searches for dying cells.

Death by Necrosis

There are many ways to achieve this, but one is to create a separate cell type, Necrotic, that has a very high target surface and lamda surface. Its volume can remain the same as before. This simulates the cell tearing apart, which, in real cells, creates a toxic environment for surviving neighobrs. This time, there is no call to self.delete_cell(cell) so that the cell’s detritus will remain there.

 <Plugin Name="Volume">
   <VolumeEnergyParameters CellType="Somatic" LambdaVolume="2.0" TargetVolume="50"/>
   <VolumeEnergyParameters CellType="Necrotic" LambdaVolume="2.0" TargetVolume="50"/>
</Plugin>

<Plugin Name="Surface">
   <SurfaceEnergyParameters CellType="Somatic" LambdaSurface="2.0" TargetSurface="50"/>
   <SurfaceEnergyParameters CellType="Necrotic" LambdaSurface="2.0" TargetSurface="200"/>
</Plugin>

When you are ready to kill a cell, just change its type to NECROTIC. In this example, all cells die at Monte Carlo Step 100.

def step(self, mcs):
    if mcs == 100:
        for cell in self.cell_list:
            cell.type = self.NECROTIC

Death by Phagocytosis

This time, another cell will absorb the volume of the cell that dies. Think of a macrophage eating a bacterium and becoming slightly larger. This code checks every bacteria cell to see if its only neighbors are macrophage(s).

def step(self, mcs):
    cells_to_delete = []
    for i, bacteria in enumerate(self.cell_list_by_type(self.BACTERIA)):
        is_only_touching_macrophage = True
        macrophage = None
        for neighbor, common_surface_area in self.get_cell_neighbor_data_list(bacteria):
            if neighbor:
                if neighbor.type == self.MACROPHAGE:
                    macrophage = neighbor
                else:
                   is_only_touching_macrophage = False

        if is_only_touching_macrophage and macrophage != None:
            #Now, the macrophage eats the bacteria.
            macrophage.targetVolume += bacteria.volume
            macrophage.targetSurface += 2 * sqrt(bacteria.volume) #Try to retain volume-to-surface ratio
            cells_to_delete.append(bacteria)

    for cell in cells_to_delete:
        self.delete_cell(cell)

Alternative Approach 1: You could also check the length of cell_list_by_type to see if it is 1, but that would prevent phagocytosis from happening if the cell is touching any of the Medium.

Alternative Approach 2: Since common_surface_area is not used in this example, CC3D has no way to know if one cell is inside of another. You could check the shared surface area against each cell’s current surface.

Note

As in apoptosis, it’s important to use move all cells to delete to a new list, cells_to_delete, so that a separate Python loop will make the calls to self.delete_cell(cell). This ensures that the first loop does not lose its place as it searches for dying cells.


How to Turn off Mitosis for Dying Cells

If you have a separate cell type for dying cells, then just add a line like if cell.type != self.NECROTIC.

class MitosisSteppable(MitosisSteppableBase):
    def __init__(self,frequency=1):
        MitosisSteppableBase.__init__(self,frequency)

    def step(self, mcs):

        cells_to_divide=[]

        for cell in self.cell_list:
            if cell.type != self.NECROTIC:
                cells_to_divide.append(cell)

        for cell in cells_to_divide:
            self.divide_cell_random_orientation(cell)


    def update_attributes(self):
        # ...

Otherwise, for apoptosis, you could check the targetVolume:

for cell in self.cell_list_by_type(self.CENTROBLAST):
    if cell.targetVolume > 0:
        cells_to_divide.append(cell)

How to Control Division Time

If you want to divide every cell every 70 MCS, for instance, you should track each cell’s last division time independently using cell.dict.

DIVISION_TIME = 70 #mcs

class MitosisSteppable(MitosisSteppableBase):
    def __init__(self,frequency=1):
        MitosisSteppableBase.__init__(self,frequency)

    def step(self, mcs):

        cells_to_divide=[]

        for cell in self.cell_list:
            last_div_time = cell.dict["last division time"]
            if mcs - last_div_time >= DIVISION_TIME:
                cell.dict["last division time"] = mcs
                cells_to_divide.append(cell)

        for cell in cells_to_divide:
            self.divide_cell_random_orientation(cell)


    def update_attributes(self):
        # ...

Note

You may like to implement some rules to reduce crowding, such as contact-inhibited growth or a very simple if-statement that prevents cells with too many neighbors from dividing. Using pressure tends to be more realistic.