use rug::Rational;
use super::exp_case::{rational_sqrt, AlgebraicExtension};
use super::number_field::{KElem, KPoly, NumberField};
type KHermiteTerms = Vec<(KPoly, KPoly, usize)>;
pub struct KRationalLogResult {
pub rational_num: KPoly,
pub rational_den: KPoly,
pub log_terms: Vec<(KElem, KElem)>,
}
fn eval_kpoly_at(field: &NumberField, p: &[KElem], point: &KElem) -> KElem {
let mut acc = NumberField::k_zero();
for c in p.iter().rev() {
acc = field.add(&field.mul(&acc, point), c);
}
acc
}
fn k_sqrt_quadratic(field: &NumberField, d: i64, elem: &KElem) -> Option<KElem> {
let zero = Rational::from(0);
let a = elem.first().cloned().unwrap_or_else(|| zero.clone());
let b = elem.get(1).cloned().unwrap_or_else(|| zero.clone());
let d_r = Rational::from(d);
if b == 0 {
if let Some(p) = rational_sqrt(&a) {
return Some(field.from_rational(&p));
}
if a != 0 {
let aod = a.clone() / d_r.clone();
if let Some(q) = rational_sqrt(&aod) {
return Some(vec![zero, q]);
}
}
return None;
}
let disc = a.clone() * a.clone() - d_r.clone() * b.clone() * b.clone();
let s = rational_sqrt(&disc)?;
for sign in [1i32, -1i32] {
let numer = if sign == 1 {
a.clone() + s.clone()
} else {
a.clone() - s.clone()
};
if numer < 0 {
continue;
}
let p_sq = numer / Rational::from(2);
if let Some(p) = rational_sqrt(&p_sq) {
if p == 0 {
continue; }
let q = b.clone() / (Rational::from(2) * p.clone());
return Some(vec![p, q]);
}
}
None
}
fn find_k_roots(
field: &NumberField,
den: &[KElem],
ext: &AlgebraicExtension,
) -> Option<Vec<KElem>> {
match NumberField::kdeg(den) {
1 => {
let c = den.first().cloned().unwrap_or_else(NumberField::k_zero);
Some(vec![field.neg(&c)])
}
2 => {
let b = den.get(1).cloned().unwrap_or_else(NumberField::k_zero);
let c = den.first().cloned().unwrap_or_else(NumberField::k_zero);
let four_c = field.mul(&c, &field.from_int(4));
let disc = field.sub(&field.mul(&b, &b), &four_c);
let AlgebraicExtension::SingleSqrt { d, .. } = ext else {
return None; };
let sqrt_disc = k_sqrt_quadratic(field, *d, &disc)?;
let two = field.from_int(2);
let two_inv = field.inv(&two)?;
let neg_b = field.neg(&b);
let r1 = field.mul(&field.add(&neg_b, &sqrt_disc), &two_inv);
let r2 = field.mul(&field.sub(&neg_b, &sqrt_disc), &two_inv);
if NumberField::kpoly_eq(std::slice::from_ref(&r1), std::slice::from_ref(&r2)) {
return None; }
Some(vec![r1, r2])
}
_ => None,
}
}
fn kyun(field: &NumberField, f: &KPoly) -> Option<Vec<(KPoly, usize)>> {
let f = field.kpoly_monic(f)?;
if NumberField::kdeg(&f) <= 0 {
return Some(vec![]);
}
let fp = field.kpoly_deriv(&f);
let a0 = field.kpoly_gcd(&f, &fp)?;
if NumberField::kdeg(&a0) == 0 {
return Some(vec![(f, 1)]); }
let mut b = field.kpoly_div_exact(&f, &a0)?;
let c = field.kpoly_div_exact(&fp, &a0)?;
let mut d = field.kpoly_sub(&c, &field.kpoly_deriv(&b));
let mut result = Vec::new();
let mut i = 1usize;
let cap = NumberField::kdeg(&f) as usize + 2;
while NumberField::kdeg(&b) > 0 {
if i > cap {
return None; }
let vi = field.kpoly_gcd(&b, &d)?;
if NumberField::kdeg(&vi) > 0 {
result.push((field.kpoly_monic(&vi)?, i));
}
let b_next = field.kpoly_div_exact(&b, &vi)?;
let c_next = field.kpoly_div_exact(&d, &vi)?;
d = field.kpoly_sub(&c_next, &field.kpoly_deriv(&b_next));
b = b_next;
i += 1;
}
Some(result)
}
fn kpartial_fractions(field: &NumberField, num: &KPoly, moduli: &[KPoly]) -> Option<Vec<KPoly>> {
let n = moduli.len();
if n == 0 {
return None;
}
if n == 1 {
return field.kpoly_mod(num, &moduli[0]).map(|a| vec![a]);
}
let one = vec![field.from_int(1)];
let mut result = Vec::with_capacity(n);
let mut cur = NumberField::kpoly_trim(num.clone());
for i in 0..n - 1 {
let mi = &moduli[i];
let rest = moduli[i + 1..]
.iter()
.fold(one.clone(), |acc, m| field.kpoly_mul(&acc, m));
let (g, _s, t) = field.kpoly_ext_gcd(mi, &rest)?;
if NumberField::kdeg(&g) != 0 {
return None; }
let ai = field.kpoly_mod(&field.kpoly_mul(&cur, &t), mi)?;
let s = field.kpoly_div_exact(&field.kpoly_sub(&cur, &field.kpoly_mul(&ai, &rest)), mi)?;
result.push(ai);
cur = s;
}
result.push(cur);
Some(result)
}
fn khermite_factor(
field: &NumberField,
ai: &KPoly,
v: &KPoly,
k: usize,
) -> Option<(Vec<(KPoly, usize)>, KPoly)> {
let vp = field.kpoly_deriv(v);
let mut a = NumberField::kpoly_trim(ai.clone());
let mut terms = Vec::new();
let mut power = k;
while power >= 2 {
let factor = field.from_int((power - 1) as i64);
let coeff = field.kpoly_mod(&field.kpoly_scale(&vp, &factor), v)?;
let inv = field.kpoly_mod_inverse(&coeff, v)?;
let neg_one = field.from_int(-1);
let b = field.kpoly_mod(&field.kpoly_mul(&field.kpoly_scale(&a, &neg_one), &inv), v)?;
let inner = field.kpoly_sub(
&field.kpoly_mul(&field.kpoly_deriv(&b), v),
&field.kpoly_scale(&field.kpoly_mul(&vp, &b), &factor),
);
a = field.kpoly_div_exact(&field.kpoly_sub(&a, &inner), v)?;
terms.push((b, power - 1));
power -= 1;
}
Some((terms, a))
}
fn khermite_reduce(
field: &NumberField,
r: &KPoly,
d: &KPoly,
) -> Option<(KHermiteTerms, KPoly, KPoly)> {
let sqf = kyun(field, d)?;
if sqf.is_empty() {
return Some((vec![], r.clone(), d.clone()));
}
let one = vec![field.from_int(1)];
let moduli: Vec<KPoly> = sqf
.iter()
.map(|(v, i)| {
let mut m = one.clone();
for _ in 0..*i {
m = field.kpoly_mul(&m, v);
}
m
})
.collect();
let parts = kpartial_fractions(field, r, &moduli)?;
let drad: KPoly = sqf
.iter()
.fold(one.clone(), |acc, (v, _)| field.kpoly_mul(&acc, v));
let mut hermite_terms: KHermiteTerms = Vec::new();
let mut h = Vec::new();
for ((v, i), ai) in sqf.iter().zip(parts.iter()) {
let cofactor = field.kpoly_div_exact(&drad, v)?; if *i == 1 {
h = field.kpoly_add(&h, &field.kpoly_mul(ai, &cofactor));
continue;
}
let (terms, leftover) = khermite_factor(field, ai, v, *i)?;
for (b, p) in terms {
if NumberField::kdeg(&b) < 0 {
continue; }
let v_pow = field.kpoly_pow(v, p as u32);
hermite_terms.push((b, v_pow, p));
}
h = field.kpoly_add(&h, &field.kpoly_mul(&leftover, &cofactor));
}
Some((hermite_terms, NumberField::kpoly_trim(h), drad))
}
pub(super) fn integrate_k_rational_with_logs(
field: &NumberField,
ext: &AlgebraicExtension,
c_num: &KPoly,
c_den: &KPoly,
) -> Option<KRationalLogResult> {
let c_num = NumberField::kpoly_trim(c_num.clone());
let c_den = NumberField::kpoly_trim(c_den.clone());
if c_den.is_empty() {
return None; }
if NumberField::kdeg(&c_den) < 1 {
return None; }
let g = field.kpoly_gcd(&c_num, &c_den)?;
let num = field.kpoly_div_exact(&c_num, &g)?;
let den_raw = field.kpoly_div_exact(&c_den, &g)?;
let den = field.kpoly_monic(&den_raw)?;
let lead_inv = {
let lead = den_raw[NumberField::kdeg(&den_raw) as usize].clone();
field.inv(&lead)?
};
let num = field.kpoly_scale(&num, &lead_inv);
if NumberField::kdeg(&den) < 1 {
return None; }
let (q, r) = field.kpoly_divrem(&num, &den)?;
let r = NumberField::kpoly_trim(r);
if r.is_empty() {
return None;
}
let (hermite_terms, h, drad) = khermite_reduce(field, &r, &den)?;
let mut log_terms = Vec::new();
let h = NumberField::kpoly_trim(h);
if !h.is_empty() && NumberField::kdeg(&drad) >= 1 {
let gh = field.kpoly_gcd(&h, &drad)?;
let h = field.kpoly_div_exact(&h, &gh)?;
let drad = field.kpoly_monic(&field.kpoly_div_exact(&drad, &gh)?)?;
if NumberField::kdeg(&drad) >= 1 {
let dprime = field.kpoly_deriv(&drad);
let roots = find_k_roots(field, &drad, ext)?;
if roots.len() != NumberField::kdeg(&drad) as usize {
return None; }
for root in &roots {
let h_val = eval_kpoly_at(field, &h, root);
let dp_val = eval_kpoly_at(field, &dprime, root);
let dp_inv = field.inv(&dp_val)?;
let residue = field.mul(&h_val, &dp_inv);
if NumberField::is_zero(&residue) {
continue;
}
log_terms.push((residue, root.clone()));
}
}
}
let poly_antideriv = field.kpoly_integrate(&NumberField::kpoly_trim(q));
let (rational_num, rational_den) = if hermite_terms.is_empty() {
(poly_antideriv, vec![field.from_int(1)])
} else {
let one = vec![field.from_int(1)];
let common_den = hermite_terms
.iter()
.fold(one.clone(), |acc, (_, v_pow, _)| {
field.kpoly_mul(&acc, v_pow)
});
let mut num = field.kpoly_mul(&poly_antideriv, &common_den);
for (b, v_pow, _) in &hermite_terms {
let cofactor = field.kpoly_div_exact(&common_den, v_pow)?;
num = field.kpoly_add(&num, &field.kpoly_mul(b, &cofactor));
}
(NumberField::kpoly_trim(num), common_den)
};
Some(KRationalLogResult {
rational_num,
rational_den,
log_terms,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::integrate::risch::exp_case::AlgebraicExtension;
fn k_sqrt2() -> (NumberField, AlgebraicExtension) {
let field = NumberField::new(vec![
Rational::from(-2),
Rational::from(0),
Rational::from(1),
]);
let ext = AlgebraicExtension::SingleSqrt {
d: 2,
sqrt_expr: crate::kernel::ExprId(0), };
(field, ext)
}
fn sqrt2_elem() -> KElem {
vec![Rational::from(0), Rational::from(1)]
}
#[test]
fn k_sqrt_perfect_squares() {
let (field, _ext) = k_sqrt2();
let two = field.from_int(2);
let s = k_sqrt_quadratic(&field, 2, &two).expect("2 is a square in Q(sqrt2)");
assert!(NumberField::kpoly_eq(
&[field.mul(&s, &s)],
&[field.reduce(&two)]
));
let nine = field.from_int(9);
let s = k_sqrt_quadratic(&field, 2, &nine).expect("9 is a perfect square");
assert!(NumberField::kpoly_eq(
&[field.mul(&s, &s)],
&[field.reduce(&nine)]
));
let one_plus_sqrt2 = field.add(&field.from_int(1), &sqrt2_elem());
let target = field.mul(&one_plus_sqrt2, &one_plus_sqrt2);
let s = k_sqrt_quadratic(&field, 2, &target).expect("(1+sqrt2)^2 is a K-square");
let s_sq = field.mul(&s, &s);
assert!(NumberField::kpoly_eq(&[s_sq], &[field.reduce(&target)]));
}
#[test]
fn k_sqrt_non_square() {
let (field, _ext) = k_sqrt2();
let three = field.from_int(3);
assert!(k_sqrt_quadratic(&field, 2, &three).is_none());
}
#[test]
fn x_times_x_plus_sqrt2_log_terms() {
let (field, ext) = k_sqrt2();
let sqrt2 = sqrt2_elem();
let den: KPoly = vec![NumberField::k_zero(), sqrt2.clone(), field.from_int(1)];
let num: KPoly = vec![field.from_int(1)];
let result = integrate_k_rational_with_logs(&field, &ext, &num, &den)
.expect("x(x+sqrt2) splits into K-linear factors");
assert!(NumberField::kdeg(&result.rational_num) < 0);
assert!(NumberField::kpoly_eq(
&result.rational_den,
&[field.from_int(1)]
));
assert_eq!(result.log_terms.len(), 2);
let sqrt2_inv = field.inv(&sqrt2).expect("sqrt2 invertible");
let neg_sqrt2_inv = field.neg(&sqrt2_inv);
let mut found_zero = false;
let mut found_neg_sqrt2 = false;
for (residue, root) in &result.log_terms {
if NumberField::kpoly_eq(std::slice::from_ref(root), &[NumberField::k_zero()]) {
found_zero = true;
assert!(NumberField::kpoly_eq(
std::slice::from_ref(residue),
std::slice::from_ref(&sqrt2_inv)
));
} else if NumberField::kpoly_eq(std::slice::from_ref(root), &[field.neg(&sqrt2)]) {
found_neg_sqrt2 = true;
assert!(NumberField::kpoly_eq(
std::slice::from_ref(residue),
std::slice::from_ref(&neg_sqrt2_inv)
));
}
}
assert!(found_zero && found_neg_sqrt2);
}
#[test]
fn polynomial_denominator_declines() {
let (field, ext) = k_sqrt2();
let den: KPoly = vec![field.from_int(1)];
let num: KPoly = vec![field.from_int(1)];
assert!(integrate_k_rational_with_logs(&field, &ext, &num, &den).is_none());
}
#[test]
fn exact_polynomial_quotient_declines() {
let (field, ext) = k_sqrt2();
let den: KPoly = vec![NumberField::k_zero(), sqrt2_elem(), field.from_int(1)];
let num = den.clone();
assert!(integrate_k_rational_with_logs(&field, &ext, &num, &den).is_none());
}
#[test]
fn repeated_factor_hermite_reduces() {
let (field, ext) = k_sqrt2();
let sqrt2 = sqrt2_elem();
let two_sqrt2 = field.mul(&field.from_int(2), &sqrt2);
let den: KPoly = vec![field.from_int(2), two_sqrt2, field.from_int(1)];
let num: KPoly = vec![field.from_int(1)];
let result = integrate_k_rational_with_logs(&field, &ext, &num, &den)
.expect("Hermite reduction handles the repeated K-factor");
assert!(result.log_terms.is_empty());
assert!(NumberField::kpoly_eq(
&result.rational_num,
&[field.neg(&field.from_int(1))]
));
let v: KPoly = vec![sqrt2.clone(), field.from_int(1)];
assert!(NumberField::kpoly_eq(&result.rational_den, &v));
}
#[test]
fn repeated_factor_with_log_remainder() {
let (field, ext) = k_sqrt2();
let sqrt2 = sqrt2_elem();
let two_sqrt2 = field.mul(&field.from_int(2), &sqrt2);
let v_sq: KPoly = vec![field.from_int(2), two_sqrt2.clone(), field.from_int(1)];
let den: KPoly = field.kpoly_mul(&v_sq, &[NumberField::k_zero(), field.from_int(1)]);
let num: KPoly = vec![field.from_int(1)];
let result = integrate_k_rational_with_logs(&field, &ext, &num, &den)
.expect("x*(x+sqrt2)^2 splits with one repeated K-factor");
assert!(NumberField::kdeg(&result.rational_den) > 0);
assert_eq!(result.log_terms.len(), 2);
}
#[test]
fn irreducible_quadratic_over_k_declines() {
let (field, ext) = k_sqrt2();
let den: KPoly = vec![field.from_int(1), NumberField::k_zero(), field.from_int(1)];
let num: KPoly = vec![field.from_int(1)];
assert!(integrate_k_rational_with_logs(&field, &ext, &num, &den).is_none());
}
}