1use crate::blocks::{self, Block, BuildContext, Frame};
5use crate::metrics::Metrics;
6use crate::{Spec, params};
7use comnum_core::rng::{Rng, fnv1a64, stream};
8use comnum_core::{Error, Result};
9use comnum_sim::grid::Condition;
10use comnum_sim::scenario::{Record, Scenario};
11use std::time::Instant;
12
13impl Spec {
14 pub fn link_for(&self, cond: &Condition) -> Result<Link> {
20 let ctx = BuildContext {
21 fiber: self.fiber.clone(),
22 base_dir: self.base_dir.clone(),
23 name: Some(self.name.clone()),
24 condition: Some(cond.label()),
25 };
26 let mut blocks: Vec<Box<dyn Block>> = Vec::with_capacity(self.link.len());
27 let mut kinds = Vec::with_capacity(self.link.len());
28 let mut labels = Vec::with_capacity(self.link.len());
29 for (i, b) in self.link.iter().enumerate() {
30 let (kind, label, p) = params::split_block(¶ms::substitute(b, cond)?)?;
31 labels.push(label.clone().unwrap_or_else(|| kind.clone()));
32 let block = blocks::build_parts(kind.clone(), label, p, &ctx)
33 .map_err(|e| e.context(format!("link block {}", i + 1)))?;
34 blocks.push(block);
35 kinds.push(kind);
36 }
37 let labels = number_labels(&labels);
38 blocks::with_registry(|registry| {
39 if registry.iter().any(|b| b.starts && b.name == kinds[0]) {
40 return Ok(());
41 }
42 let starts: Vec<&str> = registry.iter().filter(|b| b.starts).map(|b| b.name).collect();
43 Err(Error::Config(format!("the first block of the link must be one of {starts:?}")))
44 })?;
45 blocks::check_link(&kinds.iter().map(String::as_str).collect::<Vec<_>>())?;
46 for i in 0..blocks.len() {
47 if blocks[i].needs_training() {
48 let mut rng = self.training_rng(cond, i);
49 let mut s = Frame::default();
50 for b in &blocks[..i] {
51 b.apply(&mut s, &mut rng)?;
52 }
53 blocks[i].train(&s, &mut rng)?;
54 }
55 }
56 let metrics = Metrics::from_json(¶ms::substitute(&self.metrics, cond)?)?;
57 Ok(Link { blocks, kinds, labels, metrics, profile: self.profile })
58 }
59
60 fn training_rng(&self, cond: &Condition, block: usize) -> Rng {
61 stream(self.seed, &format!("{}/train", self.name), fnv1a64(cond.label().as_bytes()), block as u64)
62 }
63}
64
65pub fn link_labels(link: &[serde_json::Value]) -> Result<Vec<String>> {
71 let labels = link
72 .iter()
73 .map(|b| params::split_block(b).map(|(kind, label, _)| label.unwrap_or(kind)))
74 .collect::<Result<Vec<_>>>()?;
75 Ok(number_labels(&labels))
76}
77
78pub fn number_labels(labels: &[String]) -> Vec<String> {
81 labels
82 .iter()
83 .enumerate()
84 .map(|(i, l)| if labels.iter().filter(|g| *g == l).count() > 1 { format!("{l}_{}", i + 1) } else { l.clone() })
85 .collect()
86}
87
88pub struct Link {
90 pub blocks: Vec<Box<dyn Block>>,
92 pub kinds: Vec<String>,
94 pub labels: Vec<String>,
97 pub metrics: Metrics,
99 pub profile: bool,
102}
103
104impl Link {
105 pub fn trial(&self, rng: &mut Rng, rec: &mut Record) -> Result<Frame> {
110 let mut s = Frame { trial: rec.trial(), ..Frame::default() };
111 let t_link = Instant::now();
112 for (i, (b, kind)) in self.blocks.iter().zip(&self.kinds).enumerate() {
113 let t = Instant::now();
114 b.apply(&mut s, rng).map_err(|e| e.context(format!("link block {} ({kind})", i + 1)))?;
115 if self.profile {
116 rec.value(&format!("{}.time_ms", self.labels[i]), t.elapsed().as_secs_f64() * 1e3);
117 }
118 }
119 if self.profile {
120 rec.value("link.time_ms", t_link.elapsed().as_secs_f64() * 1e3);
121 }
122 self.metrics.record_frame(&s, rec)?;
123 Ok(s)
124 }
125}
126
127pub struct SpecScenario<'a> {
129 spec: &'a Spec,
130 profile: bool,
131}
132
133impl<'a> SpecScenario<'a> {
134 pub fn new(spec: &'a Spec) -> SpecScenario<'a> {
136 SpecScenario { spec, profile: spec.profile }
137 }
138
139 #[must_use]
141 pub fn profile(mut self, profile: bool) -> Self {
142 self.profile = profile;
143 self
144 }
145}
146
147impl Scenario for SpecScenario<'_> {
148 type State = Link;
149
150 fn name(&self) -> &str {
151 &self.spec.name
152 }
153
154 fn setup(&self, condition: &Condition) -> Result<Link> {
155 let mut link = self.spec.link_for(condition)?;
156 link.profile = self.profile;
157 Ok(link)
158 }
159
160 fn trial(&self, link: &Link, _: &Condition, rng: &mut Rng, record: &mut Record) -> Result<()> {
161 link.trial(rng, record).map(|_| ())
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn repeated_labels_take_the_position_of_the_block() {
171 let spec = Spec::from_yaml(
172 "name: twice\nlink: [{type: source, n_symbols: 64}, {type: awgn, snr_db: 10}, {type: awgn, snr_db: 10}, {type: awgn, snr_db: 10, label: last}]\nstop: {max_trials: 1}",
173 )
174 .unwrap();
175 let link = spec.link_for(&spec.conditions()[0]).unwrap();
176 assert_eq!(link.labels, ["source", "awgn_2", "awgn_3", "last"]);
177 }
178
179 #[test]
180 fn errors_name_their_place() {
181 let e = Spec::from_yaml("name: t\nlink: [source]\nseeds: 3").unwrap_err().to_string();
182 assert!(e.contains("seeds"), "{e}");
183 let spec = Spec::from_yaml("name: t\nlink: [awgn_ebn0]").unwrap();
184 let e = spec.link_for(&Condition::new()).err().unwrap().to_string();
185 assert!(e.contains("link block 1"), "{e}");
186 let spec = Spec::from_yaml("name: t\nlink: [{type: awgn_ebn0, ebn0_db: 3}, source]").unwrap();
187 assert!(spec.link_for(&Condition::new()).err().unwrap().to_string().contains("first block"));
188 let spec = Spec::from_yaml("name: t\nlink: [source]\nmetrics: {report: [fer]}").unwrap();
189 assert!(spec.link_for(&Condition::new()).err().unwrap().to_string().contains("fer"));
190 }
191}