import numpy as np
import pymunk
from SyMBac import cell_geometry
[docs]class Cell:
"""
Cells are the agents in the simulation. This class allows for instantiating `Cell` object.
.. note::
Typically the user will not need to call this class, as it will be handled by :meth:`SyMBac.cell_simulation`,
specifically all cell setup
happens when instantiating a simulation using :meth:`SyMBac.simulation.Simulation`
"""
[docs] def __init__(
self,
length,
width,
resolution,
position,
angle,
space,
dt,
growth_rate_constant,
max_length,
max_length_mean,
max_length_var,
width_var,
width_mean,
parent = None,
daughter = None,
lysis_p = 0,
pinching_sep = 0
):
"""
Initialising a cell
For info about the Pymunk objects, see the API reference. http://www.pymunk.org/en/latest/pymunk.html Cell class has been tested and works with pymunk version 6.0.0
Parameters
----------
length : float
Cell's length
width : float
Cell's width
resolution : int
Number of points defining cell's geometry
position : (float, float)
x,y coords of cell centroid
angle : float
rotation in radians of cell (counterclockwise)
space : pymunk.space.Space
The pymunk space of the cell
dt : float
Timestep the cell experiences every iteration
growth_rate_constant : float
The cell grows by a function of dt*growth_rate_constant depending on its growth model
max_length : float
The maximum length a cell reaches before dividing
max_length_mean : float
should be the same as max_length for reasons unless doing advanced simulations
max_length_var : float
The variance defining a normal distribution around max_length
width_var : float
The variance defining a normal distribution around width
width_mean : float
For reasons should be set equal to width unless using advanced features
body : pymunk.body.Body
The cell's pymunk body object
shape : pymunk.shapes.Poly
The cell's pymunk body object
ID : int
A unique identifier for each cell. At the moment just a number from 0 to 100_000_000 and cross fingers that we get no collisions.
"""
self.dt = dt
self.growth_rate_constant = growth_rate_constant
self.length = length
self.width_mean = width_mean
self.width_var = width_var
self.width = width
self.resolution = resolution
self.angle = angle
self.position = position
self.space = space
self.max_length = max_length
self.max_length_mean = max_length_mean
self.max_length_var = max_length_var
self.body, self.shape = self.create_pm_cell()
self.angle = self.body.angle
self.ID = np.random.randint(0,100_000_000)
self.lysis_p = lysis_p
self.parent = parent
self.daughter = daughter
self.pinching_sep = pinching_sep
[docs] def create_pm_cell(self):
"""
Creates a pymunk (pm) cell object, and places it into the pymunk space given when initialising the cell. If the
cell is dividing, then two cells will be created. Typically this function is called for every cell, in every
timestep to update the entire simulation.
.. note::
The return type of this function is dependent on the value returned by :meth:`SyMBac.cell.Cell.is_dividing()`.
This is not good, and will be changed in a future version.
Returns
-------
dict or (pymunk.body, pymunk.shape)
If :meth:`SyMBac.cell.Cell.is_dividing()` returns `True`, then a dictionary of values for the daughter cell
is returned. A daughter can then be created. E.g:
>>> daughter_details = cell.create_pm_cell()
>>> daughter = Cell(**daughter_details)
If :meth:`SyMBac.cell.Cell.is_dividing()` returns `False`, then only a tuple containing (pymunk.body, pymunk.shape) will be returned.
"""
if self.is_dividing():
new_length = self.length/2 - self.width/4
daughter_length = self.length - new_length - self.width/4
self.length = new_length
self.pinching_sep = 0
cell_vertices = self.calculate_vertex_list()
cell_shape = pymunk.Poly(None, cell_vertices)
self.shape = cell_shape
cell_moment = 100001
cell_mass = 1
cell_body = pymunk.Body(cell_mass,cell_moment)
cell_shape.body = cell_body
self.body = cell_body
new_x = self.position[0] + (self.length + self.width/2)/2 * np.cos(self.angle*2)
new_y = self.position[1] + (self.length+ self.width/2)/2 * np.sin(self.angle*2)
self.body.position = [new_x, new_y]
cell_body.angle = self.angle
cell_shape.friction=0
self.space.add(cell_body, cell_shape)
daughter_details = {
"length": daughter_length,
"width": np.random.normal(self.width_mean,self.width_var),
"resolution": self.resolution,
"position": [self.position[0] - (self.length+ self.width/2)/2 * np.cos(self.angle*2), self.position[1] - (self.length+ self.width/2)/2 * np.sin(self.angle*2)],
"angle": self.angle*np.random.uniform(0.95,1.05),
"space": self.space,
"dt": self.dt,
"growth_rate_constant": self.growth_rate_constant,
"max_length": np.random.normal(self.max_length_mean,self.max_length_var),
"max_length_mean": self.max_length_mean,
"max_length_var": self.max_length_var,
"width_var": self.width_var,
"width_mean": self.width_mean,
"lysis_p": self.lysis_p,
"parent": self.parent,
"pinching_sep": 0
}
return daughter_details
else:
cell_vertices = self.calculate_vertex_list()
cell_shape = pymunk.Poly(None, cell_vertices)
self.shape = cell_shape
cell_moment = 100001
cell_mass = 1
cell_body = pymunk.Body(cell_mass,cell_moment)
cell_shape.body = cell_body
self.body = cell_body
cell_body.position = self.position
cell_body.angle = self.angle
cell_shape.friction=0
self.space.add(cell_body, cell_shape)
return cell_body, cell_shape
[docs] def is_dividing(self): # This needs to be made constant or a cell can divide in one frame and not another frame
"""
Checks whether a cell is dividing by comparing its current length to its max length (defined when instnatiated).
Returns
-------
output : bool
`True` if ``self.length > self.max_length``, else `False`.
"""
if self.length > (self.max_length):
return True
else:
return False
[docs] def update_length(self):
"""
A method, typically called every timepoint to update the length of the cell according to ``self.length = self.length + self.growth_rate_constant*self.dt*self.length``.
Contains additional logic to control the amount of cell pinching happening according to the difference between
the maximum length and the current length.
Returns
-------
None
"""
self.length = self.length + self.growth_rate_constant*self.dt*self.length*np.random.uniform(0.5,1.3)
self.pinching_sep = max(0, self.length - self.max_length + self.width)
self.pinching_sep = min(self.pinching_sep, self.width - 2)
[docs] def update_position(self):
"""
A method, typically called every timepoint to keep synchronised the cell position (``self.position`` and ``self.angle``)
with the position of the cell's corresponding body in the pymunk space (``self.body.position`` and ``self.body.angle``).
Returns
-------
None
"""
self.position = self.body.position
self.angle = self.body.angle
[docs] def update_parent(self, parent):
"""
Parameters
----------
parent : :class:`SyMBac.cell.Cell`
The SyMBac cell object to assign as the parent to the current cell.
Returns
-------
None
"""
self.parent = parent
[docs] def get_angle(self):
"""
Gets the angle of the cell's pymunk body.
Returns
-------
angle : float
The cell's angle in radians.
"""
return self.body.angle
def calculate_vertex_list(self):
return cell_geometry.get_vertices(
self.length,
self.width,
self.angle,
self.resolution
)
[docs] def get_vertex_list(self):
"""
Calculates the vertex list (a set of x,y coordinates) which parameterise the outline of the cell
Returns
-------
vertices : list(tuple(float, float))
A list of vertices, each in a tuple, where the order is `(x, y)`. The coordinates are relative to the pymunk
space in which the cell exists.
"""
vertices = []
for v in self.shape.get_vertices():
x,y = v.rotated(self.shape.body.angle) + self.shape.body.position #.rotated(self.shape.body.angle)
vertices.append((x,y))
return vertices
[docs] def get_centroid(self):
"""
Calculates the centroid of the cell from the vertices.
Returns
-------
centroid : float
The cell's centroid in coordinates relative to the pymunk space which the cell exists in.
"""
vertices = self.get_vertex_list()
return cell_geometry.centroid(vertices)