Skip to main content

cantata/
fit.rs

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            // Parameter names must end in the mechanism name, eg K_mech, or be one
241            // of a series of reserved names. NB. We cannot split on the underscore
242            // as the mechanism name may contain underscores.
243            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            // Special treatment for pas/e= :(
299            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}