use super::poly_utils::{
as_integer, as_linear, as_quadratic, is_free_of, poly_degree_in, poly_int_coeffs,
};
use crate::deriv::log::{DerivationLog, RewriteStep};
use crate::integrate::engine::IntegrationError;
use crate::kernel::{ExprData, ExprId, ExprPool};
pub fn integrate_with_sqrt(
b: ExprId,
p: ExprId,
sqrt_id: ExprId,
var: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
let p_deg = poly_degree_in(p, var, pool).unwrap_or(u32::MAX);
match p_deg {
0 => integrate_b_sqrt_const(b, p, sqrt_id, var, pool, log),
1 => integrate_b_sqrt_linear(b, p, sqrt_id, var, pool, log),
2 => integrate_b_sqrt_quadratic(b, p, sqrt_id, var, pool, log),
_ => {
let actual_deg = poly_int_coeffs(p, var, pool)
.map(|cs| cs.len().saturating_sub(1))
.unwrap_or(3);
if actual_deg <= 2 {
match actual_deg {
0 => integrate_b_sqrt_const(b, p, sqrt_id, var, pool, log),
1 => integrate_b_sqrt_linear(b, p, sqrt_id, var, pool, log),
2 => integrate_b_sqrt_quadratic(b, p, sqrt_id, var, pool, log),
_ => Err(IntegrationError::NonElementary(
"integral over genus-1 (elliptic) or higher curve; no elementary antiderivative"
.to_string(),
)),
}
} else {
Err(IntegrationError::NonElementary(
"integral over genus-1 (elliptic) or higher curve; no elementary antiderivative"
.to_string(),
))
}
}
}
}
fn integrate_b_sqrt_const(
b: ExprId,
_p: ExprId,
sqrt_id: ExprId,
var: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
let int_b = crate::integrate::engine::integrate_raw(b, var, pool, log)?;
let result = pool.mul(vec![sqrt_id, int_b]);
log.push(RewriteStep::simple("alg_sqrt_const", b, result));
Ok(result)
}
fn integrate_b_sqrt_linear(
b_expr: ExprId,
p: ExprId,
sqrt_id: ExprId,
var: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
let (a, c_const) = as_linear(p, var, pool).ok_or_else(|| {
IntegrationError::NotImplemented("linear radicand extraction failed".to_string())
})?;
match try_poly_b_linear(b_expr, p, sqrt_id, var, a, c_const, pool, log) {
Ok(result) => return Ok(result),
Err(IntegrationError::NotImplemented(_)) => {} Err(e) => return Err(e),
}
match try_rational_b_linear(b_expr, p, sqrt_id, var, a, c_const, pool, log) {
Ok(result) => return Ok(result),
Err(IntegrationError::NotImplemented(_)) => {} Err(e) => return Err(e),
}
Err(IntegrationError::NotImplemented(format!(
"∫ B(x)·sqrt(P(x)) with P linear: B = {} not handled",
pool.display(b_expr)
)))
}
#[allow(clippy::too_many_arguments)]
fn try_poly_b_linear(
b_expr: ExprId,
p: ExprId,
sqrt_id: ExprId,
var: ExprId,
a: ExprId, _c_const: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
let _deg = poly_degree_in(b_expr, var, pool).ok_or_else(|| {
IntegrationError::NotImplemented("B is not a polynomial in var".to_string())
})?;
let b_coeffs_int = poly_int_coeffs(b_expr, var, pool).ok_or_else(|| {
IntegrationError::NotImplemented("B coefficients not extractable as integers".to_string())
})?;
let a_int = as_integer(a, pool).ok_or_else(|| {
IntegrationError::NotImplemented("linear coefficient a is not an integer".to_string())
})?;
let p_coeffs_int = poly_int_coeffs(p, var, pool).ok_or_else(|| {
IntegrationError::NotImplemented("P coefficients not extractable".to_string())
})?;
let c_int = p_coeffs_int
.first()
.cloned()
.unwrap_or_else(|| rug::Integer::from(0));
if a_int == 0 {
return Err(IntegrationError::NotImplemented(
"degenerate linear P: a=0".to_string(),
));
}
use rug::Rational;
let mut terms: Vec<ExprId> = Vec::new();
for (k, b_k) in b_coeffs_int.iter().enumerate() {
if *b_k == 0 {
continue;
}
let k = k as i64;
let a_pow = a_int.pow(k as u32 + 1);
for j in 0..=(k as usize) {
let j = j as i64;
let binom = binomial_coeff(k as u64, j as u64);
let neg_c_pow = neg_c_power(&c_int, k - j);
let denom = a_pow * rug::Integer::from(2 * j + 3);
let numer = b_k.clone() * binom * neg_c_pow * 2;
if numer == 0 {
continue;
}
let coeff = Rational::from((numer, denom));
if coeff == 0 {
continue;
}
let p_pow_expr = if j + 1 == 1 {
p
} else {
pool.pow(p, pool.integer(j + 1))
};
let coeff_expr = pool.rational(coeff.numer().clone(), coeff.denom().clone());
let term = pool.mul(vec![coeff_expr, p_pow_expr]);
terms.push(term);
}
}
let q_expr = match terms.len() {
0 => pool.integer(0_i32),
1 => terms[0],
_ => pool.add(terms),
};
let result = pool.mul(vec![q_expr, sqrt_id]);
log.push(RewriteStep::simple("alg_poly_linear", b_expr, result));
Ok(result)
}
#[allow(clippy::too_many_arguments)]
fn try_rational_b_linear(
b_expr: ExprId,
p: ExprId,
sqrt_id: ExprId,
var: ExprId,
a: ExprId,
_c_const: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
if let ExprData::Pow { base, exp } = pool.get(b_expr) {
if base == p {
if let Some(n) = as_integer(exp, pool) {
let two_n3 = 2 * n + 3;
if two_n3 == 0 {
return Err(IntegrationError::NotImplemented(
"pole in algebraic integration (n = -3/2)".to_string(),
));
}
let denom = pool.mul(vec![a, pool.integer(two_n3)]);
let denom_inv = pool.pow(denom, pool.integer(-1_i32));
let p_n1 = p_integer_power(p, n + 1, pool);
let result = pool.mul(vec![pool.integer(2_i32), denom_inv, p_n1, sqrt_id]);
log.push(RewriteStep::simple("alg_p_power_linear", b_expr, result));
return Ok(result);
}
}
}
if let ExprData::Mul(args) = pool.get(b_expr) {
let (const_parts, p_parts): (Vec<ExprId>, Vec<ExprId>) =
args.iter().partition(|&&id| is_free_of(id, var, pool));
if p_parts.len() == 1 {
if let ExprData::Pow { base, exp } = pool.get(p_parts[0]) {
if base == p {
if let Some(n) = as_integer(exp, pool) {
let two_n3 = 2 * n + 3;
if two_n3 == 0 {
return Err(IntegrationError::NotImplemented(
"pole in algebraic integration (n = -3/2)".to_string(),
));
}
let const_factor = match const_parts.len() {
0 => pool.integer(1_i32),
1 => const_parts[0],
_ => pool.mul(const_parts),
};
let denom = pool.mul(vec![a, pool.integer(two_n3)]);
let denom_inv = pool.pow(denom, pool.integer(-1_i32));
let p_n1 = p_integer_power(p, n + 1, pool);
let result = pool.mul(vec![
pool.integer(2_i32),
const_factor,
denom_inv,
p_n1,
sqrt_id,
]);
log.push(RewriteStep::simple("alg_rational_linear", b_expr, result));
return Ok(result);
}
}
}
}
}
Err(IntegrationError::NotImplemented(
"rational B with linear P: unsupported form".to_string(),
))
}
fn p_integer_power(p: ExprId, k: i64, pool: &ExprPool) -> ExprId {
match k {
0 => pool.integer(1_i32),
1 => p,
_ => pool.pow(p, pool.integer(k)),
}
}
fn integrate_b_sqrt_quadratic(
b_expr: ExprId,
p: ExprId,
sqrt_id: ExprId,
var: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
let (a, b_coeff, c) = as_quadratic(p, var, pool).ok_or_else(|| {
IntegrationError::NotImplemented("quadratic radicand extraction failed".to_string())
})?;
if let Ok(result) = try_poly_b_quadratic(b_expr, p, sqrt_id, var, a, b_coeff, c, pool, log) {
return Ok(result);
}
if let Ok(result) = try_rational_b_quadratic(b_expr, p, sqrt_id, var, a, b_coeff, c, pool, log)
{
return Ok(result);
}
Err(IntegrationError::NotImplemented(format!(
"∫ B(x)·sqrt(quadratic): B = {} not handled",
pool.display(b_expr)
)))
}
fn j0_quadratic(
_p: ExprId,
sqrt_id: ExprId, var: ExprId,
a: ExprId,
b_coeff: ExprId,
pool: &ExprPool,
) -> ExprId {
let sqrt_a = pool.func("sqrt", vec![a]);
let two = pool.integer(2_i32);
let two_sqrt_a_sqrt_p = pool.mul(vec![two, sqrt_a, sqrt_id]);
let two_ax = pool.mul(vec![two, a, var]);
let inner = pool.add(vec![two_sqrt_a_sqrt_p, two_ax, b_coeff]);
let log_inner = pool.func("log", vec![inner]);
let sqrt_a_inv = pool.pow(sqrt_a, pool.integer(-1_i32));
pool.mul(vec![sqrt_a_inv, log_inner])
}
#[allow(clippy::too_many_arguments)]
fn try_poly_b_quadratic(
b_expr: ExprId,
p: ExprId,
sqrt_id: ExprId,
var: ExprId,
a: ExprId,
b_coeff: ExprId,
c: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
let deg = poly_degree_in(b_expr, var, pool).ok_or_else(|| {
IntegrationError::NotImplemented("B is not polynomial for quadratic P".to_string())
})?;
if deg == 0 {
let b_const = b_expr; let result = integrate_sqrt_quadratic_base(p, sqrt_id, var, a, b_coeff, c, pool);
let scaled = pool.mul(vec![b_const, result]);
log.push(RewriteStep::simple("alg_const_sqrt_quad", b_expr, scaled));
return Ok(scaled);
}
if deg == 1 {
let b_coeffs = poly_int_coeffs(b_expr, var, pool).ok_or_else(|| {
IntegrationError::NotImplemented("degree-1 B coefficients not extractable".to_string())
})?;
let e_int = b_coeffs
.first()
.cloned()
.unwrap_or_else(|| rug::Integer::from(0));
let d_int = b_coeffs
.get(1)
.cloned()
.unwrap_or_else(|| rug::Integer::from(0));
let e_expr = pool.integer(e_int);
let d_expr = pool.integer(d_int);
let int_sqrt_p = integrate_sqrt_quadratic_base(p, sqrt_id, var, a, b_coeff, c, pool);
let three_a = pool.mul(vec![pool.integer(3_i32), a]);
let three_a_inv = pool.pow(three_a, pool.integer(-1_i32));
let p_sqrt_p = pool.mul(vec![p, sqrt_id]);
let term1 = pool.mul(vec![three_a_inv, p_sqrt_p]);
let six_a = pool.mul(vec![pool.integer(6_i32), a]);
let six_a_inv = pool.pow(six_a, pool.integer(-1_i32));
let term2 = pool.mul(vec![pool.integer(-1_i32), b_coeff, six_a_inv, int_sqrt_p]);
let int_x_sqrt_p = pool.add(vec![term1, term2]);
let part_d = pool.mul(vec![d_expr, int_x_sqrt_p]);
let part_e = pool.mul(vec![e_expr, int_sqrt_p]);
let result = pool.add(vec![part_d, part_e]);
log.push(RewriteStep::simple("alg_linear_sqrt_quad", b_expr, result));
return Ok(result);
}
Err(IntegrationError::NotImplemented(format!(
"∫ polynomial(deg {deg}) · sqrt(quadratic): not yet implemented for deg > 1"
)))
}
fn integrate_sqrt_quadratic_base(
p: ExprId,
sqrt_id: ExprId,
var: ExprId,
a: ExprId,
b_coeff: ExprId,
c: ExprId,
pool: &ExprPool,
) -> ExprId {
let two = pool.integer(2_i32);
let four = pool.integer(4_i32);
let eight = pool.integer(8_i32);
let two_ax = pool.mul(vec![two, a, var]);
let two_ax_plus_b = pool.add(vec![two_ax, b_coeff]);
let four_a = pool.mul(vec![four, a]);
let four_a_inv = pool.pow(four_a, pool.integer(-1_i32));
let term1 = pool.mul(vec![four_a_inv, two_ax_plus_b, sqrt_id]);
let four_ac = pool.mul(vec![four, a, c]);
let b2 = pool.pow(b_coeff, pool.integer(2_i32));
let neg_b2 = pool.mul(vec![pool.integer(-1_i32), b2]);
let discriminant = pool.add(vec![four_ac, neg_b2]);
let eight_a = pool.mul(vec![eight, a]);
let eight_a_inv = pool.pow(eight_a, pool.integer(-1_i32));
let j0 = j0_quadratic(p, sqrt_id, var, a, b_coeff, pool);
let term2 = pool.mul(vec![eight_a_inv, discriminant, j0]);
pool.add(vec![term1, term2])
}
#[allow(clippy::too_many_arguments)]
fn try_rational_b_quadratic(
b_expr: ExprId,
p: ExprId,
sqrt_id: ExprId,
var: ExprId,
a: ExprId,
b_coeff: ExprId,
c: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
if let ExprData::Pow { base, exp } = pool.get(b_expr) {
if base == p {
if let Some(n) = as_integer(exp, pool) {
return integrate_p_power_sqrt_quad(n, p, sqrt_id, var, a, b_coeff, c, pool, log);
}
}
}
if let ExprData::Mul(args) = pool.get(b_expr) {
let (const_parts, p_parts): (Vec<ExprId>, Vec<ExprId>) =
args.iter().partition(|&&id| is_free_of(id, var, pool));
if p_parts.len() == 1 {
if let ExprData::Pow { base, exp } = pool.get(p_parts[0]) {
if base == p {
if let Some(n) = as_integer(exp, pool) {
let const_factor = match const_parts.len() {
0 => pool.integer(1_i32),
1 => const_parts[0],
_ => pool.mul(const_parts),
};
let int_pn_sqrt = integrate_p_power_sqrt_quad(
n, p, sqrt_id, var, a, b_coeff, c, pool, log,
)?;
let result = pool.mul(vec![const_factor, int_pn_sqrt]);
return Ok(result);
}
}
}
}
}
Err(IntegrationError::NotImplemented(
"rational B with quadratic P: unsupported form".to_string(),
))
}
#[allow(clippy::too_many_arguments)]
fn integrate_p_power_sqrt_quad(
n: i64,
p: ExprId,
sqrt_id: ExprId,
var: ExprId,
a: ExprId,
b_coeff: ExprId,
c: ExprId,
pool: &ExprPool,
log: &mut DerivationLog,
) -> Result<ExprId, IntegrationError> {
match n {
-1 => {
let j0 = j0_quadratic(p, sqrt_id, var, a, b_coeff, pool);
log.push(RewriteStep::simple("alg_j0_quad", p, j0));
Ok(j0)
}
0 => {
let result = integrate_sqrt_quadratic_base(p, sqrt_id, var, a, b_coeff, c, pool);
log.push(RewriteStep::simple("alg_sqrt_quad_base", p, result));
Ok(result)
}
1 => {
let two = pool.integer(2_i32);
let two_ax = pool.mul(vec![two, a, var]);
let two_ax_b = pool.add(vec![two_ax, b_coeff]);
let eight_a = pool.mul(vec![pool.integer(8_i32), a]);
let eight_a_inv = pool.pow(eight_a, pool.integer(-1_i32));
let term1 = pool.mul(vec![eight_a_inv, two_ax_b, p, sqrt_id]);
let four_ac = pool.mul(vec![pool.integer(4_i32), a, c]);
let b2 = pool.pow(b_coeff, pool.integer(2_i32));
let neg_b2 = pool.mul(vec![pool.integer(-1_i32), b2]);
let d = pool.add(vec![four_ac, neg_b2]); let three_d = pool.mul(vec![pool.integer(3_i32), d]);
let sixteen_a = pool.mul(vec![pool.integer(16_i32), a]);
let sixteen_a_inv = pool.pow(sixteen_a, pool.integer(-1_i32));
let int_sqrt_p = integrate_sqrt_quadratic_base(p, sqrt_id, var, a, b_coeff, c, pool);
let term2 = pool.mul(vec![sixteen_a_inv, three_d, int_sqrt_p]);
let result = pool.add(vec![term1, term2]);
log.push(RewriteStep::simple("alg_p_3_2_quad", p, result));
Ok(result)
}
_ => Err(IntegrationError::NotImplemented(format!(
"∫ P^{n}·sqrt(P) with quadratic P: higher powers not implemented"
))),
}
}
fn binomial_coeff(n: u64, k: u64) -> rug::Integer {
if k > n {
return rug::Integer::from(0);
}
let k = k.min(n - k);
let mut result = rug::Integer::from(1u64);
for i in 0..k {
result *= rug::Integer::from(n - i);
result /= rug::Integer::from(i + 1);
}
result
}
fn neg_c_power(c: &rug::Integer, n: i64) -> rug::Integer {
if n == 0 {
return rug::Integer::from(1);
}
let base = rug::Integer::from(-1) * c;
if n > 0 {
let mut result = rug::Integer::from(1);
for _ in 0..n {
result *= &base;
}
result
} else {
rug::Integer::from(0)
}
}