Skip to main content

glazier_core/
motility.rs

1//! Persistent motility: a cell that has just moved keeps moving.
2//!
3//! Every site remembers how recently it was taken, and a copy is priced
4//! against the difference in that memory between the two sites it runs
5//! between. A cell that extends in one direction therefore leaves a trail of
6//! recent sites behind its front, and extending further along the same
7//! direction is the cheaper move: the cell polarises and travels, where a
8//! plain Potts cell only jiggles.
9//!
10//! This is the Act model of Niculescu, Textor and de Boer. The neighbourhood
11//! average is a geometric mean, which is zero as soon as one neighbour of the
12//! cell has forgotten, so the memory acts as a front rather than as a haze.
13
14use crate::lattice::Lattice;
15
16/// One activity value per site.
17#[derive(Clone, Debug)]
18pub struct Activity {
19    /// Steps of memory remaining at each site.
20    pub values: Vec<f64>,
21}
22
23impl Activity {
24    /// A lattice that remembers nothing.
25    #[must_use]
26    pub fn new(sites: usize) -> Self {
27        Self {
28            values: vec![0.0; sites],
29        }
30    }
31
32    /// Whether any site remembers anything.
33    #[must_use]
34    pub fn is_quiet(&self) -> bool {
35        self.values.iter().all(|&v| v <= 0.0)
36    }
37
38    /// Take one step off every site's memory.
39    pub fn decay(&mut self) {
40        for value in &mut self.values {
41            if *value > 0.0 {
42                *value -= 1.0;
43            }
44        }
45    }
46
47    /// Give a site the full memory a type states.
48    pub fn refresh(&mut self, site: usize, max_activity: f64) {
49        self.values[site] = max_activity;
50    }
51
52    /// Geometric mean of the activity over the sites of `label` around
53    /// `site`, counting the site itself.
54    ///
55    /// Zero when any of them has forgotten, which is what makes the memory a
56    /// front. A site whose cell has no other site nearby reads its own value.
57    #[must_use]
58    pub fn neighbourhood_mean(&self, lattice: &Lattice, site: usize, label: u32) -> f64 {
59        let (x, y, z) = lattice.coords(site);
60        let mut log_sum = self.values[site].max(0.0).ln();
61        let mut count = 1.0f64;
62        for &(dx, dy, dz) in &lattice.offsets {
63            let n = lattice.index(x + dx, y + dy, z + dz);
64            if n == site || lattice.labels[n] != label {
65                continue;
66            }
67            let value = self.values[n];
68            if value <= 0.0 {
69                return 0.0;
70            }
71            log_sum += value.ln();
72            count += 1.0;
73        }
74        if !log_sum.is_finite() {
75            return 0.0;
76        }
77        (log_sum / count).exp()
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    fn two_cell_lattice() -> Lattice {
86        let mut lattice = Lattice::medium(8, 8, 1, 2);
87        for x in 0..4 {
88            for y in 0..8 {
89                let index = lattice.index(x, y, 0);
90                lattice.labels[index] = 1;
91            }
92        }
93        lattice
94    }
95
96    #[test]
97    fn decay_takes_one_step_and_stops_at_nothing() {
98        let mut activity = Activity::new(4);
99        activity.refresh(0, 2.0);
100        activity.decay();
101        assert_eq!(activity.values[0], 1.0);
102        activity.decay();
103        activity.decay();
104        assert_eq!(activity.values[0], 0.0);
105    }
106
107    #[test]
108    fn a_forgotten_neighbour_takes_the_mean_to_nothing() {
109        let lattice = two_cell_lattice();
110        let mut activity = Activity::new(lattice.len());
111        let site = lattice.index(1, 4, 0);
112        activity.refresh(site, 10.0);
113        assert_eq!(activity.neighbourhood_mean(&lattice, site, 1), 0.0);
114    }
115
116    #[test]
117    fn a_uniformly_remembered_cell_reads_its_own_value() {
118        let lattice = two_cell_lattice();
119        let mut activity = Activity::new(lattice.len());
120        for (site, &label) in lattice.labels.iter().enumerate() {
121            if label == 1 {
122                activity.refresh(site, 7.0);
123            }
124        }
125        let site = lattice.index(1, 4, 0);
126        assert!((activity.neighbourhood_mean(&lattice, site, 1) - 7.0).abs() < 1e-9);
127    }
128
129    #[test]
130    fn the_mean_sits_between_the_values_it_reads() {
131        let lattice = two_cell_lattice();
132        let mut activity = Activity::new(lattice.len());
133        for (site, &label) in lattice.labels.iter().enumerate() {
134            if label == 1 {
135                activity.refresh(site, 4.0);
136            }
137        }
138        let site = lattice.index(1, 4, 0);
139        activity.refresh(site, 16.0);
140        let mean = activity.neighbourhood_mean(&lattice, site, 1);
141        assert!(mean > 4.0 && mean < 16.0, "{mean}");
142    }
143
144    #[test]
145    fn a_quiet_lattice_says_so() {
146        let mut activity = Activity::new(4);
147        assert!(activity.is_quiet());
148        activity.refresh(2, 1.0);
149        assert!(!activity.is_quiet());
150    }
151}