1use crate::field::{Exchange, Fields};
4use crate::lattice::Lattice;
5use crate::model::Model;
6use crate::moments::{Moments, Site};
7use crate::motility::Activity;
8use crate::rng::Xoshiro;
9
10fn count_moments(lattice: &Lattice, labels: usize) -> Vec<Moments> {
12 let mut moments = vec![Moments::default(); labels];
13 let extent = lattice_extent(lattice);
14 for index in 0..lattice.labels.len() {
15 let label = lattice.labels[index] as usize;
16 let (x, y, z) = lattice.coords(index);
17 let site = moments[label].unwrap(x as f64, y as f64, z as f64, extent);
18 moments[label].add(site);
19 }
20 moments
21}
22
23fn lattice_extent(lattice: &Lattice) -> (f64, f64, f64) {
25 (
26 lattice.width as f64,
27 lattice.height as f64,
28 lattice.depth as f64,
29 )
30}
31
32fn count_surface(lattice: &Lattice, labels: usize) -> Vec<i64> {
34 let mut surface = vec![0i64; labels];
35 for index in 0..lattice.labels.len() {
36 let (x, y, z) = lattice.coords(index);
37 let l = lattice.labels[index];
38 for &(dx, dy, dz) in &lattice.offsets {
39 let n = lattice.index(x + dx, y + dy, z + dz);
40 if n != index && lattice.labels[n] != l {
41 surface[l as usize] += 1;
42 }
43 }
44 }
45 surface
46}
47
48#[derive(Clone, Debug)]
51pub struct Simulation {
52 pub model: Model,
54 pub lattice: Lattice,
56 pub volume: Vec<u32>,
59 pub surface: Vec<i64>,
62 pub cell_type: Vec<u8>,
64 pub moments: Vec<Moments>,
66 pub activity: Activity,
68 pub fields: Fields,
70 pub exchange: Vec<Exchange>,
72 constrains_length: bool,
75 connectivity_ring: Vec<(i64, i64, i64)>,
78 rng: Xoshiro,
79 pub mcs: u64,
81 pub accepted: u64,
83 pub attempted: u64,
85}
86
87impl Simulation {
88 pub fn tiled(model: Model, side: usize) -> Result<Self, String> {
93 let (nx, ny, nz) = (
94 model.width / side,
95 model.height / side,
96 (model.depth / side).max(1),
97 );
98 Self::tiled_grid(model, side, nx, ny, nz)
99 }
100
101 pub fn tiled_grid(
106 model: Model,
107 side: usize,
108 nx: usize,
109 ny: usize,
110 nz: usize,
111 ) -> Result<Self, String> {
112 model.validate()?;
113 if model.n_types() < 2 {
114 return Err("a tiled start needs a cell type beside the medium".into());
115 }
116 let mut lattice = Lattice::medium(
117 model.width,
118 model.height,
119 model.depth,
120 model.neighbour_order,
121 );
122 let n = lattice.tile_grid(side, nx, ny, nz) as usize;
123 let mut volume = vec![0u32; n + 1];
124 for &label in &lattice.labels {
125 volume[label as usize] += 1;
126 }
127 let surface = count_surface(&lattice, n + 1);
128 let moments = count_moments(&lattice, n + 1);
129 let sites = lattice.labels.len();
130 let species = model.species.clone();
131 let dimensions = model.dimensions();
132 let constrains_length = model.has_length_constraint();
133 let connectivity_ring = if model.has_connectivity() {
134 crate::connectivity::ring(model.depth)
135 } else {
136 Vec::new()
137 };
138 let exchange = model.exchange.clone();
139 let seed = model.seed;
140 Ok(Self {
141 model,
142 lattice,
143 volume,
144 surface,
145 cell_type: vec![1; n + 1],
146 moments,
147 activity: Activity::new(sites),
148 fields: Fields::new(species, sites, dimensions),
149 exchange,
150 constrains_length,
151 connectivity_ring,
152 rng: Xoshiro::seed(seed),
153 mcs: 0,
154 accepted: 0,
155 attempted: 0,
156 })
157 }
158
159 pub fn set_cell_types(&mut self, types: &[u8]) {
161 for (label, &kind) in types.iter().enumerate() {
162 self.cell_type[label + 1] = kind;
163 }
164 }
165
166 #[must_use]
168 pub fn n_cells(&self) -> usize {
169 self.volume.len() - 1
170 }
171
172 fn type_of(&self, label: u32) -> u8 {
173 self.cell_type[label as usize]
174 }
175
176 #[must_use]
182 pub fn delta_energy(&self, target: usize, new: u32) -> f64 {
183 let old = self.lattice.labels[target];
184 if old == new {
185 return 0.0;
186 }
187 let (tx, ty, tz) = self.lattice.coords(target);
188 let (told, tnew) = (self.type_of(old), self.type_of(new));
189
190 let mut delta = 0.0;
191 let mut like_old = 0i64;
192 let mut like_new = 0i64;
193 let mut bonds = 0i64;
194 for &(dx, dy, dz) in &self.lattice.offsets {
195 let n = self.lattice.index(tx + dx, ty + dy, tz + dz);
196 if n == target {
197 continue;
198 }
199 bonds += 1;
200 let ln = self.lattice.labels[n];
201 let tn = self.type_of(ln);
202 if ln != old {
203 delta -= self.model.contact_energy(told, tn);
204 } else {
205 like_old += 1;
206 }
207 if ln != new {
208 delta += self.model.contact_energy(tnew, tn);
209 } else {
210 like_new += 1;
211 }
212 }
213
214 delta += self.volume_term(old, -1);
215 delta += self.volume_term(new, 1);
216 if self.constrains_length {
217 let site = (tx as f64, ty as f64, tz as f64);
218 delta += self.length_term(old, site, -1.0);
219 delta += self.length_term(new, site, 1.0);
220 }
221 delta += self.surface_term(old, 2 * like_old - bonds);
222 delta += self.surface_term(new, bonds - 2 * like_new);
223 delta
224 }
225
226 fn length_term(&self, label: u32, site: (f64, f64, f64), sign: f64) -> f64 {
228 if label == 0 {
229 return 0.0;
230 }
231 let spec = self.model.types[self.type_of(label) as usize];
232 if spec.lambda_length == 0.0 {
233 return 0.0;
234 }
235 let moments = self.moments[label as usize];
236 let unwrapped = moments.unwrap(site.0, site.1, site.2, lattice_extent(&self.lattice));
237 let before = moments.length();
238 let after = moments.with(unwrapped, sign).length();
239 spec.lambda_length
240 * ((after - spec.target_length).powi(2) - (before - spec.target_length).powi(2))
241 }
242
243 #[must_use]
250 pub fn move_work(&self, new: u32, old: u32, target: usize, source: usize) -> f64 {
251 let mut work = 0.0;
252
253 if new != 0 {
254 let spec = self.model.types[self.type_of(new) as usize];
255 if spec.lambda_activity != 0.0 && spec.max_activity > 0.0 {
256 let into = self.activity.neighbourhood_mean(&self.lattice, source, new);
257 let out_of = self.activity.neighbourhood_mean(&self.lattice, target, old);
258 work -= spec.lambda_activity / spec.max_activity * (into - out_of);
259 }
260 if spec.external.iter().any(|&v| v != 0.0) {
261 let (tx, ty, tz) = self.lattice.coords(target);
262 let (sx, sy, sz) = self.lattice.coords(source);
263 let (w, h, d) = lattice_extent(&self.lattice);
264 let step = |a: i64, b: i64, span: f64| {
265 let raw = (a - b) as f64;
266 if raw > span / 2.0 {
267 raw - span
268 } else if raw < -span / 2.0 {
269 raw + span
270 } else {
271 raw
272 }
273 };
274 work -= spec.external[0] * step(tx, sx, w)
275 + spec.external[1] * step(ty, sy, h)
276 + spec.external[2] * step(tz, sz, d);
277 }
278 }
279 work
280 }
281
282 #[must_use]
290 pub fn chemotaxis_work(&self, new: u32, target: usize, source: usize) -> f64 {
291 if new == 0 || self.model.chemotaxis.is_empty() {
292 return 0.0;
293 }
294 let row = &self.model.chemotaxis[self.type_of(new) as usize];
295 let mut work = 0.0;
296 for (index, &lambda) in row.iter().enumerate() {
297 if lambda == 0.0 {
298 continue;
299 }
300 let values = &self.fields.values[index];
301 work -= lambda * (values[target] - values[source]);
302 }
303 work
304 }
305
306 fn surface_term(&self, label: u32, change: i64) -> f64 {
309 if label == 0 || change == 0 {
310 return 0.0;
311 }
312 let spec = self.model.types[self.type_of(label) as usize];
313 if spec.lambda_surface == 0.0 {
314 return 0.0;
315 }
316 let s = self.surface[label as usize] as f64;
317 let after = s + change as f64;
318 spec.lambda_surface
319 * ((after - spec.target_surface).powi(2) - (s - spec.target_surface).powi(2))
320 }
321
322 fn volume_term(&self, label: u32, change: i64) -> f64 {
323 if label == 0 {
324 return 0.0;
325 }
326 let spec = self.model.types[self.type_of(label) as usize];
327 if spec.lambda_volume == 0.0 {
328 return 0.0;
329 }
330 let v = f64::from(self.volume[label as usize]);
331 let after = v + change as f64;
332 spec.lambda_volume
333 * ((after - spec.target_volume).powi(2) - (v - spec.target_volume).powi(2))
334 }
335
336 pub fn attempt(&mut self) -> bool {
342 let sites = self.lattice.labels.len() as u64;
343 let target = self.rng.below(sites) as usize;
344 let pick = self.rng.below(self.lattice.offsets.len() as u64) as usize;
345 let (tx, ty, tz) = self.lattice.coords(target);
346 let (dx, dy, dz) = self.lattice.offsets[pick];
347 let source = self.lattice.index(tx + dx, ty + dy, tz + dz);
348
349 let old = self.lattice.labels[target];
350 let new = self.lattice.labels[source];
351 self.attempted += 1;
352 if old == new {
353 return false;
354 }
355 if old != 0 && self.volume[old as usize] <= 1 {
356 return false;
357 }
358 if old != 0
359 && !self.connectivity_ring.is_empty()
360 && self.model.types[self.type_of(old) as usize].connected
361 && !crate::connectivity::locally_connected(
362 &self.lattice,
363 &self.connectivity_ring,
364 target,
365 old,
366 )
367 {
368 return false;
369 }
370
371 let delta = self.delta_energy(target, new)
372 + self.chemotaxis_work(new, target, source)
373 + self.move_work(new, old, target, source);
374 let accept = delta <= 0.0 || self.rng.next_f64() < (-delta / self.model.temperature).exp();
375 if accept {
376 let (tx, ty, tz) = self.lattice.coords(target);
377 let mut like_old = 0i64;
378 let mut like_new = 0i64;
379 let mut bonds = 0i64;
380 for &(dx, dy, dz) in &self.lattice.offsets {
381 let n = self.lattice.index(tx + dx, ty + dy, tz + dz);
382 if n == target {
383 continue;
384 }
385 bonds += 1;
386 let ln = self.lattice.labels[n];
387 if ln == old {
388 like_old += 1;
389 }
390 if ln == new {
391 like_new += 1;
392 }
393 }
394 self.surface[old as usize] += 2 * like_old - bonds;
395 self.surface[new as usize] += bonds - 2 * like_new;
396
397 if self.constrains_length {
398 let site = (tx as f64, ty as f64, tz as f64);
399 let extent = lattice_extent(&self.lattice);
400 let leaving = self.moments[old as usize].unwrap(site.0, site.1, site.2, extent);
401 self.moments[old as usize].remove(leaving);
402 let joining = self.moments[new as usize].unwrap(site.0, site.1, site.2, extent);
403 self.moments[new as usize].add(joining);
404 }
405
406 if new != 0 {
407 let spec = self.model.types[self.type_of(new) as usize];
408 if spec.max_activity > 0.0 {
409 self.activity.refresh(target, spec.max_activity);
410 }
411 }
412
413 self.lattice.labels[target] = new;
414 self.volume[old as usize] -= 1;
415 self.volume[new as usize] += 1;
416 self.accepted += 1;
417 }
418 accept
419 }
420
421 pub fn step(&mut self) {
425 for _ in 0..self.lattice.labels.len() {
426 self.attempt();
427 }
428 if !self.fields.is_empty() {
429 self.fields.diffuse(&self.lattice);
430 self.fields
431 .exchange(&self.lattice, &self.cell_type, &self.exchange);
432 }
433 if self.model.has_motility() {
434 self.activity.decay();
435 }
436 if self.model.has_population_events() {
437 self.divide_and_die();
438 }
439 self.mcs += 1;
440 }
441
442 #[must_use]
447 pub fn energy(&self) -> f64 {
448 let mut contact = 0.0;
449 for index in 0..self.lattice.labels.len() {
450 let (x, y, z) = self.lattice.coords(index);
451 let l = self.lattice.labels[index];
452 for &(dx, dy, dz) in &self.lattice.offsets {
453 let n = self.lattice.index(x + dx, y + dy, z + dz);
454 let ln = self.lattice.labels[n];
455 if ln != l {
456 contact += self.model.contact_energy(self.type_of(l), self.type_of(ln));
457 }
458 }
459 }
460 contact /= 2.0;
461
462 let mut volume = 0.0;
463 let mut surface = 0.0;
464 let mut length = 0.0;
465 let counted = self.recounted_surfaces();
466 for (label, &bonds) in counted.iter().enumerate().skip(1) {
467 let spec = self.model.types[self.cell_type[label] as usize];
468 volume +=
469 spec.lambda_volume * (f64::from(self.volume[label]) - spec.target_volume).powi(2);
470 if spec.lambda_length != 0.0 {
471 length += spec.lambda_length
472 * (self.moments[label].length() - spec.target_length).powi(2);
473 }
474 surface += spec.lambda_surface * (bonds as f64 - spec.target_surface).powi(2);
475 }
476 contact + volume + surface + length
477 }
478
479 #[must_use]
481 pub fn recounted_moments(&self) -> Vec<Moments> {
482 count_moments(&self.lattice, self.moments.len())
483 }
484
485 #[must_use]
487 pub fn recounted_surfaces(&self) -> Vec<i64> {
488 count_surface(&self.lattice, self.surface.len())
489 }
490
491 #[must_use]
493 pub fn recounted_volumes(&self) -> Vec<u32> {
494 let mut v = vec![0u32; self.volume.len()];
495 for &label in &self.lattice.labels {
496 v[label as usize] += 1;
497 }
498 v
499 }
500}
501
502impl Simulation {
503 pub fn divide_and_die(&mut self) -> (usize, usize) {
511 let mut divided = 0usize;
512 let mut died = 0usize;
513
514 let live: Vec<u32> = (1..self.volume.len() as u32)
515 .filter(|&label| self.volume[label as usize] > 0)
516 .collect();
517
518 for label in live {
519 let spec = self.model.types[self.type_of(label) as usize];
520 if spec.division_volume > 0.0
521 && f64::from(self.volume[label as usize]) >= spec.division_volume
522 && self.divide(label)
523 {
524 divided += 1;
525 }
526 }
527
528 let live: Vec<u32> = (1..self.volume.len() as u32)
529 .filter(|&label| self.volume[label as usize] > 0)
530 .collect();
531 for label in live {
532 let spec = self.model.types[self.type_of(label) as usize];
533 if spec.death_rate > 0.0 && self.rng.next_f64() < spec.death_rate {
534 self.kill(label);
535 died += 1;
536 }
537 }
538
539 if divided > 0 || died > 0 {
540 self.surface = self.recounted_surfaces();
541 self.moments = self.recounted_moments();
542 }
543 (divided, died)
544 }
545
546 fn unwrapped_sites(&self, label: u32) -> (Vec<(usize, Site)>, Moments) {
549 let extent = lattice_extent(&self.lattice);
550 let mut moments = Moments::default();
551 let mut out: Vec<(usize, Site)> = Vec::new();
552 for (site, &l) in self.lattice.labels.iter().enumerate() {
553 if l != label {
554 continue;
555 }
556 let (x, y, z) = self.lattice.coords(site);
557 let unwrapped = moments.unwrap(x as f64, y as f64, z as f64, extent);
558 moments.add(unwrapped);
559 out.push((site, unwrapped));
560 }
561 (out, moments)
562 }
563
564 fn divide(&mut self, label: u32) -> bool {
569 let (sites, moments) = self.unwrapped_sites(label);
570 if sites.len() < 4 {
571 return false;
572 }
573
574 let centre = moments.centroid();
575 let (axis, _) = moments.principal();
576
577 let daughter = self.volume.len() as u32;
578 let mut moved = 0u32;
579 for &(site, (x, y, z)) in &sites {
580 let along = (x - centre.0) * axis.0 + (y - centre.1) * axis.1 + (z - centre.2) * axis.2;
581 if along > 0.0 {
582 self.lattice.labels[site] = daughter;
583 moved += 1;
584 }
585 }
586 if moved == 0 || moved as usize == sites.len() {
587 for &(site, _) in &sites {
589 self.lattice.labels[site] = label;
590 }
591 return false;
592 }
593
594 self.volume[label as usize] -= moved;
595 self.volume.push(moved);
596 self.surface.push(0);
597 self.moments.push(Moments::default());
598 self.cell_type.push(self.type_of(label));
599 true
600 }
601
602 fn kill(&mut self, label: u32) {
604 for site in 0..self.lattice.labels.len() {
605 if self.lattice.labels[site] == label {
606 self.lattice.labels[site] = 0;
607 }
608 }
609 let gone = self.volume[label as usize];
610 self.volume[label as usize] = 0;
611 self.volume[0] += gone;
612 self.surface[label as usize] = 0;
613 }
614
615 #[must_use]
617 pub fn live_cells(&self) -> usize {
618 self.volume[1..].iter().filter(|&&v| v > 0).count()
619 }
620}