///|
/// - Does: Rationalizes supported radical denominators inside one expression.
/// - Input: Any `Expr` plus optional `max_passes`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Only the implemented radical-denominator patterns are handled.
pub fn radsimp(expr : Expr, max_passes? : Int = 6) -> Expr {
  fixpoint_rewrite(expr, rewrite_radsimp, max_passes~)
}

///|
/// - Does: Rewrites additive rational expressions over a common denominator and reduces them.
/// - Input: Any `Expr` plus optional `max_passes`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Uses the local structural fraction splitter rather than a full polynomial-domain algorithm.
pub fn ratsimp(expr : Expr, max_passes? : Int = 6) -> Expr {
  fixpoint_rewrite(expr, rewrite_ratsimp, max_passes~)
}

///|
/// - Does: Exposes the modular rational-simplification front door.
/// - Input: One expression plus optional basis, generators, quick/polynomial flags, and `max_passes`.
/// - Returns: One rewritten `Expr`.
/// - Limits: The current implementation ignores the modular arguments and falls back to `ratsimp`.
pub fn ratsimpmodprime(
  expr : Expr,
  basis? : Array[Expr] = [],
  gens? : Array[Expr] = [],
  quick? : Bool = true,
  polynomial? : Bool = false,
  max_passes? : Int = 6,
) -> Expr {
  let _ = basis
  let _ = gens
  let _ = quick
  let _ = polynomial
  ratsimp(expr, max_passes~)
}

///|
/// - Does: Collects additive terms that share square-root factors.
/// - Input: Any `Expr` plus optional `evaluate`.
/// - Returns: One rewritten `Expr`.
/// - Limits: The current front door ignores `evaluate` and uses the implemented structural collection only.
pub fn collect_sqrt(expr : Expr, evaluate? : Bool = true) -> Expr {
  let _ = evaluate
  collect_function_terms(expr, "sqrt")
}

///|
/// - Does: Collects repeated `Abs` factors inside multiplicative terms.
/// - Input: Any `Expr`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Only explicit `Abs` factors are grouped.
pub fn collect_abs(expr : Expr) -> Expr {
  collect_function_terms(expr, "Abs")
}

///|
/// - Does: Rationalizes one symbolic fraction `(num, den)` by removing supported radicals from the denominator.
/// - Input: Two expressions `(num, den)`.
/// - Returns: `(Expr, Expr)`.
/// - Limits: Unsupported denominators are returned unchanged.
pub fn rad_rationalize(num : Expr, den : Expr) -> (Expr, Expr) {
  match rationalized_denominator(den) {
    Some((mul_num, new_den)) => (@symcore.mul([num, mul_num]), new_den)
    None => (num, den)
  }
}

///|
/// - Does: Expands both numerator and denominator parts of a rational expression.
/// - Input: Any `Expr`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Uses structural numerator/denominator splitting and multiplication expansion only.
pub fn fraction_expand(expr : Expr) -> Expr {
  let (num, den) = split_fraction(expr)
  let n = expand_mul_expr(num)
  let d = expand_mul_expr(den)
  if d == int(1) {
    n
  } else {
    @symcore.mul([n, @symcore.pow(d, int(-1))])
  }
}

///|
/// - Does: Expands only the symbolic numerator of a rational expression.
/// - Input: Any `Expr`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Denominator structure is preserved exactly as split by the local fraction helper.
pub fn numer_expand(expr : Expr) -> Expr {
  let (num, den) = split_fraction(expr)
  let n = expand_mul_expr(num)
  if den == int(1) {
    n
  } else {
    @symcore.mul([n, @symcore.pow(den, int(-1))])
  }
}

///|
/// - Does: Expands only the symbolic denominator of a rational expression.
/// - Input: Any `Expr`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Numerator structure is preserved exactly as split by the local fraction helper.
pub fn denom_expand(expr : Expr) -> Expr {
  let (num, den) = split_fraction(expr)
  let d = expand_mul_expr(den)
  if d == int(1) {
    num
  } else {
    @symcore.mul([num, @symcore.pow(d, int(-1))])
  }
}

