use rustc_hash::{FxBuildHasher, FxHashMap};
use smallvec::smallvec;
use crate::arena::{ExprArena, ExprId, ExprNode, VarId};
#[derive(Clone, Debug, Default)]
pub struct LinearTerms {
pub coeffs: Vec<(VarId, f64)>,
pub constant: f64,
}
fn as_linear(arena: &ExprArena, id: ExprId) -> Option<LinearTerms> {
match arena.get(id) {
ExprNode::Const(c) => Some(LinearTerms { coeffs: Vec::new(), constant: *c }),
ExprNode::Var(v) => Some(LinearTerms { coeffs: vec![(*v, 1.0)], constant: 0.0 }),
ExprNode::Linear { coeffs, constant } => {
Some(LinearTerms { coeffs: coeffs.clone(), constant: *constant })
}
ExprNode::Neg(inner) => {
let inner = *inner;
as_linear(arena, inner).map(|mut t| {
t.coeffs.iter_mut().for_each(|(_, c)| *c = -*c);
t.constant = -t.constant;
t
})
}
ExprNode::Add(children) => {
let children: smallvec::SmallVec<[ExprId; 4]> = children.iter().copied().collect();
let mut acc = LinearTerms::default();
let mut map: FxHashMap<VarId, f64> =
FxHashMap::with_capacity_and_hasher(children.len() * 4, FxBuildHasher);
for child in children {
let t = as_linear(arena, child)?;
for (v, c) in t.coeffs {
*map.entry(v).or_insert(0.0) += c;
}
acc.constant += t.constant;
}
acc.coeffs = map.into_iter().collect();
Some(acc)
}
ExprNode::Mul(children) => {
let children: smallvec::SmallVec<[ExprId; 4]> = children.iter().copied().collect();
let mut scalar = 1.0;
let mut linear: Option<LinearTerms> = None;
for child in children {
if let ExprNode::Const(c) = arena.get(child) {
scalar *= c;
} else if linear.is_none() {
linear = Some(as_linear(arena, child)?);
} else {
return None;
}
}
Some(match linear {
None => LinearTerms { coeffs: Vec::new(), constant: scalar },
Some(mut t) => {
t.coeffs.iter_mut().for_each(|(_, c)| *c *= scalar);
t.constant *= scalar;
t
}
})
}
_ => None,
}
}
fn push_linear(arena: &mut ExprArena, mut t: LinearTerms) -> ExprId {
t.coeffs.retain(|(_, c)| *c != 0.0);
arena.push(ExprNode::Linear { coeffs: t.coeffs, constant: t.constant })
}
pub(crate) fn add_into(arena: &mut ExprArena, lhs: ExprId, rhs: ExprId) -> ExprId {
if let (Some(lt), Some(rt)) = (as_linear(arena, lhs), as_linear(arena, rhs)) {
let mut map: FxHashMap<VarId, f64> =
FxHashMap::with_capacity_and_hasher(lt.coeffs.len() + rt.coeffs.len(), FxBuildHasher);
for (v, c) in lt.coeffs.into_iter().chain(rt.coeffs) {
*map.entry(v).or_insert(0.0) += c;
}
return push_linear(
arena,
LinearTerms { coeffs: map.into_iter().collect(), constant: lt.constant + rt.constant },
);
}
arena.push(ExprNode::Add(smallvec![lhs, rhs]))
}
pub(crate) fn sub_into(arena: &mut ExprArena, lhs: ExprId, rhs: ExprId) -> ExprId {
let neg = neg_into(arena, rhs);
add_into(arena, lhs, neg)
}
pub(crate) fn mul_into(arena: &mut ExprArena, lhs: ExprId, rhs: ExprId) -> ExprId {
if let ExprNode::Const(c) = *arena.get(lhs) {
if let Some(mut t) = as_linear(arena, rhs) {
t.coeffs.iter_mut().for_each(|(_, co)| *co *= c);
t.constant *= c;
return push_linear(arena, t);
}
}
if let ExprNode::Const(c) = *arena.get(rhs) {
if let Some(mut t) = as_linear(arena, lhs) {
t.coeffs.iter_mut().for_each(|(_, co)| *co *= c);
t.constant *= c;
return push_linear(arena, t);
}
}
arena.push(ExprNode::Mul(smallvec![lhs, rhs]))
}
pub(crate) fn neg_into(arena: &mut ExprArena, rhs: ExprId) -> ExprId {
if let Some(mut t) = as_linear(arena, rhs) {
t.coeffs.iter_mut().for_each(|(_, c)| *c = -*c);
t.constant = -t.constant;
return push_linear(arena, t);
}
arena.push(ExprNode::Neg(rhs))
}
pub fn extract_linear(arena: &ExprArena, id: ExprId) -> Option<LinearTerms> {
as_linear(arena, id)
}