-
Notifications
You must be signed in to change notification settings - Fork 3
/
cell_division.py
190 lines (160 loc) · 6.52 KB
/
cell_division.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
"""
=============
Cell Division
=============
"""
from typing import Any, Dict
import binascii
import numpy as np
from vivarium.core.process import Step
from ecoli.library.sim_data import RAND_MAX
from ecoli.library.schema import attrs
from wholecell.utils import units
NAME = "ecoli-cell-division"
def daughter_phylogeny_id(mother_id):
return [str(mother_id) + "0", str(mother_id) + "1"]
class MarkDPeriod(Step):
"""Set division flag after D period has elapsed"""
name = "mark_d_period"
def ports_schema(self):
return {
"full_chromosome": {},
"global_time": {"_default": 0.0},
"divide": {
"_default": False,
"_updater": "set",
"_divider": {"divider": "set_value", "config": {"value": False}},
},
}
def next_update(self, timestep, states):
division_time, has_triggered_division = attrs(
states["full_chromosome"], ["division_time", "has_triggered_division"]
)
if len(division_time) < 2:
return {}
# Set division time to be the minimum division time for a chromosome
# that has not yet triggered cell division
divide_at_time = division_time[~has_triggered_division].min()
if states["global_time"] >= divide_at_time:
divide_at_time_index = np.where(division_time == divide_at_time)[0][0]
has_triggered_division = has_triggered_division.copy()
has_triggered_division[divide_at_time_index] = True
# Set flag for ensuing division Step to trigger division
return {
"full_chromosome": {
"set": {"has_triggered_division": has_triggered_division}
},
"divide": True,
}
return {}
class Division(Step):
"""
Division Deriver
* Uses dry mass threshold that can be set in config via division_threshold
* Samples division threshold from normal distribution centered around what
is expected for a medium when division_threshold == massDistribution
* If flag d_period is set to true (default), mass thresholds are ignored and
the same D period mechanism as wcEcoli is used.
"""
name = NAME
defaults: Dict[str, Any] = {
"daughter_ids_function": daughter_phylogeny_id,
"threshold": None,
"seed": 0,
}
def __init__(self, parameters=None):
super().__init__(parameters)
# must provide a composer to generate new daughters
self.agent_id = self.parameters["agent_id"]
self.composer = self.parameters["composer"]
self.composer_config = self.parameters["composer_config"]
self.random_state = np.random.RandomState(seed=self.parameters["seed"])
self.division_mass_multiplier = 1
if self.parameters["division_threshold"] == "massDistribution":
division_random_seed = (
binascii.crc32(b"CellDivision", self.parameters["seed"]) & 0xFFFFFFFF
)
division_random_state = np.random.RandomState(seed=division_random_seed)
self.division_mass_multiplier = division_random_state.normal(
loc=1.0, scale=0.1
)
self.dry_mass_inc_dict = self.parameters["dry_mass_inc_dict"]
def ports_schema(self):
return {
"division_variable": {},
"full_chromosome": {},
"agents": {"*": {}},
"media_id": {},
"division_threshold": {
"_default": self.parameters["division_threshold"],
"_updater": "set",
"_divider": {
"divider": "set_value",
"config": {"value": self.parameters["division_threshold"]},
},
},
}
def next_update(self, timestep, states):
# Figure out division threshold at first timestep if
# using massDistribution setting
if states["division_threshold"] == "massDistribution":
current_media_id = states["media_id"]
return {
"division_threshold": (
states["division_variable"]
+ self.dry_mass_inc_dict[current_media_id].asNumber(units.fg)
* self.division_mass_multiplier
)
}
division_variable = states["division_variable"]
if (division_variable >= states["division_threshold"]) and (
states["full_chromosome"]["_entryState"].sum() >= 2
):
daughter_ids = self.parameters["daughter_ids_function"](self.agent_id)
daughter_updates = []
for daughter_id in daughter_ids:
config = dict(self.composer_config)
config["agent_id"] = daughter_id
config["seed"] = self.random_state.randint(0, RAND_MAX)
# Regenerate composite to avoid unforeseen shared states
composite = self.composer(config).generate()
# Get shared process instances for partitioned processes
process_states = {
process.parameters["process"].name: (process.parameters["process"],)
for process in composite.steps.values()
if "process" in process.parameters
}
initial_state = {"process": process_states}
daughter_updates.append(
{
"key": daughter_id,
"processes": composite["processes"],
"steps": composite["steps"],
"flow": composite["flow"],
"topology": composite["topology"],
"initial_state": initial_state,
}
)
print(f"DIVIDE! MOTHER {self.agent_id} -> DAUGHTERS {daughter_ids}")
return {
"agents": {
"_divide": {"mother": self.agent_id, "daughters": daughter_updates}
}
}
return {}
class DivisionDetected(Exception):
pass
class StopAfterDivision(Step):
"""
Detect division and raise an exception that must be caught.
"""
name = "stop-after-division"
def ports_schema(self):
return {
"agents": {"*": {}},
}
def next_update(self, timestep, states):
# Raise exception once division has occurred
if len(states["agents"]) > 1:
raise DivisionDetected("More than one cell in agents store.")
return {}