///|
/// - Does: Splits surd terms into a shared radical factor and two additive partitions.
/// - Input: Any `Expr`, usually an additive radical expression.
/// - Returns: `(Expr, Expr, Expr)`.
/// - Limits: Only the supported square-root integer pattern is recognized.
pub fn split_surds(expr : Expr) -> (Expr, Expr, Expr) {
  let terms = match expr {
    Expr::Add(args) => args
    _ => [expr]
  }
  let surds : Array[(Expr, Int)] = Array::new()
  let others : Array[Expr] = Array::new()
  for term in terms {
    match parse_coeff_sqrt_int(term) {
      Some(item) => surds.push(item)
      None => others.push(term)
    }
  }
  if surds.is_empty() {
    return (int(1), int(0), expr)
  }
  let mut g = surds[0].1
  for i in 1.. t)
  for item in surds {
    let coeff = item.0
    let rad = item.1
    if rad % g == 0 {
      let inner = int(rad / g)
      a_terms.push(@symcore.mul([coeff, @symcore.function("sqrt", [inner])]))
    } else {
      b_terms.push(@symcore.mul([coeff, @symcore.function("sqrt", [int(rad)])]))
    }
  }
  (int(g), @symcore.add(a_terms), @symcore.add(b_terms))
}

///|
/// - Does: Denests supported nested square roots when algebraic identities apply.
/// - Input: Any `Expr` plus optional `max_passes`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Only the implemented denesting identities are used, so many nested radicals remain unchanged.
pub fn sqrtdenest(expr : Expr, max_passes? : Int = 6) -> Expr {
  fixpoint_rewrite(expr, rewrite_sqrtdenest, max_passes~)
}

///|
fn fixpoint_rewrite(
  expr : Expr,
  rule : (Expr) -> Expr,
  max_passes? : Int = 6,
) -> Expr {
  let passes = if max_passes <= 0 { 1 } else { max_passes }
  let mut cur = expr
  for _ in 0.. Expr) -> Expr {
  let rewritten = @symcore.map_children(expr, child => {
    rewrite_bottom_up_rational(child, rule)
  })
  rule(rewritten)
}

///|
fn rewrite_radsimp(expr : Expr) -> Expr {
  let (num, den) = split_fraction(expr)
  match den {
    Expr::Number(n) if n.is_one() => expr
    _ =>
      match rationalized_denominator(den) {
        Some((mul_num, new_den)) =>
          @symcore.mul([num, mul_num, @symcore.pow(new_den, int(-1))])
        None => expr
      }
  }
}

///|
fn rationalized_denominator(den : Expr) -> (Expr, Expr)? {
  match den {
    _ if is_radical(den) =>
      match radical_square(den) {
        Some(square) => Some((den, square))
        None => None
      }
    Expr::Add([a, b]) =>
      match (parse_signed_radical(a), parse_signed_radical(b)) {
        (Some((sa, ra)), Some((sb, rb))) => {
          let a_term = if sa > 0 { ra } else { @symcore.mul([int(-1), ra]) }
          let b_term = if sb > 0 { rb } else { @symcore.mul([int(-1), rb]) }
          let conj = @symcore.add([a_term, @symcore.mul([int(-1), b_term])])
          match (radical_square(ra), radical_square(rb)) {
            (Some(a_sq), Some(b_sq)) =>
              Some((conj, @symcore.add([a_sq, @symcore.mul([int(-1), b_sq])])))
            _ => None
          }
        }
        _ => None
      }
    _ => None
  }
}

