use rayon::{
iter::{IntoParallelIterator, ParallelIterator},
slice::{ParallelSlice, ParallelSliceMut},
};
use crate::{
MU0_OVER_4PI, chunksize,
macros::{check_length_3tup, mut_par_chunks_3tup, par_chunks_3tup},
math::{clip_nan, cross3, dot3, rss3},
physics::volumetric::{
flux_density_inside_magnetized_sphere, vector_potential_inside_magnetized_sphere,
},
};
#[inline]
pub fn flux_density_dipole_scalar(
loc: (f64, f64, f64),
moment: (f64, f64, f64),
outer_radius: f64,
obs: (f64, f64, f64),
) -> (f64, f64, f64) {
let r = (obs.0 - loc.0, obs.1 - loc.1, obs.2 - loc.2); let r2 = dot3(r.0, r.1, r.2, r.0, r.1, r.2);
let rmag = r2.sqrt(); let rhat = (r.0 / rmag, r.1 / rmag, r.2 / rmag); let r3 = r2 * rmag;
let m_dot_rhat = dot3(moment.0, moment.1, moment.2, rhat.0, rhat.1, rhat.2);
let c = MU0_OVER_4PI / r3; let c1 = 3.0 * m_dot_rhat; let tsum = (
rhat.0.mul_add(c1, -moment.0),
rhat.1.mul_add(c1, -moment.1),
rhat.2.mul_add(c1, -moment.2),
);
let inside = rmag < outer_radius;
let (mut bx, mut by, mut bz) = match inside {
true => flux_density_inside_magnetized_sphere(moment, outer_radius),
false => (c * tsum.0, c * tsum.1, c * tsum.2),
};
bx = clip_nan(bx, 0.0);
by = clip_nan(by, 0.0);
bz = clip_nan(bz, 0.0);
(bx, by, bz) }
#[inline]
pub fn flux_density_dipole(
loc: (&[f64], &[f64], &[f64]),
moment: (&[f64], &[f64], &[f64]),
outer_radius: &[f64],
obs: (&[f64], &[f64], &[f64]),
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let m = loc.0.len();
let n = obs.0.len();
check_length_3tup!(m, &loc);
check_length_3tup!(m, &moment);
check_length_3tup!(n, &obs);
check_length_3tup!(n, &out);
for i in 0..n {
for j in 0..m {
let obsi = (obs.0[i], obs.1[i], obs.2[i]);
let locj = (loc.0[j], loc.1[j], loc.2[j]);
let momentj = (moment.0[j], moment.1[j], moment.2[j]);
let roj = outer_radius[j];
let (bx, by, bz) = flux_density_dipole_scalar(locj, momentj, roj, obsi);
out.0[i] += bx;
out.1[i] += by;
out.2[i] += bz;
}
}
Ok(())
}
#[inline]
pub fn flux_density_dipole_par(
loc: (&[f64], &[f64], &[f64]),
moment: (&[f64], &[f64], &[f64]),
outer_radius: &[f64],
obs: (&[f64], &[f64], &[f64]),
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let n = chunksize(obs.0.len());
let (obsxc, obsyc, obszc) = par_chunks_3tup!(obs, n);
let (outxc, outyc, outzc) = mut_par_chunks_3tup!(out, n);
(outxc, outyc, outzc, obsxc, obsyc, obszc)
.into_par_iter()
.try_for_each(|(outx, outy, outz, obsx, obsy, obsz)| {
flux_density_dipole(
loc,
moment,
outer_radius,
(obsx, obsy, obsz),
(outx, outy, outz),
)
})?;
Ok(())
}
#[inline]
pub fn vector_potential_dipole_scalar(
loc: (f64, f64, f64),
moment: (f64, f64, f64),
outer_radius: f64,
obs: (f64, f64, f64),
) -> (f64, f64, f64) {
let r = (obs.0 - loc.0, obs.1 - loc.1, obs.2 - loc.2); let r2 = dot3(r.0, r.1, r.2, r.0, r.1, r.2); let rmag = r2.sqrt(); let rhat = (r.0 / rmag, r.1 / rmag, r.2 / rmag); let m = moment;
let mmag = rss3(m.0, m.1, m.2); let mhat = (m.0 / mmag, m.1 / mmag, m.2 / mmag);
let mhat_cross_rhat = cross3(mhat.0, mhat.1, mhat.2, rhat.0, rhat.1, rhat.2);
let inside = rmag < outer_radius;
let (mut ax, mut ay, mut az) = match inside {
true => {
vector_potential_inside_magnetized_sphere(mhat_cross_rhat, mmag, rmag, outer_radius)
}
false => {
let c = MU0_OVER_4PI * mmag / r2; (
mhat_cross_rhat.0 * c,
mhat_cross_rhat.1 * c,
mhat_cross_rhat.2 * c,
)
}
};
ax = clip_nan(ax, 0.0);
ay = clip_nan(ay, 0.0);
az = clip_nan(az, 0.0);
(ax, ay, az) }
#[inline]
pub fn vector_potential_dipole(
loc: (&[f64], &[f64], &[f64]),
moment: (&[f64], &[f64], &[f64]),
outer_radius: &[f64],
obs: (&[f64], &[f64], &[f64]),
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let m = loc.0.len();
let n = obs.0.len();
check_length_3tup!(m, &loc);
check_length_3tup!(m, &moment);
check_length_3tup!(n, &obs);
check_length_3tup!(n, &out);
for i in 0..n {
for j in 0..m {
let obsi = (obs.0[i], obs.1[i], obs.2[i]);
let locj = (loc.0[j], loc.1[j], loc.2[j]);
let momentj = (moment.0[j], moment.1[j], moment.2[j]);
let roj = outer_radius[j];
let (ax, ay, az) = vector_potential_dipole_scalar(locj, momentj, roj, obsi);
out.0[i] += ax;
out.1[i] += ay;
out.2[i] += az;
}
}
Ok(())
}
#[inline]
pub fn vector_potential_dipole_par(
loc: (&[f64], &[f64], &[f64]),
moment: (&[f64], &[f64], &[f64]),
outer_radius: &[f64],
obs: (&[f64], &[f64], &[f64]),
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let n = chunksize(obs.0.len());
let (obsxc, obsyc, obszc) = par_chunks_3tup!(obs, n);
let (outxc, outyc, outzc) = mut_par_chunks_3tup!(out, n);
(outxc, outyc, outzc, obsxc, obsyc, obszc)
.into_par_iter()
.try_for_each(|(outx, outy, outz, obsx, obsy, obsz)| {
vector_potential_dipole(
loc,
moment,
outer_radius,
(obsx, obsy, obsz),
(outx, outy, outz),
)
})?;
Ok(())
}
#[cfg(test)]
mod test {
use std::f64::consts::PI;
use crate::testing::*;
#[test]
fn test_flux_density() {
let (rtol, atol) = (1e-12, 1e-14);
let rfil = PI / 1000.0; let zfil = 0.07; let ifil = 1.0;
let loc = (0.0, 0.0, zfil);
let s = PI * rfil.powf(2.0); let m = s * ifil; let moment = (0.0, 0.0, m);
let ngrid = 6;
let xgrid = linspace(-1.0, 1.0, ngrid);
let ygrid = linspace(-1.0, 1.0, ngrid);
let zgrid = linspace(-1.0, 1.0, ngrid);
let mesh = meshgrid(&[&xgrid[..], &ygrid[..], &zgrid[..]]);
let (xmesh, ymesh, zmesh) = (&mesh[0], &mesh[1], &mesh[2]);
let nobs = xmesh.len(); let outx_circ = &mut vec![0.0; nobs][..];
let outy_circ = &mut vec![0.0; nobs][..];
let outz_circ = &mut vec![0.0; nobs][..];
crate::physics::circular_filament::flux_density_circular_filament_cartesian_par(
(&[rfil], &[zfil], &[ifil]),
(&xmesh[..], &ymesh[..], &zmesh[..]),
(outx_circ, outy_circ, outz_circ),
)
.unwrap();
let outx_dipole = &mut vec![0.0; nobs][..];
let outy_dipole = &mut vec![0.0; nobs][..];
let outz_dipole = &mut vec![0.0; nobs][..];
super::flux_density_dipole_par(
(&[loc.0], &[loc.1], &[loc.2]),
(&[moment.0], &[moment.1], &[moment.2]),
&[0.0],
(xmesh, ymesh, zmesh),
(outx_dipole, outy_dipole, outz_dipole),
)
.unwrap();
for i in 0..nobs {
assert!(approx(outx_circ[i], outx_dipole[i], rtol, atol));
assert!(approx(outy_circ[i], outy_dipole[i], rtol, atol));
assert!(approx(outz_circ[i], outz_dipole[i], rtol, atol));
}
}
#[test]
fn test_vector_potential() {
let (rtol, atol) = (1e-12, 1e-14);
let rfil = PI / 1000.0; let zfil = 0.07; let ifil = 1.0;
let loc = (0.0, 0.0, zfil);
let s = PI * rfil.powf(2.0); let m = s * ifil; let moment = (0.0, 0.0, m);
let ngrid = 6;
let xgrid = linspace(-1.0, 1.0, ngrid);
let ygrid: Vec<f64> = vec![0.0];
let zgrid = linspace(-1.0, 1.0, ngrid);
let mesh = meshgrid(&[&xgrid[..], &ygrid[..], &zgrid[..]]);
let (xmesh, ymesh, zmesh) = (&mesh[0], &mesh[1], &mesh[2]);
let nobs = xmesh.len(); let outx_circ = &mut vec![0.0; nobs][..];
let outy_circ = &mut vec![0.0; nobs][..];
let outz_circ = &mut vec![0.0; nobs][..];
crate::physics::circular_filament::vector_potential_circular_filament_par(
(&[rfil], &[zfil], &[ifil]),
(&xmesh[..], &zmesh[..]),
outy_circ,
)
.unwrap();
let outx_dipole = &mut vec![0.0; nobs][..];
let outy_dipole = &mut vec![0.0; nobs][..];
let outz_dipole = &mut vec![0.0; nobs][..];
super::vector_potential_dipole_par(
(&[loc.0], &[loc.1], &[loc.2]),
(&[moment.0], &[moment.1], &[moment.2]),
&[0.0],
(xmesh, ymesh, zmesh),
(outx_dipole, outy_dipole, outz_dipole),
)
.unwrap();
for i in 0..nobs {
assert!(approx(outx_circ[i], outx_dipole[i], rtol, atol));
assert!(approx(outy_circ[i], outy_dipole[i], rtol, atol));
assert!(approx(outz_circ[i], outz_dipole[i], rtol, atol));
}
}
#[test]
fn test_vector_potential_curl_inside_outer_radius() {
let (rtol, atol) = (1e-10, 1e-13);
let loc = (0.0, 0.0, 0.0);
let moment = (0.0, 0.0, 0.25);
let outer_radius = 0.3;
let points = [
(0.05, 0.02, -0.04),
(-0.07, 0.03, 0.06),
(0.09, -0.05, 0.01),
(-0.04, -0.03, -0.08),
];
let eps = 1e-6;
let vp = |x: f64, y: f64, z: f64| {
super::vector_potential_dipole_scalar(loc, moment, outer_radius, (x, y, z))
};
for &(x, y, z) in &points {
let b = super::flux_density_dipole_scalar(loc, moment, outer_radius, (x, y, z));
let mut da = [[0.0f64; 3]; 3];
let (ax0, ay0, az0) = vp(x - eps, y, z);
let (ax1, ay1, az1) = vp(x + eps, y, z);
da[0][0] = (ax1 - ax0) / (2.0 * eps);
da[0][1] = (ay1 - ay0) / (2.0 * eps);
da[0][2] = (az1 - az0) / (2.0 * eps);
let (ax0, ay0, az0) = vp(x, y - eps, z);
let (ax1, ay1, az1) = vp(x, y + eps, z);
da[1][0] = (ax1 - ax0) / (2.0 * eps);
da[1][1] = (ay1 - ay0) / (2.0 * eps);
da[1][2] = (az1 - az0) / (2.0 * eps);
let (ax0, ay0, az0) = vp(x, y, z - eps);
let (ax1, ay1, az1) = vp(x, y, z + eps);
da[2][0] = (ax1 - ax0) / (2.0 * eps);
da[2][1] = (ay1 - ay0) / (2.0 * eps);
da[2][2] = (az1 - az0) / (2.0 * eps);
let curl = (
da[1][2] - da[2][1],
da[2][0] - da[0][2],
da[0][1] - da[1][0],
);
let bmag = (b.0.powi(2) + b.1.powi(2) + b.2.powi(2)).sqrt();
assert!(bmag > 1e-12);
assert!(approx(curl.0, b.0, rtol, atol));
assert!(approx(curl.1, b.1, rtol, atol));
assert!(approx(curl.2, b.2, rtol, atol));
}
}
}