use crate::primitive::int;
use crate::symbolic::{
expr::{SymExpr, Primary},
simplify::{fraction::{extract_explicit_frac, make_fraction, extract_fractional}, rules::do_add, step::Step},
step_collector::StepCollector,
};
fn add_assign(lhs: &mut SymExpr, rhs: SymExpr) {
if lhs.is_float() && rhs.is_float() {
*lhs += rhs;
return;
}
match (extract_explicit_frac(&mut lhs.clone()), extract_explicit_frac(&mut rhs.clone())) {
(Some((num1, den1)), Some((num2, den2))) => {
let numerator = num1 * &den2 + num2 * &den1;
let denominator = den1 * den2;
if denominator == 1 {
*lhs = SymExpr::Primary(Primary::Integer(numerator));
} else {
*lhs = make_fraction(
SymExpr::Primary(Primary::Integer(numerator)),
SymExpr::Primary(Primary::Integer(denominator)),
);
}
},
_ => *lhs += rhs,
}
}
pub fn add_zero(expr: &SymExpr, step_collector: &mut dyn StepCollector<Step>) -> Option<SymExpr> {
let opt = do_add(expr, |terms| {
let new_terms = terms.iter()
.filter(|term| {
term.as_integer()
.map(|n| !n.is_zero())
.unwrap_or(true)
})
.cloned()
.collect::<Vec<_>>();
if new_terms.len() == terms.len() {
None
} else {
Some(SymExpr::Add(new_terms).downgrade())
}
})?;
step_collector.push(Step::AddZero);
Some(opt)
}
pub fn combine_like_terms(expr: &SymExpr, step_collector: &mut dyn StepCollector<Step>) -> Option<SymExpr> {
let opt = do_add(expr, |terms| {
let mut new_terms = terms.to_vec();
let mut current_term_idx = 0;
fn get_coeff(expr: &SymExpr) -> (SymExpr, SymExpr) {
match expr {
SymExpr::Primary(Primary::Integer(_)) | SymExpr::Primary(Primary::Float(_)) => {
(expr.clone(), SymExpr::Primary(Primary::Integer(int(1))))
},
SymExpr::Mul(factors) => {
let mut factors = factors.clone();
let fraction = extract_fractional(&mut factors)
.unwrap_or(SymExpr::Primary(Primary::Integer(int(1))));
(
fraction,
SymExpr::Mul(factors).downgrade(),
)
},
SymExpr::Exp(..) => {
if expr.is_integer_recip() {
(expr.clone(), SymExpr::Primary(Primary::Integer(int(1))))
} else {
(SymExpr::Primary(Primary::Integer(int(1))), expr.clone())
}
},
_ => (SymExpr::Primary(Primary::Integer(int(1))), expr.clone()),
}
}
while current_term_idx < new_terms.len() {
let (mut current_term_coeff, current_term_factors) = get_coeff(&new_terms[current_term_idx]);
let mut next_term_idx = current_term_idx + 1;
while next_term_idx < new_terms.len() {
let (next_term_coeff, next_term_factors) = get_coeff(&new_terms[next_term_idx]);
if current_term_factors == next_term_factors {
add_assign(&mut current_term_coeff, next_term_coeff);
new_terms.swap_remove(next_term_idx);
} else {
next_term_idx += 1;
}
}
if current_term_coeff.as_integer().map(|n| n == &1).unwrap_or(false) {
new_terms[current_term_idx] = current_term_factors;
} else {
new_terms[current_term_idx] =
current_term_coeff * current_term_factors;
}
current_term_idx += 1;
}
if new_terms.len() == terms.len() {
None
} else {
Some(SymExpr::Add(new_terms).downgrade())
}
})?;
step_collector.push(Step::CombineLikeTerms);
Some(opt)
}
pub fn all(expr: &SymExpr, step_collector: &mut dyn StepCollector<Step>) -> Option<SymExpr> {
add_zero(expr, step_collector)
.or_else(|| combine_like_terms(expr, step_collector))
}