use std::{ops::DerefMut, sync::Arc};
use smallvec::SmallVec;
use crate::{
atom::{Atom, AtomView},
coefficient::CoefficientView,
combinatorics::CombinationWithReplacementIterator,
domains::{integer::Integer, rational::Q},
poly::{Exponent, PolyVariable},
state::{RecycledAtom, Workspace},
};
impl AtomView<'_> {
pub(crate) fn expand(&self) -> Atom {
Workspace::get_local().with(|ws| {
let mut a = ws.new_atom();
self.expand_with_ws_into(ws, None, &mut a);
a.into_inner()
})
}
pub(crate) fn expand_in(&self, var: AtomView) -> Atom {
Workspace::get_local().with(|ws| {
let mut a = ws.new_atom();
self.expand_with_ws_into(ws, Some(var), &mut a);
a.into_inner()
})
}
pub(crate) fn expand_into(&self, var: Option<AtomView>, out: &mut Atom) -> bool {
Workspace::get_local().with(|ws| self.expand_with_ws_into(ws, var, out))
}
pub(crate) fn expand_with_ws_into(
&self,
workspace: &Workspace,
var: Option<AtomView>,
out: &mut Atom,
) -> bool {
let changed = self.expand_no_norm(workspace, var, out);
if changed {
let mut a = workspace.new_atom();
out.as_view().normalize(workspace, &mut a);
std::mem::swap(out, &mut a);
}
changed
}
pub(crate) fn is_expanded(&self, var: Option<AtomView>) -> bool {
match self {
AtomView::Num(_) | AtomView::Var(_) | AtomView::Fun(_) => true,
AtomView::Pow(pow_view) => {
let (base, exp) = pow_view.get_base_exp();
if !base.is_expanded(var) || !exp.is_expanded(var) {
return false;
}
if let AtomView::Num(n) = exp
&& let CoefficientView::Natural(n, 1, 0, 1) = n.get_coeff_view()
&& n.unsigned_abs() <= u32::MAX as u64
&& matches!(base, AtomView::Add(_) | AtomView::Mul(_))
{
return var.map(|s| !base.contains(s)).unwrap_or(false);
}
true
}
AtomView::Mul(mul_view) => {
for arg in mul_view {
if !arg.is_expanded(var) {
return false;
}
if matches!(arg, AtomView::Add(_)) {
return var.map(|s| !arg.contains(s)).unwrap_or(false);
}
}
true
}
AtomView::Add(add_view) => {
for arg in add_view {
if !arg.is_expanded(var) {
return false;
}
}
true
}
}
}
pub(crate) fn expand_via_poly<E: Exponent>(&self, var: Option<AtomView>) -> Atom {
let var_map = var.map(|v| Arc::new(vec![v.to_owned().try_into().unwrap()]));
let mut out = Atom::new();
Workspace::get_local().with(|ws| {
self.expand_via_poly_impl::<E>(ws, var, &var_map, &mut out);
});
out
}
fn expand_via_poly_impl<E: Exponent>(
&self,
ws: &Workspace,
var: Option<AtomView>,
var_map: &Option<Arc<Vec<PolyVariable>>>,
out: &mut Atom,
) {
if self.is_expanded(var) {
out.set_from_view(self);
return;
}
if let Some(v) = var
&& !self.contains(v)
{
out.set_from_view(self);
return;
}
match self {
AtomView::Num(_) | AtomView::Var(_) | AtomView::Fun(_) => unreachable!(),
AtomView::Pow(_) => {
if let Some(v) = var_map {
*out = self.to_polynomial_in_vars::<E>(v).flatten(true);
} else {
if let Ok(p) = self.try_to_polynomial::<_, E>(&Q, None) {
*out = p.to_expression();
} else {
self.expand_into(var, out);
}
}
}
AtomView::Mul(_) => {
if let Some(v) = var_map {
*out = self.to_polynomial_in_vars::<E>(v).flatten(true);
} else {
if let Ok(p) = self.try_to_polynomial::<_, E>(&Q, None) {
*out = p.to_expression();
} else {
self.expand_into(var, out);
}
}
}
AtomView::Add(add_view) => {
let mut t = ws.new_atom();
let add = out.to_add();
for arg in add_view {
arg.expand_via_poly_impl::<E>(ws, var, var_map, &mut t);
add.extend(t.as_view());
}
add.as_view().normalize(ws, &mut t);
std::mem::swap(out, &mut t);
}
}
}
fn expand_no_norm(&self, workspace: &Workspace, var: Option<AtomView>, out: &mut Atom) -> bool {
if let Some(s) = var
&& !self.contains_literally_or_as_symbol(s)
{
out.set_from_view(self);
return false;
}
match self {
AtomView::Pow(p) => {
let (base, exp) = p.get_base_exp();
let mut new_base = workspace.new_atom();
let mut changed = base.expand_with_ws_into(workspace, var, &mut new_base);
let mut new_exp = workspace.new_atom();
changed |= exp.expand_with_ws_into(workspace, var, &mut new_exp);
let (negative, num) = 'get_num: {
if let AtomView::Num(n) = new_exp.as_view()
&& let CoefficientView::Natural(n, 1, 0, 1) = n.get_coeff_view()
&& n.unsigned_abs() <= u32::MAX as u64
{
break 'get_num (n < 0, n.unsigned_abs() as u32);
}
let mut pow_h = workspace.new_atom();
let pow = pow_h.to_pow(new_base.as_view(), new_exp.as_view());
pow.set_normalized(!changed);
pow_h.as_view().normalize(workspace, out);
return changed;
};
if let AtomView::Add(a) = new_base.as_view() {
let mut rest_buffer = workspace.new_atom();
let mut args: SmallVec<[AtomView; 10]> = SmallVec::with_capacity(a.get_nargs());
let mut rest_args: SmallVec<[AtomView; 10]> = SmallVec::new();
for arg in a {
if let Some(s) = var
&& !arg.contains_literally_or_as_symbol(s)
{
rest_args.push(arg);
} else {
args.push(arg);
}
}
if rest_args.len() == 1 {
args.push(rest_args[0]);
} else if rest_args.len() > 1 {
let add = rest_buffer.to_add();
for arg in rest_args {
add.extend(arg);
}
add.set_normalized(true); args.push(rest_buffer.as_view());
}
let mut add_h = workspace.new_atom();
let add = add_h.to_add();
let mut ci = CombinationWithReplacementIterator::new(args.len(), num);
while let Some(new_term) = ci.next() {
let mut hh = workspace.new_atom();
let p = hh.to_mul();
let mut hhh = workspace.new_atom();
for (a, pow) in args.iter().zip(new_term) {
if *pow != 0 {
let mut new_exp_h = workspace.new_atom();
new_exp_h.to_num(*pow as i64);
hhh.to_pow(*a, new_exp_h.as_view());
p.extend(hhh.as_view());
}
}
let mut normalized_child = workspace.new_atom();
hh.as_view().normalize(workspace, &mut normalized_child);
let mut expanded_child = workspace.new_atom();
normalized_child.as_view().expand_with_ws_into(
workspace,
var,
&mut expanded_child,
);
let coeff_f = Integer::multinom(new_term);
if coeff_f != Integer::one() {
let mut coeff_h = workspace.new_atom();
coeff_h.to_num(coeff_f);
if let Atom::Mul(m) = expanded_child.deref_mut() {
m.extend(coeff_h.as_view());
add.extend(expanded_child.as_view());
} else {
let mut mul_h = workspace.new_atom();
let mul = mul_h.to_mul();
mul.extend(expanded_child.as_view());
mul.extend(coeff_h.as_view());
add.extend(mul_h.as_view());
}
} else {
add.extend(expanded_child.as_view());
}
}
if negative {
let mut num_h = workspace.new_atom();
num_h.to_num(-1i64);
let mut pow_h = workspace.new_atom();
pow_h.to_pow(add_h.as_view(), num_h.as_view());
pow_h.as_view().normalize(workspace, out);
} else {
add_h.as_view().normalize(workspace, out);
}
true
} else if let AtomView::Mul(m) = new_base.as_view() {
let mut mul_h = workspace.new_atom();
let mul = mul_h.to_mul();
let mut exp_h = workspace.new_atom();
if negative {
exp_h.to_num(-(num as i64));
} else {
exp_h.to_num(num as i64);
}
for arg in m {
let mut pow_h = workspace.new_atom();
pow_h.to_pow(arg, exp_h.as_view());
mul.extend(pow_h.as_view());
}
mul_h.as_view().normalize(workspace, out);
true
} else {
let mut pow_h = workspace.new_atom();
let pow = pow_h.to_pow(new_base.as_view(), new_exp.as_view());
pow.set_normalized(!changed);
pow_h.as_view().normalize(workspace, out);
changed
}
}
AtomView::Mul(m) => {
let mut changed = false;
let mut sum: SmallVec<[RecycledAtom; 10]> = SmallVec::new();
let mut new_sum: SmallVec<[RecycledAtom; 10]> = SmallVec::new();
for arg in m {
let mut new_arg = workspace.new_atom();
changed |= arg.expand_with_ws_into(workspace, var, &mut new_arg);
let contains_var = if let Some(s) = var
&& !new_arg.as_view().contains_literally_or_as_symbol(s)
{
false
} else {
true
};
if let AtomView::Add(a) = new_arg.as_view()
&& contains_var
{
changed = true;
for child in a {
for s in &sum {
let mut b = workspace.new_atom();
b.set_from_view(&s.as_view());
if let Atom::Mul(m) = b.deref_mut() {
m.extend(child);
new_sum.push(b);
} else {
let mut mul_h = workspace.new_atom();
let mul = mul_h.to_mul();
mul.extend(b.as_view());
mul.extend(child);
new_sum.push(mul_h);
}
}
if sum.is_empty() {
let mut b = workspace.new_atom();
b.set_from_view(&child);
new_sum.push(b);
}
}
std::mem::swap(&mut sum, &mut new_sum);
new_sum.clear();
} else if sum.is_empty() {
sum.push(new_arg);
} else {
for summand in &mut sum {
if let Atom::Mul(m) = summand.deref_mut() {
m.extend(new_arg.as_view());
} else {
let mut mul_h = workspace.new_atom();
let mul = mul_h.to_mul();
mul.extend(summand.as_view());
mul.extend(new_arg.as_view());
*summand = mul_h;
}
}
}
}
if !changed {
out.set_from_view(self);
return false;
}
debug_assert!(!sum.is_empty());
if sum.len() == 1 {
sum[0].as_view().normalize(workspace, out);
} else {
let add = out.to_add();
for x in sum {
add.extend(x.as_view());
}
}
changed
}
AtomView::Add(a) => {
let mut changed = false;
let add = out.to_add();
let mut new_arg = workspace.new_atom();
for arg in a {
changed |= arg.expand_no_norm(workspace, var, &mut new_arg);
add.extend(new_arg.as_view());
}
add.set_normalized(!changed);
changed
}
_ => {
out.set_from_view(self);
false
}
}
}
pub(crate) fn expand_num(&self) -> Atom {
let mut a = Atom::new();
Workspace::get_local().with(|ws| {
self.expand_num_impl(ws, &mut a);
});
a
}
pub(crate) fn expand_num_into(&self, out: &mut Atom) {
Workspace::get_local().with(|ws| {
self.expand_with_ws_into(ws, None, out);
})
}
pub(crate) fn expand_num_impl(&self, ws: &Workspace, out: &mut Atom) -> bool {
match self {
AtomView::Num(_) | AtomView::Var(_) | AtomView::Fun(_) => {
out.set_from_view(self);
false
}
AtomView::Pow(pow_view) => {
let (base, exp) = pow_view.get_base_exp();
let mut new_base = ws.new_atom();
let mut changed = base.expand_num_impl(ws, &mut new_base);
let mut new_exp = ws.new_atom();
changed |= exp.expand_num_impl(ws, &mut new_exp);
let mut pow_h = ws.new_atom();
pow_h.to_pow(new_base.as_view(), new_exp.as_view());
pow_h.as_view().normalize(ws, out);
changed
}
AtomView::Mul(mul_view) => {
let mut changed = false;
let mut new_mul = ws.new_atom();
let m = new_mul.to_mul();
let mut new_arg = ws.new_atom();
for arg in mul_view {
changed |= arg.expand_num_impl(ws, &mut new_arg);
m.extend(new_arg.as_view());
}
if changed {
new_mul.as_view().normalize(ws, &mut new_arg);
new_arg.as_view().expand_num_impl(ws, out);
return true;
}
if !mul_view.has_coefficient()
|| !mul_view.iter().any(|a| matches!(a, AtomView::Add(_)))
{
out.set_from_view(self);
return false;
}
let mut args: Vec<_> = mul_view.iter().collect();
let mut sum = None;
let mut num = None;
args.retain(|a| {
if let AtomView::Add(_) = a {
if sum.is_none() {
sum = Some(*a);
false
} else {
true
}
} else if let AtomView::Num(_) = a {
if num.is_none() {
num = Some(*a);
false
} else {
true
}
} else {
true
}
});
let mut add = ws.new_atom();
let add_view = add.to_add();
let n = num.unwrap();
let mut m = ws.new_atom();
if let AtomView::Add(sum) = sum.unwrap() {
for a in sum.iter() {
let mm = m.to_mul();
mm.extend(a);
mm.extend(n);
add_view.extend(m.as_view());
}
}
add_view.as_view().normalize(ws, &mut m);
let m2 = add.to_mul();
for a in args {
m2.extend(a);
}
m2.extend(m.as_view());
m2.as_view().normalize(ws, out);
true
}
AtomView::Add(add_view) => {
let mut changed = false;
let mut new = ws.new_atom();
let add = new.to_add();
let mut new_arg = ws.new_atom();
for arg in add_view {
changed |= arg.expand_num_impl(ws, &mut new_arg);
add.extend(new_arg.as_view());
}
if !changed {
out.set_from_view(self);
return false;
}
new.as_view().normalize(ws, out);
true
}
}
}
}
#[cfg(test)]
mod test {
use crate::atom::AtomCore;
use crate::{parse, symbol};
#[test]
fn expand_num() {
let exp = parse!("5+2*v3*(v1-v2)*(v4+v5)").expand_num();
let res = parse!("5+v3*(v4+v5)*(2*v1-2*v2)");
assert_eq!(exp, res);
}
#[test]
fn exponent() {
let exp = parse!("(1+v1+v2)^4").expand();
let res = parse!(
"4*v1+4*v2+6*v1^2+4*v1^3+v1^4+6*v2^2+4*v2^3+v2^4+12*v1*v2+12*v1*v2^2+4*v1*v2^3+12*v1^2*v2+6*v1^2*v2^2+4*v1^3*v2+1"
);
assert_eq!(exp, res);
}
#[test]
fn association() {
let exp = parse!("(1+v1)*(2+v2)*(3+v1)").expand();
let res = parse!("8*v1+3*v2+2*v1^2+4*v1*v2+v1^2*v2+6");
assert_eq!(exp, res);
}
#[test]
fn mul_pow() {
let exp = parse!("(v1*v2*2)^3*2").expand();
let res = parse!("v1^3*v2^3*16");
assert_eq!(exp, res);
}
#[test]
fn mul_pow_neg() {
let exp = parse!("(v1*v2*2)^-3").expand();
let res = parse!("8^-1*v1^-3*v2^-3");
assert_eq!(exp, res);
}
#[test]
fn expand_in_var() {
let exp = parse!("(1+v1)^2+(1+v2)^100").expand_in(symbol!("v1"));
let res = parse!("1+2*v1+v1^2+(v2+1)^100");
assert_eq!(exp, res);
}
#[test]
fn expand_with_poly() {
let exp = parse!("(1+v1)^2+(1+v2)^100").expand_via_poly::<u16, _>(parse!("v1"));
let res = parse!("1+2*v1+v1^2+(v2+1)^100");
assert_eq!(exp, res);
}
}