use super::poly_utils::{as_integer, is_free_of_subexpr};
use crate::kernel::{ExprData, ExprId, ExprPool};
#[derive(Debug, Clone, Copy)]
pub struct FieldElem {
pub a: ExprId, pub b: ExprId, }
impl FieldElem {
pub fn pure_rational(a: ExprId, pool: &ExprPool) -> Self {
FieldElem {
a,
b: pool.integer(0_i32),
}
}
pub fn pure_sqrt(b: ExprId, pool: &ExprPool) -> Self {
FieldElem {
a: pool.integer(0_i32),
b,
}
}
pub fn one(pool: &ExprPool) -> Self {
FieldElem {
a: pool.integer(1_i32),
b: pool.integer(0_i32),
}
}
pub fn zero(pool: &ExprPool) -> Self {
FieldElem {
a: pool.integer(0_i32),
b: pool.integer(0_i32),
}
}
pub fn add(self, other: FieldElem, pool: &ExprPool) -> FieldElem {
let a = pool.add(vec![self.a, other.a]);
let b = pool.add(vec![self.b, other.b]);
FieldElem { a, b }
}
#[allow(dead_code)]
pub fn neg(self, pool: &ExprPool) -> FieldElem {
let neg1 = pool.integer(-1_i32);
let a = pool.mul(vec![neg1, self.a]);
let b = pool.mul(vec![neg1, self.b]);
FieldElem { a, b }
}
pub fn mul(self, other: FieldElem, p: ExprId, pool: &ExprPool) -> FieldElem {
let ac = pool.mul(vec![self.a, other.a]);
let bd_p = pool.mul(vec![self.b, other.b, p]);
let new_a = pool.add(vec![ac, bd_p]);
let ad = pool.mul(vec![self.a, other.b]);
let bc = pool.mul(vec![self.b, other.a]);
let new_b = pool.add(vec![ad, bc]);
FieldElem { a: new_a, b: new_b }
}
pub fn inv(self, p: ExprId, pool: &ExprPool) -> FieldElem {
use super::poly_utils::is_zero_expr;
if is_zero_expr(self.a, pool) {
let bp = pool.mul(vec![self.b, p]);
let new_b = pool.pow(bp, pool.integer(-1_i32));
return FieldElem {
a: pool.integer(0_i32),
b: new_b,
};
}
let a2 = pool.pow(self.a, pool.integer(2_i32));
let b2_p = pool.mul(vec![pool.pow(self.b, pool.integer(2_i32)), p]);
let neg1 = pool.integer(-1_i32);
let norm = pool.add(vec![a2, pool.mul(vec![neg1, b2_p])]);
let norm_inv = pool.pow(norm, pool.integer(-1_i32));
let new_a = pool.mul(vec![self.a, norm_inv]);
let new_b = pool.mul(vec![neg1, self.b, norm_inv]);
FieldElem { a: new_a, b: new_b }
}
pub fn powi(self, n: i64, p: ExprId, pool: &ExprPool) -> FieldElem {
if n == 0 {
return FieldElem::one(pool);
}
if n < 0 {
return self.inv(p, pool).powi(-n, p, pool);
}
if n == 1 {
return self;
}
let half = self.powi(n / 2, p, pool);
let sq = half.mul(half, p, pool);
if n % 2 == 0 {
sq
} else {
sq.mul(self, p, pool)
}
}
}
pub fn decompose_sqrt(
expr: ExprId,
sqrt_id: ExprId,
p_expr: ExprId,
pool: &ExprPool,
) -> Option<(ExprId, ExprId)> {
let elem = decompose_elem(expr, sqrt_id, p_expr, pool)?;
Some((elem.a, elem.b))
}
fn decompose_elem(
expr: ExprId,
sqrt_id: ExprId,
p_expr: ExprId,
pool: &ExprPool,
) -> Option<FieldElem> {
if expr == sqrt_id {
return Some(FieldElem::pure_sqrt(pool.integer(1_i32), pool));
}
if is_free_of_subexpr(expr, sqrt_id, pool) {
return Some(FieldElem::pure_rational(expr, pool));
}
match pool.get(expr) {
ExprData::Add(args) => {
let mut acc = FieldElem::zero(pool);
for a in &args {
let elem = decompose_elem(*a, sqrt_id, p_expr, pool)?;
acc = acc.add(elem, pool);
}
Some(acc)
}
ExprData::Mul(args) => {
let mut acc = FieldElem::one(pool);
for a in &args {
let elem = decompose_elem(*a, sqrt_id, p_expr, pool)?;
acc = acc.mul(elem, p_expr, pool);
}
Some(acc)
}
ExprData::Pow { base, exp } => {
if base == sqrt_id {
let n = as_integer(exp, pool)?;
if n == 0 {
return Some(FieldElem::one(pool));
}
if n > 0 {
let n_u = n as u32;
if n_u % 2 == 0 {
let p_pow = pool.pow(p_expr, pool.integer(n_u / 2));
return Some(FieldElem::pure_rational(p_pow, pool));
} else {
let p_pow = pool.pow(p_expr, pool.integer((n_u - 1) / 2));
return Some(FieldElem::pure_sqrt(p_pow, pool));
}
} else {
let base_elem = FieldElem::pure_sqrt(pool.integer(1_i32), pool);
return Some(base_elem.powi(n, p_expr, pool));
}
}
if let Some(n) = as_integer(exp, pool) {
let base_elem = decompose_elem(base, sqrt_id, p_expr, pool)?;
return Some(base_elem.powi(n, p_expr, pool));
}
None
}
ExprData::Func { ref name, ref args } if name == "sqrt" && args.len() == 1 => {
if expr == sqrt_id {
Some(FieldElem::pure_sqrt(pool.integer(1_i32), pool))
} else {
None
}
}
_ => {
if is_free_of_subexpr(expr, sqrt_id, pool) {
Some(FieldElem::pure_rational(expr, pool))
} else {
None
}
}
}
}
use super::poly_utils::is_free_of;
use crate::integrate::risch::poly_rde::{
degree, expr_to_qpoly, poly_add, poly_mul, poly_scale, qpoly_to_expr, trim,
};
use crate::integrate::risch::rational_rde::{expr_to_qrational, poly_div_exact, poly_gcd};
type QPoly = Vec<rug::Rational>;
#[derive(Clone)]
struct AlgFrac {
a: QPoly,
b: QPoly,
d: QPoly,
}
const MAX_ALG_POW: i64 = 32;
const MAX_ALG_DEG: i64 = 64;
fn one_poly() -> QPoly {
vec![rug::Rational::from(1)]
}
impl AlgFrac {
fn rational(num: QPoly, den: QPoly) -> Self {
AlgFrac {
a: num,
b: Vec::new(),
d: den,
}
.reduced()
}
fn one() -> Self {
AlgFrac {
a: one_poly(),
b: Vec::new(),
d: one_poly(),
}
}
fn zero() -> Self {
AlgFrac {
a: Vec::new(),
b: Vec::new(),
d: one_poly(),
}
}
fn generator() -> Self {
AlgFrac {
a: Vec::new(),
b: one_poly(),
d: one_poly(),
}
}
fn too_big(&self) -> bool {
degree(&self.a).max(degree(&self.b)).max(degree(&self.d)) > MAX_ALG_DEG
}
fn reduced(self) -> Self {
let (a, b, d) = (trim(self.a), trim(self.b), trim(self.d));
if d.is_empty() {
return AlgFrac { a, b, d };
}
let g = poly_gcd(&poly_gcd(&a, &b), &d);
let (a, b, d) = if degree(&g) >= 1 {
(
poly_div_exact(&a, &g),
poly_div_exact(&b, &g),
poly_div_exact(&d, &g),
)
} else {
(a, b, d)
};
match d.last() {
Some(lead) if *lead != 1 => {
let inv = rug::Rational::from(1) / lead.clone();
AlgFrac {
a: poly_scale(&a, &inv),
b: poly_scale(&b, &inv),
d: poly_scale(&d, &inv),
}
}
_ => AlgFrac { a, b, d },
}
}
fn add(self, o: AlgFrac) -> Self {
AlgFrac {
a: poly_add(&poly_mul(&self.a, &o.d), &poly_mul(&o.a, &self.d)),
b: poly_add(&poly_mul(&self.b, &o.d), &poly_mul(&o.b, &self.d)),
d: poly_mul(&self.d, &o.d),
}
.reduced()
}
fn mul(self, o: AlgFrac, p: &QPoly) -> Self {
AlgFrac {
a: poly_add(
&poly_mul(&self.a, &o.a),
&poly_mul(&poly_mul(&self.b, &o.b), p),
),
b: poly_add(&poly_mul(&self.a, &o.b), &poly_mul(&self.b, &o.a)),
d: poly_mul(&self.d, &o.d),
}
.reduced()
}
fn inv(self, p: &QPoly) -> Option<Self> {
let minus_one = rug::Rational::from(-1);
let norm = poly_add(
&poly_mul(&self.a, &self.a),
&poly_scale(&poly_mul(&poly_mul(&self.b, &self.b), p), &minus_one),
);
if trim(norm.clone()).is_empty() {
return None; }
Some(
AlgFrac {
a: poly_mul(&self.d, &self.a),
b: poly_scale(&poly_mul(&self.d, &self.b), &minus_one),
d: norm,
}
.reduced(),
)
}
fn powi(self, n: i64, p: &QPoly) -> Option<Self> {
if n.abs() > MAX_ALG_POW {
return None;
}
if n < 0 {
return self.inv(p)?.powi(-n, p);
}
let mut acc = AlgFrac::one();
for _ in 0..n {
acc = acc.mul(self.clone(), p);
if acc.too_big() {
return None;
}
}
Some(acc)
}
fn to_expr(&self, var: ExprId, sqrt_id: ExprId, pool: &ExprPool) -> ExprId {
let den = qpoly_to_expr(&self.d, var, pool);
let inv_d = pool.pow(den, pool.integer(-1_i32));
let a = pool.mul(vec![qpoly_to_expr(&self.a, var, pool), inv_d]);
if trim(self.b.clone()).is_empty() {
return a;
}
let b = pool.mul(vec![qpoly_to_expr(&self.b, var, pool), inv_d, sqrt_id]);
pool.add(vec![a, b])
}
}
pub fn normalize_over_sqrt(expr: ExprId, radicand: ExprId, var: ExprId, pool: &ExprPool) -> ExprId {
let Some(p) = expr_to_qpoly(radicand, var, pool).map(trim) else {
return expr;
};
if degree(&p) < 1 {
return expr;
}
let sqrt_id = pool.func("sqrt", vec![qpoly_to_expr(&p, var, pool)]);
let canon = canonicalize_radicals(expr, &p, var, sqrt_id, pool);
normalize_rec(canon, sqrt_id, &p, var, pool)
}
fn canonicalize_radicals(
expr: ExprId,
p: &QPoly,
var: ExprId,
sqrt_id: ExprId,
pool: &ExprPool,
) -> ExprId {
if expr == sqrt_id {
return expr;
}
let same_radicand = |u: ExprId| expr_to_qpoly(u, var, pool).is_some_and(|q| trim(q) == *p);
match pool.get(expr) {
ExprData::Func { ref name, ref args } if name == "sqrt" && args.len() == 1 => {
if same_radicand(args[0]) {
sqrt_id
} else {
expr
}
}
ExprData::Pow { base, exp } => {
if let ExprData::Rational(r) = pool.get(exp) {
if *r.0.numer() == 1 && *r.0.denom() == 2 && same_radicand(base) {
return sqrt_id;
}
}
pool.pow(canonicalize_radicals(base, p, var, sqrt_id, pool), exp)
}
ExprData::Add(args) => pool.add(
args.iter()
.map(|&a| canonicalize_radicals(a, p, var, sqrt_id, pool))
.collect::<Vec<_>>(),
),
ExprData::Mul(args) => pool.mul(
args.iter()
.map(|&a| canonicalize_radicals(a, p, var, sqrt_id, pool))
.collect::<Vec<_>>(),
),
ExprData::Func { ref name, ref args } => pool.func(
name,
args.iter()
.map(|&a| canonicalize_radicals(a, p, var, sqrt_id, pool))
.collect::<Vec<_>>(),
),
_ => expr,
}
}
fn normalize_rec(expr: ExprId, sqrt_id: ExprId, p: &QPoly, var: ExprId, pool: &ExprPool) -> ExprId {
if is_free_of_subexpr(expr, sqrt_id, pool) {
return expr;
}
if let Some(f) = to_alg(expr, sqrt_id, p, var, pool) {
return f.to_expr(var, sqrt_id, pool);
}
match pool.get(expr) {
ExprData::Add(args) => pool.add(
args.iter()
.map(|&a| normalize_rec(a, sqrt_id, p, var, pool))
.collect::<Vec<_>>(),
),
ExprData::Mul(args) => pool.mul(
args.iter()
.map(|&a| normalize_rec(a, sqrt_id, p, var, pool))
.collect::<Vec<_>>(),
),
ExprData::Pow { base, exp } => pool.pow(normalize_rec(base, sqrt_id, p, var, pool), exp),
ExprData::Func { ref name, ref args } => pool.func(
name,
args.iter()
.map(|&a| normalize_rec(a, sqrt_id, p, var, pool))
.collect::<Vec<_>>(),
),
_ => expr,
}
}
fn to_alg(
expr: ExprId,
sqrt_id: ExprId,
p: &QPoly,
var: ExprId,
pool: &ExprPool,
) -> Option<AlgFrac> {
if expr == sqrt_id {
return Some(AlgFrac::generator());
}
if is_free_of_subexpr(expr, sqrt_id, pool) {
let numeric = matches!(pool.get(expr), ExprData::Integer(_) | ExprData::Rational(_));
if numeric || !is_free_of(expr, var, pool) {
let (n, d) = expr_to_qrational(expr, var, pool)?;
return Some(AlgFrac::rational(n, d));
}
return None;
}
let out = match pool.get(expr) {
ExprData::Add(args) => {
let mut acc = AlgFrac::zero();
for a in &args {
acc = acc.add(to_alg(*a, sqrt_id, p, var, pool)?);
if acc.too_big() {
return None;
}
}
acc
}
ExprData::Mul(args) => {
let mut acc = AlgFrac::one();
for a in &args {
acc = acc.mul(to_alg(*a, sqrt_id, p, var, pool)?, p);
if acc.too_big() {
return None;
}
}
acc
}
ExprData::Pow { base, exp } => {
let n = as_integer(exp, pool)?;
to_alg(base, sqrt_id, p, var, pool)?.powi(n, p)?
}
_ => return None,
};
Some(out)
}