use std::{
fs::File,
path::{Path, PathBuf},
str::FromStr,
};
use crate::{
err::{anyhow, Context, Result},
sup::{resolve_manifest, Components, Manifest},
Map,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
fn yes() -> bool {
true
}
fn no() -> bool {
false
}
fn zero() -> f64 {
0.0
}
#[derive(Debug, Deserialize, Serialize, Default)]
#[serde(untagged, deny_unknown_fields)]
enum MaybeVec<T> {
#[default]
Nil,
One(T),
Many(Vec<T>),
}
impl<T> From<MaybeVec<T>> for Vec<T> {
fn from(value: MaybeVec<T>) -> Self {
match value {
MaybeVec::Nil => Vec::new(),
MaybeVec::One(v) => vec![v],
MaybeVec::Many(vs) => vs,
}
}
}
fn maybe_vec<'a, S: serde::Deserializer<'a>, T: Deserialize<'a>>(
ser: S,
) -> Result<Vec<T>, S::Error> {
let v = MaybeVec::<T>::deserialize(ser)?;
Ok(v.into())
}
#[derive(Debug, Deserialize, Serialize, Default)]
#[serde(deny_unknown_fields)]
pub enum TimeBlock {
#[default]
None,
#[serde(rename = "nsteps_block")]
Steps(u64),
#[serde(rename = "tsteps_block")]
Time(f64),
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Run {
#[serde(default = "zero")]
pub tstart: f64,
pub tstop: f64,
pub dt: f64,
#[serde(rename = "dL")]
pub dl: Option<f64>,
pub spike_threshold: Option<f64>,
#[serde(default)]
pub block: TimeBlock,
pub random_seed: Option<u64>,
}
#[derive(Debug, Deserialize, Serialize, Default)]
#[serde(deny_unknown_fields, untagged)]
pub enum Conditions {
#[default]
None,
Detailled { celsius: f64, v_init: f64 },
LGN {
jitter_lower: f64,
jitter_upper: f64,
},
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Waveform {
shape: String,
del: f64,
amp: f64,
dur: f64,
freq: f64,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum NodeSet {
Name(String),
Basic {
population: Option<String>,
#[serde(flatten)]
rules: Map<String, Value>,
},
Ids(Vec<u64>),
Compound(Vec<NodeSet>),
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "input_type")]
pub enum Input {
#[serde(rename = "current_clamp")]
CurrentClamp {
module: String,
#[serde(deserialize_with = "maybe_vec")]
amp: Vec<f64>,
#[serde(deserialize_with = "maybe_vec")]
delay: Vec<f64>,
#[serde(deserialize_with = "maybe_vec")]
duration: Vec<f64>,
node_set: Option<NodeSet>,
#[serde(default = "yes")]
enabled: bool,
},
#[serde(rename = "spikes")]
Spikes {
module: String,
#[serde(deserialize_with = "maybe_vec")]
input_file: Vec<String>,
node_set: NodeSet,
},
#[serde(rename = "lfp")]
LFP {
node_set: NodeSet,
module: String,
positions_file: String,
mesh_files_dir: String,
resistance: f64,
waveform: Waveform,
},
#[serde(rename = "csv")]
CSV {
module: String,
node_set: Option<NodeSet>,
rates: Option<String>,
file: Option<String>,
},
#[serde(rename = "nwb")]
NWB {
module: String,
node_set: NodeSet,
file: String,
sweep_id: u64,
downsample: f64,
},
#[serde(rename = "syn_activity")]
SynActivity {
module: String,
precell_filter: Map<String, Value>,
#[serde(deserialize_with = "maybe_vec")]
timestamps: Vec<f64>,
node_set: NodeSet,
},
#[serde(rename = "movie")]
Movie { module: String },
}
impl Input {
fn resolve_manifest(&mut self, manifest: &Manifest, base: &Path) -> Result<()> {
match self {
Input::Spikes { input_file, .. } => input_file
.iter_mut()
.try_for_each(|i| resolve_manifest(i, manifest, base)),
Input::NWB { file, .. } => resolve_manifest(file, manifest, base),
Input::LFP {
positions_file,
mesh_files_dir,
..
} => {
resolve_manifest(positions_file, manifest, base)?;
resolve_manifest(mesh_files_dir, manifest, base)
}
Input::CSV {
file: Some(file), ..
} => resolve_manifest(file, manifest, base),
_ => Ok(()),
}
}
}
pub type Inputs = Map<String, Input>;
#[derive(Debug, Deserialize, Serialize, Default)]
pub enum SpikeSortOrder {
#[default]
None,
#[serde(rename = "time")]
Time,
#[serde(rename = "id")]
Id,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Output {
log_file: String,
output_dir: String,
spikes_file: Option<String>,
spikes_file_csv: Option<String>,
#[serde(default)]
spikes_sort_order: SpikeSortOrder,
#[serde(default = "no")]
overwrite_output_dir: bool,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Nodes {
#[serde(rename = "nodes_file")]
pub nodes: String,
#[serde(rename = "node_types_file")]
pub types: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Edges {
#[serde(rename = "edges_file")]
pub edges: String,
#[serde(rename = "edge_types_file")]
pub types: String,
}
impl Edges {
fn resolve_manifest(&mut self, manifest: &Manifest, base: &Path) -> Result<()> {
resolve_manifest(&mut self.edges, manifest, base)?;
resolve_manifest(&mut self.types, manifest, base)
}
}
impl Nodes {
fn resolve_manifest(&mut self, manifest: &Manifest, base: &Path) -> Result<()> {
resolve_manifest(&mut self.nodes, manifest, base)?;
resolve_manifest(&mut self.types, manifest, base)
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Network {
pub nodes: Vec<Nodes>,
#[serde(default)]
pub edges: Vec<Edges>,
}
impl Network {
fn resolve_manifest(&mut self, manifest: &Manifest, base: &Path) -> Result<()> {
self.nodes
.iter_mut()
.try_for_each(|n| n.resolve_manifest(manifest, base))?;
self.edges
.iter_mut()
.try_for_each(|n| n.resolve_manifest(manifest, base))
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct NetworkFile {
#[serde(default)]
manifest: Manifest,
#[serde(default)]
components: Components,
#[serde(alias = "networks")]
pub network: Network,
}
#[derive(Debug, Deserialize, Serialize, Default)]
#[serde(untagged)]
pub enum NetworkOrFile {
#[default]
Empty,
File(String),
Inline(Network),
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Report {
pub cells: NodeSet,
pub variable_name: String,
pub module: String,
#[serde(default, deserialize_with = "maybe_vec")]
pub sections: Vec<String>,
pub start_time: Option<f64>,
pub end_time: Option<f64>,
pub dt: Option<f64>,
pub unit: Option<String>,
pub file_name: Option<String>,
#[serde(default = "yes")]
pub enabled: bool,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct SimulationRaw {
#[serde(default)]
manifest: Manifest,
run: Run,
target_simulator: String,
#[serde(default)]
conditions: Conditions,
inputs: Inputs,
output: Output,
#[serde(alias = "networks", default)]
network: NetworkOrFile,
#[serde(default)]
components: Components,
#[serde(default)]
reports: Map<String, Report>,
#[serde(default)]
node_sets: Map<String, NodeSet>,
node_sets_file: Option<String>,
}
#[derive(Debug)]
pub struct Simulation {
pub run: Run,
pub target_simulator: String,
pub conditions: Conditions,
pub inputs: Inputs,
pub output: Output,
pub network: Network,
pub components: Components,
pub reports: Map<String, Report>,
pub node_sets: Map<String, NodeSet>,
}
impl Simulation {
pub fn from_file(path: &str) -> Result<Self> {
let path = PathBuf::from_str(path)
.map_err(anyhow::Error::from)
.and_then(|p| p.canonicalize().map_err(anyhow::Error::from))
.with_context(|| format!("Resolving simulation path {path:?}"))?;
let base_dir = path
.parent()
.ok_or_else(|| anyhow!("Couldn't find parent of {path:?}."))?;
let rd = File::open(&path).with_context(|| format!("Opening {path:?}"))?;
let mut raw: SimulationRaw = serde_json::de::from_reader(rd)
.with_context(|| format!("Parsing simulation {path:?}"))?;
raw.manifest.insert(
"$configdir".to_string(),
base_dir
.to_str()
.expect("Couldn't convert path to str")
.into(),
);
raw.components
.iter_mut()
.try_for_each(|(_, it)| resolve_manifest(it, &raw.manifest, base_dir))?;
raw.components
.insert("base_dir".into(), base_dir.to_str().unwrap().into());
let mut network = match raw.network {
NetworkOrFile::Empty => return Err(anyhow!("No network defined!")),
NetworkOrFile::File(file) => {
let mut path: String = base_dir.join(file).to_str().unwrap().into();
resolve_manifest(&mut path, &raw.manifest, base_dir)?;
let rd = File::open(&path).with_context(|| format!("Opening {path:?}"))?;
let mut net: NetworkFile = serde_json::de::from_reader(rd)
.with_context(|| format!("Parsing network {path:?}"))?;
net.manifest.insert(
"$configdir".to_string(),
base_dir
.to_str()
.expect("Couldn't convert path to str")
.into(),
);
net.components
.iter_mut()
.try_for_each(|(_, it)| resolve_manifest(it, &net.manifest, base_dir))?;
raw.components.append(&mut net.components);
net.network.resolve_manifest(&net.manifest, base_dir)?;
net.network
}
NetworkOrFile::Inline(net) => net,
};
network.resolve_manifest(&raw.manifest, base_dir)?;
let node_sets = {
if let Some(file) = raw.node_sets_file {
let mut path: String = base_dir.join(file).to_str().unwrap().into();
resolve_manifest(&mut path, &raw.manifest, base_dir)?;
let rd = File::open(&path).with_context(|| format!("Opening {path:?}"))?;
let nds: Map<String, NodeSet> = serde_json::de::from_reader(rd)
.with_context(|| format!("Parsing nodesets {path:?}"))?;
raw.node_sets.extend(nds);
}
raw.node_sets
};
raw.inputs
.values_mut()
.try_for_each(|i| i.resolve_manifest(&raw.manifest, base_dir))?;
Ok(Simulation {
run: raw.run,
target_simulator: raw.target_simulator,
conditions: raw.conditions,
inputs: raw.inputs,
output: raw.output,
network,
components: raw.components,
reports: raw.reports,
node_sets,
})
}
}