#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use multicalc::numerical_derivative::AutoDiffMulti;
use multicalc::scalar::c;
use multicalc::scalar_fn_vec;
use multicalc::vector_field::{
curl_2d, curl_3d, divergence_2d, divergence_3d, flux_integral_2d_custom,
flux_integral_3d_custom, line_integral_2d_custom, line_integral_3d_custom,
};
use multicalc::error::IntegrateError;
#[test]
fn line_integral_of_a_rotational_field_around_the_unit_circle() {
let field_components: [&dyn Fn(&[f64; 2]) -> f64; 2] = [
&(|point: &[f64; 2]| -> f64 { point[1] }),
&(|point: &[f64; 2]| -> f64 { -point[0] }),
];
let curve: [&dyn Fn(f64) -> f64; 2] = [
&(|t: f64| -> f64 { t.cos() }),
&(|t: f64| -> f64 { t.sin() }),
];
let integration_limit = [0.0, core::f64::consts::TAU];
let total_iterations = 100;
let circulation = line_integral_2d_custom(
&field_components,
&curve,
&integration_limit,
total_iterations,
)
.unwrap();
assert!(f64::abs(circulation + core::f64::consts::TAU) < 0.01);
}
#[test]
fn line_integral_rejects_a_zero_interval_count() {
let field_components: [&dyn Fn(&[f64; 2]) -> f64; 2] = [
&(|point: &[f64; 2]| -> f64 { point[1] }),
&(|point: &[f64; 2]| -> f64 { -point[0] }),
];
let curve: [&dyn Fn(f64) -> f64; 2] = [
&(|t: f64| -> f64 { t.cos() }),
&(|t: f64| -> f64 { t.sin() }),
];
let integration_limit = [0.0, core::f64::consts::TAU];
let result = line_integral_2d_custom(&field_components, &curve, &integration_limit, 0);
assert!(result.is_err());
assert!(result.unwrap_err() == IntegrateError::IterationsZero);
}
#[test]
fn line_integral_rejects_reversed_limits() {
let field_components: [&dyn Fn(&[f64; 2]) -> f64; 2] = [
&(|point: &[f64; 2]| -> f64 { point[1] }),
&(|point: &[f64; 2]| -> f64 { -point[0] }),
];
let curve: [&dyn Fn(f64) -> f64; 2] = [
&(|t: f64| -> f64 { t.cos() }),
&(|t: f64| -> f64 { t.sin() }),
];
let integration_limit = [10.0, 0.0];
let result = line_integral_2d_custom(&field_components, &curve, &integration_limit, 100);
assert!(result.is_err());
assert!(result.unwrap_err() == IntegrateError::LimitsIllDefined);
}
#[test]
fn flux_of_a_rotational_field_through_the_unit_circle_is_zero() {
let field_components: [&dyn Fn(&[f64; 2]) -> f64; 2] = [
&(|point: &[f64; 2]| -> f64 { point[1] }),
&(|point: &[f64; 2]| -> f64 { -point[0] }),
];
let curve: [&dyn Fn(f64) -> f64; 2] = [
&(|t: f64| -> f64 { t.cos() }),
&(|t: f64| -> f64 { t.sin() }),
];
let integration_limit = [0.0, core::f64::consts::TAU];
let total_iterations = 100;
let flux = flux_integral_2d_custom(
&field_components,
&curve,
&integration_limit,
total_iterations,
)
.unwrap();
assert!(f64::abs(flux + 0.0) < 0.01);
}
#[test]
fn flux_along_a_helix_matches_its_closed_form() {
let field_components: [&dyn Fn(&[f64; 3]) -> f64; 3] = [
&(|_: &[f64; 3]| -> f64 { 0.0 }),
&(|_: &[f64; 3]| -> f64 { 0.0 }),
&(|point: &[f64; 3]| -> f64 { point[2] }),
];
let curve: [&dyn Fn(f64) -> f64; 3] = [
&(|t: f64| -> f64 { t.cos() }),
&(|t: f64| -> f64 { t.sin() }),
&(|t: f64| -> f64 { t }),
];
let two_pi = 2.0 * core::f64::consts::PI;
let integration_limit = [0.0, two_pi];
let total_iterations = 100;
let flux = flux_integral_3d_custom(
&field_components,
&curve,
&integration_limit,
total_iterations,
)
.unwrap();
let expected = -2.0 * core::f64::consts::PI * core::f64::consts::PI;
assert!(f64::abs(flux - expected) < 1e-6);
}
#[test]
fn curl_2d_matches_its_closed_form() {
let vector_field = scalar_fn_vec!(|v: &[f64; 2]| [c(2.0) * v[0] * v[1], c(3.0) * v[1].cos()]);
let point = [1.0, core::f64::consts::PI];
let curl = curl_2d(AutoDiffMulti::default(), &vector_field, &point).unwrap();
assert!(f64::abs(curl + 2.0) < 1e-12);
}
#[test]
fn curl_3d_matches_its_closed_form() {
let vector_field = scalar_fn_vec!(|v: &[f64; 3]| [v[1], -v[0], c(2.0) * v[2]]);
let point = [1.0, 2.0, 3.0];
let curl = curl_3d(AutoDiffMulti::default(), &vector_field, &point).unwrap();
assert!(f64::abs(curl[0]) < 1e-12);
assert!(f64::abs(curl[1]) < 1e-12);
assert!(f64::abs(curl[2] + 2.0) < 1e-12);
}
#[test]
fn divergence_2d_matches_its_closed_form() {
let vector_field = scalar_fn_vec!(|v: &[f64; 2]| [c(2.0) * v[0] * v[1], c(3.0) * v[1].cos()]);
let point = [1.0, core::f64::consts::PI];
let divergence = divergence_2d(AutoDiffMulti::default(), &vector_field, &point).unwrap();
assert!(f64::abs(divergence - core::f64::consts::TAU) < 1e-12);
}
#[test]
fn divergence_3d_matches_its_closed_form() {
let vector_field = scalar_fn_vec!(|v: &[f64; 3]| [v[1], -v[0], c(2.0) * v[2]]);
let point = [0.0, 1.0, 3.0];
let divergence = divergence_3d(AutoDiffMulti::default(), &vector_field, &point).unwrap();
assert!(f64::abs(divergence - 2.0) < 1e-12);
}
#[test]
fn line_integral_along_a_helix_matches_its_closed_form() {
let field_components: [&dyn Fn(&[f64; 3]) -> f64; 3] = [
&(|_: &[f64; 3]| -> f64 { 0.0 }),
&(|_: &[f64; 3]| -> f64 { 0.0 }),
&(|point: &[f64; 3]| -> f64 { point[2] }),
];
let curve: [&dyn Fn(f64) -> f64; 3] = [
&(|t: f64| -> f64 { t.cos() }),
&(|t: f64| -> f64 { t.sin() }),
&(|t: f64| -> f64 { t }),
];
let two_pi = 2.0 * core::f64::consts::PI;
let integration_limit = [0.0, two_pi];
let total_iterations = 100;
let circulation = line_integral_3d_custom(
&field_components,
&curve,
&integration_limit,
total_iterations,
)
.unwrap();
let expected = 2.0 * core::f64::consts::PI * core::f64::consts::PI;
assert!(f64::abs(circulation - expected) < 1e-6);
}
#[test]
fn line_integral_accepts_a_negative_lower_limit() {
let field_components: [&dyn Fn(&[f64; 2]) -> f64; 2] = [
&(|point: &[f64; 2]| -> f64 { point[1] }),
&(|point: &[f64; 2]| -> f64 { point[0] }),
];
let curve: [&dyn Fn(f64) -> f64; 2] = [&(|t: f64| -> f64 { t }), &(|t: f64| -> f64 { t })];
let integration_limit = [-2.0, 1.0];
let total_iterations = 100;
let circulation = line_integral_2d_custom(
&field_components,
&curve,
&integration_limit,
total_iterations,
)
.unwrap();
let expected = -3.0;
assert!(f64::abs(circulation - expected) < 1e-9);
}
#[test]
fn divergence_3d_matches_its_closed_form_at_f32() {
let vector_field = scalar_fn_vec!(|v: &[f64; 3]| [v[1], -v[0], c(2.0) * v[2]]);
let point = [0.0_f32, 1.0, 3.0];
let divergence = divergence_3d(AutoDiffMulti::<f32>::default(), &vector_field, &point).unwrap();
assert!(f32::abs(divergence - 2.0) < 1e-6, "got {divergence}");
}