#[cfg(test)]
mod tests {
use ocas_atom::normalize::normalize;
use ocas_atom::{AtomArena, Symbol};
use ocas_core::arena::Arena;
use proptest::prelude::*;
use crate::integral::certify::certify;
use crate::integral::outcome::{Outcome, integrate_outcome};
fn expr_strategy(leaves: Vec<String>) -> impl Strategy<Value = String> {
prop::sample::select(leaves).prop_recursive(2, 8, 2, |inner| {
prop_oneof![
(inner.clone(), inner.clone()).prop_map(|(a, b)| format!("({a}) + ({b})")),
(inner.clone(), inner.clone()).prop_map(|(a, b)| format!("({a}) * ({b})")),
(inner, prop::sample::select(vec![2i64, 3, -1, -2]))
.prop_map(|(a, k)| format!("({a})^({k})")),
]
})
}
fn tower_args() -> impl Strategy<Value = Vec<(&'static str, String)>> {
prop::collection::vec(
(
prop::sample::select(vec!["log", "exp"]),
prop::sample::select(vec![
"x".to_string(),
"x^2".to_string(),
"x + 1".to_string(),
"x^2 + x".to_string(),
"2*x".to_string(),
]),
),
1..=3,
)
}
fn leaves_of(gens: &[(&'static str, String)]) -> Vec<String> {
let mut leaves = vec!["x".to_string(), "2".to_string(), "-1".to_string()];
leaves.extend(gens.iter().map(|(kind, arg)| format!("{kind}({arg})")));
leaves
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn tower_constructed_elements_integrate_safely(
(gens, f_src) in tower_args().prop_flat_map(|gens| {
let leaves = leaves_of(&gens);
expr_strategy(leaves).prop_map(move |f| (gens.clone(), f))
}),
) {
let _ = &gens; let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = Symbol::new("x");
let big_f = ocas_parse::parse(&ctx, &f_src).expect("parse F");
let f = normalize(&ctx, crate::diff(&ctx, big_f, x));
if crate::integral::node_count(f) > 400 {
return Ok(());
}
match integrate_outcome(&ctx, f, x) {
Outcome::Found { value, .. } => {
prop_assert!(!value.to_string().contains("Integral("));
}
Outcome::Unknown { .. } => {}
Outcome::ProvedNonElementary { .. } => {
return Err(TestCaseError::fail(
"no ProvedNonElementary producer exists in 0.29.0",
));
}
}
}
#[test]
fn tower_random_integrands_never_emit_uncertified(
f_src in tower_args().prop_flat_map(|gens| {
expr_strategy(leaves_of(&gens))
}),
) {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = Symbol::new("x");
let expr = ocas_parse::parse(&ctx, &f_src).expect("parse f");
if crate::integral::node_count(expr) > 200 {
return Ok(());
}
let normalized = normalize(&ctx, expr);
let result = crate::integrate(&ctx, expr, x);
if result.to_string().contains("Integral(") {
return Ok(()); }
let cert = certify(&ctx, normalized, result, x);
prop_assert!(
cert.is_ok(),
"legacy integrate emitted an uncertifiable answer for {f_src}: \
{result} ({:?})",
cert.err(),
);
}
}
#[test]
fn adversarial_dependent_generators_certify() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = Symbol::new("x");
for src in [
"exp(x)*exp(-x) + x",
"log(x) + log(3*x)",
"exp(x) + exp(2*x)",
] {
let expr = ocas_parse::parse(&ctx, src).expect("parse");
let result = crate::integrate(&ctx, expr, x);
assert!(
!result.to_string().contains("Integral("),
"dependent-generator family must integrate: {src} -> {result}"
);
let cert = certify(&ctx, normalize(&ctx, expr), result, x);
assert!(cert.is_ok(), "certificate failed for {src}: {result}");
}
}
}