///|
/// - Does: Runs the package-level simplify pipeline on one expression.
/// - Input: One `Expr`, optional `SimplifyPlan`, and optional `max_passes`.
/// - Returns: One rewritten `Expr`.
/// - Limits: The pipeline is heuristic and bounded by `max_passes`, so difficult expressions can remain only partially simplified.
pub fn simplify(
  expr : Expr,
  plan? : SimplifyPlan = SimplifyPlan::Default,
  max_passes? : Int = 8,
) -> Expr {
  let passes = if max_passes <= 0 { 1 } else { max_passes }
  simplify_deep(expr, plan, passes)
}

///|
fn simplify_deep(expr : Expr, plan : SimplifyPlan, max_passes : Int) -> Expr {
  if is_atom(expr) {
    return expr
  }
  let with_children = @symcore.map_children(expr, child => {
    simplify_deep(child, plan, max_passes)
  })
  simplify_fixed_point(with_children, plan, max_passes)
}

///|
fn simplify_fixed_point(
  expr : Expr,
  plan : SimplifyPlan,
  max_passes : Int,
) -> Expr {
  let mut cur = expr
  for _ in 0.. Expr {
  let patterns = plan_patterns(plan)
  let mut best = expr

  let canonical_signs = collect_abs(signsimp(best, max_passes=1))
  best = shorter_expr(best, canonical_signs)

  let local_patterns = apply_patterns_once(best, patterns)
  best = shorter_expr(best, local_patterns)

  if expr_has_distributable_mul(best) {
    let expanded = expand_mul_expr(best)
    let expanded_local = apply_patterns_once(expanded, patterns)
    best = shorter_expr(best, expanded_local)
    best = shorter_expr(best, simplify_rational_structure(expanded_local))
  }

  let factored = factor_terms_simple(best)
  best = shorter_expr(best, factored)

  let factored_consts = collect_const(factored)
  best = shorter_expr(best, factored_consts)

  let structured_rational = simplify_rational_structure(best)
  best = shorter_expr(best, structured_rational)

  let powers = powsimp(best, max_passes=1)
  best = shorter_expr(best, powers)
  best = shorter_expr(best, powdenest(best, max_passes=1))

  let rational_candidate = ratsimp(best, max_passes=1)
  best = shorter_expr(best, rational_candidate)
  best = shorter_expr(best, simplify_rational_structure(best))

  if expr_has_radicals(best) {
    best = shorter_expr(best, radsimp(best, max_passes=1))
    best = shorter_expr(best, sqrtdenest(best, max_passes=1))
  }

  if expr_has_trig_or_hyper(best) {
    best = shorter_expr(best, trigsimp(best, max_passes=1))
    best = shorter_expr(best, trigsimp(factor_terms_simple(best), max_passes=1))
  }

  if expr_has_exp_trig_or_hyper(best) {
    best = shorter_expr(best, exptrigsimp(best, max_passes=1))
  }

  if expr_has_logs(best) {
    best = shorter_expr(best, logcombine(best, max_passes=1))
  }

  if expr_has_gamma_or_combinatorics(best) {
    best = shorter_expr(best, gammasimp(best, max_passes=1))
    best = shorter_expr(best, combsimp(best, max_passes=1))
  }

  if expr_has_hypergeometric(best) {
    best = shorter_expr(best, hyperexpand(best, max_passes=1))
  }

  if expr_has_kronecker_delta(best) {
    best = shorter_expr(best, kroneckersimp(best, max_passes=1))
  }

  if expr_has_bessel(best) {
    best = shorter_expr(best, besselsimp(best, max_passes=1))
  }

  let finalized = apply_patterns_once(factor_terms_simple(best), patterns)
  shorter_expr(best, collect_abs(signsimp(finalized, max_passes=1)))
}

///|
fn shorter_expr(lhs : Expr, rhs : Expr) -> Expr {
  if expr_complexity(rhs) < expr_complexity(lhs) {
    rhs
  } else {
    lhs
  }
}

///|
fn expr_complexity(expr : Expr) -> Int {
  node_count(expr) * 32 + to_repr(expr).to_string().length()
}

///|
fn simplify_rational_structure(expr : Expr) -> Expr {
  let (num, den) = fraction(expr)
  if den == int(1) {
    return expr
  }
  let simp_num = simplify_rational_part(num)
  let simp_den = simplify_rational_part(den)
  let base = cancel_common_factors_simple(simp_num, simp_den)
  let expanded_num = simplify_rational_part(expand_mul_expr(simp_num))
  let expanded_den = simplify_rational_part(expand_mul_expr(simp_den))
  let expanded = cancel_common_factors_simple(expanded_num, expanded_den)
  shorter_expr(base, expanded)
}

///|
fn simplify_rational_part(expr : Expr) -> Expr {
  let mut out = factor_terms_simple(expr)
  if expr_has_distributable_mul(out) {
    out = shorter_expr(out, factor_terms_simple(expand_mul_expr(out)))
  }
  out = shorter_expr(out, collect_const(out))
  if expr_has_trig_or_hyper(out) {
    out = shorter_expr(out, trigsimp(out, max_passes=1))
  }
  if expr_has_logs(out) {
    out = shorter_expr(out, logcombine(out, max_passes=1))
  }
  out
}

///|
fn cancel_common_factors_simple(num : Expr, den : Expr) -> Expr {
  let nfs = flatten_mul_nonunit(num)
  let dfs = flatten_mul_nonunit(den)
  let den_count : Map[String, Int] = {}
  let den_factor : Map[String, Expr] = {}
  for factor in dfs {
    let key = to_repr(factor).to_string()
    den_factor[key] = factor
    match den_count.get(key) {
      Some(count) => den_count[key] = count + 1
      None => den_count[key] = 1
    }
  }
  let kept_num : Array[Expr] = Array::new()
  for factor in nfs {
    let key = to_repr(factor).to_string()
    match den_count.get(key) {
      Some(count) if count > 0 => den_count[key] = count - 1
      _ => kept_num.push(factor)
    }
  }
  let kept_den : Array[Expr] = Array::new()
  for key, count in den_count {
    for _ in 0.. Expr {
  match expr {
    Expr::Add(_) => factor_add_terms(expr)
    _ => expr
  }
}

///|
/// - Does: Factors common multiplicative terms out of additive expressions.
/// - Input: Any `Expr`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Only the implemented additive factoring cases are handled; non-additive inputs are returned unchanged.
pub fn factor_terms(expr : Expr) -> Expr {
  factor_terms_simple(expr)
}

///|
fn factor_add_terms(expr : Expr) -> Expr {
  let canonical = powsimp(expr, max_passes=1)
  match canonical {
    Expr::Add(args) => {
      if args.length() < 2 {
        return canonical
      }

      let sign_factored = factor_negative_add_sign(canonical)
      if sign_factored != canonical {
        return factor_mul_add_factors(sign_factored)
      }

      let const_factored = collect_const(canonical)
      if const_factored != canonical {
        return factor_mul_add_factors(const_factored)
      }

      match factor_special_add(canonical) {
        Some(special) => return special
        None => ()
      }

      let term_factors : Array[Array[Expr]] = Array::new()
      for arg in args {
        term_factors.push(flatten_mul_exact(arg))
      }

      let common_counts : Map[String, Int] = {}
      let common_exprs : Map[String, Expr] = {}
      for factor in term_factors[0] {
        if is_numeric_factor(factor) {
          continue
        }
        let key = to_repr(factor).to_string()
        common_exprs[key] = factor
        match common_counts.get(key) {
          Some(count) => common_counts[key] = count + 1
          None => common_counts[key] = 1
        }
      }

      for i in 1.. counts[key] = count + 1
            None => counts[key] = 1
          }
        }
        let common_keys : Array[String] = Array::new()
        for key, _ in common_counts {
          common_keys.push(key)
        }
        for key in common_keys {
          let count = common_counts[key]
          let next_count = match counts.get(key) {
            Some(other_count) =>
              if other_count < count {
                other_count
              } else {
                count
              }
            None => 0
          }
          common_counts[key] = next_count
        }
      }

      let common_factors : Array[Expr] = Array::new()
      for key, count in common_counts {
        if count <= 0 {
          continue
        }
        for _ in 0.. 0 {
            needed[key] = count
          }
        }
        let remaining : Array[Expr] = Array::new()
        for factor in factors {
          if is_numeric_factor(factor) {
            remaining.push(factor)
            continue
          }
          let key = to_repr(factor).to_string()
          match needed.get(key) {
            Some(count) if count > 0 => needed[key] = count - 1
            _ => remaining.push(factor)
          }
        }
        remainders.push(
          if remaining.is_empty() {
            int(1)
          } else {
            @symcore.mul(remaining)
          },
        )
      }

      let remainder = factor_add_terms(@symcore.add(remainders))
      @symcore.mul([@symcore.mul(common_factors), remainder])
    }
    _ => canonical
  }
}

