use rug::Rational;
use super::exp_case::{rational_sqrt, AlgebraicExtension};
use super::number_field::{KElem, KPoly, NumberField};
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,
}
}
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 dprime = field.kpoly_deriv(&den);
let gd = field.kpoly_gcd(&den, &dprime)?;
if NumberField::kdeg(&gd) > 0 {
return None; }
let roots = find_k_roots(field, &den, ext)?;
if roots.len() != NumberField::kdeg(&den) as usize {
return None;
}
let mut log_terms = Vec::new();
for root in &roots {
let r_val = eval_kpoly_at(field, &r, root);
let dp_val = eval_kpoly_at(field, &dprime, root);
let dp_inv = field.inv(&dp_val)?;
let residue = field.mul(&r_val, &dp_inv);
if NumberField::is_zero(&residue) {
continue;
}
log_terms.push((residue, root.clone()));
}
Some(KRationalLogResult {
rational_num: NumberField::kpoly_trim(q),
rational_den: vec![field.from_int(1)],
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_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_declines() {
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)];
assert!(integrate_k_rational_with_logs(&field, &ext, &num, &den).is_none());
}
#[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());
}
}