use ocas_atom::normalize::normalize;
use ocas_atom::{Atom, AtomArena, Symbol};
use super::certify::{CertDecline, Certificate, certify};
use super::{IntegrateOptions, integrate_with_options, residual};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NonElementaryWitness<'a> {
pub method: &'static str,
pub residue: Atom<'a>,
}
#[derive(Clone, Copy, Debug)]
pub enum Outcome<'a> {
Found {
value: Atom<'a>,
certificate: Certificate<'a>,
},
ProvedNonElementary {
witness: NonElementaryWitness<'a>,
},
Unknown {
residue: Atom<'a>,
uncertified: Option<Atom<'a>>,
},
}
impl<'a> Outcome<'a> {
pub fn value(self) -> Atom<'a> {
match self {
Outcome::Found { value, .. } => value,
Outcome::Unknown { residue, .. } => residue,
Outcome::ProvedNonElementary { witness } => witness.residue,
}
}
pub fn is_found(self) -> bool {
matches!(self, Outcome::Found { .. })
}
pub fn certificate(self) -> Option<Certificate<'a>> {
match self {
Outcome::Found { certificate, .. } => Some(certificate),
_ => None,
}
}
pub fn uncertified(self) -> Option<Atom<'a>> {
match self {
Outcome::Unknown { uncertified, .. } => uncertified,
_ => None,
}
}
}
pub fn integrate_outcome<'a>(ctx: &'a AtomArena<'a>, expr: Atom<'a>, var: Symbol) -> Outcome<'a> {
integrate_outcome_with_options(ctx, expr, var, IntegrateOptions::default())
}
pub fn integrate_outcome_with_options<'a>(
ctx: &'a AtomArena<'a>,
expr: Atom<'a>,
var: Symbol,
options: IntegrateOptions,
) -> Outcome<'a> {
gated(ctx, expr, var, options).outcome
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug)]
pub struct DiagnosticOutcome<'a> {
pub raw: Atom<'a>,
pub certificate: Result<Certificate<'a>, CertDecline>,
pub outcome: Outcome<'a>,
}
#[doc(hidden)]
pub fn integrate_diagnostic<'a>(
ctx: &'a AtomArena<'a>,
expr: Atom<'a>,
var: Symbol,
options: IntegrateOptions,
) -> DiagnosticOutcome<'a> {
gated(ctx, expr, var, options)
}
fn gated<'a>(
ctx: &'a AtomArena<'a>,
expr: Atom<'a>,
var: Symbol,
options: IntegrateOptions,
) -> DiagnosticOutcome<'a> {
let normalized = normalize(ctx, expr);
let raw = integrate_with_options(ctx, expr, var, options);
let certificate = if residual::contains_residue(raw) {
Err(CertDecline::NotInField)
} else {
certify(ctx, normalized, raw, var)
};
let outcome = match certificate {
Ok(cert) => Outcome::Found {
value: raw,
certificate: cert,
},
Err(reason) => Outcome::Unknown {
residue: if residual::contains_residue(raw) {
raw
} else {
super::fallback(ctx, normalized, var)
},
uncertified: if residual::contains_residue(raw) {
None
} else {
debug_assert!(matches!(
reason,
CertDecline::NotInField | CertDecline::Budget | CertDecline::NonZero
));
Some(raw)
},
},
};
DiagnosticOutcome {
raw,
certificate,
outcome,
}
}
#[cfg(test)]
mod tests {
use ocas_core::arena::Arena;
use ocas_parse::parse;
use super::*;
#[test]
fn certified_pairs_are_found() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let f = parse(&ctx, "2*x").expect("parse");
let outcome = integrate_outcome(&ctx, f, Symbol::new("x"));
assert!(outcome.is_found());
assert!(outcome.certificate().is_some());
assert!(!outcome.value().to_string().contains("Integral("));
}
#[test]
fn non_elementary_integrals_are_unknown_with_a_residue() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let f = parse(&ctx, "exp(x^3)").expect("parse");
let outcome = integrate_outcome(&ctx, f, Symbol::new("x"));
assert!(!outcome.is_found());
assert!(outcome.value().to_string().contains("Integral("));
}
#[test]
fn diagnostic_reports_the_raw_candidate() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let f = parse(&ctx, "x^2*sin(x)").expect("parse");
let d = integrate_diagnostic(&ctx, f, Symbol::new("x"), IntegrateOptions::default());
assert!(!d.raw.to_string().contains("Integral("));
assert_eq!(d.raw, d.outcome.value());
}
}