///|
fn factor_negative_add_sign(expr : Expr) -> Expr {
  match expr {
    Expr::Add(args) => {
      if args.is_empty() {
        return expr
      }
      for arg in args {
        let (coeff, _) = split_add_term(arg)
        if coeff.compare(@symnum.BigRational::zero()) >= 0 {
          return expr
        }
      }
      let flipped = args.map(arg => @symcore.mul([int(-1), arg]))
      @symcore.mul([int(-1), @symcore.add(flipped)])
    }
    _ => expr
  }
}

///|
fn factor_mul_add_factors(expr : Expr) -> Expr {
  match expr {
    Expr::Mul(args) => {
      let out : Array[Expr] = Array::new()
      let mut changed = false
      for arg in args {
        match arg {
          Expr::Add(_) => {
            let factored = factor_add_terms(arg)
            out.push(factored)
            changed = changed || factored != arg
          }
          _ => out.push(arg)
        }
      }
      if changed {
        @symcore.mul(out)
      } else {
        expr
      }
    }
    _ => expr
  }
}

///|
fn factor_special_add(expr : Expr) -> Expr? {
  match try_factor_perfect_square(expr) {
    Some(factored) => Some(factored)
    None => try_factor_difference_of_squares(expr)
  }
}

///|
fn try_factor_perfect_square(expr : Expr) -> Expr? {
  let terms = match expr {
    Expr::Add(args) if args.length() == 3 => args
    _ => return None
  }
  for i in 0..
          match exact_square_root(terms[j]) {
            Some(b) =>
              match match_perfect_square_cross(terms[k], a, b) {
                Some(sign) => {
                  let binomial = if sign > 0 {
                    @symcore.add([a, b])
                  } else {
                    @symcore.add([a, @symcore.mul([int(-1), b])])
                  }
                  return Some(@symcore.pow(binomial, int(2)))
                }
                None => ()
              }
            None => ()
          }
        None => ()
      }
    }
  }
  None
}

