use rayon::{
iter::{IntoParallelIterator, ParallelIterator},
slice::{ParallelSlice, ParallelSliceMut},
};
use super::{
QuadratureKind, TRIANGLE_NEAR_SUBDIVISION_DISTANCE_FACTOR, calc_tri_area, map_tri_uv,
triangle_basis_current_density, triangle_quadrature_points,
};
use crate::MU0_OVER_4PI;
use crate::chunksize;
use crate::macros::{check_length_3tup, mut_par_chunks_3tup, par_chunks_3tup};
use crate::math::cross3;
use crate::mesh::TriangleMeshView;
use crate::mesh::elements::tri::tri3::{
closest_point as triangle_closest_point,
max_edge_length_squared as triangle_max_edge_length_squared,
subdivide_about_point as triangle_subdivide_about_point,
};
use crate::physics::point_source::current_element::flux_density_current_element_scalar;
const TRIANGLE_B_DUFFY_EDGE_SAMPLES: usize = 32;
const TRIANGLE_B_DUFFY_SURFACE_TOL_FACTOR: f64 = 1e-12;
#[inline]
fn triangle_flux_density_inner(
n0: [f64; 3],
n1: [f64; 3],
n2: [f64; 3],
current_density: [f64; 3],
obs: [f64; 3],
quad_kind: QuadratureKind,
) -> [f64; 3] {
let tri_area = calc_tri_area(n0, n1, n2); let quad_points = triangle_quadrature_points(quad_kind);
let mut b = [0.0; 3];
for qp in quad_points {
let (c, u, v) = (qp[0], qp[1], qp[2]);
let src = map_tri_uv(n0, n1, n2, [u, v]); let moment = [
current_density[0] * c * tri_area, current_density[1] * c * tri_area, current_density[2] * c * tri_area, ];
let contrib = flux_density_current_element_scalar(src, moment, obs); b[0] += contrib[0]; b[1] += contrib[1]; b[2] += contrib[2]; }
b
}
#[inline]
fn norm3(v: [f64; 3]) -> f64 {
v[0].mul_add(v[0], v[1].mul_add(v[1], v[2] * v[2])).sqrt()
}
#[inline]
fn add_scaled(a: [f64; 3], b: [f64; 3], scale: f64) -> [f64; 3] {
[
a[0] + scale * b[0],
a[1] + scale * b[1],
a[2] + scale * b[2],
]
}
#[inline]
fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
#[inline]
fn accum_cross_scaled(out: &mut [f64; 3], k: [f64; 3], r: [f64; 3], scale: f64) {
let k_cross_r = cross3(k[0], k[1], k[2], r[0], r[1], r[2]);
out[0] += scale * k_cross_r.0;
out[1] += scale * k_cross_r.1;
out[2] += scale * k_cross_r.2;
}
#[inline]
fn triangle_flux_density_surface_duffy(
n0: [f64; 3],
n1: [f64; 3],
n2: [f64; 3],
current_density: [f64; 3],
closest: [f64; 3],
min_sub_area: f64,
length_ref: f64,
) -> [f64; 3] {
let mut b = [0.0; 3];
for tri in triangle_subdivide_about_point(closest, n0, n1, n2) {
let [p, va, vb] = tri;
let area_sub = calc_tri_area(p, va, vb); if area_sub <= min_sub_area {
continue;
}
let qa = sub(va, closest); let qb = sub(vb, closest); let dq = sub(qb, qa); let mut sub_b = [0.0; 3];
for i in 0..TRIANGLE_B_DUFFY_EDGE_SAMPLES {
let eta = (i as f64 + 0.5) / TRIANGLE_B_DUFFY_EDGE_SAMPLES as f64;
let q = add_scaled(qa, dq, eta); let qnorm = norm3(q); if qnorm == 0.0 {
continue;
}
let scale = (qnorm / length_ref).ln() / qnorm.powi(3); accum_cross_scaled(&mut sub_b, current_density, q, scale); }
let scale = -MU0_OVER_4PI * 2.0 * area_sub / TRIANGLE_B_DUFFY_EDGE_SAMPLES as f64; b[0] += scale * sub_b[0]; b[1] += scale * sub_b[1]; b[2] += scale * sub_b[2]; }
b
}
#[inline]
fn triangle_flux_density_duffy(
n0: [f64; 3],
n1: [f64; 3],
n2: [f64; 3],
current_density: [f64; 3],
obs: [f64; 3],
closest: [f64; 3],
max_edge_sq: f64,
) -> Option<[f64; 3]> {
let h = sub(obs, closest); let surface_tol_sq = TRIANGLE_B_DUFFY_SURFACE_TOL_FACTOR.powi(2) * max_edge_sq; let min_sub_area = max_edge_sq * 1e-14; let length_ref = max_edge_sq.sqrt();
if h[0].mul_add(h[0], h[1].mul_add(h[1], h[2] * h[2])) <= surface_tol_sq {
Some(triangle_flux_density_surface_duffy(
n0,
n1,
n2,
current_density,
closest,
min_sub_area,
length_ref,
))
} else {
None
}
}
#[inline]
pub fn triangle_flux_density_basis(
n0: [f64; 3],
n1: [f64; 3],
n2: [f64; 3],
obs: [f64; 3],
quad_kind: QuadratureKind,
) -> [f64; 3] {
let (_, jref) = triangle_basis_current_density(n0, n1, n2); let max_edge_sq = triangle_max_edge_length_squared(n0, n1, n2); let closest = triangle_closest_point(obs, n0, n1, n2); let dx = obs[0] - closest[0]; let dy = obs[1] - closest[1]; let dz = obs[2] - closest[2]; let dist_sq = dx.mul_add(dx, dy.mul_add(dy, dz * dz)); let subdiv_threshold_sq = TRIANGLE_NEAR_SUBDIVISION_DISTANCE_FACTOR.powi(2) * max_edge_sq;
if dist_sq > subdiv_threshold_sq {
return triangle_flux_density_inner(n0, n1, n2, jref, obs, quad_kind);
}
if let Some(b) = triangle_flux_density_duffy(n0, n1, n2, jref, obs, closest, max_edge_sq) {
return b;
}
let mut b = [0.0; 3]; let min_sub_area = max_edge_sq * 1e-14; for tri in triangle_subdivide_about_point(closest, n0, n1, n2) {
let [a, b0, c] = tri;
if calc_tri_area(a, b0, c) <= min_sub_area {
continue;
}
let contrib = triangle_flux_density_inner(a, b0, c, jref, obs, quad_kind);
b[0] += contrib[0]; b[1] += contrib[1]; b[2] += contrib[2]; }
b
}
#[inline]
pub fn flux_density_triangle(
n0: [f64; 3],
n1: [f64; 3],
n2: [f64; 3],
s: [f64; 3],
obs: [f64; 3],
quad_kind: QuadratureKind,
) -> [f64; 3] {
let b_n0 = triangle_flux_density_basis(n0, n1, n2, obs, quad_kind);
let b_n1 = triangle_flux_density_basis(n1, n2, n0, obs, quad_kind);
let b_n2 = triangle_flux_density_basis(n2, n0, n1, obs, quad_kind);
[
s[0] * b_n0[0] + s[1] * b_n1[0] + s[2] * b_n2[0], s[0] * b_n0[1] + s[1] * b_n1[1] + s[2] * b_n2[1], s[0] * b_n0[2] + s[1] * b_n1[2] + s[2] * b_n2[2], ]
}
#[inline]
fn validate_flux_density_mapping_inputs(
outx: &[f64],
outy: &[f64],
outz: &[f64],
nobs: usize,
nnode: usize,
) -> Result<(), &'static str> {
let expected = nobs
.checked_mul(nnode)
.ok_or("Flux-density mapping size overflow")?;
if outx.len() != expected || outy.len() != expected || outz.len() != expected {
return Err("Output dimension mismatch");
}
Ok(())
}
#[inline]
fn flux_density_triangle_mesh_mapping_chunk(
obs: (&[f64], &[f64], &[f64]),
mesh: &TriangleMeshView<'_>,
quad_kind: QuadratureKind,
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let nobs = obs.0.len(); check_length_3tup!(nobs, obs);
validate_flux_density_mapping_inputs(out.0, out.1, out.2, nobs, mesh.nnode())?;
out.0.fill(0.0); out.1.fill(0.0); out.2.fill(0.0);
for iobs in 0..nobs {
let row_offset = iobs * mesh.nnode(); let obs_i = [obs.0[iobs], obs.1[iobs], obs.2[iobs]];
for itri in 0..mesh.len() {
let (tri_nodes, idx) = mesh.triangle_nodes_and_indices(itri);
let b0 = triangle_flux_density_basis(
tri_nodes[0],
tri_nodes[1],
tri_nodes[2],
obs_i,
quad_kind,
); let b1 = triangle_flux_density_basis(
tri_nodes[1],
tri_nodes[2],
tri_nodes[0],
obs_i,
quad_kind,
); let b2 = triangle_flux_density_basis(
tri_nodes[2],
tri_nodes[0],
tri_nodes[1],
obs_i,
quad_kind,
);
out.0[row_offset + idx[0]] += b0[0]; out.1[row_offset + idx[0]] += b0[1]; out.2[row_offset + idx[0]] += b0[2];
out.0[row_offset + idx[1]] += b1[0]; out.1[row_offset + idx[1]] += b1[1]; out.2[row_offset + idx[1]] += b1[2];
out.0[row_offset + idx[2]] += b2[0]; out.1[row_offset + idx[2]] += b2[1]; out.2[row_offset + idx[2]] += b2[2]; }
}
Ok(())
}
#[inline]
fn flux_density_triangle_mesh_inner(
obs: (&[f64], &[f64], &[f64]),
mesh: &TriangleMeshView<'_>,
s: &[f64],
quad_kind: QuadratureKind,
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let nobs = obs.0.len();
check_length_3tup!(nobs, obs);
check_length_3tup!(nobs, out);
mesh.validate_nodal_values(s)?;
out.0.fill(0.0); out.1.fill(0.0); out.2.fill(0.0);
for i in 0..nobs {
let obs_i = [obs.0[i], obs.1[i], obs.2[i]];
for j in 0..mesh.len() {
let tri_nodes = mesh.triangle_nodes(j);
let tri_s = mesh.triangle_scalars(j, s);
let contrib = flux_density_triangle(
tri_nodes[0],
tri_nodes[1],
tri_nodes[2],
tri_s,
obs_i,
quad_kind,
);
out.0[i] += contrib[0]; out.1[i] += contrib[1]; out.2[i] += contrib[2]; }
}
Ok(())
}
#[inline]
pub fn flux_density_triangle_mesh_mapping(
obs: (&[f64], &[f64], &[f64]),
mesh: &TriangleMeshView<'_>,
quad_kind: QuadratureKind,
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
flux_density_triangle_mesh_mapping_chunk(obs, mesh, quad_kind, out)
}
#[inline]
pub fn flux_density_triangle_mesh_mapping_par(
obs: (&[f64], &[f64], &[f64]),
mesh: &TriangleMeshView<'_>,
quad_kind: QuadratureKind,
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
let nobs = obs.0.len(); check_length_3tup!(nobs, obs);
validate_flux_density_mapping_inputs(out.0, out.1, out.2, nobs, mesh.nnode())?;
if nobs == 0 || mesh.nnode() == 0 {
return flux_density_triangle_mesh_mapping_chunk(obs, mesh, quad_kind, out);
}
let nrow = chunksize(nobs); let nflat = nrow
.checked_mul(mesh.nnode())
.ok_or("Flux-density mapping size overflow")?;
let (xpc, ypc, zpc) = par_chunks_3tup!(obs, nrow);
let bxc = out.0.par_chunks_mut(nflat);
let byc = out.1.par_chunks_mut(nflat);
let bzc = out.2.par_chunks_mut(nflat);
(bxc, byc, bzc, xpc, ypc, zpc)
.into_par_iter()
.try_for_each(|(bx, by, bz, xp, yp, zp)| {
flux_density_triangle_mesh_mapping_chunk((xp, yp, zp), mesh, quad_kind, (bx, by, bz))
})?;
Ok(())
}
#[inline]
pub fn triangle_mesh_flux_density_from_potential_vectors(
bx_map: &[f64],
by_map: &[f64],
bz_map: &[f64],
s: &[f64],
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
if by_map.len() != bx_map.len() || bz_map.len() != bx_map.len() {
return Err("Flux-density mapping dimension mismatch");
}
if s.is_empty() {
if bx_map.is_empty() && out.0.is_empty() && out.1.is_empty() && out.2.is_empty() {
return Ok(());
}
return Err("Flux-density mapping dimension mismatch");
}
if !bx_map.len().is_multiple_of(s.len()) {
return Err("Flux-density mapping dimension mismatch");
}
let nobs = bx_map.len() / s.len(); check_length_3tup!(nobs, out);
for iobs in 0..nobs {
let row_offset = iobs * s.len(); let mut bx = 0.0; let mut by = 0.0; let mut bz = 0.0; for inode in 0..s.len() {
bx += bx_map[row_offset + inode] * s[inode]; by += by_map[row_offset + inode] * s[inode]; bz += bz_map[row_offset + inode] * s[inode]; }
out.0[iobs] = bx; out.1[iobs] = by; out.2[iobs] = bz; }
Ok(())
}
#[inline]
pub fn flux_density_triangle_mesh(
obs: (&[f64], &[f64], &[f64]),
mesh: &TriangleMeshView<'_>,
s: &[f64],
quad_kind: QuadratureKind,
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
flux_density_triangle_mesh_inner(obs, mesh, s, quad_kind, out)
}
#[inline]
pub fn flux_density_triangle_mesh_par(
obs: (&[f64], &[f64], &[f64]),
mesh: &TriangleMeshView<'_>,
s: &[f64],
quad_kind: QuadratureKind,
out: (&mut [f64], &mut [f64], &mut [f64]),
) -> Result<(), &'static str> {
mesh.validate_nodal_values(s)?;
let n = chunksize(obs.0.len());
let (xpc, ypc, zpc) = par_chunks_3tup!(obs, 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_triangle_mesh_inner((xp, yp, zp), mesh, s, quad_kind, (bx, by, bz))
})?;
Ok(())
}