use sva_formula::affine::apply_scalar;
use sva_formula::closed_form::children;
use sva_formula::{Body, C64, ClosedForm, Fold, Origin, Part, Var, normalize_closed_form};
pub fn is_constant(f: &Body) -> bool {
match f {
Body::Line | Body::Node(_) | Body::Param(_) | Body::Index(_) => false,
other => children(other).iter().all(|p| is_constant(&p.body)),
}
}
pub fn constant_value(body: &Body, var: Var) -> Option<f64> {
if !is_constant(body) {
return None;
}
let form = ClosedForm {
var,
body: sva_formula::series::written_out(body)?,
origin: Origin::UNKNOWN,
};
let sum = normalize_closed_form(&form).ok()?;
let [lane] = sum.lanes.as_slice() else {
return None;
};
if !lane.series.is_empty() || !lane.modal.is_empty() {
return None;
}
match lane.atoms.as_slice() {
[] => Some(0.0),
[atom] => atom.is_bare().then_some(atom.c.re),
_ => None,
}
}
pub fn constant_modulo(a: f64, b: f64) -> Option<f64> {
folded_number(&Body::Fold(Fold::Mod, vec![number(a), number(b)]))
}
pub fn constant_call(name: &str, positional: &[f64], named: &[(&str, f64)]) -> Option<f64> {
let bodies: Vec<Body> = positional
.iter()
.map(|x| Body::Const(C64::real(*x)))
.collect();
folded_number(&super::calls::arithmetic(
name,
bodies,
named,
Var::T,
Origin::UNKNOWN,
)?)
}
fn folded_number(body: &Body) -> Option<f64> {
unbounded(body).or_else(|| constant_value(body, Var::T))
}
fn number(x: f64) -> Part {
Part::new(Origin::UNKNOWN, Body::Const(C64::real(x)))
}
pub fn unbounded(body: &Body) -> Option<f64> {
(is_constant(body) && holds_infinite(body))
.then(|| scalar(body))
.flatten()
}
pub fn holds_infinite(f: &Body) -> bool {
matches!(f, Body::Const(c) if !c.is_finite())
|| children(f).iter().any(|p| holds_infinite(&p.body))
}
fn scalar(f: &Body) -> Option<f64> {
let of = |p: &Part| scalar(&p.body);
Some(match f {
Body::Const(c) if c.im == 0.0 => c.re,
Body::Add(parts) => parts.iter().try_fold(0.0, |a, p| Some(a + of(p)?))?,
Body::Mul(parts) => parts.iter().try_fold(1.0, |a, p| Some(a * of(p)?))?,
Body::Div(a, b) => of(a)? / of(b)?,
Body::Pow(base, n) => of(base)?.powi(*n),
Body::Apply(op, x) => apply_scalar(*op, C64::real(of(x)?)).re,
Body::Fold(op, parts) => match (op, parts.as_slice()) {
(Fold::Max, [a, b]) => of(a)?.max(of(b)?),
(Fold::Min, [a, b]) => of(a)?.min(of(b)?),
(Fold::Mod, [a, b]) => of(a)?.rem_euclid(of(b)?),
_ => return None,
},
_ => return None,
})
}
pub(crate) fn number_of(tys: &crate::typing::Typing, id: sva_formula::NodeId) -> Option<f64> {
match tys.value(id) {
crate::typing::Value::ClosedForm(form) => match form.body {
Body::Const(c) if c.im == 0.0 => Some(c.re),
_ => constant_value(&form.body, form.var),
},
_ => None,
}
}