use super::{
QuadratureKind, TRIANGLE_SELF_DUFFY_SAMPLES, calc_tri_area, map_tri_uv,
triangle_basis_current_densities, triangle_quadrature_points, triangles_identical,
};
use crate::MU0_OVER_4PI;
use crate::chunksize;
use crate::math::{cartesian_to_cylindrical, dot3, rss3};
use crate::mesh::TriangleMeshView;
use crate::mesh::elements::tri::tri3::subdivide_about_point as triangle_subdivide_about_point;
use crate::physics::circular_filament::vector_potential_circular_filament_scalar;
use crate::physics::linear_filament::vector_potential_linear_filament_scalar;
use crate::physics::point_source::dipole::vector_potential_dipole_scalar;
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
#[inline]
fn triangle_scalar_potential_regular(
n0: [f64; 3],
n1: [f64; 3],
n2: [f64; 3],
obs: [f64; 3],
quad_kind: QuadratureKind,
) -> f64 {
let tri_area = calc_tri_area(n0, n1, n2); let quad_points = triangle_quadrature_points(quad_kind);
let mut out = 0.0; for qp in quad_points {
let src = map_tri_uv(n0, n1, n2, [qp[1], qp[2]]); let dist = rss3(obs[0] - src[0], obs[1] - src[1], obs[2] - src[2]); out += qp[0] * tri_area / dist; }
out
}
#[inline]
fn triangle_scalar_potential_self_duffy(
n0: [f64; 3],
n1: [f64; 3],
n2: [f64; 3],
obs: [f64; 3],
) -> f64 {
let mut out = 0.0;
for [p, va, vb] in triangle_subdivide_about_point(obs, n0, n1, n2) {
let area_sub = calc_tri_area(p, va, vb); if area_sub == 0.0 {
continue;
}
let mut line_integral = 0.0; for i in 0..TRIANGLE_SELF_DUFFY_SAMPLES {
let eta = (i as f64 + 0.5) / TRIANGLE_SELF_DUFFY_SAMPLES as f64;
let edge_vec = [
(1.0 - eta).mul_add(va[0] - obs[0], eta * (vb[0] - obs[0])),
(1.0 - eta).mul_add(va[1] - obs[1], eta * (vb[1] - obs[1])),
(1.0 - eta).mul_add(va[2] - obs[2], eta * (vb[2] - obs[2])),
];
line_integral += 1.0 / rss3(edge_vec[0], edge_vec[1], edge_vec[2]); }
out += area_sub * line_integral / TRIANGLE_SELF_DUFFY_SAMPLES as f64; }
out
}
#[inline]
pub fn triangle_geometric_coupling_regular(
src0: [f64; 3],
src1: [f64; 3],
src2: [f64; 3],
tgt0: [f64; 3],
tgt1: [f64; 3],
tgt2: [f64; 3],
quad_kind: QuadratureKind,
) -> f64 {
let tri_area_tgt = calc_tri_area(tgt0, tgt1, tgt2); let quad_points_tgt = triangle_quadrature_points(quad_kind);
let mut out = 0.0; for qp in quad_points_tgt {
let obs = map_tri_uv(tgt0, tgt1, tgt2, [qp[1], qp[2]]); out += qp[0]
* tri_area_tgt
* triangle_scalar_potential_regular(src0, src1, src2, obs, quad_kind); }
out
}
#[inline]
fn triangle_geometric_coupling_self(
n0: [f64; 3],
n1: [f64; 3],
n2: [f64; 3],
quad_kind: QuadratureKind,
) -> f64 {
let tri_area = calc_tri_area(n0, n1, n2); let quad_points = triangle_quadrature_points(quad_kind);
let mut out = 0.0; for qp in quad_points {
let obs = map_tri_uv(n0, n1, n2, [qp[1], qp[2]]); out += qp[0] * tri_area * triangle_scalar_potential_self_duffy(n0, n1, n2, obs); }
out
}
#[inline]
pub fn triangle_geometric_coupling(
src0: [f64; 3],
src1: [f64; 3],
src2: [f64; 3],
tgt0: [f64; 3],
tgt1: [f64; 3],
tgt2: [f64; 3],
quad_kind: QuadratureKind,
) -> f64 {
if triangles_identical(src0, src1, src2, tgt0, tgt1, tgt2) {
return triangle_geometric_coupling_self(src0, src1, src2, quad_kind);
}
0.5 * (triangle_geometric_coupling_regular(src0, src1, src2, tgt0, tgt1, tgt2, quad_kind)
+ triangle_geometric_coupling_regular(tgt0, tgt1, tgt2, src0, src1, src2, quad_kind))
}
#[inline]
pub fn triangle_basis_mutual_inductance_block(
src0: [f64; 3],
src1: [f64; 3],
src2: [f64; 3],
tgt0: [f64; 3],
tgt1: [f64; 3],
tgt2: [f64; 3],
quad_kind: QuadratureKind,
) -> [[f64; 3]; 3] {
let g = triangle_geometric_coupling(src0, src1, src2, tgt0, tgt1, tgt2, quad_kind);
let ksrc = triangle_basis_current_densities(src0, src1, src2);
let ktgt = triangle_basis_current_densities(tgt0, tgt1, tgt2);
let mut out = [[0.0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
out[i][j] = MU0_OVER_4PI
* g
* dot3(
ksrc[i][0], ksrc[i][1], ksrc[i][2], ktgt[j][0], ktgt[j][1], ktgt[j][2],
);
}
}
out
}
#[inline]
fn scatter_triangle_block(
out: &mut [f64],
nnode: usize,
src_idx: [usize; 3],
tgt_idx: [usize; 3],
block: [[f64; 3]; 3],
) {
for i in 0..3 {
let row = src_idx[i] * nnode; for j in 0..3 {
out[row + tgt_idx[j]] += block[i][j]; }
}
}
#[inline]
fn validate_inductance_matrix_inputs(
lmat: &[f64],
s_src: &[f64],
s_tgt: &[f64],
) -> Result<usize, &'static str> {
let nnode = s_src.len(); if s_tgt.len() != nnode {
return Err("Nodal scalar dimension mismatch");
}
if lmat.len() != nnode * nnode {
return Err("Inductance matrix dimension mismatch");
}
Ok(nnode)
}
#[inline]
fn validate_inductance_mapping_inputs(
map: &[f64],
nnode_tgt: usize,
nsrc: usize,
) -> Result<(), &'static str> {
let expected = nnode_tgt
.checked_mul(nsrc)
.ok_or("Inductance mapping size overflow")?;
if map.len() != expected {
return Err("Output dimension mismatch");
}
Ok(())
}
#[inline]
fn validate_flux_linkage_mapping_vector_inputs(
map: &[f64],
coeffs_src: &[f64],
out: &[f64],
) -> Result<(usize, usize), &'static str> {
let nsrc = coeffs_src.len(); if nsrc == 0 {
if map.is_empty() && out.is_empty() {
return Ok((0, 0));
}
return Err("Source coefficient dimension mismatch");
}
if !map.len().is_multiple_of(nsrc) {
return Err("Inductance mapping dimension mismatch");
}
let nnode_tgt = map.len() / nsrc; if out.len() != nnode_tgt {
return Err("Flux-linkage output dimension mismatch");
}
Ok((nnode_tgt, nsrc))
}
#[inline]
fn triangle_mesh_source_mapping_from_vector_potential_fn<F>(
mesh_tgt: &TriangleMeshView<'_>,
nsrc: usize,
quad_kind: QuadratureKind,
out: &mut [f64],
eval_a: F,
) -> Result<(), &'static str>
where
F: Fn(usize, [f64; 3]) -> [f64; 3] + Sync,
{
validate_inductance_mapping_inputs(out, mesh_tgt.nnode(), nsrc)?;
out.fill(0.0);
for itgt in 0..mesh_tgt.len() {
let (tgt_nodes, tgt_idx) = mesh_tgt.triangle_nodes_and_indices(itgt);
let tri_area = calc_tri_area(tgt_nodes[0], tgt_nodes[1], tgt_nodes[2]); let ktgt = triangle_basis_current_densities(tgt_nodes[0], tgt_nodes[1], tgt_nodes[2]);
for qp in triangle_quadrature_points(quad_kind) {
let obs = map_tri_uv(tgt_nodes[0], tgt_nodes[1], tgt_nodes[2], [qp[1], qp[2]]); let w = qp[0] * tri_area;
for isrc in 0..nsrc {
let a = eval_a(isrc, obs); for ibasis in 0..3 {
out[tgt_idx[ibasis] * nsrc + isrc] += dot3(
ktgt[ibasis][0],
ktgt[ibasis][1],
ktgt[ibasis][2],
a[0],
a[1],
a[2],
) * w; }
}
}
}
Ok(())
}
#[inline]
fn triangle_mesh_source_mapping_from_vector_potential_fn_par<F>(
mesh_tgt: &TriangleMeshView<'_>,
nsrc: usize,
quad_kind: QuadratureKind,
out: &mut [f64],
eval_a: F,
) -> Result<(), &'static str>
where
F: Fn(usize, [f64; 3]) -> [f64; 3] + Sync + Send,
{
let matrix_len = mesh_tgt
.nnode()
.checked_mul(nsrc)
.ok_or("Inductance mapping size overflow")?;
if out.len() != matrix_len {
return Err("Output dimension mismatch");
}
let chunk = chunksize(mesh_tgt.len().max(1)); let starts: Vec<usize> = (0..mesh_tgt.len()).step_by(chunk).collect();
let mut partial_buffers = Vec::with_capacity(starts.len());
for _ in 0..starts.len() {
let mut local = Vec::new();
if local.try_reserve_exact(matrix_len).is_err() {
return triangle_mesh_source_mapping_from_vector_potential_fn(
mesh_tgt, nsrc, quad_kind, out, eval_a,
);
}
local.resize(matrix_len, 0.0); partial_buffers.push(local);
}
let partials: Vec<Vec<f64>> = starts
.into_par_iter()
.zip(partial_buffers.into_par_iter())
.map(|(start, mut local)| {
let end = (start + chunk).min(mesh_tgt.len());
for itgt in start..end {
let (tgt_nodes, tgt_idx) = mesh_tgt.triangle_nodes_and_indices(itgt);
let tri_area = calc_tri_area(tgt_nodes[0], tgt_nodes[1], tgt_nodes[2]); let ktgt =
triangle_basis_current_densities(tgt_nodes[0], tgt_nodes[1], tgt_nodes[2]);
for qp in triangle_quadrature_points(quad_kind) {
let obs = map_tri_uv(tgt_nodes[0], tgt_nodes[1], tgt_nodes[2], [qp[1], qp[2]]); let w = qp[0] * tri_area;
for isrc in 0..nsrc {
let a = eval_a(isrc, obs); for ibasis in 0..3 {
local[tgt_idx[ibasis] * nsrc + isrc] += dot3(
ktgt[ibasis][0],
ktgt[ibasis][1],
ktgt[ibasis][2],
a[0],
a[1],
a[2],
) * w; }
}
}
}
local
})
.collect();
out.fill(0.0); for partial in partials {
for (dst, val) in out.iter_mut().zip(partial.into_iter()) {
*dst += val;
}
}
Ok(())
}
#[inline]
pub fn triangle_mesh_inductance_matrix(
mesh: &TriangleMeshView<'_>,
quad_kind: QuadratureKind,
out: &mut [f64],
) -> Result<(), &'static str> {
let nnode = mesh.nnode();
if out.len() != nnode * nnode {
return Err("Output dimension mismatch");
}
out.fill(0.0);
for isrc in 0..mesh.len() {
let (src_nodes, src_idx) = mesh.triangle_nodes_and_indices(isrc);
for itgt in 0..mesh.len() {
let (tgt_nodes, tgt_idx) = mesh.triangle_nodes_and_indices(itgt);
let block = triangle_basis_mutual_inductance_block(
src_nodes[0],
src_nodes[1],
src_nodes[2],
tgt_nodes[0],
tgt_nodes[1],
tgt_nodes[2],
quad_kind,
);
scatter_triangle_block(out, nnode, src_idx, tgt_idx, block);
}
}
Ok(())
}
#[inline]
pub fn triangle_mesh_inductance_matrix_par(
mesh: &TriangleMeshView<'_>,
quad_kind: QuadratureKind,
out: &mut [f64],
) -> Result<(), &'static str> {
let nnode = mesh.nnode();
let matrix_len = mesh
.nnode()
.checked_mul(mesh.nnode())
.ok_or("Inductance matrix size overflow")?;
if out.len() != matrix_len {
return Err("Output dimension mismatch");
}
let chunk = chunksize(mesh.len().max(1)); let starts: Vec<usize> = (0..mesh.len()).step_by(chunk).collect();
let mut partial_buffers = Vec::with_capacity(starts.len());
for _ in 0..starts.len() {
let mut local = Vec::new();
if local.try_reserve_exact(matrix_len).is_err() {
return triangle_mesh_inductance_matrix(mesh, quad_kind, out);
}
local.resize(matrix_len, 0.0); partial_buffers.push(local);
}
let partials: Vec<Vec<f64>> = starts
.into_par_iter()
.zip(partial_buffers.into_par_iter())
.map(|(start, mut local)| {
let end = (start + chunk).min(mesh.len());
for isrc in start..end {
let (src_nodes, src_idx) = mesh.triangle_nodes_and_indices(isrc);
for itgt in 0..mesh.len() {
let (tgt_nodes, tgt_idx) = mesh.triangle_nodes_and_indices(itgt);
let block = triangle_basis_mutual_inductance_block(
src_nodes[0],
src_nodes[1],
src_nodes[2],
tgt_nodes[0],
tgt_nodes[1],
tgt_nodes[2],
quad_kind,
);
scatter_triangle_block(&mut local, nnode, src_idx, tgt_idx, block);
}
}
local
})
.collect();
out.fill(0.0); for partial in partials {
for (dst, val) in out.iter_mut().zip(partial.into_iter()) {
*dst += val; }
}
Ok(())
}
#[inline]
pub fn triangle_mesh_inductance_from_potential_vectors(
lmat: &[f64],
s_src: &[f64],
s_tgt: &[f64],
) -> Result<f64, &'static str> {
let nnode = validate_inductance_matrix_inputs(lmat, s_src, s_tgt)?;
let mut out = 0.0; for i in 0..nnode {
let row = &lmat[i * nnode..(i + 1) * nnode];
for j in 0..nnode {
out += s_src[i] * row[j] * s_tgt[j]; }
}
Ok(out)
}
#[cfg_attr(not(test), allow(dead_code))]
#[inline]
pub(crate) fn triangle_mesh_inductive_energy(lmat: &[f64], s: &[f64]) -> Result<f64, &'static str> {
Ok(0.5 * triangle_mesh_inductance_from_potential_vectors(lmat, s, s)?) }
#[inline]
pub fn triangle_mesh_flux_linkage_from_source_coefficients(
map: &[f64],
coeffs_src: &[f64],
out: &mut [f64],
) -> Result<(), &'static str> {
let (nnode_tgt, nsrc) = validate_flux_linkage_mapping_vector_inputs(map, coeffs_src, out)?;
if nsrc == 0 {
return Ok(());
}
for inode in 0..nnode_tgt {
let row = &map[inode * nsrc..(inode + 1) * nsrc];
out[inode] = 0.0;
for isrc in 0..nsrc {
out[inode] += row[isrc] * coeffs_src[isrc];
}
}
Ok(())
}
#[inline]
pub fn triangle_mesh_interaction_energy_from_source_coefficients(
map: &[f64],
s_tgt: &[f64],
coeffs_src: &[f64],
) -> Result<f64, &'static str> {
let (nnode_tgt, nsrc) = validate_flux_linkage_mapping_vector_inputs(map, coeffs_src, s_tgt)?;
if nsrc == 0 {
return Ok(0.0);
}
let mut out = 0.0;
for inode in 0..nnode_tgt {
let row = &map[inode * nsrc..(inode + 1) * nsrc];
let mut psi = 0.0;
for isrc in 0..nsrc {
psi += row[isrc] * coeffs_src[isrc];
}
out += s_tgt[inode] * psi;
}
Ok(out)
}
#[inline]
pub fn triangle_mesh_inductance_mapping_from_linear_filaments(
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
wire_radius: &[f64],
mesh_tgt: &TriangleMeshView<'_>,
quad_kind: QuadratureKind,
out: &mut [f64],
) -> Result<(), &'static str> {
let nfil = xyzfil.0.len(); if xyzfil.1.len() != nfil
|| xyzfil.2.len() != nfil
|| dlxyzfil.0.len() != nfil
|| dlxyzfil.1.len() != nfil
|| dlxyzfil.2.len() != nfil
|| wire_radius.len() != nfil
{
return Err("Source dimension mismatch");
}
triangle_mesh_source_mapping_from_vector_potential_fn(
mesh_tgt,
nfil,
quad_kind,
out,
|ifil, obs| {
let start = (xyzfil.0[ifil], xyzfil.1[ifil], xyzfil.2[ifil]);
let end = (
xyzfil.0[ifil] + dlxyzfil.0[ifil],
xyzfil.1[ifil] + dlxyzfil.1[ifil],
xyzfil.2[ifil] + dlxyzfil.2[ifil],
);
let a = vector_potential_linear_filament_scalar(
(start, end, 1.0),
wire_radius[ifil],
(obs[0], obs[1], obs[2]),
);
[a.0, a.1, a.2]
},
)
}
#[inline]
pub fn triangle_mesh_inductance_mapping_from_linear_filaments_par(
xyzfil: (&[f64], &[f64], &[f64]),
dlxyzfil: (&[f64], &[f64], &[f64]),
wire_radius: &[f64],
mesh_tgt: &TriangleMeshView<'_>,
quad_kind: QuadratureKind,
out: &mut [f64],
) -> Result<(), &'static str> {
let nfil = xyzfil.0.len(); if xyzfil.1.len() != nfil
|| xyzfil.2.len() != nfil
|| dlxyzfil.0.len() != nfil
|| dlxyzfil.1.len() != nfil
|| dlxyzfil.2.len() != nfil
|| wire_radius.len() != nfil
{
return Err("Source dimension mismatch");
}
triangle_mesh_source_mapping_from_vector_potential_fn_par(
mesh_tgt,
nfil,
quad_kind,
out,
|ifil, obs| {
let start = (xyzfil.0[ifil], xyzfil.1[ifil], xyzfil.2[ifil]);
let end = (
xyzfil.0[ifil] + dlxyzfil.0[ifil],
xyzfil.1[ifil] + dlxyzfil.1[ifil],
xyzfil.2[ifil] + dlxyzfil.2[ifil],
);
let a = vector_potential_linear_filament_scalar(
(start, end, 1.0),
wire_radius[ifil],
(obs[0], obs[1], obs[2]),
);
[a.0, a.1, a.2]
},
)
}
#[inline]
pub fn triangle_mesh_inductance_mapping_from_circular_filaments(
rfil: &[f64],
zfil: &[f64],
mesh_tgt: &TriangleMeshView<'_>,
quad_kind: QuadratureKind,
out: &mut [f64],
) -> Result<(), &'static str> {
let nfil = rfil.len(); if zfil.len() != nfil {
return Err("Source dimension mismatch");
}
triangle_mesh_source_mapping_from_vector_potential_fn(
mesh_tgt,
nfil,
quad_kind,
out,
|ifil, obs| {
let (robs, phiobs, zobs) = cartesian_to_cylindrical(obs[0], obs[1], obs[2]);
let a_phi = vector_potential_circular_filament_scalar(
(rfil[ifil], zfil[ifil], 1.0),
(robs, zobs),
);
[-a_phi * libm::sin(phiobs), a_phi * libm::cos(phiobs), 0.0]
},
)
}
#[inline]
pub fn triangle_mesh_inductance_mapping_from_circular_filaments_par(
rfil: &[f64],
zfil: &[f64],
mesh_tgt: &TriangleMeshView<'_>,
quad_kind: QuadratureKind,
out: &mut [f64],
) -> Result<(), &'static str> {
let nfil = rfil.len(); if zfil.len() != nfil {
return Err("Source dimension mismatch");
}
triangle_mesh_source_mapping_from_vector_potential_fn_par(
mesh_tgt,
nfil,
quad_kind,
out,
|ifil, obs| {
let (robs, phiobs, zobs) = cartesian_to_cylindrical(obs[0], obs[1], obs[2]);
let a_phi = vector_potential_circular_filament_scalar(
(rfil[ifil], zfil[ifil], 1.0),
(robs, zobs),
);
[-a_phi * libm::sin(phiobs), a_phi * libm::cos(phiobs), 0.0]
},
)
}
#[inline]
pub fn triangle_mesh_flux_linkage_mapping_from_dipoles(
loc: (&[f64], &[f64], &[f64]),
moment_dir: (&[f64], &[f64], &[f64]),
outer_radius: &[f64],
mesh_tgt: &TriangleMeshView<'_>,
quad_kind: QuadratureKind,
out: &mut [f64],
) -> Result<(), &'static str> {
let ndip = loc.0.len(); if loc.1.len() != ndip
|| loc.2.len() != ndip
|| moment_dir.0.len() != ndip
|| moment_dir.1.len() != ndip
|| moment_dir.2.len() != ndip
|| outer_radius.len() != ndip
{
return Err("Source dimension mismatch");
}
triangle_mesh_source_mapping_from_vector_potential_fn(
mesh_tgt,
ndip,
quad_kind,
out,
|idip, obs| {
let a = vector_potential_dipole_scalar(
(loc.0[idip], loc.1[idip], loc.2[idip]),
(moment_dir.0[idip], moment_dir.1[idip], moment_dir.2[idip]),
outer_radius[idip],
(obs[0], obs[1], obs[2]),
);
[a.0, a.1, a.2]
},
)
}
#[inline]
pub fn triangle_mesh_flux_linkage_mapping_from_dipoles_par(
loc: (&[f64], &[f64], &[f64]),
moment_dir: (&[f64], &[f64], &[f64]),
outer_radius: &[f64],
mesh_tgt: &TriangleMeshView<'_>,
quad_kind: QuadratureKind,
out: &mut [f64],
) -> Result<(), &'static str> {
let ndip = loc.0.len(); if loc.1.len() != ndip
|| loc.2.len() != ndip
|| moment_dir.0.len() != ndip
|| moment_dir.1.len() != ndip
|| moment_dir.2.len() != ndip
|| outer_radius.len() != ndip
{
return Err("Source dimension mismatch");
}
triangle_mesh_source_mapping_from_vector_potential_fn_par(
mesh_tgt,
ndip,
quad_kind,
out,
|idip, obs| {
let a = vector_potential_dipole_scalar(
(loc.0[idip], loc.1[idip], loc.2[idip]),
(moment_dir.0[idip], moment_dir.1[idip], moment_dir.2[idip]),
outer_radius[idip],
(obs[0], obs[1], obs[2]),
);
[a.0, a.1, a.2]
},
)
}
#[inline]
pub fn triangle_basis_mutual_inductance(
src0: [f64; 3],
src1: [f64; 3],
src2: [f64; 3],
src_basis: usize,
tgt0: [f64; 3],
tgt1: [f64; 3],
tgt2: [f64; 3],
tgt_basis: usize,
quad_kind: QuadratureKind,
) -> f64 {
triangle_basis_mutual_inductance_block(src0, src1, src2, tgt0, tgt1, tgt2, quad_kind)[src_basis]
[tgt_basis]
}
#[inline]
pub fn triangle_inductance_from_potential_vectors(
m_block: [[f64; 3]; 3],
s_src: [f64; 3],
s_tgt: [f64; 3],
) -> f64 {
let mut out = 0.0;
for i in 0..3 {
for j in 0..3 {
out += s_src[i] * m_block[i][j] * s_tgt[j];
}
}
out
}