"""
The cell simulation with logging.
"""
from .simulation import System, CellLine, World, behavior
from .logging import logged, FullLog
import random as rnd
[docs]class SimpleCells(CellLine):
"""A cell line representing simple cells with default .
A cell from this line performs:
- Cell division,
- Cell death,
- Cell migration,
- Cell genome mutation.
Each behavior has an associated probability and it
is possible to assign different weights to them,
so that some behaviors are more probable to be selected
than others. Also, it is easy to change the behaviors
(add more, remove, change, etc.), to do this, subclass
and add your own behaviors as in the '_init_behaviors'
method of this class.
"""
def __init__(self, *args, genome_alphabet=None, **kwargs):
# Initialize as usual.
super().__init__(*args, **kwargs)
# Register the default actions
self.add_behaviors(*self._init_behaviors())
# ---
def _init_behaviors(self):
"""Initialize the behaviors for cells in this lineage."""
behaviors = [
# --- Cell mutation
behavior('mutation',
actionfn=self.mutation,
probability=self.mutation_probability),
# --- Cell migration
behavior('migration',
actionfn=self.migration,
probability=self.migration_probability),
# --- Cell division
behavior('division',
actionfn=self.division,
probability=self.division_probability),
# --- Cell death
behavior('death',
actionfn=self.death,
probability=self.death_probability),
# The behavior function handles the probabilistic nature
# of the behavior execution and adds the capability to
# integrate a log that triggers when the action is performed.
# More info: See the source for the behavior function.
]
# The relative weights of each action.
# If an action has a bigger weigth than
# the others, it has a correspondingly
# bigger probability to be chosen
weights = [1] * len(behaviors)
return behaviors, weights
# ---
[docs] @logged('newcell', prepare=False)
def add_cell_to(self, site):
"""Add a new, initialized cell to the given site.
Return the added cell to the caller.
"""
new = self.new_cell()
new.add_to(site)
# Return the new cell
return new
# ---
[docs] @staticmethod
def migration_probability(cell):
"""Migration probability for this cell."""
return 1
# ---
[docs] @staticmethod
def migration(cell, *args, **kwargs):
"""Migrate to a neighboring cell."""
# Get the destination site
next_site = cell.site.random_neighbor()
# Migrate to the new site
cell.site.remove_guest(cell)
next_site.add_guest(cell)
cell.site = next_site
return cell
# ---
[docs] @staticmethod
def mutation_probability(cell):
"""Probability to mutate if selected for it."""
return 1
# ---
[docs] @staticmethod
def mutation(cell, *args, **kwargs):
"""Do a single site mutation."""
# Get the genome characteristics
# Convert alphabet to tuple to call rnd.choice with it
alphabet = tuple(cell.genome_alphabet)
genome_length = len(cell.genome)
# Assemble the mutation
position = rnd.randrange(genome_length) # Pick a random position in the genome
mutated = rnd.choice(alphabet)
# Mutate
cell.add_mutation(position, mutated)
return cell
# ---
[docs] @staticmethod
def death_probability(cell):
"""Cellular death probability."""
# Avoid killing all cells.
if cell.lineage.total_cells > 1:
return 0.6
else:
return 0
# ---
[docs] @staticmethod
def death(cell, *args, **kwargs):
"""Cellular death."""
cell.site.remove_guest(cell)
cell.lineage.handle_death(cell)
return cell
# ---
@staticmethod
def _init_daughter(cell):
"Add a new daughter of the cell in an appropriate site."
# Create the daughter cell
daughter = cell.new_daughter()
# Place the daughter
site = cell.site
daughter.add_to(site.random_neighbor())
return daughter
# ---
[docs] @staticmethod
def division_probability(cell):
"""Probability that this cell will divide if selected for division."""
return 1
# ---
[docs] @staticmethod
def division(cell, *args, preserve_father=False, **kwargs):
"""Cell division.
Get a new daughter of this cell and place it in a nearby
neighboring site.
"""
# Create the daughter cell and add it to a site
daughter = SimpleCells._init_daughter(cell)
# If the preserveFather flag is set, we want a
# father cell and a daughter cell after the division (like
# the gemation process on yeasts), else, we want both
# resulting cells to be daughters of the father cell.
if not preserve_father:
# New daughter placed on an appropriate site
other_daughter = SimpleCells._init_daughter(cell)
# Remove previous cell
SimpleCells.death(cell)
return daughter, other_daughter
# ---
# --- SimpleCells
[docs]class CellSystem(System):
"""A system simulating cell growth.
A cell system is a system subclass, with
the automatic initialization of two main entities:
1. Cells represented by a cell line, and;
2. A 'world' representing the space that the cells
inhabit.
Each part can be accessed by ``system['cells']`` and
``system['world']`` respectively.
Also, the system has a 'log' that follows and makes a record
of the cells' actions. This record is in ``system.log``
Example:
.. code-block:: python
>>> from cellsystem import *
# The cell system will simulate cell growth
# while saving a log of the process.
>>> system = CellSystem(grid_shape=(100, 100),
init_genome='AAAAAAAAAA')
# Initialize the first cell
# in the middle of the grid
>>> system.seed()
New cell 0 added @ (50, 50)
# Take 10 steps forward in time
>>> system.run(steps=10)
Cell no. 0 mutating @ site (50, 50) (father None)
Initial mutations: []
Initial genome: AAAAAAAAAA
Final mutations: [(9, 'G')]
Final genome: AAAAAAAAAG
Cell no. 0 migrating from site (50, 50) (father None)
New site: (50, 50)
Cell no. 0 dividing @ (50, 50)
New cells: 1 @ (49, 49) and 2 @ (49, 51)
Cell no. 2 mutating @ site (49, 51) (father None)
Initial mutations: [(9, 'G')]
Initial genome: AAAAAAAAAG
Final mutations: [(9, 'G'), (8, 'T')]
Final genome: AAAAAAAATG
Cell no. 1 death @ site (49, 49) (father None)
Cell no. 2 dividing @ (49, 51)
New cells: 3 @ (48, 51) and 4 @ (49, 50)
Cell no. 4 dividing @ (49, 50)
New cells: 5 @ (50, 50) and 6 @ (50, 51)
Cell no. 3 migrating from site (48, 51) (father 2)
New site: (49, 50)
Cell no. 6 mutating @ site (50, 51) (father 4)
Initial mutations: [(9, 'G'), (8, 'T')]
Initial genome: AAAAAAAATG
Final mutations: [(9, 'G'), (8, 'T'), (8, 'A')]
Final genome: AAAAAAAAAG
Cell no. 5 dividing @ (50, 50)
New cells: 7 @ (51, 51) and 8 @ (50, 50)
...
...
...
# Prepare to explore the simulation logs
>>> history = system['log']
# Watch the ancestry tree
>>> print(history.ancestry())
/-1
|
| /-13
| /-|
-- /-| /-| \-14
| | |
| | \-10
| |
\-| /-7
| /-|
| | | /-11
| | \-|
\-| | /-15
| \-|
| \-16
|
\-6
# Let's see the mutational history
>>> print(history.mutations())
/- /-AAAAAACAAG
|
-- /- /- /-|--AAAAAAAATG
|
\-CAAAAAAATG
# Now, let's see the cells' evolution in time and space!
>>> history.worldlines().show()
Each log can be deactivated and reactivated at will, also,
it is possible to add, remove and modify logs according to
your own needs. This can be done by subclassing.
Also, it is possible to handle more then one world or cell line
and to change them completely and add any other entity that may
be useful. See the __init__ method of this class for an example
of how this is done.
"""
def __init__(self, *args,
grid_shape=(100, 100),
init_genome=None,
**kwargs):
"""Initialization process."""
super().__init__(*args, **kwargs)
# Initialize world
self.add_entity( World(shape=grid_shape),
name='world',
procesable=False) # <- This means that this
# is a passive entity.
# Initialize the cells
self.add_entity( SimpleCells(genome=init_genome),
name='cells')
# Initialize log
self.register_log( FullLog() )
# ---
[docs] def seed(self):
'Place a single cell in the middle of the world.'
# Fetch the middle of the grid
world = self['world']
# Add cell
self['cells'].add_cell_to( world.middle,
log=self.log )
# ---
# --- CellSystem