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
103
104
105
106
107
108
109
#[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-examples/bacterial_rods/src/custom_domain.rs
 8
 9
10
11
12
13
14
#[derive(Clone, Domain)]
pub struct MyDomain<const D2: usize> {
    #[DomainRngSeed]
    pub cuboid: CartesianCuboid<f64, D2>,
    pub gravity: f64,
    pub damping: f64,
}
cellular_raza-examples/bacterial_rods/src/custom_domain.rs
57
58
59
60
61
62
63
#[derive(Clone, SubDomain, Serialize)]
pub struct MySubDomain<const D2: usize> {
    #[Base]
    pub subdomain: CartesianSubDomain<f64, D2>,
    #[Force]
    pub force: MySubDomainForce,
}

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
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
#[test]
fn derive_interaction_generics() {
    use cellular_raza_concepts::{CalcError, 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 get_interaction_information(&self) -> [usize; D] {
            self.index.clone()
        }
        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))
        }
    }

    #[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]);
}