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}