1use anyhow::bail;
2use serde::{de, Deserialize, Deserializer, Serialize};
3use serde_json::Value;
4
5use crate::{
6 err::{Context, Result},
7 Map,
8};
9
10use std::{fs::File, path::PathBuf};
11
12fn de_f64_or_string_as_f64<'de, D: Deserializer<'de>>(deserializer: D) -> Result<f64, D::Error> {
13 Ok(match Value::deserialize(deserializer)? {
14 Value::String(s) => s.parse().map_err(de::Error::custom)?,
15 Value::Number(num) => num
16 .as_f64()
17 .ok_or_else(|| de::Error::custom("Invalid number"))?,
18 _ => return Err(de::Error::custom("wrong type")),
19 })
20}
21
22#[derive(Debug, Serialize, Deserialize, Clone)]
23#[serde(untagged)]
24pub enum Attribute {
25 String(String),
26 Float(f64),
27}
28
29#[derive(Debug, Deserialize, Serialize)]
30pub struct Conditions {
31 pub celsius: Option<f64>,
32 pub erev: Vec<RevPot>,
33}
34
35#[derive(Debug, Deserialize, Serialize)]
36pub struct RevPot {
37 pub section: String,
38 #[serde(flatten)]
39 pub values: Map<String, f64>,
40}
41
42#[derive(Debug, Deserialize, Serialize)]
43#[serde(deny_unknown_fields)]
44pub struct Section {
45 pub section: String,
46 #[serde(deserialize_with = "de_f64_or_string_as_f64")]
47 pub value: f64,
48 pub mechanism: String,
49 pub name: String,
50}
51
52#[derive(Debug, Deserialize, Serialize)]
53#[serde(deny_unknown_fields)]
54pub struct Cm {
55 pub section: String,
56 pub cm: f64,
57}
58
59#[derive(Debug, Deserialize, Serialize)]
60#[serde(deny_unknown_fields)]
61pub struct Passive {
62 pub ra: Option<f64>,
63 pub e_pas: Option<f64>,
64 #[serde(default)]
65 pub cm: Vec<Cm>,
66}
67
68#[derive(Debug, Deserialize, Serialize)]
69pub struct Fitting {}
70
71#[derive(Debug, Deserialize, Serialize)]
72pub struct AxonMorph {}
73
74#[derive(Debug, Deserialize, Serialize)]
75#[serde(deny_unknown_fields)]
76pub struct Fit {
77 pub conditions: Vec<Conditions>,
78 pub genome: Vec<Section>,
79 pub passive: Vec<Passive>,
80 #[serde(default)]
81 pub fitting: Vec<Fitting>,
82 #[serde(default)]
83 pub axon_morph: Vec<AxonMorph>,
84}
85
86#[derive(Debug)]
87pub struct Mechanism {
88 pub name: String,
89 pub parameters: Map<String, f64>,
90 pub globals: Map<String, f64>,
91}
92
93impl Mechanism {
94 fn new(name: &str) -> Self {
95 Self {
96 name: name.to_string(),
97 parameters: Map::new(),
98 globals: Map::new(),
99 }
100 }
101}
102
103#[derive(Debug, Default)]
104pub struct Parameter {
105 pub cm: Option<f64>,
106 pub ra: Option<f64>,
107 pub tk: Option<f64>,
108}
109
110#[derive(Debug)]
111pub struct MechanismData {
112 pub name: String,
113 pub parameters: Map<String, f64>,
114 pub globals: Map<String, f64>,
115}
116
117#[derive(Debug)]
118pub struct Decor {
119 pub mechanisms: Vec<(String, MechanismData)>,
120 pub parameters: Vec<(String, Parameter)>,
121 pub erev: Vec<(String, String, f64)>,
122 pub defaults: Parameter,
123}
124
125impl Decor {
126 pub fn to_acc(&self) -> Result<String> {
127 let mut acc = String::new();
128 acc.push_str(
129 "(arbor-component
130 (meta-data
131 (version \"0.9-dev\"))
132 (decor",
133 );
134 if let Some(v) = self.defaults.cm {
135 acc.push_str(&format!(
136 "\n (default
137 (membrane-capacitance {} (scalar 1.0)))",
138 0.01 * v
139 ));
140 }
141 if let Some(v) = self.defaults.ra {
142 acc.push_str(&format!(
143 "\n (default
144 (axial-resistivity {} (scalar 1.0)))",
145 v
146 ));
147 }
148 if let Some(v) = self.defaults.tk {
149 acc.push_str(&format!(
150 "\n (default
151 (temperature-kelvin {} (scalar 1.0)))",
152 v
153 ));
154 }
155
156 for (k, v) in &self.parameters {
157 if let Some(v) = v.cm {
158 acc.push_str(&format!(
159 "\n (paint (region \"{}\")
160 (membrane-capacitance {} (scalar 1.0)))",
161 k,
162 0.01 * v
163 ));
164 }
165 if let Some(v) = v.ra {
166 acc.push_str(&format!(
167 "\n (paint (region \"{}\")
168 (axial-resistivity {} (scalar 1.0)))",
169 k, v
170 ));
171 }
172 if let Some(v) = v.tk {
173 acc.push_str(&format!(
174 "\n (paint (region \"{}\")
175 (temperature-kelvin {} (scalar 1.0)))",
176 k, v
177 ));
178 }
179 }
180 for (reg, ion, erev) in &self.erev {
181 acc.push_str(&format!(
182 "\n (paint (region \"{}\")
183 (ion-reversal-potential \"{}\" {} (scalar 1.0)))",
184 reg, ion, erev
185 ));
186 }
187 for (
188 reg,
189 MechanismData {
190 name,
191 parameters,
192 globals,
193 },
194 ) in self.mechanisms.iter()
195 {
196 let mut mech = name.to_string();
197 let mut sep = '/';
198 for (k, v) in globals {
199 mech = format!("{}{}{}={}", mech, sep, k, v);
200 sep = ',';
201 }
202 acc.push_str(&format!(
203 "\n (paint (region \"{}\")
204 (density
205 (mechanism \"{}\"",
206 reg, mech
207 ));
208 for (k, v) in parameters {
209 acc.push_str(&format!("\n (\"{}\" {})", k, v));
210 }
211 acc.push_str(")))");
212 }
213 acc.push_str("))\n");
214 Ok(acc)
215 }
216}
217
218impl Fit {
219 pub fn from_file(path: &PathBuf) -> Result<Self> {
220 let rd = File::open(path).with_context(|| format!("Opening {path:?}"))?;
221 let fit =
222 serde_json::de::from_reader(rd).with_context(|| format!("Parsing fit {path:?}"))?;
223 Ok(fit)
224 }
225
226 pub fn decor(&self) -> Result<Decor> {
227 let mut mec = Map::new();
228 let mut par: Map<String, Parameter> = Map::new();
229 let mut def = Parameter::default();
230 let mut erev = Vec::new();
231
232 for section in &self.genome {
233 let mut mech = section.mechanism.to_string();
234 if mech.is_empty() {
235 mech.push_str("pas");
236 }
237
238 let region = section.section.to_string();
239
240 if let Some(param) = section.name.strip_suffix(&format!("_{}", mech)) {
244 mec.entry(region.to_string())
245 .or_insert_with(Map::new)
246 .entry(mech.to_string())
247 .or_insert_with(|| Mechanism::new(&mech))
248 .parameters
249 .insert(param.to_string(), section.value);
250 } else if mech == "pas" {
251 let v = par.entry(section.section.to_string()).or_default();
252 match section.name.as_ref() {
253 "cm" | "Cm" => v.cm = Some(section.value),
254 "ra" | "Ra" => v.ra = Some(section.value),
255
256 x => bail!("Unexpected key {x}"),
257 }
258 } else {
259 bail!("Section: parameter must end in mechanism name, or be empty *and* the name key must be one of cm, ra. Found mech={} and name={}",
260 mech, section.name);
261 }
262 }
263
264 for passive in &self.passive {
265 for cm in &passive.cm {
266 let sec = cm.section.to_string();
267 par.entry(sec).or_default().cm = Some(cm.cm);
268 }
269 if let Some(ra) = passive.ra {
270 def.ra = Some(ra);
271 }
272
273 if let Some(e_pas) = passive.e_pas {
274 for mechs in mec.values_mut() {
275 mechs
276 .entry("pas".to_string())
277 .or_insert_with(|| Mechanism::new("pas"))
278 .globals
279 .insert("e".to_string(), e_pas);
280 }
281 }
282 }
283
284 for cond in &self.conditions {
285 if let Some(celsius) = cond.celsius {
286 def.tk = Some(celsius + 273.15);
287 }
288 for kvs in &cond.erev {
289 let sec = kvs.section.to_string();
290 for (key, value) in &kvs.values {
291 erev.push((sec.clone(), key.chars().skip(1).collect(), *value));
292 }
293 }
294 }
295
296 let mut mechanisms = Vec::new();
297 for (reg, mut mechs) in mec.into_iter() {
298 if let Some(pas) = mechs.get_mut("pas") {
300 if pas.parameters.contains_key("e") {
301 pas.globals.insert("e".to_string(), pas.parameters["e"]);
302 pas.parameters.remove("e");
303 }
304 }
305
306 for (mech, data) in mechs {
307 mechanisms.push((
308 reg.clone(),
309 MechanismData {
310 name: mech.clone(),
311 parameters: data.parameters.clone(),
312 globals: data.globals.clone(),
313 },
314 ));
315 }
316 }
317
318 Ok(Decor {
319 mechanisms,
320 parameters: par.into_iter().collect(),
321 erev,
322 defaults: def,
323 })
324 }
325}