use crate::blocks::{self, Block, BuildContext, Frame};
use crate::metrics::Metrics;
use crate::{Spec, params};
use comnum_core::rng::{Rng, fnv1a64, stream};
use comnum_core::{Error, Result};
use comnum_sim::grid::Condition;
use comnum_sim::scenario::{Record, Scenario};
use std::time::Instant;
impl Spec {
pub fn link_for(&self, cond: &Condition) -> Result<Link> {
let ctx = BuildContext {
fiber: self.fiber.clone(),
base_dir: self.base_dir.clone(),
name: Some(self.name.clone()),
condition: Some(cond.label()),
};
let mut blocks: Vec<Box<dyn Block>> = Vec::with_capacity(self.link.len());
let mut kinds = Vec::with_capacity(self.link.len());
let mut labels = Vec::with_capacity(self.link.len());
for (i, b) in self.link.iter().enumerate() {
let (kind, label, p) = params::split_block(¶ms::substitute(b, cond)?)?;
labels.push(label.clone().unwrap_or_else(|| kind.clone()));
let block = blocks::build_parts(kind.clone(), label, p, &ctx)
.map_err(|e| e.context(format!("link block {}", i + 1)))?;
blocks.push(block);
kinds.push(kind);
}
let labels = number_labels(&labels);
blocks::with_registry(|registry| {
if registry.iter().any(|b| b.starts && b.name == kinds[0]) {
return Ok(());
}
let starts: Vec<&str> = registry.iter().filter(|b| b.starts).map(|b| b.name).collect();
Err(Error::Config(format!("the first block of the link must be one of {starts:?}")))
})?;
blocks::check_link(&kinds.iter().map(String::as_str).collect::<Vec<_>>())?;
for i in 0..blocks.len() {
if blocks[i].needs_training() {
let mut rng = self.training_rng(cond, i);
let mut s = Frame::default();
for b in &blocks[..i] {
b.apply(&mut s, &mut rng)?;
}
blocks[i].train(&s, &mut rng)?;
}
}
let metrics = Metrics::from_json(¶ms::substitute(&self.metrics, cond)?)?;
Ok(Link { blocks, kinds, labels, metrics, profile: self.profile })
}
fn training_rng(&self, cond: &Condition, block: usize) -> Rng {
stream(self.seed, &format!("{}/train", self.name), fnv1a64(cond.label().as_bytes()), block as u64)
}
}
pub fn link_labels(link: &[serde_json::Value]) -> Result<Vec<String>> {
let labels = link
.iter()
.map(|b| params::split_block(b).map(|(kind, label, _)| label.unwrap_or(kind)))
.collect::<Result<Vec<_>>>()?;
Ok(number_labels(&labels))
}
pub fn number_labels(labels: &[String]) -> Vec<String> {
labels
.iter()
.enumerate()
.map(|(i, l)| if labels.iter().filter(|g| *g == l).count() > 1 { format!("{l}_{}", i + 1) } else { l.clone() })
.collect()
}
pub struct Link {
pub blocks: Vec<Box<dyn Block>>,
pub kinds: Vec<String>,
pub labels: Vec<String>,
pub metrics: Metrics,
pub profile: bool,
}
impl Link {
pub fn trial(&self, rng: &mut Rng, rec: &mut Record) -> Result<Frame> {
let mut s = Frame { trial: rec.trial(), ..Frame::default() };
let t_link = Instant::now();
for (i, (b, kind)) in self.blocks.iter().zip(&self.kinds).enumerate() {
let t = Instant::now();
b.apply(&mut s, rng).map_err(|e| e.context(format!("link block {} ({kind})", i + 1)))?;
if self.profile {
rec.value(&format!("{}.time_ms", self.labels[i]), t.elapsed().as_secs_f64() * 1e3);
}
}
if self.profile {
rec.value("link.time_ms", t_link.elapsed().as_secs_f64() * 1e3);
}
self.metrics.record_frame(&s, rec)?;
Ok(s)
}
}
pub struct SpecScenario<'a> {
spec: &'a Spec,
profile: bool,
}
impl<'a> SpecScenario<'a> {
pub fn new(spec: &'a Spec) -> SpecScenario<'a> {
SpecScenario { spec, profile: spec.profile }
}
#[must_use]
pub fn profile(mut self, profile: bool) -> Self {
self.profile = profile;
self
}
}
impl Scenario for SpecScenario<'_> {
type State = Link;
fn name(&self) -> &str {
&self.spec.name
}
fn setup(&self, condition: &Condition) -> Result<Link> {
let mut link = self.spec.link_for(condition)?;
link.profile = self.profile;
Ok(link)
}
fn trial(&self, link: &Link, _: &Condition, rng: &mut Rng, record: &mut Record) -> Result<()> {
link.trial(rng, record).map(|_| ())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn repeated_labels_take_the_position_of_the_block() {
let spec = Spec::from_yaml(
"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}",
)
.unwrap();
let link = spec.link_for(&spec.conditions()[0]).unwrap();
assert_eq!(link.labels, ["source", "awgn_2", "awgn_3", "last"]);
}
#[test]
fn errors_name_their_place() {
let e = Spec::from_yaml("name: t\nlink: [source]\nseeds: 3").unwrap_err().to_string();
assert!(e.contains("seeds"), "{e}");
let spec = Spec::from_yaml("name: t\nlink: [awgn_ebn0]").unwrap();
let e = spec.link_for(&Condition::new()).err().unwrap().to_string();
assert!(e.contains("link block 1"), "{e}");
let spec = Spec::from_yaml("name: t\nlink: [{type: awgn_ebn0, ebn0_db: 3}, source]").unwrap();
assert!(spec.link_for(&Condition::new()).err().unwrap().to_string().contains("first block"));
let spec = Spec::from_yaml("name: t\nlink: [source]\nmetrics: {report: [fer]}").unwrap();
assert!(spec.link_for(&Condition::new()).err().unwrap().to_string().contains("fer"));
}
}