///|
fn try_factor_difference_of_squares(expr : Expr) -> Expr? {
  let terms = match expr {
    Expr::Add(args) if args.length() == 2 => args
    _ => return None
  }
  for i in 0..
        match exact_negative_square_root(terms[j]) {
          Some(b) =>
            return Some(
              @symcore.mul([
                @symcore.add([a, @symcore.mul([int(-1), b])]),
                @symcore.add([a, b]),
              ]),
            )
          None => ()
        }
      None => ()
    }
  }
  None
}

///|
fn exact_square_root(term : Expr) -> Expr? {
  match term {
    Expr::Pow(base, Expr::Number(exp)) =>
      if exp.compare(@symnum.BigRational::from_int(2)) == 0 {
        Some(base)
      } else {
        None
      }
    Expr::Number(n) =>
      match rational_sqrt(n) {
        Some(root) => Some(@symcore.Expr::Number(root))
        None => None
      }
    _ => None
  }
}

///|
fn exact_negative_square_root(term : Expr) -> Expr? {
  match term {
    Expr::Mul(args) if args.length() == 2 => {
      let mut saw_neg_one = false
      let mut square : Expr? = None
      for arg in args {
        match arg {
          Expr::Number(c) if c.compare(@symnum.BigRational::from_int(-1)) == 0 =>
            saw_neg_one = true
          _ => if square is None { square = Some(arg) } else { return None }
        }
      }
      if saw_neg_one {
        match square {
          Some(value) => exact_square_root(value)
          None => None
        }
      } else {
        None
      }
    }
    Expr::Number(n) =>
      if n.compare(@symnum.BigRational::zero()) < 0 {
        exact_square_root(@symcore.Expr::Number(n.neg_r()))
      } else {
        None
      }
    _ => None
  }
}

