cellular_raza_concepts/cycle.rs
1use crate::errors::{DeathError, DivisionError};
2
3use serde::{Deserialize, Serialize};
4
5/// Contains all events which can arise during the cell cycle and need to be communciated to
6/// the simulation engine (see also [Cycle]).
7#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
8pub enum CycleEvent {
9 /// A cell-event which calls the [Cycle::divide] method which will
10 /// spawn an additional cell and modify the existing one.
11 Division,
12 /// Immediately removes the cell from the simulation domain. No function will be called.
13 Remove,
14 /// The cell enters a dying mode.
15 /// It is still continuously updating via the [Cycle::update_conditional_phased_death] its
16 /// properties but now checking if the death phase is completed.
17 /// [CycleEvent::Remove] will be carried out when the condition reaches true.
18 PhasedDeath,
19}
20
21/// This trait represents all cycles of a cell and works in tandem with the [CycleEvent] enum.
22///
23/// The `update_cycle` function is designed to be called frequently and return only something if a
24/// specific cycle event is supposed to be occuring. Backends should implement
25/// the functionality to call the corresponding functions as needed.
26///
27/// ## Stochasticity
28/// In order to make sure that results are reproducible, the provided rng parameter should be used.
29/// Should a user fall back to the option to use the threaded rng, this simulation cannot guarantee
30/// deterministic results anymore.
31/// We plan to include the stochastic aspect into individual [`Event`](CycleEvent) variants such
32/// that the correct handling of integrating the underlying stochastic process can be
33/// carried out by the [backend](https://docs.rs/cellular_raza-core/backend).
34///
35/// ## Example implementation
36/// This could be an example of a very simplified cell-agent.
37/// The user is free to do anything with this function that is desired but is also responsible for
38/// keeping track of all the variables. This means for example that intracellular values might need
39/// to be adjusted (most often halfed) and new positions need to be assigned to the cells such that
40/// the cells are not overlapping ...
41///
42/// ```
43/// use rand::Rng;
44/// use rand_chacha::ChaCha8Rng;
45/// use cellular_raza_concepts::{Cycle, CycleEvent, DivisionError};
46///
47/// // We define our cell struct with all parameters needed for this cell-agent.
48/// #[derive(Clone)]
49/// struct Cell {
50/// // Size of the cell (spherical)
51/// radius: f64,
52/// // Track the age of the cell
53/// current_age: f64,
54/// // Used in cycle later. Divide cell if older than maximum age.
55/// maximum_age: f64,
56/// // The position of the cell. We cannot have two positions which are the same. Thus we need
57/// // to update the position as well.
58/// position: [f64; 2],
59/// }
60///
61/// impl Cycle<Cell> for Cell {
62/// fn update_cycle(rng: &mut ChaCha8Rng, dt: &f64, cell: &mut Cell) -> Option<CycleEvent> {
63/// // Increase the current age of the cell
64/// cell.current_age += dt;
65///
66/// // If the cell is older than the current age, return a division event
67/// if cell.current_age > cell.maximum_age {
68/// return Some(CycleEvent::Division)
69/// }
70/// None
71/// }
72///
73/// fn divide(rng: &mut ChaCha8Rng, cell: &mut Cell) -> Result<Cell, DivisionError> {
74/// // Prepare the original cell for division.
75/// // Set the radius of both cells to half of the original radius.
76/// cell.radius *= 0.5;
77///
78/// // Also set the current age of the cell to zero again
79/// cell.current_age = 0.0;
80///
81/// // Clone the existing cell
82/// let mut new_cell = (*cell).clone();
83///
84/// // Define a new position for both cells
85/// // To do this: Pick a random number as an angle.
86/// let angle = rng.random_range(0.0..2.0*std::f64::consts::PI);
87///
88/// // Calculate the new position of the original and new cell with this angle
89/// let pos = [
90/// cell.radius * angle.cos(),
91/// cell.radius * angle.sin()
92/// ];
93/// let new_pos = [
94/// cell.radius * (angle+std::f64::consts::FRAC_PI_2).cos(),
95/// cell.radius * (angle+std::f64::consts::FRAC_PI_2).sin()
96/// ];
97///
98/// // Set new positions
99/// cell.position = pos;
100/// new_cell.position = new_pos;
101///
102/// // Finally return the new cell
103/// return Ok(new_cell);
104/// }
105/// }
106/// ```
107pub trait Cycle<Cell = Self, Float = f64> {
108 /// Continuously updates cellular properties and may spawn a [CycleEvent] which
109 /// then calls the corresponding functions (see also [CycleEvent]).
110 #[must_use]
111 fn update_cycle(
112 rng: &mut rand_chacha::ChaCha8Rng,
113 dt: &Float,
114 cell: &mut Cell,
115 ) -> Option<CycleEvent>;
116
117 /// Performs division of the cell by modifying the existing one and spawning an additional cell.
118 /// The user is responsible for correctly adjusting cell-specific values such as intracellular
119 /// concentrations or position of the two resulting cells.
120 /// Corresponds to [CycleEvent::Division].
121 #[must_use]
122 fn divide(rng: &mut rand_chacha::ChaCha8Rng, cell: &mut Cell) -> Result<Cell, DivisionError>;
123
124 /// Method corresponding to the [CycleEvent::PhasedDeath] event.
125 /// Update the cell while returning a boolean which indicates if the updating procedure has
126 /// finished. As soon as the return value is `true` the cell is removed.
127 #[allow(unused)]
128 #[must_use]
129 fn update_conditional_phased_death(
130 rng: &mut rand_chacha::ChaCha8Rng,
131 dt: &Float,
132 cell: &mut Cell,
133 ) -> Result<bool, DeathError> {
134 Ok(true)
135 }
136}
137
138#[allow(unused)]
139#[doc(hidden)]
140mod test_derive {
141 /// ```
142 /// use cellular_raza_concepts::{Cycle, CycleEvent, DeathError, DivisionError, CellAgent};
143 /// struct MyCycle;
144 ///
145 /// impl Cycle<MyAgent> for MyCycle {
146 /// fn divide(rng: &mut rand_chacha::ChaCha8Rng, cell: &mut MyAgent) -> Result<MyAgent, DivisionError> {
147 /// panic!("This should never be called")
148 /// }
149 ///
150 /// fn update_cycle(
151 /// rng: &mut rand_chacha::ChaCha8Rng,
152 /// dt: &f64,
153 /// cell: &mut MyAgent,
154 /// ) -> Option<CycleEvent> {
155 /// panic!("This should never be called")
156 /// }
157 /// }
158 ///
159 /// #[derive(CellAgent)]
160 /// struct MyAgent {
161 /// #[Cycle]
162 /// cycle: MyCycle
163 /// }
164 /// ```
165 fn derive_cycle_default() {}
166
167 /// ```
168 /// use cellular_raza_concepts::{Cycle, CycleEvent, DeathError, DivisionError, CellAgent};
169 /// struct MyCycle;
170 ///
171 /// impl Cycle<MyAgent, f32> for MyCycle {
172 /// fn divide(rng: &mut rand_chacha::ChaCha8Rng, cell: &mut MyAgent) -> Result<MyAgent, DivisionError> {
173 /// panic!("This should never be called")
174 /// }
175 ///
176 /// fn update_cycle(
177 /// rng: &mut rand_chacha::ChaCha8Rng,
178 /// dt: &f32,
179 /// cell: &mut MyAgent,
180 /// ) -> Option<CycleEvent> {
181 /// panic!("This should never be called")
182 /// }
183 /// }
184 ///
185 /// #[derive(CellAgent)]
186 /// struct MyAgent {
187 /// #[Cycle]
188 /// cycle: MyCycle
189 /// }
190 /// ```
191 fn derive_cycle_f32() {}
192
193 /// ```
194 /// use cellular_raza_concepts::{Cycle, CycleEvent, DeathError, DivisionError, CellAgent};
195 /// struct MyCycle<F> {
196 /// some_property: F,
197 /// }
198 ///
199 /// impl<F> Cycle<MyAgent<F>, F> for MyCycle<F> {
200 /// fn divide(rng: &mut rand_chacha::ChaCha8Rng, cell: &mut MyAgent<F>) -> Result<MyAgent<F>, DivisionError> {
201 /// panic!("This should never be called")
202 /// }
203 ///
204 /// fn update_cycle(
205 /// rng: &mut rand_chacha::ChaCha8Rng,
206 /// dt: &F,
207 /// cell: &mut MyAgent<F>,
208 /// ) -> Option<CycleEvent> {
209 /// panic!("This should never be called")
210 /// }
211 /// }
212 ///
213 /// #[derive(CellAgent)]
214 /// struct MyAgent<F> {
215 /// #[Cycle]
216 /// cycle: MyCycle<F>
217 /// }
218 /// ```
219 fn derive_cycle_generic_float() {}
220
221 /// ```
222 /// use cellular_raza_concepts::{Cycle, CycleEvent, DeathError, DivisionError, CellAgent};
223 /// struct MyCycle<G> {
224 /// some_property: G,
225 /// }
226 ///
227 /// impl<G> Cycle<MyAgent<G>> for MyCycle<G>
228 /// where
229 /// G: Clone
230 /// {
231 /// fn divide(rng: &mut rand_chacha::ChaCha8Rng, cell: &mut MyAgent<G>) -> Result<MyAgent<G>, DivisionError> {
232 /// panic!("This should never be called")
233 /// }
234 ///
235 /// fn update_cycle(
236 /// rng: &mut rand_chacha::ChaCha8Rng,
237 /// dt: &f64,
238 /// cell: &mut MyAgent<G>,
239 /// ) -> Option<CycleEvent> {
240 /// panic!("This should never be called")
241 /// }
242 /// }
243 ///
244 /// #[derive(CellAgent)]
245 /// struct MyAgent<G>
246 /// where
247 /// G: Clone
248 /// {
249 /// #[Cycle]
250 /// cycle: MyCycle<G>
251 /// }
252 /// ```
253 fn derive_cycle_generic_float_where_clause() {}
254
255 /// ```
256 /// use cellular_raza_concepts::{Cycle, CycleEvent, DeathError, DivisionError, CellAgent};
257 /// struct MyCycle;
258 ///
259 /// impl Cycle<MyAgent> for MyCycle {
260 /// fn divide(rng: &mut rand_chacha::ChaCha8Rng, cell: &mut MyAgent) -> Result<MyAgent, DivisionError> {
261 /// panic!("This should never be called")
262 /// }
263 ///
264 /// fn update_cycle(
265 /// rng: &mut rand_chacha::ChaCha8Rng,
266 /// dt: &f64,
267 /// cell: &mut MyAgent,
268 /// ) -> Option<CycleEvent> {
269 /// panic!("This should never be called")
270 /// }
271 /// }
272 ///
273 /// #[derive(CellAgent)]
274 /// struct MyAgent(
275 /// #[Cycle]
276 /// MyCycle
277 /// );
278 /// ```
279 fn derive_cycle_unnamed() {}
280
281 /// ```
282 /// use cellular_raza_concepts::{Cycle, CycleEvent, DeathError, DivisionError, CellAgent};
283 /// struct MyCycle;
284 ///
285 /// impl Cycle<MyAgent, f32> for MyCycle {
286 /// fn divide(rng: &mut rand_chacha::ChaCha8Rng, cell: &mut MyAgent) -> Result<MyAgent, DivisionError> {
287 /// panic!("This should never be called")
288 /// }
289 ///
290 /// fn update_cycle(
291 /// rng: &mut rand_chacha::ChaCha8Rng,
292 /// dt: &f32,
293 /// cell: &mut MyAgent,
294 /// ) -> Option<CycleEvent> {
295 /// panic!("This should never be called")
296 /// }
297 /// }
298 ///
299 /// #[derive(CellAgent)]
300 /// struct MyAgent(
301 /// #[Cycle]
302 /// MyCycle
303 /// );
304 /// ```
305 fn derive_cycle_f32_unnamed() {}
306
307 /// ```
308 /// use cellular_raza_concepts::{Cycle, CycleEvent, DeathError, DivisionError, CellAgent};
309 /// struct MyCycle<F> {
310 /// some_property: F,
311 /// }
312 ///
313 /// impl<F> Cycle<MyAgent<F>, F> for MyCycle<F> {
314 /// fn divide(rng: &mut rand_chacha::ChaCha8Rng, cell: &mut MyAgent<F>) -> Result<MyAgent<F>, DivisionError> {
315 /// panic!("This should never be called")
316 /// }
317 ///
318 /// fn update_cycle(
319 /// rng: &mut rand_chacha::ChaCha8Rng,
320 /// dt: &F,
321 /// cell: &mut MyAgent<F>,
322 /// ) -> Option<CycleEvent> {
323 /// panic!("This should never be called")
324 /// }
325 /// }
326 ///
327 /// #[derive(CellAgent)]
328 /// struct MyAgent<F>(
329 /// #[Cycle]
330 /// MyCycle<F>
331 /// );
332 /// ```
333 fn derive_cycle_generic_float_unnamed() {}
334
335 /// ```
336 /// use cellular_raza_concepts::{Cycle, CycleEvent, DeathError, DivisionError, CellAgent};
337 /// struct MyCycle<G> {
338 /// some_property: G,
339 /// }
340 ///
341 /// impl<G> Cycle<MyAgent<G>> for MyCycle<G>
342 /// where
343 /// G: Clone
344 /// {
345 /// fn divide(rng: &mut rand_chacha::ChaCha8Rng, cell: &mut MyAgent<G>) -> Result<MyAgent<G>, DivisionError> {
346 /// panic!("This should never be called")
347 /// }
348 ///
349 /// fn update_cycle(
350 /// rng: &mut rand_chacha::ChaCha8Rng,
351 /// dt: &f64,
352 /// cell: &mut MyAgent<G>,
353 /// ) -> Option<CycleEvent> {
354 /// panic!("This should never be called")
355 /// }
356 /// }
357 ///
358 /// #[derive(CellAgent)]
359 /// struct MyAgent<G>
360 /// (
361 /// #[Cycle]
362 /// MyCycle<G>
363 /// )
364 /// where
365 /// G: Clone;
366 /// ```
367 fn derive_cycle_generic_float_where_clause_unnamed() {}
368}