use symplex::prelude::*;
const REF: &[(f64, [&str; 7])] = &[
(
0.5,
[
"0.93846980724081290422840467359971263",
"0.24226845767487388638395457614153164",
"0.0000080536272413574740859781853303090647",
"-0.44451873350670655714839847506833191",
"-1.4714723926702430691885846353232975",
"1.0634833707413235192631844154453565",
"0.92441907122766586178192416753021699",
],
),
(
5.0,
[
"-0.17759677131433830434739701307475871",
"-0.32757913759146522203773432191016913",
"0.26114054612017009005480553851291853",
"-0.30851762524903378007364898421204661",
"0.14786314339122684480105067548803721",
"27.239871823604446894544232075884419",
"0.0036910983340425942747352610074569951",
],
),
(
12.0,
[
"0.047689310796833536623811689141429138",
"-0.22344710449062761236769771636429716",
"-0.073470963101658581265788425544661515",
"-0.22523731263436143368768510978745849",
"-0.057099218260896521050415271335470805",
"18948.925349296308861208108229753966",
"0.0000022008253973114914005155997745454167",
],
),
(
20.0,
[
"0.16702466434058315472732054470138404",
"0.06683312417585004557899297419364672",
"0.15116976798239497460710045572485227",
"0.062640596809383831161729014105358174",
"-0.16551161436252129586397602324369624",
"43558282.559553533272106660089217692",
"0.00000000057412378153365242927167020616229738",
],
),
(
50.0,
[
"0.055812327669251815004750478529433968",
"-0.097511828125175137661458953873701614",
"-0.081400247696569639643974037928222066",
"-0.098064995470077079029211453440370432",
"-0.05679566856201476794181954923776336",
"293255378384933632665.46750794568539",
"3.4101677497894955139206755123529522e-23",
],
),
(
100.0,
[
"0.019985850304223122424228390950848991",
"-0.07714535201411215803268549492723447",
"-0.074195736964513920834135049813019587",
"-0.077244313365083152254228221367198771",
"-0.02037231200275979330470393266641456",
"1.0737517071310738235197208576034947e+42",
"4.6566282291759020189390052894838864e-45",
],
),
];
const NAMES: [&str; 7] = ["J0", "J1", "J5", "Y0", "Y1", "I0", "K0"];
fn point(ctx: &Context, x: f64) -> Ex {
if x == 0.5 {
ctx.rational(1, 2)
} else {
ctx.int(x as i64)
}
}
fn functions(ctx: &Context, x: &Ex) -> [Ex; 7] {
[
x.bessel_j(&ctx.int(0)),
x.bessel_j(&ctx.int(1)),
x.bessel_j(&ctx.int(5)),
x.bessel_y(&ctx.int(0)),
x.bessel_y(&ctx.int(1)),
x.bessel_i(&ctx.int(0)),
x.bessel_k(&ctx.int(0)),
]
}
fn sig_digits(s: &str) -> String {
let mant = s.split(['e', 'E']).next().unwrap_or(s);
let digits: String = mant.chars().filter(|c| c.is_ascii_digit()).collect();
digits.trim_start_matches('0').to_string()
}
fn agreeing_digits(got: &str, want: &str) -> usize {
let g = sig_digits(got);
let w = sig_digits(want);
g.chars().zip(w.chars()).take_while(|(a, b)| a == b).count()
}
fn assert_digits(got: &str, want: &str, min: usize, what: &str) {
let gf: f64 = got
.parse()
.unwrap_or_else(|_| panic!("{what}: unparsable {got}"));
let wf: f64 = want.parse().unwrap();
assert!(
((gf - wf) / wf).abs() < 1e-13,
"{what}: magnitude mismatch {got} vs {want}"
);
let n = agreeing_digits(got, want);
assert!(n >= min, "{what}: only {n} digits agree: {got} vs {want}");
}
fn check_30_digits(idx: usize) {
let ctx = Context::new();
for (x, refs) in REF {
let e = &functions(&ctx, &point(&ctx, *x))[idx];
let got = e
.eval_decimal(30)
.unwrap_or_else(|err| panic!("{}({x}): {err}", NAMES[idx]));
assert_digits(&got, refs[idx], 25, &format!("{}({x})", NAMES[idx]));
}
}
#[test]
fn evalf_bessel_j0_30_digits() {
check_30_digits(0);
}
#[test]
fn evalf_bessel_j1_30_digits() {
check_30_digits(1);
}
#[test]
fn evalf_bessel_j5_30_digits() {
check_30_digits(2);
}
#[test]
fn evalf_bessel_y0_30_digits() {
check_30_digits(3);
}
#[test]
fn evalf_bessel_y1_30_digits() {
check_30_digits(4);
}
#[test]
fn evalf_bessel_i0_30_digits() {
check_30_digits(5);
}
#[test]
fn evalf_bessel_k0_30_digits() {
check_30_digits(6);
}
#[test]
fn evalf_bessel_f64_reproducer() {
let ctx = Context::new();
let v = ctx.int(20).bessel_j(&ctx.int(0)).eval_f64().unwrap();
assert!((v - 0.16702466434058315).abs() < 1e-15, "J0(20) = {v}");
let v = ctx.int(20).bessel_j(&ctx.int(5)).eval_f64().unwrap();
assert!((v - 0.15116976798239497).abs() < 1e-15, "J5(20) = {v}");
let y = ctx.int(30).bessel_y(&ctx.int(0)).eval_f64().unwrap();
assert!((y + 0.11729573168666402).abs() < 1e-15, "Y0(30) = {y}");
}
#[test]
fn evalf_bessel_f64_all_points() {
let ctx = Context::new();
for (x, refs) in REF {
for (i, e) in functions(&ctx, &point(&ctx, *x)).iter().enumerate() {
let got = e
.eval_f64()
.unwrap_or_else(|err| panic!("{}({x}): {err}", NAMES[i]));
let want: f64 = refs[i].parse().unwrap();
let rel = ((got - want) / want).abs();
assert!(
rel < 1e-14,
"{}({x}) = {got}, want {want}, rel {rel:e}",
NAMES[i]
);
}
}
}
#[test]
fn evalf_bessel_half_order_and_negative_argument() {
let ctx = Context::new();
let half = ctx.rational(1, 2);
let got = ctx.int(50).bessel_j(&half).eval_decimal(30).unwrap();
assert_digits(
&got,
"-0.029605831888924612568029521626679575",
25,
"J_{1/2}(50)",
);
let got = ctx.int(3).bessel_j(&half).eval_decimal(30).unwrap();
assert_digits(
&got,
"0.065008182877375778114004696404628946",
25,
"J_{1/2}(3)",
);
for x in [20i64, 35] {
let j0 = ctx.int(-x).bessel_j(&ctx.int(0)).eval_decimal(30).unwrap();
let j0p = ctx.int(x).bessel_j(&ctx.int(0)).eval_decimal(30).unwrap();
assert_eq!(j0, j0p, "J0(−{x})");
let j1 = ctx.int(-x).bessel_j(&ctx.int(1)).eval_f64().unwrap();
let j1p = ctx.int(x).bessel_j(&ctx.int(1)).eval_f64().unwrap();
assert_eq!(j1, -j1p, "J1(−{x})");
}
}
#[test]
fn evalf_bessel_huge_argument() {
let ctx = Context::new();
let x = ctx.int(1_000_000);
let got = x.bessel_j(&ctx.int(0)).eval_decimal(30).unwrap();
assert_digits(
&got,
"0.00033104301373987374098796304221962544",
25,
"J0(1e6)",
);
let got = x.bessel_y(&ctx.int(0)).eval_decimal(30).unwrap();
assert_digits(
&got,
"-0.00072596852233517916568272174336768651",
25,
"Y0(1e6)",
);
}
#[test]
fn evalf_bessel_order_ten() {
let ctx = Context::new();
let x = ctx.int(20);
let got = x.bessel_j(&ctx.int(10)).eval_decimal(30).unwrap();
assert_digits(&got, "0.18648255802394508321410826451219876", 25, "J10(20)");
let got = x.bessel_y(&ctx.int(10)).eval_decimal(30).unwrap();
assert_digits(
&got,
"-0.043894653515658394899365436176372007",
25,
"Y10(20)",
);
}
#[test]
fn evalf_trig_exp_integrals_large_argument() {
let ctx = Context::new();
let cases: &[(&str, Ex, &str)] = &[
(
"Si(50)",
ctx.int(50).si(),
"1.5516170724859358947279855948593775",
),
(
"Ci(100)",
ctx.int(100).ci(),
"-0.0051488251426104921444435539053444979",
),
(
"Ei(40)",
ctx.int(40).ei(),
"6039718263611241.5783592314185106913",
),
(
"li(40)",
ctx.int(40).li(),
"15.839544272256263910693699094336842",
),
(
"I0(30)",
ctx.int(30).bessel_i(&ctx.int(0)),
"781672297823.97748971738981670529501",
),
(
"K0(30)",
ctx.int(30).bessel_k(&ctx.int(0)),
"2.1324774964630563711668960629653764e-14",
),
];
for (what, e, want) in cases {
let got = e
.eval_decimal(30)
.unwrap_or_else(|err| panic!("{what}: {err}"));
assert_digits(&got, want, 25, what);
}
}
#[test]
fn compiled_runtime_bessel_1e12() {
let ctx = Context::new();
let x = ctx.symbol("x");
let fns = functions(&ctx, &x).map(|e| e.compile(&["x"]).expect("compiles"));
for (p, refs) in REF {
for (i, f) in fns.iter().enumerate() {
let got = f(&[*p]);
let want: f64 = refs[i].parse().unwrap();
let rel = ((got - want) / want).abs();
assert!(
rel < 1e-12,
"{}({p}) = {got}, want {want}, rel {rel:e}",
NAMES[i]
);
}
}
}