use super::common;
use symplex::matrix::Matrix;
use symplex::prelude::*;
use symplex::vector::{
CoordinateSystem, curl, curl_in, directional_derivative, divergence, divergence_in, gradient,
gradient_in, hessian, is_conservative, is_irrotational, is_solenoidal, laplacian, laplacian_in,
line_integral_scalar, line_integral_vector, scalar_potential,
};
#[test]
fn cartesian_functions_unchanged_and_curl_grad_zero() {
let ctx = Context::new();
let (x, y, z) = (ctx.symbol("x"), ctx.symbol("y"), ctx.symbol("z"));
let f = &(&x.powi(2) * &y) + &(&z * &x.sin());
let g = gradient(&f, &[&x, &y, &z]);
assert_eq!(
g,
gradient_in(&f, &[&x, &y, &z], CoordinateSystem::Cartesian)
);
let c = curl(&g, &[&x, &y, &z]).simplify();
assert_eq!(c.is_zero(), Some(true));
assert_eq!(is_conservative(&g, &[&x, &y, &z]), Some(true));
assert_eq!(is_irrotational(&g, &[&x, &y, &z]), Some(true));
let field = Matrix::col_vector(vec![&y * &z, &x * &z.powi(2), &x.exp() * &y]);
let cf = curl(&field, &[&x, &y, &z]);
assert_eq!(is_solenoidal(&cf, &[&x, &y, &z]), Some(true));
assert_eq!(
divergence(&cf, &[&x, &y, &z]).expand().simplify(),
ctx.int(0)
);
assert_eq!(
laplacian(&(&x.powi(2) + &y.powi(2) + &z.powi(2)), &[&x, &y, &z]),
ctx.int(6)
);
assert_eq!(hessian(&f, &[&x, &y, &z]).is_symmetric(), Some(true));
}
#[test]
fn spherical_vs_cartesian_laplacian_of_r_squared_and_harmonics() {
let ctx = Context::new();
let (r, th, ph) = (ctx.symbol("r"), ctx.symbol("theta"), ctx.symbol("phi"));
let vars = [&r, &th, &ph];
let sph = CoordinateSystem::Spherical;
assert_eq!(laplacian_in(&r.powi(2), &vars, sph).simplify(), ctx.int(6));
assert!(
laplacian_in(&(ctx.int(1) / &r), &vars, sph)
.simplify()
.is_zero_structural()
);
let y1 = &r * &th.cos();
assert!(
laplacian_in(&y1, &vars, sph)
.simplify()
.is_zero_structural()
);
let y2 = &r.powi(2) * &(&(&th.cos().powi(2) * 3) - &ctx.int(1)) / 2;
let l2 = laplacian_in(&y2, &vars, sph).simplify();
assert!(l2.is_zero_structural(), "∇²(r² P₂(cos θ)) = {l2}");
let xx = &(&r * &th.sin()) * &ph.cos();
let lx = laplacian_in(&xx, &vars, sph).simplify();
assert!(lx.is_zero_structural(), "∇²x = {lx}");
let g = gradient_in(&r, &vars, sph);
assert_eq!(g, matrix![ctx, [1], [0], [0]]);
let coulomb = Matrix::col_vector(vec![ctx.int(1) / r.powi(2), ctx.int(0), ctx.int(0)]);
assert!(
divergence_in(&coulomb, &vars, sph)
.simplify()
.is_zero_structural()
);
let radial = Matrix::col_vector(vec![r.clone(), ctx.int(0), ctx.int(0)]);
assert_eq!(divergence_in(&radial, &vars, sph).simplify(), ctx.int(3));
let f = &(&r.powi(2) * &th.sin()) * &ph.cos();
let c = curl_in(&gradient_in(&f, &vars, sph), &vars, sph).simplify();
assert_eq!(c.is_zero(), Some(true), "{c}");
}
#[test]
fn cylindrical_identities() {
let ctx = Context::new();
let (r, ph, z) = (ctx.symbol("r"), ctx.symbol("phi"), ctx.symbol("z"));
let vars = [&r, &ph, &z];
let cyl = CoordinateSystem::Cylindrical;
assert_eq!(
cyl.scale_factors(&vars),
vec![ctx.one(), r.clone(), ctx.one()]
);
assert_eq!(
laplacian_in(&(&r.powi(2) + &z.powi(2)), &vars, cyl).simplify(),
ctx.int(6)
);
assert!(
laplacian_in(&r.ln(), &vars, cyl)
.simplify()
.is_zero_structural()
);
assert!(
laplacian_in(&(&r * &ph.cos()), &vars, cyl)
.simplify()
.is_zero_structural()
);
let rot = Matrix::col_vector(vec![ctx.int(0), r.clone(), ctx.int(0)]);
assert_eq!(
curl_in(&rot, &vars, cyl).simplify(),
matrix![ctx, [0], [0], [2]]
);
assert!(
divergence_in(&rot, &vars, cyl)
.simplify()
.is_zero_structural()
);
let wire = Matrix::col_vector(vec![ctx.int(0), ctx.int(1) / &r, ctx.int(0)]);
assert_eq!(curl_in(&wire, &vars, cyl).simplify().is_zero(), Some(true));
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let f_cart = &x.powi(2) * &y;
let f_cyl = &(&r.powi(3) * &ph.cos().powi(2)) * &ph.sin();
let lap_cart = laplacian(&f_cart, &[&x, &y, &z]);
let lap_cyl = laplacian_in(&f_cyl, &vars, cyl);
for (rv, pv) in [(1.5f64, 0.3f64), (2.0, 1.1), (0.7, 2.6)] {
let rr = ctx.rational((rv * 1e6) as i64, 1_000_000);
let pp = ctx.rational((pv * 1e6) as i64, 1_000_000);
let xv = rv * pv.cos();
let yv = rv * pv.sin();
let a = lap_cyl.subs(&r, &rr).subs(&ph, &pp).eval_f64().unwrap();
let b = lap_cart
.subs(&x, &ctx.rational((xv * 1e9) as i64, 1_000_000_000))
.subs(&y, &ctx.rational((yv * 1e9) as i64, 1_000_000_000))
.eval_f64()
.unwrap();
assert!(
common::approx_eq(a, b, 1e-6),
"Laplacian mismatch: {a} vs {b}"
);
}
}
#[test]
fn directional_derivative_and_potential() {
let ctx = Context::new();
let (x, y, z) = (ctx.symbol("x"), ctx.symbol("y"), ctx.symbol("z"));
let f = &(&x * &y) + &z.powi(2);
let d = matrix![ctx, [0], [0], [1]];
assert_eq!(directional_derivative(&f, &[&x, &y, &z], &d), &z * 2);
let phi = &(&x.powi(3) * &y) + &(&z * &y.cos()) + &x.exp();
let field = gradient(&phi, &[&x, &y, &z]);
let back = scalar_potential(&field, &[&x, &y, &z]).unwrap();
assert!(
(&back - &phi).expand().simplify().is_zero_structural(),
"{back}"
);
let rot = Matrix::col_vector(vec![-&y, x.clone(), ctx.int(0)]);
assert!(matches!(
scalar_potential(&rot, &[&x, &y, &z]),
Err(SymplexError::ComputationFailed { .. })
));
assert!(matches!(
scalar_potential(&rot, &[&x, &y]),
Err(SymplexError::InvalidArgument { .. })
));
}
#[test]
fn line_integrals_scalar_and_vector() {
let ctx = Context::new();
let (x, y, z, t) = (
ctx.symbol("x"),
ctx.symbol("y"),
ctx.symbol("z"),
ctx.symbol("t"),
);
let helix = [t.cos(), t.sin(), t.clone()];
let len = line_integral_scalar(
&ctx.int(1),
&[&x, &y, &z],
&helix,
&t,
&ctx.int(0),
&(ctx.pi() * 2),
);
let expected = 2.0 * std::f64::consts::PI * 2f64.sqrt();
assert!(
common::approx_eq(len.eval_f64().unwrap(), expected, 1e-12),
"{len}"
);
let zs = line_integral_scalar(&z, &[&x, &y, &z], &helix, &t, &ctx.int(0), &(ctx.pi() * 2));
let expected = 2f64.sqrt() * (2.0 * std::f64::consts::PI).powi(2) / 2.0;
assert!(
common::approx_eq(zs.eval_f64().unwrap(), expected, 1e-12),
"{zs}"
);
let phi = &(&x * &y) + &z.powi(2);
let f = gradient(&phi, &[&x, &y, &z]);
let seg = [t.clone(), &t * 2, &t * 3]; let w_seg = line_integral_vector(&f, &[&x, &y, &z], &seg, &t, &ctx.int(0), &ctx.int(1));
let arc = [t.clone(), &t.powi(2) * 2, &t.powi(3) * 3]; let w_arc = line_integral_vector(&f, &[&x, &y, &z], &arc, &t, &ctx.int(0), &ctx.int(1));
assert_eq!(w_seg.simplify(), ctx.int(11));
assert_eq!(w_arc.simplify(), ctx.int(11));
let rot = Matrix::col_vector(vec![-&y, x.clone()]);
let circle = [&t.cos() * 2, &t.sin() * 2];
let circ = line_integral_vector(&rot, &[&x, &y], &circle, &t, &ctx.int(0), &(ctx.pi() * 2));
assert_eq!(circ.simplify(), ctx.pi() * 8);
}
#[test]
#[should_panic(expected = "exactly 3 variables")]
fn spherical_requires_three_variables() {
let ctx = Context::new();
let (r, th) = (ctx.symbol("r"), ctx.symbol("theta"));
let _ = laplacian_in(&r, &[&r, &th], CoordinateSystem::Spherical);
}