///|
fn match_perfect_square_cross(term : Expr, a : Expr, b : Expr) -> Int? {
  if term == @symcore.mul([int(2), a, b]) {
    return Some(1)
  }
  if term == @symcore.mul([int(-2), a, b]) {
    return Some(-1)
  }
  None
}

///|
fn flatten_mul_exact(expr : Expr) -> Array[Expr] {
  match expr {
    Expr::Mul(args) => {
      let out : Array[Expr] = Array::new()
      for arg in args {
        for factor in expand_factorable_term(arg) {
          out.push(factor)
        }
      }
      out
    }
    _ => expand_factorable_term(expr)
  }
}

///|
fn flatten_mul_nonunit(expr : Expr) -> Array[Expr] {
  match expr {
    Expr::Mul(args) => {
      let out : Array[Expr] = Array::new()
      for arg in args {
        for factor in expand_factorable_term(arg) {
          if factor != int(1) {
            out.push(factor)
          }
        }
      }
      out
    }
    Expr::Number(n) if n.is_one() => []
    _ => expand_factorable_term(expr)
  }
}

///|
fn expand_factorable_term(expr : Expr) -> Array[Expr] {
  match expr {
    Expr::Pow(base, Expr::Number(exp)) if exp.is_integral() => {
      let power = exp.numerator().to_int()
      if power > 1 && power <= 32 {
        let out : Array[Expr] = Array::new()
        for _ in 0.. [expr]
  }
}

///|
fn is_numeric_factor(expr : Expr) -> Bool {
  expr is Expr::Number(_)
}

///|
fn expr_has_logs(expr : Expr) -> Bool {
  expr_contains_function(expr, name => name == "log")
}

///|
fn expr_has_trig_or_hyper(expr : Expr) -> Bool {
  expr_contains_function(expr, name => {
    is_trig_name(name) ||
    name == "sinh" ||
    name == "cosh" ||
    name == "tanh" ||
    name == "coth" ||
    name == "sech" ||
    name == "csch"
  })
}

///|
fn expr_has_exp_trig_or_hyper(expr : Expr) -> Bool {
  expr_contains_function(expr, name => {
    name == "exp" ||
    is_trig_name(name) ||
    name == "sinh" ||
    name == "cosh" ||
    name == "tanh" ||
    name == "coth" ||
    name == "sech" ||
    name == "csch"
  })
}

///|
fn expr_has_gamma_or_combinatorics(expr : Expr) -> Bool {
  expr_contains_function(expr, name => {
    name == "gamma" ||
    name == "factorial" ||
    name == "factorial2" ||
    name == "binomial"
  })
}

///|
fn expr_has_hypergeometric(expr : Expr) -> Bool {
  expr_contains_function(expr, name => name == "hyper")
}

///|
fn expr_has_radicals(expr : Expr) -> Bool {
  match expr {
    _ if @symcore.application_has_name(expr, "sqrt", arity=1) => true
    Expr::Pow(_, Expr::Number(exp)) if is_half(exp) => true
    _ => {
      for child in @symcore.children(expr) {
        if expr_has_radicals(child) {
          return true
        }
      }
      false
    }
  }
}

///|
fn expr_has_kronecker_delta(expr : Expr) -> Bool {
  expr_contains_function(expr, name => name == "KroneckerDelta")
}

///|
fn expr_has_bessel(expr : Expr) -> Bool {
  expr_contains_function(expr, name => {
    name == "besselj" ||
    name == "bessely" ||
    name == "besseli" ||
    name == "besselk"
  })
}

///|
fn expr_has_distributable_mul(expr : Expr) -> Bool {
  match expr {
    Expr::Mul(args) => {
      for arg in args {
        if arg is Expr::Add(_) {
          return true
        }
      }
      for arg in args {
        if expr_has_distributable_mul(arg) {
          return true
        }
      }
      false
    }
    _ => {
      for child in @symcore.children(expr) {
        if expr_has_distributable_mul(child) {
          return true
        }
      }
      false
    }
  }
}

///|
fn expr_contains_function(expr : Expr, predicate : (String) -> Bool) -> Bool {
  match expr {
    _ =>
      match @symcore.application_name(expr) {
        Some(name) if predicate(name) => true
        _ => {
          for child in @symcore.children(expr) {
            if expr_contains_function(child, predicate) {
              return true
            }
          }
          false
        }
      }
  }
}

///|
/// - Does: Combines powers with compatible bases and exponents.
/// - Input: Any `Expr` plus optional `max_passes`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Only the pattern-driven power rules in this package are applied.
pub fn powsimp(expr : Expr, max_passes? : Int = 6) -> Expr {
  simplify_with_patterns(
    expr,
    [
      SimplifyPattern::FoldConstants,
      SimplifyPattern::PowDenest,
      SimplifyPattern::MulLikeBases,
      SimplifyPattern::FoldConstants,
    ],
    max_passes~,
  )
}

///|
/// - Does: Simplifies trigonometric expressions with the package's supported trig identities.
/// - Input: Any `Expr` plus optional `max_passes`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Only the implemented trig and hyperbolic identities are used.
pub fn trigsimp(expr : Expr, max_passes? : Int = 6) -> Expr {
  simplify_with_patterns(
    expr,
    [
      SimplifyPattern::FoldConstants,
      SimplifyPattern::FunctionIdentities,
      SimplifyPattern::TrigPythagorean,
      SimplifyPattern::FoldConstants,
    ],
    max_passes~,
  )
}

///|
/// - Does: Normalizes signs in additive and multiplicative expressions.
/// - Input: Any `Expr` plus optional `max_passes`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Uses the package's local canonical-sign rules and does not infer extra assumptions.
pub fn signsimp(expr : Expr, max_passes? : Int = 6) -> Expr {
  simplify_with_patterns(
    expr,
    [
      SimplifyPattern::FoldConstants,
      SimplifyPattern::AddLikeTerms,
      SimplifyPattern::FoldConstants,
    ],
    max_passes~,
  )
}

///|
/// - Does: Runs a custom ordered list of simplify patterns for a bounded number of passes.
/// - Input: One `Expr`, one `Array[SimplifyPattern]`, and optional `max_passes`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Runs exactly the provided pattern list and stops after the bounded pass count.
pub fn simplify_with_patterns(
  expr : Expr,
  patterns : Array[SimplifyPattern],
  max_passes? : Int = 8,
) -> Expr {
  let passes = if max_passes <= 0 { 1 } else { max_passes }
  let mut cur = expr
  for _ in 0.. Expr {
  let mut cur = expr
  for pattern in patterns {
    cur = rewrite_bottom_up(cur, pattern)
  }
  cur
}

///|
fn rewrite_bottom_up(expr : Expr, pattern : SimplifyPattern) -> Expr {
  let rewritten = @symcore.map_children(expr, child => {
    rewrite_bottom_up(child, pattern)
  })
  apply_pattern(pattern, rewritten)
}

///|
/// - Does: Applies one simplify pattern to the current node only.
/// - Input: One `SimplifyPattern` and one `Expr`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Child traversal is handled by higher-level callers, not by this front door.
pub fn apply_pattern(pattern : SimplifyPattern, expr : Expr) -> Expr {
  match pattern {
    SimplifyPattern::FoldConstants => fold_constants(expr)
    SimplifyPattern::AddLikeTerms => add_like_terms(expr)
    SimplifyPattern::MulLikeBases => mul_like_bases(expr)
    SimplifyPattern::PowDenest => pow_denest(expr)
    SimplifyPattern::TrigPythagorean => trig_pythagorean(expr)
    SimplifyPattern::FunctionIdentities => function_identities(expr)
  }
}

///|
fn fold_constants(expr : Expr) -> Expr {
  match expr {
    Expr::Add(args) => @symcore.add(args)
    Expr::Mul(args) => @symcore.mul(args)
    Expr::Pow(base, exp) => @symcore.pow(base, exp)
    _ => expr
  }
}

///|
priv struct AddTerm {
  base : Expr
  coeff : @symnum.BigRational
}

///|
fn add_like_terms(expr : Expr) -> Expr {
  match expr {
    Expr::Add(args) => {
      let minus_one = @symnum.BigRational::from_int(-1)
      let mut const_term = @symnum.BigRational::zero()
      let terms : Map[String, AddTerm] = {}
      for arg in args {
        let (coeff, base_opt) = split_add_term(arg)
        match base_opt {
          None => const_term = const_term.add_r(coeff)
          Some(base_expr) => {
            let key = to_repr(base_expr).to_string()
            match terms.get(key) {
              Some(acc) =>
                terms[key] = AddTerm::{
                  base: acc.base,
                  coeff: acc.coeff.add_r(coeff),
                }
              None => terms[key] = AddTerm::{ base: base_expr, coeff }
            }
          }
        }
      }
      let out : Array[Expr] = Array::new()
      for _, acc in terms {
        if acc.coeff.is_zero() {
          continue
        }
        if acc.coeff.is_one() {
          out.push(acc.base)
        } else if acc.coeff.compare(minus_one) == 0 {
          out.push(@symcore.mul([int(-1), acc.base]))
        } else {
          out.push(@symcore.mul([@symcore.Expr::Number(acc.coeff), acc.base]))
        }
      }
      if !const_term.is_zero() {
        out.push(@symcore.Expr::Number(const_term))
      }
      @symcore.add(out)
    }
    _ => expr
  }
}

///|
fn split_add_term(term : Expr) -> (@symnum.BigRational, Expr?) {
  match term {
    Expr::Number(n) => (n, None)
    Expr::Mul(args) => {
      let mut coeff = @symnum.BigRational::one()
      let factors : Array[Expr] = Array::new()
      for arg in args {
        match arg {
          Expr::Number(n) => coeff = coeff.mul_r(n)
          _ => factors.push(arg)
        }
      }
      if factors.is_empty() {
        (coeff, None)
      } else if factors.length() == 1 {
        (coeff, Some(factors[0]))
      } else {
        (coeff, Some(@symcore.mul(factors)))
      }
    }
    _ => (@symnum.BigRational::one(), Some(term))
  }
}

///|
priv struct BasePow {
  base : Expr
  exp : @symnum.BigRational
}

///|
fn mul_like_bases(expr : Expr) -> Expr {
  match expr {
    Expr::Mul(args) => {
      let mut const_factor = @symnum.BigRational::one()
      let powers : Map[String, BasePow] = {}
      for arg in args {
        match arg {
          Expr::Number(n) => const_factor = const_factor.mul_r(n)
          Expr::Pow(base, Expr::Number(exp)) => {
            let key = to_repr(base).to_string()
            match powers.get(key) {
              Some(acc) =>
                powers[key] = BasePow::{
                  base: acc.base,
                  exp: acc.exp.add_r(exp),
                }
              None => powers[key] = BasePow::{ base, exp }
            }
          }
          _ => {
            let key = to_repr(arg).to_string()
            match powers.get(key) {
              Some(acc) =>
                powers[key] = BasePow::{
                  base: acc.base,
                  exp: acc.exp.add_r(@symnum.BigRational::one()),
                }
              None =>
                powers[key] = BasePow::{
                  base: arg,
                  exp: @symnum.BigRational::one(),
                }
            }
          }
        }
      }
      if const_factor.is_zero() {
        return int(0)
      }
      let factors : Array[Expr] = Array::new()
      for _, acc in powers {
        if acc.exp.is_zero() {
          continue
        }
        if acc.exp.is_one() {
          factors.push(acc.base)
        } else {
          factors.push(@symcore.pow(acc.base, @symcore.Expr::Number(acc.exp)))
        }
      }
      if factors.is_empty() || !const_factor.is_one() {
        factors.push(@symcore.Expr::Number(const_factor))
      }
      @symcore.mul(factors)
    }
    _ => expr
  }
}

///|
fn pow_denest(expr : Expr) -> Expr {
  match expr {
    Expr::Pow(Expr::Pow(base, inner_exp), outer_exp) =>
      match (exact_numeric_value(inner_exp), exact_numeric_value(outer_exp)) {
        (Some(a), Some(b)) =>
          @symcore.pow(base, @symcore.Expr::Number(a.mul_r(b)))
        _ => expr
      }
    Expr::Pow(base, exp) =>
      match (exact_numeric_value(base), exact_numeric_value(exp)) {
        (Some(base_value), Some(exp_value)) =>
          match eval_numeric_pow(base_value, exp_value) {
            Some(v) => numeric_result_like(expr, @symcore.Expr::Number(v))
            None =>
              match float_source_precision(expr) {
                Some(prec) => {
                  let evaled = @symcore.evalf(expr, prec~)
                  if evaled == expr {
                    expr
                  } else {
                    evaled
                  }
                }
                None => expr
              }
          }
        _ => expr
      }
    _ => expr
  }
}

///|
fn eval_numeric_pow(
  base : @symnum.BigRational,
  exp : @symnum.BigRational,
) -> @symnum.BigRational? {
  if !exp.is_integral() {
    return None
  }
  let n = exp.numerator().to_int()
  if n < 0 && base.is_zero() {
    return None
  }
  let abs_n = if n < 0 { -n } else { n }
  let mut out = @symnum.BigRational::one()
  for _ in 0..= 0 {
    Some(out)
  } else {
    match
      (
        try? @symnum.BigRational::one().div_r(out) :
        Result[@symnum.BigRational, @symnum.RationalError]) {
      Ok(value) => Some(value)
      Err(_) => None
    }
  }
}

///|
fn function_identities(expr : Expr) -> Expr {
  match named_unary_application(expr) {
    Some((name, arg)) =>
      match exact_numeric_value(arg) {
        Some(numeric) =>
          match name {
            "sin" | "tan" if numeric.is_zero() =>
              numeric_result_like(arg, int(0))
            "cos" | "exp" if numeric.is_zero() =>
              numeric_result_like(arg, int(1))
            "log" if numeric.is_one() => numeric_result_like(arg, int(0))
            "sqrt" if numeric.is_zero() => numeric_result_like(arg, int(0))
            "sqrt" if numeric.is_one() => numeric_result_like(arg, int(1))
            "Abs" | "abs" =>
              if numeric.compare(@symnum.BigRational::zero()) < 0 {
                numeric_result_like(arg, @symcore.Expr::Number(numeric.neg_r()))
              } else {
                numeric_result_like(arg, @symcore.Expr::Number(numeric))
              }
            _ =>
              match (name, arg) {
                ("log", Expr::Symbol(sym_name)) if sym_name == "E" => int(1)
                _ => expr
              }
          }
        None =>
          match (name, arg) {
            ("log", Expr::Symbol(sym_name)) if sym_name == "E" => int(1)
            _ => expr
          }
      }
    _ => expr
  }
}

///|
fn trig_pythagorean(expr : Expr) -> Expr {
  match expr {
    Expr::Add(args) => {
      let used : Array[Bool] = Array::make(args.length(), false)
      let out : Array[Expr] = Array::new()
      let mut changed = false
      for i in 0.. {
              used[i] = true
              used[j] = true
              out.push(int(1))
              changed = true
              paired = true
              break
            }
            _ => ()
          }
          match (cos_inner, trig_square_arg(args[j], "sin")) {
            (Some(lhs), Some(rhs)) if lhs == rhs => {
              used[i] = true
              used[j] = true
              out.push(int(1))
              changed = true
              paired = true
              break
            }
            _ => ()
          }
        }
        if !paired {
          used[i] = true
          out.push(arg)
        }
      }
      if changed {
        @symcore.add(out)
      } else {
        expr
      }
    }
    _ => expr
  }
}

///|
fn trig_square_arg(term : Expr, name : String) -> Expr? {
  match pow_named_unary_application(term) {
    Some((func, arg, Expr::Number(exp))) =>
      if func == name && exp.compare(@symnum.BigRational::from_int(2)) == 0 {
        Some(arg)
      } else {
        None
      }
    _ => None
  }
}