use crate::calculus::differential::{gradient_4d, partial_derivative};
pub struct VectorField4D<F>
where
F: Fn(&[f64; 4]) -> [f64; 4],
{
pub field: F,
}
impl<F> VectorField4D<F>
where
F: Fn(&[f64; 4]) -> [f64; 4],
{
pub fn new(field: F) -> Self {
Self { field }
}
pub fn eval(&self, point: &[f64; 4]) -> [f64; 4] {
(self.field)(point)
}
pub fn divergence(&self, point: &[f64; 4], h: f64) -> f64 {
divergence_4d(&self.field, point, h)
}
}
pub fn scalar_field_4d<F>(f: F) -> impl Fn(&[f64; 4]) -> f64
where
F: Fn(&[f64; 4]) -> f64,
{
f
}
pub fn vector_field_4d<F>(f: F) -> VectorField4D<F>
where
F: Fn(&[f64; 4]) -> [f64; 4],
{
VectorField4D::new(f)
}
pub fn divergence_4d<F>(field: &F, point: &[f64; 4], h: f64) -> f64
where
F: Fn(&[f64; 4]) -> [f64; 4],
{
let f1 = |p: &[f64]| field(&[p[0], p[1], p[2], p[3]])[0];
let f2 = |p: &[f64]| field(&[p[0], p[1], p[2], p[3]])[1];
let f3 = |p: &[f64]| field(&[p[0], p[1], p[2], p[3]])[2];
let f4 = |p: &[f64]| field(&[p[0], p[1], p[2], p[3]])[3];
partial_derivative(&f1, point, 0, h)
+ partial_derivative(&f2, point, 1, h)
+ partial_derivative(&f3, point, 2, h)
+ partial_derivative(&f4, point, 3, h)
}
pub fn curl_4d<F>(field: &F, point: &[f64; 4], h: f64) -> [f64; 6]
where
F: Fn(&[f64; 4]) -> [f64; 4],
{
let f = |p: &[f64; 4]| field(p);
let f1 = |p: &[f64]| f(&[p[0], p[1], p[2], p[3]])[0];
let f2 = |p: &[f64]| f(&[p[0], p[1], p[2], p[3]])[1];
let f3 = |p: &[f64]| f(&[p[0], p[1], p[2], p[3]])[2];
let f4 = |p: &[f64]| f(&[p[0], p[1], p[2], p[3]])[3];
[
partial_derivative(&f2, point, 0, h) - partial_derivative(&f1, point, 1, h),
partial_derivative(&f3, point, 0, h) - partial_derivative(&f1, point, 2, h),
partial_derivative(&f4, point, 0, h) - partial_derivative(&f1, point, 3, h),
partial_derivative(&f3, point, 1, h) - partial_derivative(&f2, point, 2, h),
partial_derivative(&f4, point, 1, h) - partial_derivative(&f2, point, 3, h),
partial_derivative(&f4, point, 2, h) - partial_derivative(&f3, point, 3, h),
]
}
pub fn gradient_field<F>(scalar_field: F) -> impl Fn(&[f64; 4]) -> [f64; 4]
where
F: Fn(&[f64]) -> f64,
{
move |point: &[f64; 4]| gradient_4d(&scalar_field, point, 1e-7)
}
pub fn radial_field() -> impl Fn(&[f64; 4]) -> [f64; 4] {
|p: &[f64; 4]| {
let norm = (p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3]).sqrt();
if norm == 0.0 {
[0.0, 0.0, 0.0, 0.0]
} else {
[p[0] / norm, p[1] / norm, p[2] / norm, p[3] / norm]
}
}
}
pub fn constant_field(value: [f64; 4]) -> impl Fn(&[f64; 4]) -> [f64; 4] {
move |_: &[f64; 4]| value
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_divergence_identity() {
let f = |p: &[f64; 4]| [p[0], p[1], p[2], p[3]];
let div = divergence_4d(&f, &[1.0, 2.0, 3.0, 4.0], 1e-5);
assert!((div - 4.0).abs() < 1e-4);
}
#[test]
fn test_divergence_zero() {
let f = |p: &[f64; 4]| [p[1], -p[0], 0.0, 0.0];
let div = divergence_4d(&f, &[1.0, 2.0, 0.0, 0.0], 1e-5);
assert!(div.abs() < 1e-4);
}
#[test]
fn test_curl_4d() {
let f = |p: &[f64; 4]| [p[1], -p[0], 0.0, 0.0];
let curl = curl_4d(&f, &[1.0, 2.0, 0.0, 0.0], 1e-5);
assert!(curl[0].abs() > 1e-4);
}
#[test]
fn test_radial_field() {
let field = radial_field();
let v = field(&[3.0, 0.0, 0.0, 0.0]);
assert!((v[0] - 1.0).abs() < 1e-10);
assert!(v[1].abs() < 1e-10);
assert!(v[2].abs() < 1e-10);
assert!(v[3].abs() < 1e-10);
}
#[test]
fn test_vector_field_4d() {
let field = vector_field_4d(|p: &[f64; 4]| [p[0], p[1], p[2], p[3]]);
let v = field.eval(&[1.0, 2.0, 3.0, 4.0]);
assert_eq!(v, [1.0, 2.0, 3.0, 4.0]);
}
}