use super::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Debug)]
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(crate) enum StandaloneError {
Declined(String),
Fault(String),
}
pub(in crate::simulate_array) fn eval_standalone_rule(
rule: &AlgebraicRule,
inputs: &[(&str, &ndarray::ArrayD<f64>)],
param_names: &[String],
params: &[f64],
const_arrays: &ConstArrayScope,
pipeline: bool,
) -> Result<ndarray::ArrayD<f64>, StandaloneError> {
let prog = build_standalone_program(rule, inputs, param_names, const_arrays, pipeline)
.map_err(|b| StandaloneError::Declined(b.reason))?;
run_standalone_program(prog, rule, param_names, params)
}
fn build_standalone_program(
rule: &AlgebraicRule,
inputs: &[(&str, &ndarray::ArrayD<f64>)],
param_names: &[String],
const_arrays: &ConstArrayScope,
pipeline: bool,
) -> LResult<TapeProgram> {
let name = observed_rule_var(rule);
with_standalone_builder(
name,
param_names,
const_arrays,
crate::precision::is_f32(),
pipeline,
|mut b| {
let _rule_precision = crate::precision::has_variable_overrides()
.then(|| crate::precision::enter(crate::precision::of_variable(name)));
for (input, value) in inputs {
let lv = if value.ndim() == 0 {
LV::Lit(crate::precision::active().round(value[ndarray::IxDyn(&[])]))
} else {
b.standalone_array_input(value)?
};
b.obs_defined
.insert((*input).to_string(), ObsVal::Taped(lv));
}
let v = b.lower_observed_rule(rule)?;
b.flush_rule_fault()?;
b.obs_defined.insert(name.clone(), v);
Ok(b.finish_standalone(rule))
},
)
}
pub(super) fn with_standalone_builder<R>(
name: &str,
param_names: &[String],
const_arrays: &ConstArrayScope,
f32_document: bool,
pipeline: bool,
f: impl for<'b> FnOnce(TapeBuilder<'b>) -> R,
) -> R {
let var_shapes: IndexMap<String, VarShape> = IndexMap::new();
let discrete: HashSet<String> = HashSet::new();
let forcing = ForcingInputs {
shapes: FxHashMap::default(),
unsized_: HashSet::new(),
discrete: &discrete,
};
let mut obs_tier: FxHashMap<String, Cadence> = FxHashMap::default();
obs_tier.insert(name.to_string(), Cadence::Const);
let mut b = TapeBuilder::new(
&var_shapes,
param_names,
obs_tier,
const_arrays,
f32_document,
&forcing,
);
b.array_bool_reductions = pipeline;
b.begin_rule(RuleInfo {
name: name.to_string(),
kind: RuleKind::Observed(0),
cadence: Cadence::Const,
});
f(b)
}
impl TapeBuilder<'_> {
pub(super) fn standalone_array_input(&mut self, value: &ndarray::ArrayD<f64>) -> LResult<LV> {
if !value.is_empty() {
return self.emit_const_array(value);
}
let ones = DimI::from_elem(1, value.ndim());
let lv = self.emit_zero_array(value.shape(), &ones);
if let LV::Arr(s) = lv {
self.known.insert(s, Known::Vals(Rc::new(Vec::new())));
}
Ok(lv)
}
pub(super) fn finish_standalone(mut self, rule: &AlgebraicRule) -> TapeProgram {
self.end_rule();
let exports = self.compute_exports(std::slice::from_ref(rule), true);
self.finish(exports, None, false)
}
pub(super) fn known_inputs<'n>(
&self,
names: impl IntoIterator<Item = &'n String>,
) -> Option<Vec<(String, ndarray::ArrayD<f64>)>> {
names
.into_iter()
.map(|name| {
let (shape, vals) = self.known_named(name)?;
let a = ndarray::ArrayD::from_shape_vec(ndarray::IxDyn(&shape), (*vals).clone())
.ok()?;
Some((name.clone(), a))
})
.collect()
}
}
fn run_standalone_program(
prog: TapeProgram,
rule: &AlgebraicRule,
param_names: &[String],
params: &[f64],
) -> Result<ndarray::ArrayD<f64>, StandaloneError> {
run_standalone_export(prog, observed_rule_var(rule), param_names, params)
}
pub(super) fn run_standalone_export(
prog: TapeProgram,
name: &str,
param_names: &[String],
params: &[f64],
) -> Result<ndarray::ArrayD<f64>, StandaloneError> {
let var_shapes: IndexMap<String, VarShape> = IndexMap::new();
let forcing: RefCell<HashMap<String, ndarray::ArrayD<f64>>> = RefCell::new(HashMap::new());
let mut ctx = super::super::TapeCtx::new(std::sync::Arc::new(prog));
ctx.set_exports_active(true);
crate::simulate_array::take_const_array_oob();
super::super::run_tape_call(
&mut ctx,
&RhsCall {
rhs_rules: &[],
observed_rules: &[],
var_shapes: &var_shapes,
param_names,
state: &[],
params,
forcing: &forcing,
t: 0.0,
declared: empty_declared_names(),
rule_set: next_rule_set_id(),
},
&mut [],
&mut RhsStats::default(),
);
if let Some(msg) = crate::simulate_array::take_const_array_oob() {
return Err(StandaloneError::Fault(msg));
}
ctx.exported_observeds()
.get(name)
.cloned()
.ok_or_else(|| StandaloneError::Declined(format!("observed `{name}` was not exported")))
}
impl TapeBuilder<'_> {
pub(super) fn fold_const_observed(&self, name: &str) -> Option<(DimU, Rc<Vec<f64>>)> {
if let Some(hit) = self.folded.borrow().get(name) {
return hit.clone();
}
let out = self.fold_const_observed_uncached(name);
self.folded
.borrow_mut()
.insert(name.to_string(), out.clone());
out
}
fn fold_const_observed_uncached(&self, name: &str) -> Option<(DimU, Rc<Vec<f64>>)> {
if self.obs_tier.get(name) != Some(&Cadence::Const) {
return None;
}
if !matches!(self.obs_defined.get(name), Some(ObsVal::Taped(_))) {
return None;
}
let rule = self
.observed_rules
.iter()
.find(|r| observed_rule_var(r) == name)?;
let mut reads: HashSet<String> = HashSet::new();
collect_variable_names(observed_rule_body(rule), &mut reads);
let mut names: Vec<&String> = reads
.iter()
.filter(|r| r.as_str() != name && self.obs_defined.contains_key(r.as_str()))
.collect();
names.sort();
let mut values = self.known_inputs(names)?;
let mut baked: Vec<&String> = reads
.iter()
.filter(|r| self.baked_params.contains_key(r.as_str()))
.collect();
baked.sort();
for p in baked {
let x = ndarray::ArrayD::from_elem(ndarray::IxDyn(&[]), self.baked_params[p]);
values.push((p.clone(), x));
}
let inputs: Vec<(&str, &ndarray::ArrayD<f64>)> =
values.iter().map(|(n, a)| (n.as_str(), a)).collect();
let a = eval_standalone_rule(rule, &inputs, &[], &[], self.const_arrays, false).ok()?;
let shape: DimU = a.shape().iter().copied().collect();
Some((shape, Rc::new(a.iter().copied().collect())))
}
}
pub(in crate::simulate_array) fn collect_variable_names(expr: &Expr, out: &mut HashSet<String>) {
match expr {
Expr::Variable(v) => {
out.insert(v.clone());
}
Expr::Operator(node) => {
for a in &node.args {
collect_variable_names(a, out);
}
node.for_each_child(&mut |c| collect_variable_names(c, out));
for spec in node.ranges.iter().flat_map(|r| r.values()) {
if let Some((offsets, _)) = spec.ragged() {
out.insert(offsets.to_string());
}
}
for clause in node.join.iter().flatten() {
out.extend(clause.on.iter().flatten().cloned());
if let Some(ov) = &clause.overlap {
out.extend(ov.src_env.iter().chain(ov.tgt_env.iter()).cloned());
}
}
}
_ => {}
}
}