use num_bigint::BigInt;
use num_rational::Ratio;
use num_traits::Signed;
use smallvec::SmallVec;
use crate::base::arena::Arena;
use crate::base::node::{ExprId, ExprNode};
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum DisplayCategory {
Polynomial,
Function,
Constant,
}
pub(crate) fn display_sort_key(
arena: &Arena,
id: ExprId,
) -> (DisplayCategory, i64, SmallVec<[u8; 24]>, SmallVec<[u8; 24]>) {
let cat = display_category(arena, id);
let degree = estimate_display_degree(arena, id);
let var_key = dominant_var_key(arena, id);
let canon_key = SmallVec::from_slice(arena.sort_key(id).as_bytes());
(cat, -(degree as i64), var_key, canon_key)
}
pub(crate) fn display_category(arena: &Arena, id: ExprId) -> DisplayCategory {
match arena.node(id) {
ExprNode::Num(_)
| ExprNode::Pi
| ExprNode::E
| ExprNode::ImaginaryUnit
| ExprNode::EulerGamma
| ExprNode::Catalan
| ExprNode::GoldenRatio
| ExprNode::PhysicalConstant(_, _)
| ExprNode::BoolTrue
| ExprNode::BoolFalse => DisplayCategory::Constant,
ExprNode::Infinity | ExprNode::NegInfinity | ExprNode::ComplexInfinity | ExprNode::NaN => {
DisplayCategory::Constant
}
ExprNode::Symbol(_) => DisplayCategory::Polynomial,
ExprNode::Pow(base, exp) => {
if matches!(arena.node(*base), ExprNode::Symbol(_))
&& let Some(r) = arena.as_num(*exp)
&& r.is_integer()
{
return DisplayCategory::Polynomial;
}
DisplayCategory::Function
}
ExprNode::Mul(children) => {
for &child in children.iter() {
let child_cat = display_category(arena, child);
if child_cat == DisplayCategory::Polynomial {
return DisplayCategory::Polynomial;
}
}
if children
.iter()
.all(|&c| matches!(arena.node(c), ExprNode::Num(_)))
{
DisplayCategory::Constant
} else {
DisplayCategory::Function
}
}
ExprNode::Neg(inner) => display_category(arena, *inner),
_ => DisplayCategory::Function,
}
}
pub(crate) fn estimate_display_degree(arena: &Arena, id: ExprId) -> u32 {
match arena.node(id) {
ExprNode::Num(_)
| ExprNode::Pi
| ExprNode::E
| ExprNode::ImaginaryUnit
| ExprNode::EulerGamma
| ExprNode::Catalan
| ExprNode::GoldenRatio
| ExprNode::PhysicalConstant(_, _)
| ExprNode::BoolTrue
| ExprNode::BoolFalse
| ExprNode::Infinity
| ExprNode::NegInfinity
| ExprNode::ComplexInfinity
| ExprNode::NaN => 0,
ExprNode::Symbol(_) => 1,
ExprNode::Pow(_base, exp) => {
if let Some(r) = arena.as_num(*exp)
&& r.is_integer()
&& !r.is_negative()
{
return r.to_integer().try_into().unwrap_or(1);
}
1
}
ExprNode::Mul(children) => {
let mut total_degree = 0u32;
for &child in children.iter() {
let d = estimate_display_degree(arena, child);
if d > 0 {
total_degree += d;
}
}
total_degree
}
ExprNode::Neg(inner) => estimate_display_degree(arena, *inner),
_ => 0,
}
}
pub(crate) fn dominant_var_key(arena: &Arena, id: ExprId) -> SmallVec<[u8; 24]> {
match arena.node(id) {
ExprNode::Symbol(_) => SmallVec::from_slice(arena.sort_key(id).as_bytes()),
ExprNode::Pow(base, _) => {
if matches!(arena.node(*base), ExprNode::Symbol(_)) {
SmallVec::from_slice(arena.sort_key(*base).as_bytes())
} else {
SmallVec::new()
}
}
ExprNode::Mul(children) => {
for &child in children.iter() {
let key = dominant_var_key(arena, child);
if !key.is_empty() {
return key;
}
}
SmallVec::new()
}
ExprNode::Neg(inner) => dominant_var_key(arena, *inner),
_ => SmallVec::new(),
}
}
pub(crate) fn is_neg_one_mul(arena: &Arena, id: ExprId) -> bool {
if let ExprNode::Mul(children) = arena.node(id)
&& let Some(&first) = children.first()
&& let ExprNode::Num(nid) = arena.node(first)
{
let r = arena.num(*nid);
return *r == Ratio::from(BigInt::from(-1));
}
false
}
pub(crate) fn is_neg_coeff_mul(arena: &Arena, id: ExprId) -> bool {
if let ExprNode::Mul(children) = arena.node(id)
&& children.len() >= 2
&& let Some(&first) = children.first()
&& let ExprNode::Num(nid) = arena.node(first)
{
let r = arena.num(*nid);
return r.is_negative() && *r != Ratio::from(BigInt::from(-1));
}
false
}
pub(crate) fn extract_negative_power(arena: &Arena, id: ExprId) -> Option<(ExprId, String)> {
if let ExprNode::Pow(base, exp) = arena.node(id)
&& let ExprNode::Num(nid) = arena.node(*exp)
{
let r = arena.num(*nid);
if r.is_negative() {
let pos_r = -r.clone();
if pos_r.is_integer() {
let n = pos_r.to_integer();
return Some((*base, format!("{}", n)));
} else {
return Some((
*base,
format!("\\frac{{{}}}{{{}}}", pos_r.numer(), pos_r.denom()),
));
}
}
}
None
}