Building Blocks

Building Blocks

cellular_raza is designed such that researchers are flexible when it comes to designing new models for their respective use-cases. However it is often desirable to not start from scratch completely but rely on existing work and extend its behaviour. cellular_raza-building_blocks provide components or complete cellular agents to be used when constructing new behaviour. To reuse these existing components, we provide the CellAgent and SubDomain derive macros.

CellAgent

Let’s assume that we want to define a new cell type which uses the existing Brownian3D mechanics aspect. To define this new cell, we simply define a new struct which contains the existing mechanics. Before the struct keyword we add the #[derive(CellAgent)] macro and specify that we want to derive the #[Mechanics] aspect.

#[derive(CellAgent)]
struct MyCellType {
    #[Mechanics]
    mechanics: Brownian3D,
}

This macro will now generate code that implements the Position, Velocity and Mechanics traits. To get a glimpse of what is going on under the hood, we look at the generated code of the Position trait.

#[allow(non_upper_case_globals)]
const _: () = {
    #[automatically_derived]
    impl<__cr_private_Pos> Position<__cr_private_Pos> for MyCellType
    where
        Brownian3D: Position<__cr_private_Pos>,
    {
        #[inline]
        fn pos(&self) -> __cr_private_Pos {
            <Brownian3D as Position<__cr_private_Pos>>::pos(&self.mechanics)
        }
        #[inline]
        fn set_pos(&mut self, pos: &__cr_private_Pos) {
            <Brownian3D as Position<
                __cr_private_Pos,
            >>::set_pos(&mut self.mechanics, pos)
        }
    }
}

This is very convenient and we can combine multiple building blocks or specify our own new types. Many of the already existing examples such as the cell-sorting example uses this approach.

cellular_raza-examples/cell_sorting/src/main.rs
105
106
107
108
109
110
111
#[derive(CellAgent, Clone, Deserialize, Serialize)]
struct Cell {
    #[Interaction]
    interaction: CellSpecificInteraction,
    #[Mechanics]
    mechanics: NewtonDamped3D,
}

Domain & SubDomain

We also provide the Domain and SubDomain macros to derive functionality related to the simulation domain. Often it is only necessary to redefine the SortCells and SubDomainMechanics traits in order to reuse an existing domain for a new cell-agent type.

In particular, most effort is often spent on the DomainCreateSubDomains traits which decomposes the domain. Deriving these functionalities is thus greatly convenient.

These derive macros are both used is used in the bacterial-rods example.

cellular_raza-building-blocks/src/cell_building_blocks/bacterial_rods.rs
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/// Cells are represented by rods
#[derive(Domain)]
pub struct CartesianCuboidRods<F, const D: usize> {
    /// The base-cuboid which is being repurposed
    #[DomainRngSeed]
    pub domain: CartesianCuboid<F, D>,
    /// Gel pressure which is only relevant for 3D simulations and always acts with constant
    /// force downwards (negative z-direction).
    pub gel_pressure: F,
    /// Computes friction at all surfaces of the box
    pub surface_friction: F,
    /// The distance for which the friction will be applied
    pub surface_friction_distance: F,
}
cellular_raza-building-blocks/src/cell_building_blocks/bacterial_rods.rs
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
/// The corresponding SubDomain of the [CartesianCuboidRods] domain.
#[derive(Clone, SubDomain, Serialize, Deserialize)]
#[serde(bound = "
F: 'static
    + PartialEq
    + Clone
    + core::fmt::Debug
    + Serialize
    + for<'a> Deserialize<'a>,
[usize; D]: Serialize + for<'a> Deserialize<'a>,
")]
pub struct CartesianSubDomainRods<F, const D: usize> {
    /// Base subdomain as created by the [CartesianCuboid] domain.
    #[Base]
    pub subdomain: CartesianSubDomain<F, D>,
    /// See [CartesianCuboidRods]
    pub gel_pressure: F,
    /// Computes friction at all surfaces of the box
    pub surface_friction: F,
    /// The distance for which the friction will be applied
    pub surface_friction_distance: F,
}

Generics

All derive macros have designed with generics in mind and should work out-of-the box. If you find any bugs please let us know. For instance, the following example compiles and is part of our test-suite:

cellular_raza-concepts/tests/derive_cell_agent.rs
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
#[test]
fn derive_interaction_generics() {
    use cellular_raza_concepts::Interaction;
    use cellular_raza_concepts_derive::CellAgent;

    struct InteractionModel<const D: usize> {
        index: [usize; D],
    }

    impl<const D: usize> Interaction<f32, f32, f32, [usize; D]> for InteractionModel<D> {
        fn calculate_force_between(
            &self,
            _own_pos: &f32,
            _own_vel: &f32,
            _ext_pos: &f32,
            _ext_vel: &f32,
            _ext_info: &[usize; D],
        ) -> Result<(f32, f32), cellular_raza_concepts::CalcError> {
            Ok((0.0, 0.0))
        }
    }

    impl<const D: usize> InteractionInformation<[usize; D]> for InteractionModel<D> {
        fn get_interaction_information(&self) -> [usize; D] {
            self.index
        }
    }

    #[derive(CellAgent)]
    struct NewAgent<const D: usize> {
        #[Interaction]
        interaction: InteractionModel<D>,
    }

    let my_agent = NewAgent {
        interaction: InteractionModel { index: [1, 2, 3] },
    };
    assert_eq!(my_agent.get_interaction_information(), [1, 2, 3]);
}