///|
fn rewrite_ratsimp(expr : Expr) -> Expr {
  match expr {
    Expr::Add(args) => {
      if args.is_empty() {
        return expr
      }
      let nums : Array[Expr] = Array::new()
      let dens : Array[Expr] = Array::new()
      for arg in args {
        let (n, d) = split_fraction(arg)
        nums.push(n)
        dens.push(d)
      }
      let common_den = lcm_denominator(dens)
      let num_terms : Array[Expr] = Array::new()
      for i in 0.. canceled
        None => cancel_common_factors(rebuilt)
      }
    }
    _ =>
      match poly_cancel_expr(expr) {
        Some(canceled) => canceled
        None => cancel_common_factors(expr)
      }
  }
}

///|
fn lcm_denominator(dens : Array[Expr]) -> Expr {
  let counts : Map[String, Int] = {}
  let factors : Map[String, Expr] = {}
  for den in dens {
    let local_counts = factor_count_map(den)
    for key, count in local_counts {
      match counts.get(key) {
        Some(prev) => if count > prev { counts[key] = count }
        None => counts[key] = count
      }
      factors[key] = factor_expr_by_key(den, key)
    }
  }
  factors_from_counts(counts, factors)
}

///|
fn denominator_quotient(common_den : Expr, den : Expr) -> Expr {
  let common_counts = factor_count_map(common_den)
  let common_factors = factor_expr_map(common_den)
  let local_counts = factor_count_map(den)
  let out_counts : Map[String, Int] = {}
  for key, total in common_counts {
    let used = match local_counts.get(key) {
      Some(count) => count
      None => 0
    }
    let remain = total - used
    if remain > 0 {
      out_counts[key] = remain
    }
  }
  factors_from_counts(out_counts, common_factors)
}

///|
fn expr_symbol_names_for_poly(expr : Expr) -> Array[String] {
  let names : Array[String] = []
  for sym in @symcore.free_symbols(expr) {
    match sym {
      Expr::Symbol(name) => if !names.contains(name) { names.push(name) }
      Expr::Dummy(name, _) => if !names.contains(name) { names.push(name) }
      _ => ()
    }
  }
  names.sort()
  names
}

///|
fn poly_cancel_expr(expr : Expr) -> Expr? {
  let (num, den) = split_fraction(expr)
  let gens = expr_symbol_names_for_poly(@symcore.mul([num, den]))
  if gens.is_empty() {
    return None
  }
  let num_poly_res : Result[@sympolys.Poly, @sympolys.PolyError] = try? @sympolys.Poly::from_expr(
    num,
    gens,
    @sympolys.Domain::EX,
  )
  let den_poly_res : Result[@sympolys.Poly, @sympolys.PolyError] = try? @sympolys.Poly::from_expr(
    den,
    gens,
    @sympolys.Domain::EX,
  )
  match (num_poly_res, den_poly_res) {
    (Ok(num_poly), Ok(den_poly)) => {
      if den_poly.is_zero() {
        return None
      }
      let cancel_res : Result[
        (@sympolys.Poly, @sympolys.Poly),
        @sympolys.PolyError,
      ] = try? @sympolys.cancel(num_poly, den_poly)
      match cancel_res {
        Ok((num_s, den_s)) => {
          let num_expr = num_s.to_expr()
          let den_expr = den_s.to_expr()
          Some(
            if den_expr == int(1) {
              num_expr
            } else {
              @symcore.mul([num_expr, @symcore.pow(den_expr, int(-1))])
            },
          )
        }
        Err(_) => None
      }
    }
    _ => None
  }
}

///|
fn factor_integral_number_rational(expr : Expr) -> Array[Expr]? {
  match expr {
    Expr::Number(n) if n.is_integral() => {
      let value = n.numerator().to_int()
      if value == 0 {
        return Some([expr])
      }
      let sign = if value < 0 { -1 } else { 1 }
      let mut rest = if value < 0 { -value } else { value }
      if rest <= 1 {
        return Some([expr])
      }
      let out : Array[Expr] = []
      if sign < 0 {
        out.push(int(-1))
      }
      let mut factor = 2
      while factor * factor <= rest {
        while rest % factor == 0 {
          out.push(int(factor))
          rest = rest / factor
        }
        factor += if factor == 2 { 1 } else { 2 }
      }
      if rest > 1 {
        out.push(int(rest))
      }
      Some(out)
    }
    _ => None
  }
}

