///|
/// - Does: Simplifies supported combinatorial and factorial-style identities.
/// - Input: Any `Expr` plus optional `max_passes`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Only the implemented factorial, gamma, and binomial identities are applied.
pub fn combsimp(expr : Expr, max_passes? : Int = 6) -> Expr {
fixpoint_special(expr, rewrite_combsimp, max_passes~)
}
///|
/// - Does: Expands supported hypergeometric calls into closed forms when known.
/// - Input: Any `Expr` plus optional `max_passes`.
/// - Returns: One rewritten `Expr`.
/// - Limits: Only a small supported family of hypergeometric signatures is expanded.
pub fn hyperexpand(expr : Expr, max_passes? : Int = 6) -> Expr {
fixpoint_special(expr, rewrite_hyperexpand, max_passes~)
}
///|
fn fixpoint_special(
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_special(child, rule)
})
rule(rewritten)
}
///|
fn rewrite_combsimp(expr : Expr) -> Expr {
match expr {
Expr::Mul(args) => reduce_factorial_ratios(args)
_ =>
match (@symcore.application_name(expr), @symcore.application_args(expr)) {
(Some(name), Some(args)) => rewrite_combinatorial_call(name, args, expr)
_ => expr
}
}
}
///|
fn rewrite_combinatorial_call(
name : String,
args : Array[Expr],
original : Expr,
) -> Expr {
match (name, args) {
("factorial", [arg]) =>
match exact_numeric_value(arg) {
Some(value) =>
match to_nonnegative_int(value) {
Some(k) => @symcore.Expr::Number(int_factorial_rational(k))
None => original
}
None => original
}
("gamma", [arg]) =>
match exact_numeric_value(arg) {
Some(value) =>
match to_positive_int(value) {
Some(k) => @symcore.Expr::Number(int_factorial_rational(k - 1))
None => original
}
None => original
}
("binomial", [n_expr, k_expr]) =>
match
(
match exact_numeric_value(n_expr) {
Some(value) => to_nonnegative_int(value)
None => None
},
match exact_numeric_value(k_expr) {
Some(value) => to_nonnegative_int(value)
None => None
},
) {
(Some(ni), Some(ki)) =>
if ki > ni {
int(0)
} else if ki == 0 || ki == ni {
int(1)
} else if ki == 1 {
@symcore.Expr::Number(
exact_numeric_value(n_expr).unwrap_or(@symnum.BigRational::zero()),
)
} else {
@symcore.Expr::Number(int_binomial_rational(ni, ki))
}
(None, Some(0)) => int(1)
(None, Some(1)) => n_expr
_ => original
}
_ => original
}
}
///|
fn reduce_factorial_ratios(args : Array[Expr]) -> Expr {
let mut reduced = @symcore.mul(args)
for i in 0..
reduced = @symcore.mul([
@symcore.add([b, int(1)]),
strip_factor_pair(args, i, j),
])
(Some(a), Some(b)) if b == @symcore.add([a, int(1)]) =>
reduced = @symcore.mul([
@symcore.pow(@symcore.add([a, int(1)]), int(-1)),
strip_factor_pair(args, i, j),
])
_ => ()
}
}
}
reduced
}
///|
fn strip_factor_pair(args : Array[Expr], idx_a : Int, idx_b : Int) -> Expr {
let kept : Array[Expr] = Array::new()
for i in 0.. Expr? {
unary_application_arg(expr, "factorial")
}
///|
fn inverse_factorial_arg(expr : Expr) -> Expr? {
match pow_named_unary_application(expr) {
Some((name, arg, Expr::Number(exp))) =>
if name == "factorial" &&
exp.is_integral() &&
exp.numerator().to_int() == -1 {
Some(arg)
} else {
None
}
_ => None
}
}
///|
fn rewrite_hyperexpand(expr : Expr) -> Expr {
match @symcore.application_args(expr) {
Some([a_params, b_params, z]) if @symcore.application_has_name(
expr,
"hyper",
arity=3,
) =>
match (parse_tuple(a_params), parse_tuple(b_params)) {
(Some(a), Some(b)) =>
match (a.length(), b.length()) {
(0, 0) => @symcore.function("exp", [z])
(1, 1) if a[0] == b[0] => @symcore.function("exp", [z])
(1, 0) if a[0] == int(1) =>
@symcore.pow(
@symcore.add([int(1), @symcore.mul([int(-1), z])]),
int(-1),
)
_ => expr
}
_ => expr
}
_ => expr
}
}
///|
fn parse_tuple(expr : Expr) -> Array[Expr]? {
@symcore.tuple_items(expr)
}
///|
fn to_nonnegative_int(n : @symnum.BigRational) -> Int? {
if !n.is_integral() {
return None
}
let k = n.numerator().to_int()
if k < 0 {
None
} else {
Some(k)
}
}
///|
fn to_positive_int(n : @symnum.BigRational) -> Int? {
match to_nonnegative_int(n) {
Some(k) if k > 0 => Some(k)
_ => None
}
}
///|
fn int_factorial_bigint(n : Int) -> BigInt {
let mut out = BigInt::from_int(1)
for i in 2..<=n {
out = out.mul(BigInt::from_int(i))
}
out
}
///|
fn int_factorial_rational(n : Int) -> @symnum.BigRational {
@symnum.BigRational::from_bigint(int_factorial_bigint(n))
}
///|
fn int_binomial_rational(n : Int, k : Int) -> @symnum.BigRational {
let kk = if k < n - k { k } else { n - k }
let mut num = BigInt::from_int(1)
let mut den = BigInt::from_int(1)
for i in 1..<=kk {
num = num.mul(BigInt::from_int(n - kk + i))
den = den.mul(BigInt::from_int(i))
}
@symnum.BigRational::new(num, den) catch {
_ => @symnum.BigRational::zero()
}
}