use std::collections::BTreeMap;
use sva_formula::{Body, C64, NodeId, SpectralSum, Var};
use sva_samples::{
ALIAS_OVERSAMPLE, AliasScore, Audible, Buffer, CollapseError, Detail, Label, Refs, Rule,
Source, eval_spectral_sum_at, eval_written_at, lane_of, measure_alias, truncate_spectral_sum,
truncate_written, unary,
};
use crate::error::{Diagnostic, EngineError, Located};
use crate::refs;
use crate::render::Render;
use crate::typing::Value;
enum Point {
SpectralSum(Box<SpectralSum>),
Written {
body: Box<Body>,
refs: BTreeMap<NodeId, Point>,
},
Operation {
name: String,
args: Vec<Point>,
widths: Vec<usize>,
},
Buffer(NodeId),
}
pub fn point_sample(
held: &Render,
id: NodeId,
score: AliasScore,
) -> Result<(Buffer, Label), EngineError> {
let tree = plan(held, id)?;
let rate = held.config.rate;
let len = held
.config
.horizon
.len(rate)
.map_err(|e| refused(held, id, &e))?;
let width = usize::from(held.tys.ty(id).width);
let mut planes = Vec::with_capacity(width);
for component in 0..width.max(1) {
planes.push(sweep(held, id, &tree, component, len, 1)?);
}
let start_secs = held.config.horizon.start_secs;
let alias_db = match score {
AliasScore::Asked => {
let high = sweep(held, id, &tree, 0, len * ALIAS_OVERSAMPLE, ALIAS_OVERSAMPLE)?;
Some(
measure_alias(
&planes[0],
&high,
ALIAS_OVERSAMPLE,
f64::from(rate),
start_secs,
)
.asr_db,
)
}
AliasScore::NotAsked => None,
};
let mut buffer = Buffer::of_planes(rate, planes);
buffer.origin_secs = held.config.horizon.start_secs;
let detail = Detail::Point {
rule: Rule::PointSampled,
alias_db,
};
Ok((
buffer,
Label::new(Source::Measured, held.config.profile.name, rate, detail),
))
}
fn sweep(
held: &Render,
id: NodeId,
tree: &Point,
component: usize,
len: usize,
oversample: usize,
) -> Result<Vec<f64>, EngineError> {
let step = 1.0 / (f64::from(held.config.rate) * oversample as f64);
let origin = held.config.horizon.start_secs;
(0..len)
.map(|i| {
value(held, tree, component, origin + i as f64 * step)
.map(|v| v.re)
.map_err(|e| refused(held, id, &e))
})
.collect()
}
fn value(held: &Render, tree: &Point, component: usize, t: f64) -> Result<C64, CollapseError> {
match tree {
Point::SpectralSum(sum) => eval_spectral_sum_at(sum, component, t),
Point::Written { body, refs } => eval_written_at(
body,
component,
t,
&Reads {
held,
planned: refs,
},
),
Point::Operation { name, args, widths } => {
operation(held, name, args, widths, component, t)
}
Point::Buffer(id) => Ok(read(held, *id, component, t)),
}
}
struct Reads<'a> {
held: &'a Render,
planned: &'a BTreeMap<NodeId, Point>,
}
impl Refs for Reads<'_> {
fn value(&self, id: NodeId, component: usize, t: f64) -> Result<C64, CollapseError> {
value(self.held, &self.planned[&id], component, t)
}
fn width(&self, id: NodeId) -> usize {
usize::from(self.held.tys.ty(id).width)
}
}
fn read(held: &Render, id: NodeId, component: usize, t: f64) -> C64 {
let buffer = held.buffers.get(&id).expect("a read is materialized first");
let at = ((t - buffer.origin_secs) * f64::from(buffer.rate)).round();
if at < 0.0 {
return C64::ZERO;
}
let plane = buffer.plane(component.min(buffer.width.saturating_sub(1)));
C64::real(plane.get(at as usize).copied().unwrap_or(0.0))
}
fn operation(
held: &Render,
name: &str,
args: &[Point],
widths: &[usize],
component: usize,
t: f64,
) -> Result<C64, CollapseError> {
if name == "join" {
let (at, inner) = lane_of(widths, component)
.ok_or(CollapseError::NotEvaluable("a component past the width"))?;
return value(held, &args[at], inner, t);
}
if name == "ch" {
let k = value(held, &args[1], 0, t)?.re.round();
let width = widths.first().copied().unwrap_or(1);
let k = usize::try_from(k as i64).unwrap_or(usize::MAX);
if k >= width {
return Err(CollapseError::NotEvaluable("a component past the width"));
}
return value(held, &args[0], k, t);
}
let mut held_args = Vec::with_capacity(args.len());
for arg in args {
held_args.push(value(held, arg, component, t)?);
}
let pair = || (held_args[0], held_args[1]);
let first = || held_args[0];
Ok(match name {
"+" => held_args.iter().fold(C64::ZERO, |a, b| a + *b),
"*" => held_args.iter().fold(C64::ONE, |a, b| a * *b),
"-" => pair().0 - pair().1,
"/" => pair().0 / pair().1,
"%" => C64::real(pair().0.re.rem_euclid(pair().1.re)),
"max" => C64::real(pair().0.re.max(pair().1.re)),
"min" => C64::real(pair().0.re.min(pair().1.re)),
"pow" => power(pair().0, pair().1.re),
"crop" => match t >= held_args[1].re && t <= held_args[2].re {
true => first(),
false => C64::ZERO,
},
_ => match sva_formula::Unary::from_name(name) {
Some(op) => unary(op, first()),
None => return Err(CollapseError::NotEvaluable("this operation")),
},
})
}
fn power(x: C64, exponent: f64) -> C64 {
let whole = exponent.round();
if (exponent - whole).abs() > f64::EPSILON {
return C64::real(x.re.powf(exponent));
}
match whole >= 0.0 {
true => x.powi(whole as u32),
false => x.powi((-whole) as u32).inv(),
}
}
fn plan(held: &Render, id: NodeId) -> Result<Point, EngineError> {
if !held.tys.ty(id).is_closed_form() {
return Ok(Point::Buffer(id));
}
let band = Audible::of(&held.config.profile, held.config.rate);
let left = match refs::spectral_sum_of(&held.tys, id, Var::T) {
Ok(sum) => {
let held = truncate_spectral_sum(&sum, band).map_err(|e| refused(held, id, &e))?;
return Ok(Point::SpectralSum(Box::new(held)));
}
Err(left) => left,
};
match held.tys.value(id) {
Value::Op { name, args } => {
let mut set = Vec::with_capacity(args.len());
for arg in args {
set.push(plan(held, *arg)?);
}
Ok(Point::Operation {
name: name.clone(),
args: set,
widths: args
.iter()
.map(|arg| usize::from(held.tys.ty(*arg).width))
.collect(),
})
}
Value::ClosedForm(form) if form.var == Var::T => {
let mut refs = BTreeMap::new();
for node in refs::nodes_in(&form.body) {
refs.insert(node, plan(held, node)?);
}
let body = truncate_written(&form.body, band).map_err(|e| refused(held, id, &e))?;
Ok(Point::Written {
body: Box::new(body),
refs,
})
}
_ => Err(left),
}
}
fn refused(held: &Render, id: NodeId, e: &CollapseError) -> EngineError {
EngineError::refused(Diagnostic {
code: e.code().to_string(),
message: e.to_string(),
location: Located::at(held.tys.name(id), None),
help: "write the subterm inside sample(...), or give the observation a window".to_string(),
})
}