use rayon::{
iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator},
slice::{ParallelSlice, ParallelSliceMut},
};
use crate::{chunksize, math::cross3};
#[cfg(test)]
use crate::math::rss3;
use crate::{MU0_OVER_4PI, macros::*};
const MIN_WIRE_THICKNESS: f64 = 1e-10;
const GL3_UNIT_NODES: [f64; 3] = [0.11270166537925831, 0.5, 0.8872983346207417];
const GL3_UNIT_WEIGHTS: [f64; 3] = [0.2777777777777778, 0.4444444444444444, 0.2777777777777778];
pub fn inductance_piecewise_linear_filaments(
xyzfil0: (&[f64], &[f64], &[f64]),
dlxyzfil0: (&[f64], &[f64], &[f64]),
xyzfil1: (&[f64], &[f64], &[f64]),
dlxyzfil1: (&[f64], &[f64], &[f64]),
wire_radius: &[f64],
) -> Result<f64, &'static str> {
let (xfil1, yfil1, zfil1) = xyzfil1;
let (dlxfil1, dlyfil1, dlzfil1) = dlxyzfil1;
let n = xyzfil0.0.len();
check_length!(
n,
xyzfil0.0,
xyzfil0.1,
xyzfil0.2,
dlxyzfil0.0,
dlxyzfil0.1,
dlxyzfil0.2,
wire_radius
);
let m = xfil1.len();
check_length!(m, xfil1, yfil1, zfil1, dlxfil1, dlyfil1, dlzfil1);
let (xfil0, yfil0, zfil0) = xyzfil0;
let (dlxfil0, dlyfil0, dlzfil0) = dlxyzfil0;
let mut inductance = 0.0;
for j in 0..m {
let dltgt = (dlxfil1[j], dlyfil1[j], dlzfil1[j]); for (iq, tq) in GL3_UNIT_NODES.iter().enumerate() {
let obs = (
dltgt.0.mul_add(*tq, xfil1[j]), dltgt.1.mul_add(*tq, yfil1[j]), dltgt.2.mul_add(*tq, zfil1[j]), );
let mut ax = 0.0; let mut ay = 0.0; let mut az = 0.0;
for i in 0..n {
let fil0 = (xfil0[i], yfil0[i], zfil0[i]); let fil1 = (
fil0.0 + dlxfil0[i],
fil0.1 + dlyfil0[i],
fil0.2 + dlzfil0[i],
); let (axc, ayc, azc) =
vector_potential_linear_filament_scalar((fil0, fil1, 1.0), wire_radius[i], obs);
ax += axc; ay += ayc; az += azc; }
inductance += GL3_UNIT_WEIGHTS[iq] * (ax * dltgt.0 + ay * dltgt.1 + az * dltgt.2); }
}
Ok(inductance)
}
pub fn inductance_linear_filaments(
xyzfil_tgt: (&[f64], &[f64], &[f64]),
dlxyzfil_tgt: (&[f64], &[f64], &[f64]),
xyzfil_src: (&[f64], &[f64], &[f64]),
dlxyzfil_src: (&[f64], &[f64], &[f64]),
wire_radius_src: &[f64],
out: &mut [f64],
) -> Result<(), &'static str> {
let ntgt = xyzfil_tgt.0.len();
check_length!(
ntgt,
xyzfil_tgt.0,
xyzfil_tgt.1,
xyzfil_tgt.2,
dlxyzfil_tgt.0,
dlxyzfil_tgt.1,
dlxyzfil_tgt.2,
out
);
let nsrc = xyzfil_src.0.len();
check_length!(
nsrc,
xyzfil_src.0,
xyzfil_src.1,
xyzfil_src.2,
dlxyzfil_src.0,
dlxyzfil_src.1,
dlxyzfil_src.2,
wire_radius_src
);
out.fill(0.0);
for j in 0..ntgt {
out[j] = inductance_piecewise_linear_filaments(
xyzfil_src,
dlxyzfil_src,
(
&xyzfil_tgt.0[j..j + 1],
&xyzfil_tgt.1[j..j + 1],
&xyzfil_tgt.2[j..j + 1],
),
(
&dlxyzfil_tgt.0[j..j + 1],
&dlxyzfil_tgt.1[j..j + 1],
&dlxyzfil_tgt.2[j..j + 1],
),
wire_radius_src,
)?; }
Ok(())
}
pub fn inductance_linear_filaments_matrix(
xyzfil_tgt: (&[f64], &[f64], &[f64]),
dlxyzfil_tgt: (&[f64], &[f64], &[f64]),
xyzfil_src: (&[f64], &[f64], &[f64]),
dlxyzfil_src: (&[f64], &[f64], &[f64]),
wire_radius_src: &[f64],
out: &mut [f64],
) -> Result<(), &'static str> {
let ntgt = xyzfil_tgt.0.len();
check_length!(
ntgt,
xyzfil_tgt.0,
xyzfil_tgt.1,
xyzfil_tgt.2,
dlxyzfil_tgt.0,
dlxyzfil_tgt.1,
dlxyzfil_tgt.2
);
let nsrc = xyzfil_src.0.len();
check_length!(
nsrc,
xyzfil_src.0,
xyzfil_src.1,
xyzfil_src.2,
dlxyzfil_src.0,
dlxyzfil_src.1,
dlxyzfil_src.2,
wire_radius_src
);
let expected_len = nsrc
.checked_mul(ntgt)
.ok_or("Output size overflow in inductance_linear_filaments_matrix")?;
check_length!(expected_len, out);
out.fill(0.0);
for i in 0..nsrc {
let row = &mut out[i * ntgt..(i + 1) * ntgt];
inductance_linear_filaments(
xyzfil_tgt,
dlxyzfil_tgt,
(
&xyzfil_src.0[i..i + 1],
&xyzfil_src.1[i..i + 1],
&xyzfil_src.2[i..i + 1],
),
(
&dlxyzfil_src.0[i..i + 1],
&dlxyzfil_src.1[i..i + 1],
&dlxyzfil_src.2[i..i + 1],
),
&wire_radius_src[i..i + 1],
row,
)?;
}
Ok(())
}
pub fn inductance_linear_filaments_matrix_par(
xyzfil_tgt: (&[f64], &[f64], &[f64]),
dlxyzfil_tgt: (&[f64], &[f64], &[f64]),
xyzfil_src: (&[f64], &[f64], &[f64]),
dlxyzfil_src: (&[f64], &[f64], &[f64]),
wire_radius_src: &[f64],
out: &mut [f64],
) -> Result<(), &'static str> {
let ntgt = xyzfil_tgt.0.len();
check_length!(
ntgt,
xyzfil_tgt.0,
xyzfil_tgt.1,
xyzfil_tgt.2,
dlxyzfil_tgt.0,
dlxyzfil_tgt.1,
dlxyzfil_tgt.2
);
let nsrc = xyzfil_src.0.len();
check_length!(
nsrc,
xyzfil_src.0,
xyzfil_src.1,
xyzfil_src.2,
dlxyzfil_src.0,
dlxyzfil_src.1,
dlxyzfil_src.2,
wire_radius_src
);
let expected_len = nsrc
.checked_mul(ntgt)
.ok_or("Output size overflow in inductance_linear_filaments_matrix_par")?;
check_length!(expected_len, out);
out.fill(0.0);
out.par_chunks_mut(ntgt)
.enumerate()
.try_for_each(|(i, row)| {
inductance_linear_filaments(
xyzfil_tgt,
dlxyzfil_tgt,
(
&xyzfil_src.0[i..i + 1],
&xyzfil_src.1[i..i + 1],
&xyzfil_src.2[i..i + 1],
),
(
&dlxyzfil_src.0[i..i + 1],
&dlxyzfil_src.1[i..i + 1],
&dlxyzfil_src.2[i..i + 1],
),
&wire_radius_src[i..i + 1],
row,
)
})?;
Ok(())
}
pub fn flux_density_linear_filament_par(
xyzp: (&[f64], &[f64], &[f64]),
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
ifil: &[f64],
wire_radius: &[f64],
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let n = chunksize(xyzp.0.len());
let (xpc, ypc, zpc) = par_chunks_3tup!(xyzp, n);
let (bxc, byc, bzc) = mut_par_chunks_3tup!(out, n);
(bxc, byc, bzc, xpc, ypc, zpc)
.into_par_iter()
.try_for_each(|(bx, by, bz, xp, yp, zp)| {
flux_density_linear_filament(
(xp, yp, zp),
xyzfil,
dlxyzfil,
ifil,
wire_radius,
(bx, by, bz),
)
})?;
Ok(())
}
pub fn flux_density_linear_filament_matrix_par(
xyzp: (&[f64], &[f64], &[f64]),
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
ifil: &[f64],
wire_radius: &[f64],
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let nfil = xyzfil.0.len();
let nobs = xyzp.0.len();
if nobs == 0 || nfil == 0 {
return flux_density_linear_filament_matrix(xyzp, xyzfil, dlxyzfil, ifil, wire_radius, out);
}
let n = chunksize(nobs);
(
xyzp.0.par_chunks(n),
xyzp.1.par_chunks(n),
xyzp.2.par_chunks(n),
out.0.par_chunks_mut(n * nfil),
out.1.par_chunks_mut(n * nfil),
out.2.par_chunks_mut(n * nfil),
)
.into_par_iter()
.try_for_each(|(xp, yp, zp, bx, by, bz)| {
flux_density_linear_filament_matrix(
(xp, yp, zp),
xyzfil,
dlxyzfil,
ifil,
wire_radius,
(bx, by, bz),
)
})?;
Ok(())
}
pub fn flux_density_linear_filament(
xyzp: (&[f64], &[f64], &[f64]),
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
ifil: &[f64],
wire_radius: &[f64],
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let (xp, yp, zp) = xyzp;
let (xfil, yfil, zfil) = xyzfil;
let (dlxfil, dlyfil, dlzfil) = dlxyzfil;
let (bx, by, bz) = out;
let n = xfil.len();
let m = xp.len();
check_length!(m, xp, yp, zp, bx, by, bz);
check_length!(
n,
xfil,
yfil,
zfil,
dlxfil,
dlyfil,
dlzfil,
ifil,
wire_radius
);
bx.fill(0.0);
by.fill(0.0);
bz.fill(0.0);
for i in 0..n {
for j in 0..m {
let fil0 = (xfil[i], yfil[i], zfil[i]); let fil1 = (fil0.0 + dlxfil[i], fil0.1 + dlyfil[i], fil0.2 + dlzfil[i]); let current = ifil[i];
let obs = (xp[j], yp[j], zp[j]);
let (bxc, byc, bzc) =
flux_density_linear_filament_scalar((fil0, fil1, current), wire_radius[i], obs);
bx[j] += bxc;
by[j] += byc;
bz[j] += bzc;
}
}
Ok(())
}
pub fn flux_density_linear_filament_matrix(
xyzp: (&[f64], &[f64], &[f64]),
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
ifil: &[f64],
wire_radius: &[f64],
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let (xp, yp, zp) = xyzp;
let (xfil, yfil, zfil) = xyzfil;
let (dlxfil, dlyfil, dlzfil) = dlxyzfil;
let (bx, by, bz) = out;
let nfil = xfil.len();
let nobs = xp.len();
check_length!(nobs, xp, yp, zp);
check_length!(
nfil,
xfil,
yfil,
zfil,
dlxfil,
dlyfil,
dlzfil,
ifil,
wire_radius
);
let expected_len = nobs
.checked_mul(nfil)
.ok_or("Output size overflow in flux_density_linear_filament_matrix")?;
check_length!(expected_len, bx, by, bz);
bx.fill(0.0);
by.fill(0.0);
bz.fill(0.0);
for j in 0..nobs {
let obs = (xp[j], yp[j], zp[j]); let row = j * nfil;
for i in 0..nfil {
let fil0 = (xfil[i], yfil[i], zfil[i]); let fil1 = (fil0.0 + dlxfil[i], fil0.1 + dlyfil[i], fil0.2 + dlzfil[i]); let current = ifil[i]; let (bxc, byc, bzc) =
flux_density_linear_filament_scalar((fil0, fil1, current), wire_radius[i], obs);
bx[row + i] = bxc; by[row + i] = byc; bz[row + i] = bzc; }
}
Ok(())
}
pub fn flux_density_linear_filament_scalar(
xyzifil: ((f64, f64, f64), (f64, f64, f64), f64),
wire_radius: f64,
xyzobs: (f64, f64, f64),
) -> (f64, f64, f64) {
use crate::math::{PointLineDistance, point_line_distance_with_endpoints};
let (start, end, ifil) = xyzifil;
let PointLineDistance {
perp,
perp_hat,
dist_a,
dist_b,
frac,
para_a,
para_b,
ab_norm: dlhat,
} = point_line_distance_with_endpoints(start, end, xyzobs, wire_radius);
let sin_theta_a = para_a / dist_a; let sin_theta_b = para_b / dist_b;
let kappa = -MU0_OVER_4PI * ifil * (sin_theta_b - sin_theta_a);
let c = frac * kappa / perp;
let (cx, cy, cz) = cross3(
dlhat.0, dlhat.1, dlhat.2, perp_hat.0, perp_hat.1, perp_hat.2,
);
let bx = c * cx; let by = c * cy; let bz = c * cz;
if frac > 1e6 * f64::EPSILON && perp > MIN_WIRE_THICKNESS {
return (bx, by, bz);
} else {
return (0.0, 0.0, 0.0);
}
}
pub fn vector_potential_linear_filament_par(
xyzp: (&[f64], &[f64], &[f64]),
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
ifil: &[f64],
wire_radius: &[f64],
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let n = chunksize(xyzp.0.len());
let (xpc, ypc, zpc) = par_chunks_3tup!(xyzp, n);
let (bxc, byc, bzc) = mut_par_chunks_3tup!(out, n);
(bxc, byc, bzc, xpc, ypc, zpc)
.into_par_iter()
.try_for_each(|(bx, by, bz, xp, yp, zp)| {
vector_potential_linear_filament(
(xp, yp, zp),
xyzfil,
dlxyzfil,
ifil,
wire_radius,
(bx, by, bz),
)
})?;
Ok(())
}
pub fn vector_potential_linear_filament_matrix_par(
xyzp: (&[f64], &[f64], &[f64]),
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
ifil: &[f64],
wire_radius: &[f64],
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let nfil = xyzfil.0.len();
let nobs = xyzp.0.len();
if nobs == 0 || nfil == 0 {
return vector_potential_linear_filament_matrix(
xyzp,
xyzfil,
dlxyzfil,
ifil,
wire_radius,
out,
);
}
let n = chunksize(nobs);
(
xyzp.0.par_chunks(n),
xyzp.1.par_chunks(n),
xyzp.2.par_chunks(n),
out.0.par_chunks_mut(n * nfil),
out.1.par_chunks_mut(n * nfil),
out.2.par_chunks_mut(n * nfil),
)
.into_par_iter()
.try_for_each(|(xp, yp, zp, ax, ay, az)| {
vector_potential_linear_filament_matrix(
(xp, yp, zp),
xyzfil,
dlxyzfil,
ifil,
wire_radius,
(ax, ay, az),
)
})?;
Ok(())
}
pub fn vector_potential_linear_filament(
xyzp: (&[f64], &[f64], &[f64]),
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
ifil: &[f64],
wire_radius: &[f64],
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let (xp, yp, zp) = xyzp;
let (xfil, yfil, zfil) = xyzfil;
let (dlxfil, dlyfil, dlzfil) = dlxyzfil;
let (ax, ay, az) = out;
let n = xfil.len();
let m = xp.len();
check_length!(m, xp, yp, zp, ax, ay, az);
check_length!(
n,
xfil,
yfil,
zfil,
dlxfil,
dlyfil,
dlzfil,
ifil,
wire_radius
);
ax.fill(0.0);
ay.fill(0.0);
az.fill(0.0);
for i in 0..n {
for j in 0..m {
let fil0 = (xfil[i], yfil[i], zfil[i]); let fil1 = (fil0.0 + dlxfil[i], fil0.1 + dlyfil[i], fil0.2 + dlzfil[i]); let current = ifil[i];
let obs = (xp[j], yp[j], zp[j]);
let (axc, ayc, azc) =
vector_potential_linear_filament_scalar((fil0, fil1, current), wire_radius[i], obs);
ax[j] += axc;
ay[j] += ayc;
az[j] += azc;
}
}
Ok(())
}
pub fn vector_potential_linear_filament_matrix(
xyzp: (&[f64], &[f64], &[f64]),
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
ifil: &[f64],
wire_radius: &[f64],
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let (xp, yp, zp) = xyzp;
let (xfil, yfil, zfil) = xyzfil;
let (dlxfil, dlyfil, dlzfil) = dlxyzfil;
let (ax, ay, az) = out;
let nfil = xfil.len();
let nobs = xp.len();
check_length!(nobs, xp, yp, zp);
check_length!(
nfil,
xfil,
yfil,
zfil,
dlxfil,
dlyfil,
dlzfil,
ifil,
wire_radius
);
let expected_len = nobs
.checked_mul(nfil)
.ok_or("Output size overflow in vector_potential_linear_filament_matrix")?;
check_length!(expected_len, ax, ay, az);
ax.fill(0.0);
ay.fill(0.0);
az.fill(0.0);
for j in 0..nobs {
let obs = (xp[j], yp[j], zp[j]); let row = j * nfil;
for i in 0..nfil {
let fil0 = (xfil[i], yfil[i], zfil[i]); let fil1 = (fil0.0 + dlxfil[i], fil0.1 + dlyfil[i], fil0.2 + dlzfil[i]); let current = ifil[i]; let (axc, ayc, azc) =
vector_potential_linear_filament_scalar((fil0, fil1, current), wire_radius[i], obs);
ax[row + i] = axc; ay[row + i] = ayc; az[row + i] = azc; }
}
Ok(())
}
#[inline]
pub fn vector_potential_linear_filament_scalar(
xyzifil: ((f64, f64, f64), (f64, f64, f64), f64),
wire_radius: f64,
xyzobs: (f64, f64, f64),
) -> (f64, f64, f64) {
use crate::math::{PointLineDistance, point_line_distance_with_endpoints};
let (start, end, ifil) = xyzifil;
let core_radius = wire_radius.max(MIN_WIRE_THICKNESS);
let PointLineDistance {
perp,
dist_a,
dist_b,
frac,
para_a,
para_b,
ab_norm: dlhat,
perp_hat: _,
} = point_line_distance_with_endpoints(start, end, xyzobs, core_radius);
let sin_theta_a = para_a / dist_a; let sin_theta_b = para_b / dist_b;
let kappa = -MU0_OVER_4PI * ifil * (sin_theta_b - sin_theta_a);
let perp2 = perp * perp;
let k1 = if para_b >= 0.0 {
perp2 / (dist_b + para_b)
} else {
dist_b - para_b
}; let k2 = if para_a >= 0.0 {
perp2 / (dist_a + para_a)
} else {
dist_a - para_a
}; let a_edge = MU0_OVER_4PI * ifil * libm::log((k1 / k2).max(0.0));
let blend = 0.5 * frac.mul_add(-frac, 1.0); let a_mag = kappa.mul_add(blend, a_edge);
let (ax, ay, az) = (a_mag * dlhat.0, a_mag * dlhat.1, a_mag * dlhat.2);
(ax, ay, az)
}
pub fn body_force_density_linear_filament_scalar(
xyzifil: ((f64, f64, f64), (f64, f64, f64), f64),
wire_radius: f64,
xyzobs: (f64, f64, f64),
jobs: (f64, f64, f64),
) -> (f64, f64, f64) {
let (bx, by, bz) = flux_density_linear_filament_scalar(xyzifil, wire_radius, xyzobs);
cross3(jobs.0, jobs.1, jobs.2, bx, by, bz) }
pub fn body_force_density_linear_filament(
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
ifil: &[f64],
wire_radius: &[f64],
xyzobs: (&[f64], &[f64], &[f64]),
jobs: (&[f64], &[f64], &[f64]),
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let (xp, yp, zp) = xyzobs;
let (jx, jy, jz) = jobs;
let (xfil, yfil, zfil) = xyzfil;
let (dlxfil, dlyfil, dlzfil) = dlxyzfil;
let (outx, outy, outz) = out;
let n = xfil.len();
let m = xp.len();
check_length!(
n,
xfil,
yfil,
zfil,
dlxfil,
dlyfil,
dlzfil,
ifil,
wire_radius
);
check_length!(m, xp, yp, zp, jx, jy, jz, outx, outy, outz);
outx.fill(0.0);
outy.fill(0.0);
outz.fill(0.0);
for i in 0..n {
for j in 0..m {
let fil0 = (xfil[i], yfil[i], zfil[i]); let fil1 = (fil0.0 + dlxfil[i], fil0.1 + dlyfil[i], fil0.2 + dlzfil[i]); let obs = (xp[j], yp[j], zp[j]); let jj = (jx[j], jy[j], jz[j]);
let (jxbx, jxby, jxbz) = body_force_density_linear_filament_scalar(
(fil0, fil1, ifil[i]),
wire_radius[i],
obs,
jj,
);
outx[j] += jxbx;
outy[j] += jxby;
outz[j] += jxbz;
}
}
Ok(())
}
pub fn body_force_density_linear_filament_par(
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
ifil: &[f64],
wire_radius: &[f64],
xyzobs: (&[f64], &[f64], &[f64]),
jobs: (&[f64], &[f64], &[f64]),
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let n = chunksize(xyzobs.0.len());
let (xpc, ypc, zpc) = par_chunks_3tup!(xyzobs, n);
let (jxc, jyc, jzc) = par_chunks_3tup!(jobs, n);
let (outxc, outyc, outzc) = mut_par_chunks_3tup!(out, n);
(outxc, outyc, outzc, xpc, ypc, zpc, jxc, jyc, jzc)
.into_par_iter()
.try_for_each(|(outx, outy, outz, xp, yp, zp, jx, jy, jz)| {
body_force_density_linear_filament(
xyzfil,
dlxyzfil,
ifil,
wire_radius,
(xp, yp, zp),
(jx, jy, jz),
(outx, outy, outz),
)
})?;
Ok(())
}
#[cfg(test)]
mod test {
use std::f64::consts::{E, PI};
use super::*;
use crate::physics::point_source::segment::{
flux_density_point_segment, vector_potential_point_segment,
};
use crate::testing::*;
fn distributed_filaments_rect_grid(
start: (f64, f64, f64),
end: (f64, f64, f64),
wire_radius: f64,
total_current: f64,
n_side: usize,
) -> (
Vec<f64>,
Vec<f64>,
Vec<f64>,
Vec<f64>,
Vec<f64>,
Vec<f64>,
Vec<f64>,
Vec<f64>,
) {
assert!(n_side > 0);
assert!(approx(start.0, end.0, 0.0, 1e-15));
assert!(approx(start.1, end.1, 0.0, 1e-15));
let mut xfil_ref = Vec::new();
let mut yfil_ref = Vec::new();
let mut zfil_ref = Vec::new();
let cell = 2.0 * wire_radius / n_side as f64;
for i in 0..n_side {
let x = start.0 - wire_radius + (i as f64 + 0.5) * cell;
for j in 0..n_side {
let y = start.1 - wire_radius + (j as f64 + 0.5) * cell;
let dx = x - start.0;
let dy = y - start.1;
if dx * dx + dy * dy <= wire_radius * wire_radius {
xfil_ref.push(x);
yfil_ref.push(y);
zfil_ref.push(start.2);
}
}
}
let nsub = xfil_ref.len();
assert!(
nsub > 10000,
"expected more than 10000 distributed filaments, got {}",
nsub
);
let dlx_ref = vec![end.0 - start.0; nsub];
let dly_ref = vec![end.1 - start.1; nsub];
let dlz_ref = vec![end.2 - start.2; nsub];
let ifil_ref = vec![total_current / nsub as f64; nsub];
let wire_radius_ref = vec![0.0; nsub];
(
xfil_ref,
yfil_ref,
zfil_ref,
dlx_ref,
dly_ref,
dlz_ref,
ifil_ref,
wire_radius_ref,
)
}
#[test]
fn test_body_force_density() {
let (rtol, atol) = (1e-9, 1e-10);
let ndiscr = 100;
let (rfil, zfil, nfil) = example_circular_filaments();
for i in 0..rfil.len() {
let (ri, zi, ni) = (rfil[i], zfil[i], nfil[i]);
let (x, y, z) = discretize_circular_filament(ri, zi, ndiscr);
let dl = (&diff(&x)[..], &diff(&y)[..], &diff(&z)[..]);
let (jxbx, jxby, jxbz) = (
&mut vec![0.0; ndiscr - 1],
&mut vec![0.0; ndiscr - 1],
&mut vec![0.0; ndiscr - 1],
);
body_force_density_linear_filament(
(&x[..ndiscr - 1], &y[..ndiscr - 1], &z[..ndiscr - 1]),
dl,
&vec![ni; ndiscr - 1][..],
&vec![0.0; ndiscr - 1][..],
(&x[..ndiscr - 1], &y[..ndiscr - 1], &z[..ndiscr - 1]),
dl,
(jxbx, jxby, jxbz),
)
.unwrap();
let jxbx_sum: f64 = jxbx.iter().sum();
let jxby_sum: f64 = jxby.iter().sum();
let jxbz_sum: f64 = jxbz.iter().sum();
assert!(approx(0.0, jxbx_sum, rtol, atol));
assert!(approx(0.0, jxby_sum, rtol, atol));
assert!(approx(0.0, jxbz_sum, rtol, atol));
for j in 0..ndiscr - 1 {
let r: (f64, f64, f64) = (x[j], y[j], 0.0);
let rxjxb = cross3(r.0, r.1, r.2, jxbx[j], jxby[j], jxbz[j]);
assert!(approx(0.0, rss3(rxjxb.0, rxjxb.1, rxjxb.2), rtol, 1e-8));
}
}
for i in 0..rfil.len() {
let (ri, zif, ni) = (rfil[i], zfil[i], nfil[i]);
let (xi, yi, zi) = discretize_circular_filament(ri, zif, ndiscr);
let dli = (&diff(&xi)[..], &diff(&yi)[..], &diff(&zi)[..]);
for j in 0..rfil.len() {
if j == i {
continue;
}
let (rj, zjf, nj) = (rfil[j], zfil[j], nfil[j]);
let (xj, yj, zj) = discretize_circular_filament(rj, zjf, ndiscr);
let dlj = (&diff(&xj)[..], &diff(&yj)[..], &diff(&zj)[..]);
let mid = (
&midpoints(&xj)[..],
&midpoints(&yj)[..],
&midpoints(&zj)[..],
);
let (jxbx, jxby, jxbz) = (
&mut vec![0.0; ndiscr - 1],
&mut vec![0.0; ndiscr - 1],
&mut vec![0.0; ndiscr - 1],
);
body_force_density_linear_filament(
(&xi[..ndiscr - 1], &yi[..ndiscr - 1], &zi[..ndiscr - 1]),
dli,
&vec![ni * nj; xi.len() - 1][..],
&vec![0.0; xi.len() - 1][..],
mid,
dlj,
(jxbx, jxby, jxbz),
)
.unwrap();
let jxbx_sum: f64 = jxbx.iter().sum();
let jxby_sum: f64 = jxby.iter().sum();
let jxbz_sum: f64 = jxbz.iter().sum();
assert!(approx(0.0, jxbx_sum, rtol, atol));
assert!(approx(0.0, jxby_sum, rtol, atol));
assert!(jxbz_sum.signum() == (zif - zjf).signum());
}
}
}
#[test]
fn test_flux_density_against_point_segment_discretization() {
let (rtol, atol) = (1e-6, 1e-12);
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let xfil = [start.0];
let yfil = [start.1];
let zfil = [start.2];
let dlx = [end.0 - start.0];
let dly = [end.1 - start.1];
let dlz = [end.2 - start.2];
let xyzfil = (&xfil[..], &yfil[..], &zfil[..]);
let dlxyz = (&dlx[..], &dly[..], &dlz[..]);
let ngrid = 100;
let span = 10.0;
let xvals: Vec<f64> = (0..ngrid)
.map(|i| -span + (2.0 * span) * (i as f64) / (ngrid as f64 - 1.0))
.collect();
let yvals = xvals.clone();
let zvals = xvals.clone();
let total = ngrid * ngrid * ngrid;
let mut xp = Vec::with_capacity(total);
let mut yp = Vec::with_capacity(total);
let mut zp = Vec::with_capacity(total);
for &x in &xvals {
for &y in &yvals {
for &z in &zvals {
xp.push(x);
yp.push(y);
zp.push(z);
}
}
}
let xyzp = (&xp[..], &yp[..], &zp[..]);
let nseg = 1000;
let dz = (end.2 - start.2) / nseg as f64;
let mut xfil_ps = Vec::with_capacity(nseg);
let mut yfil_ps = Vec::with_capacity(nseg);
let mut zfil_ps = Vec::with_capacity(nseg);
for i in 0..nseg {
xfil_ps.push(start.0);
yfil_ps.push(start.1);
zfil_ps.push(start.2 + dz * i as f64);
}
let dlx = vec![0.0; nseg];
let dly = vec![0.0; nseg];
let dlz = vec![dz; nseg];
for ¤t in &[1.0, E] {
let ifil = [current];
let ifil_ps = vec![current; nseg];
let mut bx = vec![0.0; total];
let mut by = vec![0.0; total];
let mut bz = vec![0.0; total];
flux_density_linear_filament(
xyzp,
xyzfil,
dlxyz,
&ifil,
&[0.0],
(&mut bx, &mut by, &mut bz),
)
.unwrap();
let mut bx_ps = vec![0.0; total];
let mut by_ps = vec![0.0; total];
let mut bz_ps = vec![0.0; total];
flux_density_point_segment(
xyzp,
(&xfil_ps, &yfil_ps, &zfil_ps),
(&dlx, &dly, &dlz),
&ifil_ps,
(&mut bx_ps, &mut by_ps, &mut bz_ps),
)
.unwrap();
for i in 0..xp.len() {
assert!(
approx(bx[i], bx_ps[i], rtol, atol),
"current={} A: bx is {}, should be {}",
current,
bx[i],
bx_ps[i]
);
assert!(
approx(by[i], by_ps[i], rtol, atol),
"current={} A: by is {}, should be {}",
current,
by[i],
by_ps[i]
);
assert!(
approx(bz[i], bz_ps[i], rtol, atol),
"current={} A: bz is {}, should be {}",
current,
bz[i],
bz_ps[i]
);
}
}
}
#[test]
fn test_flux_density_linear_falloff_inside_wire() {
let (rtol, atol) = (1e-10, 1e-14);
let wire_radius = 0.1;
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let ifil = [1.0];
let xfil = [start.0];
let yfil = [start.1];
let zfil = [start.2];
let dlx = [end.0 - start.0];
let dly = [end.1 - start.1];
let dlz = [end.2 - start.2];
let ratios = [1.0, 0.8, 0.6, 0.4, 0.2, 0.0];
let xp: Vec<f64> = ratios.iter().map(|r| r * wire_radius).collect();
let yp: Vec<f64> = vec![0.0; ratios.len()];
let zp: Vec<f64> = vec![0.0; ratios.len()];
let mut bx = vec![0.0; ratios.len()];
let mut by = vec![0.0; ratios.len()];
let mut bz = vec![0.0; ratios.len()];
flux_density_linear_filament(
(&xp, &yp, &zp),
(&xfil, &yfil, &zfil),
(&dlx, &dly, &dlz),
&ifil,
&[wire_radius],
(&mut bx, &mut by, &mut bz),
)
.unwrap();
let bmag: Vec<f64> = bx
.iter()
.zip(by.iter())
.zip(bz.iter())
.map(|((bx, by), bz)| (bx * bx + by * by + bz * bz).sqrt())
.collect();
let b_edge = bmag[0];
for (i, &ratio) in ratios.iter().enumerate() {
let expected = b_edge * ratio;
assert!(
approx(expected, bmag[i], rtol, atol),
"ratio {}: |B| = {:.6e}, expected {:.6e}",
ratio,
bmag[i],
expected
);
}
}
#[test]
fn test_flux_density_against_distributed_filaments_equivalent_area() {
let wire_radius = 0.1;
let start = (0.0, 0.0, -1.0);
let end = (0.0, 0.0, 1.0);
let ifil = [1.0];
let xfil = [start.0];
let yfil = [start.1];
let zfil = [start.2];
let dlx = [end.0 - start.0];
let dly = [end.1 - start.1];
let dlz = [end.2 - start.2];
let (xfil_ref, yfil_ref, zfil_ref, dlx_ref, dly_ref, dlz_ref, ifil_ref, wire_radius_ref) =
distributed_filaments_rect_grid(start, end, wire_radius, ifil[0], 120);
let axis_z = [-10.0, -0.4, 0.0, 0.4, 10.0];
let outside_x = [0.12, 0.16, 0.2, 0.3, 0.5, 10.0];
let mut xp = Vec::with_capacity(axis_z.len() + outside_x.len());
let mut yp = Vec::with_capacity(axis_z.len() + outside_x.len());
let mut zp = Vec::with_capacity(axis_z.len() + outside_x.len());
for &z in &axis_z {
xp.push(0.0);
yp.push(0.0);
zp.push(z);
}
for &x in &outside_x {
xp.push(x);
yp.push(0.0);
zp.push(0.0);
}
let mut bx = vec![0.0; xp.len()];
let mut by = vec![0.0; xp.len()];
let mut bz = vec![0.0; xp.len()];
flux_density_linear_filament(
(&xp[..], &yp[..], &zp[..]),
(&xfil, &yfil, &zfil),
(&dlx, &dly, &dlz),
&ifil,
&[wire_radius],
(&mut bx[..], &mut by[..], &mut bz[..]),
)
.unwrap();
let mut bx_ref = vec![0.0; xp.len()];
let mut by_ref = vec![0.0; xp.len()];
let mut bz_ref = vec![0.0; xp.len()];
flux_density_linear_filament(
(&xp[..], &yp[..], &zp[..]),
(&xfil_ref, &yfil_ref, &zfil_ref),
(&dlx_ref, &dly_ref, &dlz_ref),
&ifil_ref,
&wire_radius_ref,
(&mut bx_ref[..], &mut by_ref[..], &mut bz_ref[..]),
)
.unwrap();
let mut abs_err_axis = Vec::with_capacity(axis_z.len());
let mut rel_err_outside = Vec::with_capacity(outside_x.len());
for i in 0..xp.len() {
let b = rss3(bx[i], by[i], bz[i]);
let b_ref = rss3(bx_ref[i], by_ref[i], bz_ref[i]);
if i < axis_z.len() {
let err = (b - b_ref).abs();
assert!(err.is_finite());
abs_err_axis.push(err);
} else {
let denom = b_ref.abs().max(1e-30);
let err = (b - b_ref).abs() / denom;
assert!(err.is_finite());
rel_err_outside.push(err);
}
}
let max_abs_axis = abs_err_axis.iter().copied().fold(0.0, f64::max);
let max_rel_outside = rel_err_outside.iter().copied().fold(0.0, f64::max);
let mean_rel_outside = rel_err_outside.iter().sum::<f64>() / rel_err_outside.len() as f64;
assert!(
max_abs_axis < 1e-13,
"max axis absolute error too large: {:.6e}, abs_err_axis={:?}",
max_abs_axis,
abs_err_axis
);
assert!(
max_rel_outside < 1e-3,
"max outside relative error too large: {:.6e}, rel_err_outside={:?}",
max_rel_outside,
rel_err_outside
);
assert!(
mean_rel_outside < 1e-3,
"mean outside relative error too large: {:.6e}, rel_err_outside={:?}",
mean_rel_outside,
rel_err_outside
);
}
#[test]
fn test_point_line_distance_endpoint_clamp_values() {
use crate::math::point_line_distance_with_endpoints;
let (rtol, atol) = (1e-12, 1e-15);
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let wire_radius = 0.1;
let x = 0.02;
let at_endpoint =
point_line_distance_with_endpoints(start, end, (x, 0.0, end.2), wire_radius);
assert!(approx(0.1, at_endpoint.perp, rtol, atol));
assert!(approx(0.2, at_endpoint.frac, rtol, atol));
let halfway =
point_line_distance_with_endpoints(start, end, (x, 0.0, end.2 + 0.05), wire_radius);
assert!(approx(0.1, halfway.perp, rtol, atol));
assert!(approx(0.2, halfway.frac, rtol, atol));
let outside =
point_line_distance_with_endpoints(start, end, (x, 0.0, end.2 + 0.1), wire_radius);
assert!(approx(0.1, outside.perp, rtol, atol));
assert!(approx(0.2, outside.frac, rtol, atol));
}
#[test]
fn test_flux_density_no_endpoint_blend_differs_from_thin_outside_projection() {
let wire_radius = 0.1;
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let ifil = 1.0;
let x = 0.02;
let overhangs = [0.0, 0.02, 0.05, 0.1, 0.2];
let mut diff = Vec::with_capacity(overhangs.len());
for &overhang in &overhangs {
let obs = (x, 0.0, end.2 + overhang);
let b_finite =
flux_density_linear_filament_scalar((start, end, ifil), wire_radius, obs);
let b_thin = flux_density_linear_filament_scalar((start, end, ifil), 0.0, obs);
diff.push(rss3(
b_finite.0 - b_thin.0,
b_finite.1 - b_thin.1,
b_finite.2 - b_thin.2,
));
}
assert!(diff[0] > 0.0);
assert!(diff[diff.len() - 1] > 0.0);
assert!(diff.iter().all(|d| d.is_finite()));
}
#[test]
fn test_flux_density_centerline_finite() {
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let ifil = [1.0];
let xfil = [start.0];
let yfil = [start.1];
let zfil = [start.2];
let dlx = [end.0 - start.0];
let dly = [end.1 - start.1];
let dlz = [end.2 - start.2];
let z_points = [-0.5, -0.25, 0.0, 0.25, 0.5];
let xp: Vec<f64> = vec![0.0; z_points.len()];
let yp: Vec<f64> = vec![0.0; z_points.len()];
let zp: Vec<f64> = z_points.to_vec();
for &wire_radius in &[0.0, 0.01, 0.1] {
let mut bx = vec![0.0; z_points.len()];
let mut by = vec![0.0; z_points.len()];
let mut bz = vec![0.0; z_points.len()];
flux_density_linear_filament(
(&xp, &yp, &zp),
(&xfil, &yfil, &zfil),
(&dlx, &dly, &dlz),
&ifil,
&[wire_radius],
(&mut bx, &mut by, &mut bz),
)
.unwrap();
for i in 0..z_points.len() {
assert!(bx[i].is_finite(), "bx[{}] is {}", i, bx[i]);
assert!(by[i].is_finite(), "by[{}] is {}", i, by[i]);
assert!(bz[i].is_finite(), "bz[{}] is {}", i, bz[i]);
}
}
}
#[test]
fn test_flux_density_axis_endpoint_midpoint_nonsingular_scalar() {
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let midpoint = (0.0, 0.0, 0.5 * (start.2 + end.2));
let ifil = 1.0;
let axis_points = [("start", start), ("midpoint", midpoint), ("end", end)];
for &wire_radius in &[0.0, 0.01, 0.1] {
for &(label, p) in &axis_points {
let b = flux_density_linear_filament_scalar((start, end, ifil), wire_radius, p);
assert!(
b.0.is_finite(),
"Bx is non-finite at {} for wire_radius={}",
label,
wire_radius
);
assert!(
b.1.is_finite(),
"By is non-finite at {} for wire_radius={}",
label,
wire_radius
);
assert!(
b.2.is_finite(),
"Bz is non-finite at {} for wire_radius={}",
label,
wire_radius
);
}
}
}
#[test]
fn test_vector_potential_against_point_segment_discretization() {
let (rtol, atol) = (1e-6, 1e-15);
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let xfil = [start.0];
let yfil = [start.1];
let zfil = [start.2];
let dlx = [end.0 - start.0];
let dly = [end.1 - start.1];
let dlz = [end.2 - start.2];
let xyzfil = (&xfil[..], &yfil[..], &zfil[..]);
let dlxyz = (&dlx[..], &dly[..], &dlz[..]);
let ngrid = 100;
let span = 10.0;
let xvals: Vec<f64> = (0..ngrid)
.map(|i| -span + (2.0 * span) * (i as f64) / (ngrid as f64 - 1.0))
.collect();
let yvals = xvals.clone();
let zvals = xvals.clone();
let total = ngrid * ngrid * ngrid;
let mut xp = Vec::with_capacity(total);
let mut yp = Vec::with_capacity(total);
let mut zp = Vec::with_capacity(total);
for &x in &xvals {
for &y in &yvals {
for &z in &zvals {
xp.push(x);
yp.push(y);
zp.push(z);
}
}
}
let xyzp = (&xp[..], &yp[..], &zp[..]);
let nseg = 1000;
let dz = (end.2 - start.2) / nseg as f64;
let mut xfil_ps = Vec::with_capacity(nseg);
let mut yfil_ps = Vec::with_capacity(nseg);
let mut zfil_ps = Vec::with_capacity(nseg);
for i in 0..nseg {
xfil_ps.push(start.0);
yfil_ps.push(start.1);
zfil_ps.push(start.2 + dz * i as f64);
}
let dlx = vec![0.0; nseg];
let dly = vec![0.0; nseg];
let dlz = vec![dz; nseg];
for ¤t in &[1.0, E] {
let ifil = [current];
let ifil_ps = vec![current; nseg];
let mut ax = vec![0.0; total];
let mut ay = vec![0.0; total];
let mut az = vec![0.0; total];
vector_potential_linear_filament(
xyzp,
xyzfil,
dlxyz,
&ifil,
&[0.0],
(&mut ax, &mut ay, &mut az),
)
.unwrap();
let mut ax_ps = vec![0.0; total];
let mut ay_ps = vec![0.0; total];
let mut az_ps = vec![0.0; total];
vector_potential_point_segment(
xyzp,
(&xfil_ps, &yfil_ps, &zfil_ps),
(&dlx, &dly, &dlz),
&ifil_ps,
(&mut ax_ps, &mut ay_ps, &mut az_ps),
)
.unwrap();
for i in 0..xp.len() {
assert!(
approx(ax[i], ax_ps[i], rtol, atol),
"current={} A: ax is {}, should be {}",
current,
ax[i],
ax_ps[i]
);
assert!(
approx(ay[i], ay_ps[i], rtol, atol),
"current={} A: ay is {}, should be {}",
current,
ay[i],
ay_ps[i]
);
assert!(
approx(az[i], az_ps[i], rtol, atol),
"current={} A: az is {}, should be {}",
current,
az[i],
az_ps[i]
);
}
}
}
#[test]
fn test_vector_potential_quadratic_falloff_inside_wire() {
let (rtol, atol) = (1e-10, 1e-14);
let wire_radius = 0.1;
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let ifil = [1.0];
let xfil = [start.0];
let yfil = [start.1];
let zfil = [start.2];
let dlx = [end.0 - start.0];
let dly = [end.1 - start.1];
let dlz = [end.2 - start.2];
let ratios = [1.0, 0.8, 0.6, 0.4, 0.2, 0.0];
let xp: Vec<f64> = ratios.iter().map(|r| r * wire_radius).collect();
let yp: Vec<f64> = vec![0.0; ratios.len()];
let zp: Vec<f64> = vec![0.0; ratios.len()];
let mut ax = vec![0.0; ratios.len()];
let mut ay = vec![0.0; ratios.len()];
let mut az = vec![0.0; ratios.len()];
vector_potential_linear_filament(
(&xp, &yp, &zp),
(&xfil, &yfil, &zfil),
(&dlx, &dly, &dlz),
&ifil,
&[wire_radius],
(&mut ax, &mut ay, &mut az),
)
.unwrap();
let amag: Vec<f64> = ax
.iter()
.zip(ay.iter())
.zip(az.iter())
.map(|((ax, ay), az)| (ax * ax + ay * ay + az * az).sqrt())
.collect();
let a_edge = amag[0];
let pld = crate::math::point_line_distance_with_endpoints(
start,
end,
(wire_radius, 0.0, 0.0),
wire_radius,
);
let sin_theta_a = pld.para_a / pld.dist_a;
let sin_theta_b = pld.para_b / pld.dist_b;
let kappa = -MU0_OVER_4PI * ifil[0] * (sin_theta_b - sin_theta_a);
for (i, &ratio) in ratios.iter().enumerate() {
let expected = a_edge + 0.5 * kappa * (1.0 - ratio * ratio);
assert!(
approx(expected, amag[i], rtol, atol),
"ratio {}: |A| = {:.6e}, expected {:.6e}",
ratio,
amag[i],
expected
);
}
}
#[test]
fn test_vector_potential_against_distributed_filaments_equivalent_area() {
let wire_radius = 0.1;
let start = (0.0, 0.0, -1.0);
let end = (0.0, 0.0, 1.0);
let ifil = [1.0];
let xfil = [start.0];
let yfil = [start.1];
let zfil = [start.2];
let dlx = [end.0 - start.0];
let dly = [end.1 - start.1];
let dlz = [end.2 - start.2];
let (xfil_ref, yfil_ref, zfil_ref, dlx_ref, dly_ref, dlz_ref, ifil_ref, wire_radius_ref) =
distributed_filaments_rect_grid(start, end, wire_radius, ifil[0], 120);
let axis_z = [-10.0, -0.4, 0.0, 0.4, 10.0];
let outside_x = [0.12, 0.16, 0.2, 0.3, 0.5, 10.0];
let mut xp = Vec::with_capacity(axis_z.len() + outside_x.len());
let mut yp = Vec::with_capacity(axis_z.len() + outside_x.len());
let mut zp = Vec::with_capacity(axis_z.len() + outside_x.len());
for &z in &axis_z {
xp.push(0.0);
yp.push(0.0);
zp.push(z);
}
for &x in &outside_x {
xp.push(x);
yp.push(0.0);
zp.push(0.0);
}
let mut ax = vec![0.0; xp.len()];
let mut ay = vec![0.0; xp.len()];
let mut az = vec![0.0; xp.len()];
vector_potential_linear_filament(
(&xp[..], &yp[..], &zp[..]),
(&xfil, &yfil, &zfil),
(&dlx, &dly, &dlz),
&ifil,
&[wire_radius],
(&mut ax[..], &mut ay[..], &mut az[..]),
)
.unwrap();
let mut ax_ref = vec![0.0; xp.len()];
let mut ay_ref = vec![0.0; xp.len()];
let mut az_ref = vec![0.0; xp.len()];
vector_potential_linear_filament(
(&xp[..], &yp[..], &zp[..]),
(&xfil_ref, &yfil_ref, &zfil_ref),
(&dlx_ref, &dly_ref, &dlz_ref),
&ifil_ref,
&wire_radius_ref,
(&mut ax_ref[..], &mut ay_ref[..], &mut az_ref[..]),
)
.unwrap();
let mut rel_err_axis = Vec::with_capacity(axis_z.len());
let mut rel_err_outside = Vec::with_capacity(outside_x.len());
for i in 0..xp.len() {
let a = rss3(ax[i], ay[i], az[i]);
let a_ref = rss3(ax_ref[i], ay_ref[i], az_ref[i]);
let denom = a_ref.abs().max(1e-30);
let err = (a - a_ref).abs() / denom;
assert!(err.is_finite());
if i < axis_z.len() {
rel_err_axis.push(err);
} else {
rel_err_outside.push(err);
}
}
let max_rel_axis = rel_err_axis.iter().copied().fold(0.0, f64::max);
let mean_rel_axis = rel_err_axis.iter().sum::<f64>() / rel_err_axis.len() as f64;
let max_rel_outside = rel_err_outside.iter().copied().fold(0.0, f64::max);
let mean_rel_outside = rel_err_outside.iter().sum::<f64>() / rel_err_outside.len() as f64;
assert!(
max_rel_axis < 1e-3,
"max axis relative error too large: {:.6e}, rel_err_axis={:?}",
max_rel_axis,
rel_err_axis
);
assert!(
mean_rel_axis < 1e-3,
"mean axis relative error too large: {:.6e}, rel_err_axis={:?}",
mean_rel_axis,
rel_err_axis
);
assert!(
max_rel_outside < 1e-3,
"max outside relative error too large: {:.6e}, rel_err_outside={:?}",
max_rel_outside,
rel_err_outside
);
assert!(
mean_rel_outside < 1e-3,
"mean outside relative error too large: {:.6e}, rel_err_outside={:?}",
mean_rel_outside,
rel_err_outside
);
}
#[test]
fn test_vector_potential_no_endpoint_blend_differs_from_thin_outside_projection() {
let wire_radius = 0.1;
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let ifil = 1.0;
let x = 0.02;
let overhangs = [0.0, 0.02, 0.05, 0.1, 0.2];
let mut diff = Vec::with_capacity(overhangs.len());
for &overhang in &overhangs {
let obs = (x, 0.0, end.2 + overhang);
let a_finite =
vector_potential_linear_filament_scalar((start, end, ifil), wire_radius, obs);
let a_thin = vector_potential_linear_filament_scalar((start, end, ifil), 0.0, obs);
diff.push(rss3(
a_finite.0 - a_thin.0,
a_finite.1 - a_thin.1,
a_finite.2 - a_thin.2,
));
}
assert!(diff[0] > 0.0);
assert!(diff[diff.len() - 1] > 0.0);
assert!(diff.iter().all(|d| d.is_finite()));
}
#[test]
fn test_vector_potential_centerline_finite() {
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let ifil = [1.0];
let xfil = [start.0];
let yfil = [start.1];
let zfil = [start.2];
let dlx = [end.0 - start.0];
let dly = [end.1 - start.1];
let dlz = [end.2 - start.2];
let z_points = [-0.5, -0.25, 0.0, 0.25, 0.5];
let xp: Vec<f64> = vec![0.0; z_points.len()];
let yp: Vec<f64> = vec![0.0; z_points.len()];
let zp: Vec<f64> = z_points.to_vec();
for &wire_radius in &[0.0, 0.01, 0.1] {
let mut ax = vec![0.0; z_points.len()];
let mut ay = vec![0.0; z_points.len()];
let mut az = vec![0.0; z_points.len()];
vector_potential_linear_filament(
(&xp, &yp, &zp),
(&xfil, &yfil, &zfil),
(&dlx, &dly, &dlz),
&ifil,
&[wire_radius],
(&mut ax, &mut ay, &mut az),
)
.unwrap();
for i in 0..z_points.len() {
assert!(ax[i].is_finite(), "ax[{}] is {}", i, ax[i]);
assert!(ay[i].is_finite(), "ay[{}] is {}", i, ay[i]);
assert!(az[i].is_finite(), "az[{}] is {}", i, az[i]);
}
}
}
#[test]
fn test_vector_potential_axis_endpoint_midpoint_nonsingular_scalar() {
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let midpoint = (0.0, 0.0, 0.5 * (start.2 + end.2));
let ifil = 1.0;
let axis_points = [("start", start), ("midpoint", midpoint), ("end", end)];
for &wire_radius in &[0.0, 0.01, 0.1] {
for &(label, p) in &axis_points {
let a = vector_potential_linear_filament_scalar((start, end, ifil), wire_radius, p);
assert!(
a.0.is_finite(),
"Ax is non-finite at {} for wire_radius={}",
label,
wire_radius
);
assert!(
a.1.is_finite(),
"Ay is non-finite at {} for wire_radius={}",
label,
wire_radius
);
assert!(
a.2.is_finite(),
"Az is non-finite at {} for wire_radius={}",
label,
wire_radius
);
}
}
}
#[test]
fn test_vector_potential_curl_matches_b_inside_wire() {
let (rtol, atol) = (5e-3, 1e-12);
let start = (0.0, 0.0, -0.5);
let end = (0.0, 0.0, 0.5);
let ifil = 1.0;
let wire_radius = 0.1;
let p = (0.03, 0.04, 0.0); let eps = 1e-6;
let a_at = |x: f64, y: f64, z: f64| {
vector_potential_linear_filament_scalar((start, end, ifil), wire_radius, (x, y, z))
};
let (_ax0, ay0, az0) = a_at(p.0 - eps, p.1, p.2);
let (_ax1, ay1, az1) = a_at(p.0 + eps, p.1, p.2);
let daz_dx = (az1 - az0) / (2.0 * eps);
let day_dx = (ay1 - ay0) / (2.0 * eps);
let (ax0, _ay0, az0) = a_at(p.0, p.1 - eps, p.2);
let (ax1, _ay1, az1) = a_at(p.0, p.1 + eps, p.2);
let daz_dy = (az1 - az0) / (2.0 * eps);
let dax_dy = (ax1 - ax0) / (2.0 * eps);
let (ax0, ay0, _az0) = a_at(p.0, p.1, p.2 - eps);
let (ax1, ay1, _az1) = a_at(p.0, p.1, p.2 + eps);
let day_dz = (ay1 - ay0) / (2.0 * eps);
let dax_dz = (ax1 - ax0) / (2.0 * eps);
let curl_a = (daz_dy - day_dz, dax_dz - daz_dx, day_dx - dax_dy);
let b = flux_density_linear_filament_scalar((start, end, ifil), wire_radius, p);
assert!(
approx(b.0, curl_a.0, rtol, atol),
"bx = {:.6e}, curl_a.x = {:.6e}",
b.0,
curl_a.0
);
assert!(
approx(b.1, curl_a.1, rtol, atol),
"by = {:.6e}, curl_a.y = {:.6e}",
b.1,
curl_a.1
);
assert!(
approx(b.2, curl_a.2, rtol, atol),
"bz = {:.6e}, curl_a.z = {:.6e}",
b.2,
curl_a.2
);
}
#[test]
fn test_vector_potential() {
let xyz = [0.0];
let dlxyz = [1.0];
const NFIL: usize = 10;
let xfil2: Vec<f64> = (0..NFIL).map(|i| (i as f64).sin() + PI).collect();
let yfil2: Vec<f64> = (0..NFIL).map(|i| (i as f64).cos() - PI).collect();
let zfil2: Vec<f64> = (0..NFIL)
.map(|i| (i as f64) - (NFIL as f64) / 2.0 + PI)
.collect();
let xyzfil2 = (
&xfil2[..=NFIL - 2],
&yfil2[..=NFIL - 2],
&zfil2[..=NFIL - 2],
);
let dlxfil2: Vec<f64> = (0..=NFIL - 2).map(|i| xfil2[i + 1] - xfil2[i]).collect();
let dlyfil2: Vec<f64> = (0..=NFIL - 2).map(|i| yfil2[i + 1] - yfil2[i]).collect();
let dlzfil2: Vec<f64> = (0..=NFIL - 2).map(|i| zfil2[i + 1] - zfil2[i]).collect();
let dlxyzfil2 = (&dlxfil2[..], &dlyfil2[..], &dlzfil2[..]);
let mut xquad2 = vec![0.0; 3 * (NFIL - 1)];
let mut yquad2 = vec![0.0; 3 * (NFIL - 1)];
let mut zquad2 = vec![0.0; 3 * (NFIL - 1)];
for i in 0..NFIL - 1 {
let row = 3 * i;
for (iq, tq) in GL3_UNIT_NODES.iter().enumerate() {
xquad2[row + iq] = dlxfil2[i].mul_add(*tq, xfil2[i]);
yquad2[row + iq] = dlyfil2[i].mul_add(*tq, yfil2[i]);
zquad2[row + iq] = dlzfil2[i].mul_add(*tq, zfil2[i]);
}
}
let outx = &mut [0.0; 3 * (NFIL - 1)];
let outy = &mut [0.0; 3 * (NFIL - 1)];
let outz = &mut [0.0; 3 * (NFIL - 1)];
vector_potential_linear_filament(
(&xquad2, &yquad2, &zquad2),
(&xyz, &xyz, &xyz),
(&dlxyz, &dlxyz, &dlxyz),
&[1.0],
&[0.0],
(outx, outy, outz),
)
.unwrap();
let m_from_a = (0..NFIL - 1)
.map(|i| {
let row = 3 * i;
(0..3)
.map(|iq| {
let idx = row + iq;
GL3_UNIT_WEIGHTS[iq]
* (outx[idx] * dlxfil2[i]
+ outy[idx] * dlyfil2[i]
+ outz[idx] * dlzfil2[i])
})
.sum::<f64>()
})
.sum::<f64>();
let wire_radius = [0.0];
let m = inductance_piecewise_linear_filaments(
(&xyz, &xyz, &xyz),
(&dlxyz, &dlxyz, &dlxyz),
xyzfil2,
dlxyzfil2,
&wire_radius,
)
.unwrap();
assert!(
approx(m, m_from_a, 1e-12, 1e-15),
"m = {:.3e}, m_from_a = {:.3e}",
m,
m_from_a
);
let vp = |x: f64, y: f64, z: f64| {
let mut outx = [0.0];
let mut outy = [0.0];
let mut outz = [0.0];
vector_potential_linear_filament(
(&[x], &[y], &[z]),
(&xyz, &xyz, &xyz),
(&dlxyz, &dlxyz, &dlxyz),
&[1.0],
&[0.0],
(&mut outx, &mut outy, &mut outz),
)
.unwrap();
(outx[0], outy[0], outz[0])
};
let vals = [
0.25, 0.5, 2.1, 10.0, 100.0, 1000.0, -1000.0, -100.0, -10.0, -2.0, -0.5, -0.25,
];
for x in vals.iter() {
for y in vals.iter() {
for z in vals.iter() {
if x == y && x == z {
continue;
}
let x = &(x + 1e-2); let y = &(y + 1e-2);
let z = &(z - 1e-2);
let r = rss3(*x, *y, *z);
let atol = 1e-12 / r.max(1.0); let eps = 1e-8 * r;
let mut da = [[0.0; 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 daz_dy = da[1][2];
let day_dz = da[2][1];
let daz_dx = da[0][2];
let dax_dz = da[2][0];
let day_dx = da[0][1];
let dax_dy = da[1][0];
let ca = [daz_dy - day_dz, dax_dz - daz_dx, day_dx - dax_dy];
let mut bx = [0.0];
let mut by = [0.0];
let mut bz = [0.0];
flux_density_linear_filament(
(&[*x][..], &[*y][..], &[*z][..]),
(&xyz[..], &xyz[..], &xyz[..]),
(&dlxyz[..], &dlxyz[..], &dlxyz[..]),
&[1.0],
&[0.0],
(&mut bx, &mut by, &mut bz),
)
.unwrap();
println!("x,y,z = {:.2},{:.2},{:.2}", x, y, z);
assert!(
approx(bx[0], ca[0], 1e-6, atol),
"bx = {:.6e}, ca[0] = {:.6e}",
bx[0],
ca[0]
);
assert!(
approx(by[0], ca[1], 1e-6, atol),
"by = {:.6e}, ca[1] = {:.6e}",
by[0],
ca[1]
);
assert!(
approx(bz[0], ca[2], 1e-6, atol),
"bz = {:.6e}, ca[2] = {:.6e}",
bz[0],
ca[2],
);
}
}
}
}
#[test]
fn test_serial_vs_parallel() {
const NFIL: usize = 10;
const NOBS: usize = 100;
let xfil: Vec<f64> = (0..NFIL).map(|i| (i as f64).sin()).collect();
let yfil: Vec<f64> = (0..NFIL).map(|i| (i as f64).cos()).collect();
let zfil: Vec<f64> = (0..NFIL)
.map(|i| (i as f64) - (NFIL as f64) / 2.0)
.collect();
let xyzfil = (&xfil[..=NFIL - 2], &yfil[..=NFIL - 2], &zfil[..=NFIL - 2]);
let dlxfil: Vec<f64> = (0..=NFIL - 2).map(|i| xfil[i + 1] - xfil[i]).collect();
let dlyfil: Vec<f64> = (0..=NFIL - 2).map(|i| yfil[i + 1] - yfil[i]).collect();
let dlzfil: Vec<f64> = (0..=NFIL - 2).map(|i| zfil[i + 1] - zfil[i]).collect();
let dlxyzfil = (&dlxfil[..], &dlyfil[..], &dlzfil[..]);
let ifil: &[f64] = &(0..NFIL - 1).map(|i| i as f64).collect::<Vec<f64>>()[..];
let xp: Vec<f64> = (0..NOBS).map(|i| 2.0 * (i as f64).sin() + 2.1).collect();
let yp: Vec<f64> = (0..NOBS).map(|i| 4.0 * (2.0 * i as f64).cos()).collect();
let zp: Vec<f64> = (0..NOBS).map(|i| (0.1 * i as f64).exp()).collect();
let xyzp = (&xp[..], &yp[..], &zp[..]);
let out0 = &mut [0.0; NOBS];
let out1 = &mut [1.0; NOBS];
let out2 = &mut [2.0; NOBS];
let out3 = &mut [3.0; NOBS];
let out4 = &mut [4.0; NOBS];
let out5 = &mut [5.0; NOBS];
let wire_radius = vec![0.0; ifil.len()];
flux_density_linear_filament(
xyzp,
xyzfil,
dlxyzfil,
ifil,
&wire_radius,
(out0, out1, out2),
)
.unwrap();
flux_density_linear_filament_par(
xyzp,
xyzfil,
dlxyzfil,
ifil,
&wire_radius,
(out3, out4, out5),
)
.unwrap();
for i in 0..NOBS {
assert_eq!(out0[i], out3[i]);
assert_eq!(out1[i], out4[i]);
assert_eq!(out2[i], out5[i]);
}
let out0 = &mut [0.0; NOBS];
let out1 = &mut [1.0; NOBS];
let out2 = &mut [2.0; NOBS];
let out3 = &mut [3.0; NOBS];
let out4 = &mut [4.0; NOBS];
let out5 = &mut [5.0; NOBS];
let wire_radius = vec![0.0; ifil.len()];
vector_potential_linear_filament(
xyzp,
xyzfil,
dlxyzfil,
ifil,
&wire_radius,
(out0, out1, out2),
)
.unwrap();
vector_potential_linear_filament_par(
xyzp,
xyzfil,
dlxyzfil,
ifil,
&wire_radius,
(out3, out4, out5),
)
.unwrap();
for i in 0..NOBS {
assert_eq!(out0[i], out3[i]);
assert_eq!(out1[i], out4[i]);
assert_eq!(out2[i], out5[i]);
}
}
#[test]
fn test_vector_potential_matrix_contracts_to_vector() {
const NFIL: usize = 6;
const NOBS: usize = 5;
let xfil: Vec<f64> = (0..=NFIL).map(|i| 0.2 * i as f64).collect();
let yfil: Vec<f64> = (0..=NFIL).map(|i| (0.3 * i as f64).sin()).collect();
let zfil: Vec<f64> = (0..=NFIL).map(|i| (0.2 * i as f64).cos()).collect();
let xyzfil = (&xfil[..NFIL], &yfil[..NFIL], &zfil[..NFIL]);
let dlxfil: Vec<f64> = (0..NFIL).map(|i| xfil[i + 1] - xfil[i]).collect();
let dlyfil: Vec<f64> = (0..NFIL).map(|i| yfil[i + 1] - yfil[i]).collect();
let dlzfil: Vec<f64> = (0..NFIL).map(|i| zfil[i + 1] - zfil[i]).collect();
let dlxyzfil = (&dlxfil[..], &dlyfil[..], &dlzfil[..]);
let ifil: Vec<f64> = (0..NFIL).map(|i| 0.5 + i as f64).collect();
let wire_radius: Vec<f64> = (0..NFIL).map(|i| 1e-3 * (1.0 + i as f64)).collect();
let xp: Vec<f64> = (0..NOBS).map(|i| 0.1 + 0.4 * i as f64).collect();
let yp: Vec<f64> = (0..NOBS).map(|i| -0.3 + 0.2 * i as f64).collect();
let zp: Vec<f64> = (0..NOBS).map(|i| 0.2 - 0.1 * i as f64).collect();
let xyzp = (&xp[..], &yp[..], &zp[..]);
let mut ax = vec![0.0; NOBS];
let mut ay = vec![0.0; NOBS];
let mut az = vec![0.0; NOBS];
vector_potential_linear_filament(
xyzp,
xyzfil,
dlxyzfil,
&ifil,
&wire_radius,
(&mut ax, &mut ay, &mut az),
)
.unwrap();
let mut axm = vec![0.0; NOBS * NFIL];
let mut aym = vec![0.0; NOBS * NFIL];
let mut azm = vec![0.0; NOBS * NFIL];
let mut axmp = vec![0.0; NOBS * NFIL];
let mut aymp = vec![0.0; NOBS * NFIL];
let mut azmp = vec![0.0; NOBS * NFIL];
vector_potential_linear_filament_matrix(
xyzp,
xyzfil,
dlxyzfil,
&ifil,
&wire_radius,
(&mut axm, &mut aym, &mut azm),
)
.unwrap();
vector_potential_linear_filament_matrix_par(
xyzp,
xyzfil,
dlxyzfil,
&ifil,
&wire_radius,
(&mut axmp, &mut aymp, &mut azmp),
)
.unwrap();
assert_eq!(axm, axmp);
assert_eq!(aym, aymp);
assert_eq!(azm, azmp);
for j in 0..NOBS {
let row = j * NFIL;
assert!(approx(
ax[j],
axm[row..row + NFIL].iter().sum(),
1e-12,
1e-15
));
assert!(approx(
ay[j],
aym[row..row + NFIL].iter().sum(),
1e-12,
1e-15
));
assert!(approx(
az[j],
azm[row..row + NFIL].iter().sum(),
1e-12,
1e-15
));
}
}
#[test]
fn test_flux_density_matrix_contracts_to_vector() {
const NFIL: usize = 6;
const NOBS: usize = 5;
let xfil: Vec<f64> = (0..=NFIL).map(|i| 0.2 * i as f64).collect();
let yfil: Vec<f64> = (0..=NFIL).map(|i| (0.3 * i as f64).sin()).collect();
let zfil: Vec<f64> = (0..=NFIL).map(|i| (0.2 * i as f64).cos()).collect();
let xyzfil = (&xfil[..NFIL], &yfil[..NFIL], &zfil[..NFIL]);
let dlxfil: Vec<f64> = (0..NFIL).map(|i| xfil[i + 1] - xfil[i]).collect();
let dlyfil: Vec<f64> = (0..NFIL).map(|i| yfil[i + 1] - yfil[i]).collect();
let dlzfil: Vec<f64> = (0..NFIL).map(|i| zfil[i + 1] - zfil[i]).collect();
let dlxyzfil = (&dlxfil[..], &dlyfil[..], &dlzfil[..]);
let ifil: Vec<f64> = (0..NFIL).map(|i| 0.5 + i as f64).collect();
let wire_radius: Vec<f64> = (0..NFIL).map(|i| 1e-3 * (1.0 + i as f64)).collect();
let xp: Vec<f64> = (0..NOBS).map(|i| 0.1 + 0.4 * i as f64).collect();
let yp: Vec<f64> = (0..NOBS).map(|i| -0.3 + 0.2 * i as f64).collect();
let zp: Vec<f64> = (0..NOBS).map(|i| 0.2 - 0.1 * i as f64).collect();
let xyzp = (&xp[..], &yp[..], &zp[..]);
let mut bx = vec![0.0; NOBS];
let mut by = vec![0.0; NOBS];
let mut bz = vec![0.0; NOBS];
flux_density_linear_filament(
xyzp,
xyzfil,
dlxyzfil,
&ifil,
&wire_radius,
(&mut bx, &mut by, &mut bz),
)
.unwrap();
let mut bxm = vec![0.0; NOBS * NFIL];
let mut bym = vec![0.0; NOBS * NFIL];
let mut bzm = vec![0.0; NOBS * NFIL];
let mut bxmp = vec![0.0; NOBS * NFIL];
let mut bymp = vec![0.0; NOBS * NFIL];
let mut bzmp = vec![0.0; NOBS * NFIL];
flux_density_linear_filament_matrix(
xyzp,
xyzfil,
dlxyzfil,
&ifil,
&wire_radius,
(&mut bxm, &mut bym, &mut bzm),
)
.unwrap();
flux_density_linear_filament_matrix_par(
xyzp,
xyzfil,
dlxyzfil,
&ifil,
&wire_radius,
(&mut bxmp, &mut bymp, &mut bzmp),
)
.unwrap();
assert_eq!(bxm, bxmp);
assert_eq!(bym, bymp);
assert_eq!(bzm, bzmp);
for j in 0..NOBS {
let row = j * NFIL;
assert!(approx(
bx[j],
bxm[row..row + NFIL].iter().sum(),
1e-12,
1e-15
));
assert!(approx(
by[j],
bym[row..row + NFIL].iter().sum(),
1e-12,
1e-15
));
assert!(approx(
bz[j],
bzm[row..row + NFIL].iter().sum(),
1e-12,
1e-15
));
}
}
#[test]
fn test_inductance_linear_filaments_contracts_piecewise() {
const NSRC: usize = 4;
const NTGT: usize = 5;
let xsrc: Vec<f64> = (0..NSRC).map(|i| -0.4 + 0.3 * i as f64).collect();
let ysrc: Vec<f64> = (0..NSRC).map(|i| 0.2 * (i as f64).sin()).collect();
let zsrc: Vec<f64> = (0..NSRC).map(|i| -0.1 + 0.15 * i as f64).collect();
let xyzsrc = (&xsrc[..], &ysrc[..], &zsrc[..]);
let dlxsrc: Vec<f64> = (0..NSRC).map(|i| 0.05 + 0.01 * i as f64).collect();
let dlysrc: Vec<f64> = (0..NSRC).map(|i| -0.03 + 0.02 * i as f64).collect();
let dlzsrc: Vec<f64> = (0..NSRC).map(|i| 0.04 - 0.01 * i as f64).collect();
let dlxyzsrc = (&dlxsrc[..], &dlysrc[..], &dlzsrc[..]);
let wire_radius: Vec<f64> = (0..NSRC).map(|i| 2e-3 * (1.0 + i as f64)).collect();
let xtgt: Vec<f64> = (0..NTGT).map(|i| 0.6 + 0.25 * i as f64).collect();
let ytgt: Vec<f64> = (0..NTGT).map(|i| -0.15 + 0.05 * i as f64).collect();
let ztgt: Vec<f64> = (0..NTGT).map(|i| 0.3 - 0.07 * i as f64).collect();
let xyztgt = (&xtgt[..], &ytgt[..], &ztgt[..]);
let dlxtgt: Vec<f64> = (0..NTGT).map(|i| -0.04 + 0.01 * i as f64).collect();
let dlytgt: Vec<f64> = (0..NTGT).map(|i| 0.03 + 0.005 * i as f64).collect();
let dlztgt: Vec<f64> = (0..NTGT).map(|i| 0.02 - 0.004 * i as f64).collect();
let dlxyztgt = (&dlxtgt[..], &dlytgt[..], &dlztgt[..]);
let mut out = vec![0.0; NTGT];
inductance_linear_filaments(xyztgt, dlxyztgt, xyzsrc, dlxyzsrc, &wire_radius, &mut out)
.unwrap();
let total =
inductance_piecewise_linear_filaments(xyzsrc, dlxyzsrc, xyztgt, dlxyztgt, &wire_radius)
.unwrap();
assert!(approx(total, out.iter().sum(), 1e-12, 1e-15));
for j in 0..NTGT {
let mj = inductance_piecewise_linear_filaments(
xyzsrc,
dlxyzsrc,
(&xtgt[j..j + 1], &ytgt[j..j + 1], &ztgt[j..j + 1]),
(&dlxtgt[j..j + 1], &dlytgt[j..j + 1], &dlztgt[j..j + 1]),
&wire_radius,
)
.unwrap();
assert!(approx(mj, out[j], 1e-12, 1e-15));
}
}
#[test]
fn test_inductance_linear_filaments_matrix_contracts_to_vector() {
const NSRC: usize = 4;
const NTGT: usize = 5;
let xsrc: Vec<f64> = (0..NSRC).map(|i| -0.4 + 0.3 * i as f64).collect();
let ysrc: Vec<f64> = (0..NSRC).map(|i| 0.2 * (i as f64).sin()).collect();
let zsrc: Vec<f64> = (0..NSRC).map(|i| -0.1 + 0.15 * i as f64).collect();
let xyzsrc = (&xsrc[..], &ysrc[..], &zsrc[..]);
let dlxsrc: Vec<f64> = (0..NSRC).map(|i| 0.05 + 0.01 * i as f64).collect();
let dlysrc: Vec<f64> = (0..NSRC).map(|i| -0.03 + 0.02 * i as f64).collect();
let dlzsrc: Vec<f64> = (0..NSRC).map(|i| 0.04 - 0.01 * i as f64).collect();
let dlxyzsrc = (&dlxsrc[..], &dlysrc[..], &dlzsrc[..]);
let wire_radius: Vec<f64> = (0..NSRC).map(|i| 2e-3 * (1.0 + i as f64)).collect();
let xtgt: Vec<f64> = (0..NTGT).map(|i| 0.6 + 0.25 * i as f64).collect();
let ytgt: Vec<f64> = (0..NTGT).map(|i| -0.15 + 0.05 * i as f64).collect();
let ztgt: Vec<f64> = (0..NTGT).map(|i| 0.3 - 0.07 * i as f64).collect();
let xyztgt = (&xtgt[..], &ytgt[..], &ztgt[..]);
let dlxtgt: Vec<f64> = (0..NTGT).map(|i| -0.04 + 0.01 * i as f64).collect();
let dlytgt: Vec<f64> = (0..NTGT).map(|i| 0.03 + 0.005 * i as f64).collect();
let dlztgt: Vec<f64> = (0..NTGT).map(|i| 0.02 - 0.004 * i as f64).collect();
let dlxyztgt = (&dlxtgt[..], &dlytgt[..], &dlztgt[..]);
let mut out = vec![0.0; NTGT];
inductance_linear_filaments(xyztgt, dlxyztgt, xyzsrc, dlxyzsrc, &wire_radius, &mut out)
.unwrap();
let mut mm = vec![0.0; NSRC * NTGT];
let mut mmp = vec![0.0; NSRC * NTGT];
inductance_linear_filaments_matrix(
xyztgt,
dlxyztgt,
xyzsrc,
dlxyzsrc,
&wire_radius,
&mut mm,
)
.unwrap();
inductance_linear_filaments_matrix_par(
xyztgt,
dlxyztgt,
xyzsrc,
dlxyzsrc,
&wire_radius,
&mut mmp,
)
.unwrap();
assert_eq!(mm, mmp);
for j in 0..NTGT {
let contracted = (0..NSRC).map(|i| mm[i * NTGT + j]).sum::<f64>();
assert!(approx(out[j], contracted, 1e-12, 1e-15));
}
}
}