///|
fn flatten_mul_nonunit_rational(expr : Expr) -> Array[Expr] {
  match expr {
    Expr::Mul(args) => {
      let out : Array[Expr] = []
      for arg in args {
        for factor in flatten_mul_nonunit_rational(arg) {
          if factor != int(1) {
            out.push(factor)
          }
        }
      }
      out
    }
    Expr::Pow(base, Expr::Number(exp)) if exp.is_integral() => {
      let power = exp.numerator().to_int()
      if power > 1 && power <= 32 {
        let base_factors = flatten_mul_nonunit_rational(base)
        let out : Array[Expr] = []
        for _ in 0.. []
    _ =>
      match factor_integral_number_rational(expr) {
        Some(factors) => factors.filter(f => f != int(1))
        None => [expr]
      }
  }
}

///|
fn factor_count_map(expr : Expr) -> Map[String, Int] {
  let counts : Map[String, Int] = {}
  for factor in flatten_mul_nonunit_rational(expr) {
    let key = to_repr(factor).to_string()
    match counts.get(key) {
      Some(prev) => counts[key] = prev + 1
      None => counts[key] = 1
    }
  }
  counts
}

///|
fn factor_expr_map(expr : Expr) -> Map[String, Expr] {
  let factors : Map[String, Expr] = {}
  for factor in flatten_mul_nonunit_rational(expr) {
    let key = to_repr(factor).to_string()
    factors[key] = factor
  }
  factors
}

///|
fn factor_expr_by_key(expr : Expr, key : String) -> Expr {
  for factor in flatten_mul_nonunit_rational(expr) {
    if to_repr(factor).to_string() == key {
      return factor
    }
  }
  expr
}

///|
fn factors_from_counts(
  counts : Map[String, Int],
  factors : Map[String, Expr],
) -> Expr {
  let out : Array[Expr] = Array::new()
  for key, count in counts {
    for _ in 0.. Expr {
  match unary_application_arg(expr, "sqrt") {
    Some(arg) =>
      match try_denest_quadratic_surds(arg) {
        Some(denested) => denested
        None => expr
      }
    None => expr
  }
}

///|
fn try_denest_quadratic_surds(arg : Expr) -> Expr? {
  let (a, b) = match parse_a_plus_two_sqrt_b(arg) {
    Some(pair) => pair
    None => return None
  }
  let four = @symnum.BigRational::from_int(4)
  let disc = a.mul_r(a).add_r(b.mul_r(four).neg_r())
  match rational_sqrt(disc) {
    Some(sdisc) => {
      let half = @symnum.BigRational::from_ints(1, 2) catch { _ => return None }
      let m = a.add_r(sdisc).mul_r(half)
      let n = a.add_r(sdisc.neg_r()).mul_r(half)
      if m.compare(@symnum.BigRational::zero()) < 0 ||
        n.compare(@symnum.BigRational::zero()) < 0 {
        return None
      }
      Some(
        @symcore.add([
          @symcore.function("sqrt", [@symcore.Expr::Number(m)]),
          @symcore.function("sqrt", [@symcore.Expr::Number(n)]),
        ]),
      )
    }
    None => None
  }
}

///|
fn parse_a_plus_two_sqrt_b(
  expr : Expr,
) -> (@symnum.BigRational, @symnum.BigRational)? {
  match expr {
    Expr::Add([x, y]) =>
      match (parse_numeric_term(x), parse_two_sqrt_term(y)) {
        (Some(a), Some(b)) => Some((a, b))
        _ =>
          match (parse_numeric_term(y), parse_two_sqrt_term(x)) {
            (Some(a), Some(b)) => Some((a, b))
            _ => None
          }
      }
    _ => None
  }
}

///|
fn parse_numeric_term(term : Expr) -> @symnum.BigRational? {
  exact_numeric_value(term)
}

///|
fn parse_two_sqrt_term(term : Expr) -> @symnum.BigRational? {
  match term {
    Expr::Mul(args) if args.length() == 2 => {
      let mut saw_two = false
      let mut radical : Expr? = None
      for arg in args {
        match arg {
          Expr::Number(c) if c.compare(@symnum.BigRational::from_int(2)) == 0 =>
            saw_two = true
          _ => if radical is None { radical = Some(arg) } else { return None }
        }
      }
      if saw_two {
        match radical {
          Some(r) => radical_radicand_as_rational(r)
          None => None
        }
      } else {
        None
      }
    }
    _ => None
  }
}

///|
fn radical_radicand_as_rational(expr : Expr) -> @symnum.BigRational? {
  match unary_application_arg(expr, "sqrt") {
    Some(arg) => exact_numeric_value(arg)
    None =>
      match expr {
        Expr::Pow(base, exp) =>
          match (exact_numeric_value(base), exact_numeric_value(exp)) {
            (Some(b), Some(e)) if is_half(e) => Some(b)
            _ => None
          }
        _ => None
      }
  }
}

///|
fn split_fraction(expr : Expr) -> (Expr, Expr) {
  split_fraction_simple(expr)
}

///|
fn cancel_common_factors(expr : Expr) -> Expr {
  let (num, den) = split_fraction(expr)
  let simp_num = factor_terms_simple(
    signsimp(expand_mul_expr(num), max_passes=2),
  )
  let simp_den = factor_terms_simple(
    signsimp(expand_mul_expr(den), max_passes=2),
  )
  let nfs = flatten_mul_nonunit(simp_num)
  let dfs = flatten_mul_nonunit(simp_den)
  let den_count : Map[String, Int] = {}
  let den_factor : Map[String, Expr] = {}
  for f in dfs {
    let key = to_repr(f).to_string()
    den_factor[key] = f
    match den_count.get(key) {
      Some(c) => den_count[key] = c + 1
      None => den_count[key] = 1
    }
  }
  let kept_num : Array[Expr] = Array::new()
  for f in nfs {
    let key = to_repr(f).to_string()
    match den_count.get(key) {
      Some(c) if c > 0 => den_count[key] = c - 1
      _ => kept_num.push(f)
    }
  }
  let kept_den : Array[Expr] = Array::new()
  for key, c in den_count {
    if c <= 0 {
      continue
    }
    for _ in 0.. Expr {
  match expr {
    Expr::Add(args) => {
      let grouped : Map[String, Array[Expr]] = {}
      let grouped_arg : Map[String, Expr] = {}
      let rest : Array[Expr] = Array::new()
      for arg in args {
        match parse_coeff_times_named_unary(arg, target_name) {
          Some((coeff, inner)) => {
            let key = to_repr(inner).to_string()
            match grouped.get(key) {
              Some(coeffs) => coeffs.push(coeff)
              None => {
                grouped[key] = [coeff]
                grouped_arg[key] = inner
              }
            }
          }
          None => rest.push(arg)
        }
      }
      for key, coeffs in grouped {
        let coeff_sum = @symcore.add(coeffs)
        let inner = grouped_arg[key]
        let name = if target_name == "Abs" { "Abs" } else { target_name }
        rest.push(@symcore.mul([coeff_sum, @symcore.function(name, [inner])]))
      }
      @symcore.add(rest)
    }
    _ => expr
  }
}

///|
fn parse_coeff_times_named_unary(
  expr : Expr,
  target_name : String,
) -> (Expr, Expr)? {
  let matches_target = fn(name : String) -> Bool {
    if target_name == "Abs" {
      name == "Abs" || name == "abs"
    } else {
      name == target_name
    }
  }
  match expr {
    _ =>
      match named_unary_application(expr) {
        Some((name, inner)) if matches_target(name) => Some((int(1), inner))
        _ =>
          match expr {
            Expr::Mul(args) => {
              let mut seen = false
              let mut inner_expr = int(0)
              let coeff_factors : Array[Expr] = Array::new()
              for arg in args {
                match arg {
                  _ =>
                    match named_unary_application(arg) {
                      Some((name, inner)) if matches_target(name) && !seen => {
                        seen = true
                        inner_expr = inner
                      }
                      _ => coeff_factors.push(arg)
                    }
                }
              }
              if seen {
                Some((@symcore.mul(coeff_factors), inner_expr))
              } else {
                None
              }
            }
            _ => None
          }
      }
  }
}

///|
fn expand_mul_expr(expr : Expr) -> Expr {
  match expr {
    Expr::Add(args) => {
      let expanded : Array[Expr] = Array::new()
      let mut changed = false
      for arg in args {
        let next = expand_mul_expr(arg)
        changed = changed || next != arg
        expanded.push(next)
      }
      if changed {
        @symcore.add(expanded)
      } else {
        expr
      }
    }
    Expr::Mul(args) => {
      let expanded : Array[Expr] = Array::new()
      let mut changed = false
      let mut has_sum = false
      for arg in args {
        let next = expand_mul_expr(arg)
        changed = changed || next != arg
        match next {
          Expr::Add(_) => has_sum = true
          _ => ()
        }
        expanded.push(next)
      }
      if has_sum {
        expand_mul_sequence(expanded)
      } else if changed {
        @symcore.mul(expanded)
      } else {
        expr
      }
    }
    Expr::Pow(base, exp) => {
      let base_expanded = expand_mul_expr(base)
      let exp_expanded = expand_mul_expr(exp)
      if base_expanded == base && exp_expanded == exp {
        expr
      } else {
        @symcore.pow(base_expanded, exp_expanded)
      }
    }
    _ =>
      match @symcore.application_parts(expr) {
        Some((head, args)) => {
          let expanded : Array[Expr] = Array::new()
          let mut changed = false
          for arg in args {
            let next = expand_mul_expr(arg)
            changed = changed || next != arg
            expanded.push(next)
          }
          if changed {
            @symcore.raw_apply(head, expanded).unwrap_or(expr)
          } else {
            expr
          }
        }
        None => expr
      }
  }
}

///|
fn additive_terms(expr : Expr) -> Array[Expr] {
  match expr {
    Expr::Add(args) => args
    _ => [expr]
  }
}

///|
fn expand_mul_sums_range(
  sums : Array[Expr],
  start : Int,
  end_ : Int,
) -> Array[Expr] {
  let len = end_ - start
  if len <= 0 {
    return [int(1)]
  }
  if len == 1 {
    return additive_terms(sums[start])
  }
  let mid = start + len / 2
  let left = expand_mul_sums_range(sums, start, mid)
  let right = expand_mul_sums_range(sums, mid, end_)
  let products : Array[Expr] = Array::new()
  for a in left {
    for b in right {
      products.push(@symcore.mul([a, b]))
    }
  }
  additive_terms(@symcore.add(products))
}

///|
fn expand_mul_sequence(factors : Array[Expr]) -> Expr {
  let plain : Array[Expr] = Array::new()
  let sums : Array[Expr] = Array::new()
  for factor in factors {
    match factor {
      Expr::Add(_) => sums.push(factor)
      _ => plain.push(factor)
    }
  }
  if sums.is_empty() {
    return @symcore.mul(factors)
  }
  let plain_term = if plain.is_empty() { int(1) } else { @symcore.mul(plain) }
  let terms = expand_mul_sums_range(sums, 0, sums.length())
  let scaled : Array[Expr] = Array::new()
  for term in terms {
    if plain.is_empty() {
      scaled.push(term)
    } else {
      scaled.push(@symcore.mul([plain_term, term]))
    }
  }
  if scaled.length() == 1 {
    scaled[0]
  } else {
    @symcore.add(scaled)
  }
}

///|
fn parse_coeff_sqrt_int(expr : Expr) -> (Expr, Int)? {
  match unary_application_arg(expr, "sqrt") {
    Some(Expr::Number(n)) =>
      if n.is_integral() && n.numerator().to_int() > 0 {
        Some((int(1), n.numerator().to_int()))
      } else {
        None
      }
    Some(_) => None
    None =>
      match expr {
        Expr::Mul(args) => {
          let mut found = false
          let mut rad = 0
          let coeff_factors : Array[Expr] = Array::new()
          for arg in args {
            match unary_application_arg(arg, "sqrt") {
              Some(Expr::Number(n)) if !found =>
                if n.is_integral() && n.numerator().to_int() > 0 {
                  found = true
                  rad = n.numerator().to_int()
                } else {
                  return None
                }
              Some(_) if !found => return None
              _ => coeff_factors.push(arg)
            }
          }
          if found {
            Some((@symcore.mul(coeff_factors), rad))
          } else {
            None
          }
        }
        _ => None
      }
  }
}

///|
fn int_gcd(a : Int, b : Int) -> Int {
  let mut x = if a < 0 { -a } else { a }
  let mut y = if b < 0 { -b } else { b }
  if x == 0 {
    return if y == 0 { 1 } else { y }
  }
  while y != 0 {
    let r = x % y
    x = y
    y = r
  }
  if x == 0 {
    1
  } else {
    x
  }
}

///|
fn parse_signed_radical(expr : Expr) -> (Int, Expr)? {
  if is_radical(expr) {
    return Some((1, expr))
  }
  match expr {
    Expr::Mul(args) if args.length() == 2 => {
      let mut coeff : @symnum.BigRational? = None
      let mut radical : Expr? = None
      for arg in args {
        match arg {
          Expr::Number(c) => coeff = Some(c)
          _ => if radical is None { radical = Some(arg) } else { return None }
        }
      }
      match (coeff, radical) {
        (Some(c), Some(r)) if is_radical(r) &&
          c.compare(@symnum.BigRational::from_int(-1)) == 0 => Some((-1, r))
        (Some(c), Some(r)) if is_radical(r) && c.is_one() => Some((1, r))
        _ => None
      }
    }
    _ => None
  }
}

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

///|
fn radical_square(expr : Expr) -> Expr? {
  match expr {
    _ =>
      match unary_application_arg(expr, "sqrt") {
        Some(arg) => Some(arg)
        None =>
          match expr {
            Expr::Pow(base, Expr::Number(exp)) =>
              if is_half(exp) {
                Some(base)
              } else {
                None
              }
            _ => None
          }
      }
  }
}

///|
fn is_half(value : @symnum.BigRational) -> Bool {
  value.numerator().equal_int(1) && value.denominator().equal_int(2)
}

///|
fn int_sqrt_if_square(n : Int) -> Int? {
  if n < 0 {
    return None
  }
  let mut i = 0
  while i * i < n {
    i = i + 1
  }
  if i * i == n {
    Some(i)
  } else {
    None
  }
}

///|
fn rational_sqrt(v : @symnum.BigRational) -> @symnum.BigRational? {
  if !v.is_integral() {
    let num = v.numerator().to_int()
    let den = v.denominator().to_int()
    match (int_sqrt_if_square(num), int_sqrt_if_square(den)) {
      (Some(sn), Some(sd)) =>
        Some(
          @symnum.BigRational::new(BigInt::from_int(sn), BigInt::from_int(sd)) catch {
            _ => return None
          },
        )
      _ => None
    }
  } else {
    let n = v.numerator().to_int()
    match int_sqrt_if_square(n) {
      Some(s) => Some(@symnum.BigRational::from_int(s))
      None => None
    }
  }
}