1#[derive(Clone, Copy, Debug, Default, PartialEq)]
10pub struct CellType {
11 pub target_volume: f64,
13 pub lambda_volume: f64,
15 pub target_surface: f64,
17 pub lambda_surface: f64,
19 pub division_volume: f64,
21 pub death_rate: f64,
23 pub target_length: f64,
25 pub lambda_length: f64,
27 pub connected: bool,
29 pub max_activity: f64,
32 pub lambda_activity: f64,
34 pub external: [f64; 3],
38}
39
40#[derive(Clone, Debug)]
42pub struct Model {
43 pub species: Vec<crate::field::Species>,
45 pub exchange: Vec<crate::field::Exchange>,
47 pub chemotaxis: Vec<Vec<f64>>,
50 pub width: usize,
52 pub height: usize,
54 pub depth: usize,
57 pub contact: Vec<f64>,
59 pub types: Vec<CellType>,
61 pub temperature: f64,
63 pub neighbour_order: u8,
65 pub seed: u64,
67}
68
69impl Model {
70 #[must_use]
72 pub fn n_types(&self) -> usize {
73 self.types.len()
74 }
75
76 #[must_use]
78 pub fn dimensions(&self) -> usize {
79 if self.depth > 1 { 3 } else { 2 }
80 }
81
82 #[must_use]
84 pub fn has_motility(&self) -> bool {
85 self.types
86 .iter()
87 .any(|t| t.lambda_activity != 0.0 && t.max_activity > 0.0)
88 }
89
90 #[must_use]
92 pub fn has_external_potential(&self) -> bool {
93 self.types
94 .iter()
95 .any(|t| t.external.iter().any(|&v| v != 0.0))
96 }
97
98 #[must_use]
100 pub fn has_connectivity(&self) -> bool {
101 self.types.iter().any(|t| t.connected)
102 }
103
104 #[must_use]
107 pub fn has_length_constraint(&self) -> bool {
108 self.types.iter().any(|t| t.lambda_length != 0.0)
109 }
110
111 #[must_use]
113 pub fn has_chemotaxis(&self) -> bool {
114 self.chemotaxis
115 .iter()
116 .any(|row| row.iter().any(|&l| l != 0.0))
117 }
118
119 #[must_use]
122 pub fn has_population_events(&self) -> bool {
123 self.types
124 .iter()
125 .any(|t| t.division_volume > 0.0 || t.death_rate > 0.0)
126 }
127
128 #[must_use]
130 pub fn contact_energy(&self, a: u8, b: u8) -> f64 {
131 self.contact[a as usize * self.n_types() + b as usize]
132 }
133
134 pub fn validate(&self) -> Result<(), String> {
139 let n = self.n_types();
140 if n == 0 {
141 return Err("a model needs at least the medium type".into());
142 }
143 if self.contact.len() != n * n {
144 return Err(format!(
145 "contact matrix is {} entries for {n} types, want {}",
146 self.contact.len(),
147 n * n
148 ));
149 }
150 for a in 0..n {
151 for b in 0..n {
152 let (ab, ba) = (self.contact[a * n + b], self.contact[b * n + a]);
153 if (ab - ba).abs() > 1e-12 {
154 return Err(format!("contact matrix is asymmetric at ({a}, {b})"));
155 }
156 }
157 }
158 if self.width == 0 || self.height == 0 || self.depth == 0 {
159 return Err("the lattice has a zero dimension".into());
160 }
161 if !matches!(self.neighbour_order, 1..=3) {
162 return Err(format!(
163 "neighbour order {} is outside the range one to three",
164 self.neighbour_order
165 ));
166 }
167 if self.temperature <= 0.0 {
168 return Err("the temperature must be positive".into());
169 }
170 if !self.exchange.is_empty() && self.exchange.len() != n {
171 return Err(format!(
172 "exchange has {} entries for {n} types",
173 self.exchange.len()
174 ));
175 }
176 if !self.chemotaxis.is_empty() && self.chemotaxis.len() != n {
177 return Err(format!(
178 "chemotaxis has {} entries for {n} types",
179 self.chemotaxis.len()
180 ));
181 }
182 for (index, row) in self.chemotaxis.iter().enumerate() {
183 if !row.is_empty() && row.len() != self.species.len() {
184 return Err(format!(
185 "type {index} states {} chemotactic sensitivities for {} species",
186 row.len(),
187 self.species.len()
188 ));
189 }
190 }
191 for (index, spec) in self.exchange.iter().enumerate() {
192 for (what, list) in [("secretion", &spec.secretion), ("uptake", &spec.uptake)] {
193 if !list.is_empty() && list.len() != self.species.len() {
194 return Err(format!(
195 "type {index} states {} {what} rates for {} species",
196 list.len(),
197 self.species.len()
198 ));
199 }
200 }
201 }
202 Ok(())
203 }
204}
205
206impl Default for Model {
207 fn default() -> Self {
211 Self {
212 species: Vec::new(),
213 exchange: Vec::new(),
214 chemotaxis: Vec::new(),
215 width: 1,
216 height: 1,
217 depth: 1,
218 contact: vec![0.0],
219 types: vec![CellType::default()],
220 temperature: 10.0,
221 neighbour_order: 2,
222 seed: 0,
223 }
224 }
225}