use super::*;
use crate::parse::parse;
use std::collections::HashMap;
type Entry = (&'static str, &'static str, usize, &'static str);
#[rustfmt::skip]
pub(crate) const CORPUS: &[Entry] = &[
("separable", "y'=y", 1, "yp - y"),
("separable", "logistic", 1, "yp - y*(1 - y)"),
("separable", "y'=x*y", 1, "yp - x*y"),
("separable", "y'=x^2*y^3", 1, "yp - x^2*y^3"),
("separable", "y'=exp(x)*y", 1, "yp - exp(x)*y"),
("separable", "y'=1+y^2", 1, "yp - 1 - y^2"),
("separable", "y'=y/x", 1, "yp - y/x"),
("separable", "y'=x*exp(-x^2)*y", 1, "yp - x*exp(-x^2)*y"),
("separable", "y'=log(x)*y", 1, "yp - log(x)*y"),
("separable", "y'=y*sin(x)^2", 1, "yp - y*sin(x)^2"),
("separable", "y'=y/(x*log(x))", 1, "yp - y/(x*log(x))"),
("separable", "y'=y*x/(1+x^2)", 1, "yp - y*x/(1 + x^2)"),
("linear1", "y'-3y=x", 1, "yp - 3*y - x"),
("linear1", "y'+y=exp(x)", 1, "yp + y - exp(x)"),
("linear1", "y'+y/x=1", 1, "yp + y/x - 1"),
("linear1", "y'+2xy=x", 1, "yp + 2*x*y - x"),
("linear1", "y'+3y=x", 1, "yp + 3*y - x"),
("linear1", "y'+y*tan(x)=sin(x)", 1, "yp + y*tan(x) - sin(x)"),
("linear1", "y'-y/x=x*log(x)", 1, "yp - y/x - x*log(x)"),
("linear1", "y'+y/x=log(x)/x", 1, "yp + y/x - log(x)/x"),
("linear1", "y'+y=1/x", 1, "yp + y - 1/x"),
("linear1", "y'+y*x/(1+x^2)=x", 1, "yp + y*x/(1 + x^2) - x"),
("linear1", "y'+y/(x*log(x))=1", 1, "yp + y/(x*log(x)) - 1"),
("linear1", "y'-2xy=exp(x^2)*sin(x)", 1, "yp - 2*x*y - exp(x^2)*sin(x)"),
("linear1", "y'+y=x*exp(-x)", 1, "yp + y - x*exp(-x)"),
("linear1", "y'+y/x=cos(x)/x", 1, "yp + y/x - cos(x)/x"),
("bernoulli", "y'+y=y^2", 1, "yp + y - y^2"),
("bernoulli", "y'+y/x=y^2", 1, "yp + y/x - y^2"),
("bernoulli", "y'-y=x*y^2", 1, "yp - y - x*y^2"),
("bernoulli", "y'+y=x^2*y^3", 1, "yp + y - x^2*y^3"),
("bernoulli", "y'+y/x=x*y^2", 1, "yp + y/x - x*y^2"),
("bernoulli", "y'+y*tan(x)=y^3", 1, "yp + y*tan(x) - y^3"),
("exact", "(2x+y)+(x+2y)y'", 1, "(2*x + y) + (x + 2*y)*yp"),
("exact", "sin(xy) potential", 1, "y*cos(x*y) + x*cos(x*y)*yp"),
("exact", "(2x*log(x)+y)+(x+2y)y'", 1, "(2*x*log(x) + y) + (x + 2*y)*yp"),
("exact", "(1/x+y)+(x+1/y)y'", 1, "(1/x + y) + (x + 1/y)*yp"),
("exact", "exp(x)sin(y)", 1, "exp(x)*sin(y) + exp(x)*cos(y)*yp"),
("exact", "(y/(1+x^2))+atan(x)y'", 1, "y/(1 + x^2) + atan(x)*yp"),
("homog", "y'=1+y/x", 1, "yp - 1 - y/x"),
("homog", "y'=(x^2+y^2)/(x*y)", 1, "yp - (x^2 + y^2)/(x*y)"),
("homog", "y'=(x+y)/(x-y)", 1, "yp - (x + y)/(x - y)"),
("homog", "y'=y/x+tan(y/x)", 1, "yp - y/x - tan(y/x)"),
("homog", "y'=(y^2)/(x^2)", 1, "yp - y^2/x^2"),
("homog", "y'=(x+3y)/(x-y)", 1, "yp - (x + 3*y)/(x - y)"),
("clairaut", "y=x y'+(y')^2", 1, "y - x*yp - yp^2"),
("clairaut", "y=x y'+exp(y')", 1, "y - x*yp - exp(yp)"),
("clairaut", "y=x y'-log(y')", 1, "y - x*yp + log(yp)"),
("clairaut", "y=x y'+1/y'", 1, "y - x*yp - 1/yp"),
("riccati", "y'=(y-x)^2+1", 1, "yp - (y - x)^2 - 1"),
("riccati", "y'=y^2-2xy+x^2+1", 1, "yp - y^2 + 2*x*y - x^2 - 1"),
("riccati", "y'=y^2-2xy+x^2+x", 1, "yp - y^2 + 2*x*y - x^2 - x"),
("cc2-hom", "y''-y=0", 2, "ypp - y"),
("cc2-hom", "y''+y=0", 2, "ypp + y"),
("cc2-hom", "y''-3y'+2y=0", 2, "ypp - 3*yp + 2*y"),
("cc2-hom", "y''+2y'+y=0", 2, "ypp + 2*yp + y"),
("cc2-hom", "y''+2y'+5y=0", 2, "ypp + 2*yp + 5*y"),
("cc2-hom", "4y''-4y'+y=0", 2, "4*ypp - 4*yp + y"),
("cc2-poly-exp", "y''+y=x^2", 2, "ypp + y - x^2"),
("cc2-poly-exp", "y''-y'-2y=exp(3x)", 2, "ypp - yp - 2*y - exp(3*x)"),
("cc2-poly-exp", "y''+y=x*exp(x)", 2, "ypp + y - x*exp(x)"),
("cc2-poly-exp", "y''+y'-6y=x*e^{2x}", 2, "ypp + yp - 6*y - x*exp(2*x)"),
("cc2-poly-exp", "y''+y=cos(x) (res)", 2, "ypp + y - cos(x)"),
("cc2-poly-exp", "y''-2y'+y=e^x (res)",2, "ypp - 2*yp + y - exp(x)"),
("cc2-poly-exp", "y''+4y=sin(2x)(res)",2, "ypp + 4*y - sin(2*x)"),
("cc2-vop", "y''+y=sec(x)", 2, "ypp + y - 1/cos(x)"),
("cc2-vop", "y''+y=tan(x)", 2, "ypp + y - tan(x)"),
("cc2-vop", "y''-y=1/x", 2, "ypp - y - 1/x"),
("cc2-vop", "y''-2y'+y=e^x/x", 2, "ypp - 2*yp + y - exp(x)/x"),
("cc2-vop", "y''+y=log(x)", 2, "ypp + y - log(x)"),
("cc2-vop", "y''-y=exp(x)/(1+exp(x))", 2, "ypp - y - exp(x)/(1 + exp(x))"),
("cc2-vop", "y''+y=1/x^2", 2, "ypp + y - 1/x^2"),
("cc2-vop", "y''-3y'+2y=1/(1+e^-x)", 2, "ypp - 3*yp + 2*y - 1/(1 + exp(-x))"),
("cc2-vop", "y''+y=x/(1+x^2)", 2, "ypp + y - x/(1 + x^2)"),
("cc2-vop", "y''-y=1/(1+exp(x))", 2, "ypp - y - 1/(1 + exp(x))"),
("cc2-vop", "y''+y=csc(x)", 2, "ypp + y - 1/sin(x)"),
("cc2-vop", "y''-y=exp(x)/x", 2, "ypp - y - exp(x)/x"),
("cc2-vop", "y''+y=sec(x)*tan(x)", 2, "ypp + y - tan(x)/cos(x)"),
("cc2-vop", "y''-4y=1/x", 2, "ypp - 4*y - 1/x"),
("cc2-vop", "y''+y=1/(1+sin(x))", 2, "ypp + y - 1/(1 + sin(x))"),
("cc2-vop", "y''-y=x/(x^2-1)", 2, "ypp - y - x/(x^2 - 1)"),
("euler", "x^2y''+xy'-y=0", 2, "x^2*ypp + x*yp - y"),
("euler", "x^2y''-3xy'+4y=0", 2, "x^2*ypp - 3*x*yp + 4*y"),
("euler", "x^2y''+xy'+y=0", 2, "x^2*ypp + x*yp + y"),
("euler-nh", "x^2y''+xy'-y=x^2", 2, "x^2*ypp + x*yp - y - x^2"),
("euler-nh", "x^2y''-2xy'+2y=x^3", 2, "x^2*ypp - 2*x*yp + 2*y - x^3"),
("euler-nh", "x^2y''+xy'-y=log(x)", 2, "x^2*ypp + x*yp - y - log(x)"),
("euler-nh", "x^2y''-xy'+y=x", 2, "x^2*ypp - x*yp + y - x"),
("varcoef", "y''-(2/x)y'+(2/x^2)y=0", 2, "ypp - (2/x)*yp + (2/x^2)*y"),
("varcoef", "(1-x^2)y''-2xy'+2y=0", 2, "(1 - x^2)*ypp - 2*x*yp + 2*y"),
("varcoef", "x y''-(x+1)y'+y=0", 2, "x*ypp - (x + 1)*yp + y"),
("varcoef", "y''-y'/x=x", 2, "ypp - yp/x - x"),
("varcoef", "x^2y''+xy'-y=x", 2, "x^2*ypp + x*yp - y - x"),
("higher", "y'''-y'=0", 3, "yppp - yp"),
("higher", "y'''-6y''+11y'-6y=0", 3, "yppp - 6*ypp + 11*yp - 6*y"),
("higher", "y''''-y=0", 4, "ypppp - y"),
("higher", "y'''-3y''+3y'-y=0", 3, "yppp - 3*ypp + 3*yp - y"),
("higher", "y'''-y'=exp(x)", 3, "yppp - yp - exp(x)"),
("higher", "y'''-y'=1/x", 3, "yppp - yp - 1/x"),
("higher", "y'''+y'=sec(x)", 3, "yppp + yp - 1/cos(x)"),
("higher", "y'''+y'=tan(x)", 3, "yppp + yp - tan(x)"),
("higher", "y'''-y'=x^2", 3, "yppp - yp - x^2"),
];
fn build(entry: &Entry, pool: &ExprPool) -> Option<OdeInput> {
let (_, _, order, src) = *entry;
build_ode(order, src, pool)
}
pub(crate) fn build_ode(order: usize, src: &str, pool: &ExprPool) -> Option<OdeInput> {
let x = pool.symbol("x", Domain::Real);
let y = pool.symbol("y", Domain::Real);
let (input, derivs) = OdeInput::higher_order(x, y, order, pool);
let mut syms: HashMap<String, ExprId> = HashMap::new();
syms.insert("x".to_owned(), x);
syms.insert("y".to_owned(), y);
for (k, &d) in derivs.iter().enumerate() {
syms.insert(format!("y{}", "p".repeat(k + 1)), d);
}
let eq = parse(src, pool, &mut syms).ok()?;
Some(input.with_equation(eq))
}
fn outcome(entry: &Entry) -> (String, String) {
let pool = ExprPool::new();
let Some(input) = build(entry, &pool) else {
return ("PARSE_ERR".to_owned(), String::new());
};
match dsolve(&input, &pool) {
Ok(res) => match res.solutions.first() {
Some(s) => {
let verdict = match residual_is_zero(&input, s.y_of_x, &s.constants, &pool) {
Ok(()) => "SOLVED",
Err(_) => "UNVERIFIED",
};
(verdict.to_owned(), s.method.to_owned())
}
None => ("EMPTY".to_owned(), String::new()),
},
Err(DsolveError::VerificationFailed(_)) => ("VERIFY_FAIL".to_owned(), String::new()),
Err(_) => ("DECLINED".to_owned(), String::new()),
}
}
#[test]
#[ignore = "measurement harness; run explicitly with --nocapture"]
fn decline_split_report() {
let (mut no_candidate, mut gate_refused) = (0usize, 0usize);
for e in CORPUS {
let pool = ExprPool::new();
let Some(input) = build(e, &pool) else {
continue;
};
let _ = super::verify::take_gate_tally();
let solved = dsolve(&input, &pool).is_ok();
let (offered, refused) = super::verify::take_gate_tally();
if solved {
continue;
}
if refused > 0 {
gate_refused += 1;
println!(
"GATE_REFUSED\t{}\t{}\t{offered} offered, {refused} refused",
e.0, e.1
);
} else {
no_candidate += 1;
println!("NO_CANDIDATE\t{}\t{}", e.0, e.1);
}
}
println!("SPLIT\tno_candidate={no_candidate}\tgate_refused={gate_refused}");
}
#[test]
#[ignore = "measurement harness; run explicitly with --nocapture"]
fn corpus_report() {
let mut solved = 0usize;
for e in CORPUS {
eprintln!("=== {}\t{}", e.0, e.1);
let (status, method) = outcome(e);
eprintln!("--- {status}\t{}\t{}\t{method}", e.0, e.1);
if status == "SOLVED" {
solved += 1;
}
println!("{status}\t{}\t{}\t{method}", e.0, e.1);
}
println!("TOTAL\t{solved}/{}", CORPUS.len());
}
#[test]
#[ignore = "integrator feedback probe; run explicitly with --nocapture"]
fn integrator_gap_probe() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let mut syms: HashMap<String, ExprId> = HashMap::new();
syms.insert("x".to_owned(), x);
for src in [
"exp(x)/x",
"exp(-1*x)/x",
"exp(2*x)/x",
"sin(x)*log(x)",
"sin(x)/x^2",
"exp(-2*x)/(1 + exp(-1*x))",
"exp(2*x)/(1 + exp(x))",
"sin(x)*tan(x)",
"sin(x)*tan(x)/(cos(x)^2 + sin(x)^2)",
"exp(x)^2/(1 + exp(x))",
] {
let e = parse(src, &pool, &mut syms).expect("probe source parses");
let verdict = match crate::integrate::engine::integrate(e, x, &pool) {
Ok(d) => format!("CLOSES {}", pool.display(d.value)),
Err(err) => format!("DECLINES {err}"),
};
println!("∫ {src} dx\t{verdict}");
}
}