use nalgebra::DMatrix;
use rayon::prelude::*;
use super::{evaluate, KernelError, Mutual};
use crate::filament::Filament;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Execution {
Scalar,
Simd,
#[default]
Parallel,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PairMask {
group: Vec<usize>,
coupled: Vec<bool>,
groups: usize,
}
impl PairMask {
pub fn all(filaments: usize) -> Self {
Self {
group: vec![0; filaments],
coupled: vec![true],
groups: 1,
}
}
pub fn from_groups(
group: Vec<usize>,
groups: usize,
coupled: impl Fn(usize, usize) -> bool,
) -> Self {
let mut table = vec![false; groups * groups];
for a in 0..groups {
for b in a..groups {
let keep = a == b || coupled(a, b) || coupled(b, a);
table[a * groups + b] = keep;
table[b * groups + a] = keep;
}
}
Self {
group,
coupled: table,
groups,
}
}
pub fn len(&self) -> usize {
self.group.len()
}
pub fn is_empty(&self) -> bool {
self.group.is_empty()
}
pub fn groups(&self) -> usize {
self.groups
}
pub fn keeps(&self, i: usize, j: usize) -> bool {
match (self.group.get(i), self.group.get(j)) {
(Some(&a), Some(&b)) => self
.coupled
.get(a * self.groups + b)
.copied()
.unwrap_or(true),
_ => true,
}
}
pub fn keeps_everything(&self) -> bool {
self.coupled.iter().all(|&keep| keep)
}
pub fn kept_pairs(&self) -> usize {
let mut sizes = vec![0usize; self.groups];
for &g in &self.group {
if let Some(slot) = sizes.get_mut(g) {
*slot += 1;
}
}
let mut dropped = 0usize;
for a in 0..self.groups {
for b in (a + 1)..self.groups {
if !self.coupled[a * self.groups + b] {
dropped += sizes[a] * sizes[b];
}
}
}
self.total_pairs() - dropped
}
pub fn total_pairs(&self) -> usize {
let n = self.group.len();
n * (n + 1) / 2
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MutualBatch {
pub values: DMatrix<f64>,
pub unresolved_pairs: Vec<(usize, usize)>,
}
pub fn mutual_batch(rows: &[Filament], cols: &[Filament]) -> Result<DMatrix<f64>, KernelError> {
mutual_batch_with(rows, cols, Execution::Parallel)
}
pub fn mutual_batch_with(
rows: &[Filament],
cols: &[Filament],
execution: Execution,
) -> Result<DMatrix<f64>, KernelError> {
let data = evaluate_batch(rows, cols, execution, |m| m.value)?;
Ok(DMatrix::from_row_slice(rows.len(), cols.len(), &data))
}
pub fn mutual_batch_detailed(
rows: &[Filament],
cols: &[Filament],
) -> Result<MutualBatch, KernelError> {
mutual_batch_detailed_with(rows, cols, Execution::Parallel)
}
pub fn mutual_batch_detailed_with(
rows: &[Filament],
cols: &[Filament],
execution: Execution,
) -> Result<MutualBatch, KernelError> {
let data = evaluate_batch(rows, cols, execution, |m| (m.value, m.resolved))?;
let (nrows, ncols) = (rows.len(), cols.len());
let unresolved_pairs = data
.iter()
.enumerate()
.filter(|(_, (_, resolved))| !resolved)
.map(|(index, _)| (index / ncols, index % ncols))
.collect();
Ok(MutualBatch {
values: DMatrix::from_fn(nrows, ncols, |i, j| data[i * ncols + j].0),
unresolved_pairs,
})
}
fn evaluate_batch<T: Clone + Default + Send>(
rows: &[Filament],
cols: &[Filament],
execution: Execution,
select: impl Fn(Mutual) -> T + Sync,
) -> Result<Vec<T>, KernelError> {
let (nrows, ncols) = (rows.len(), cols.len());
if nrows == 0 || ncols == 0 {
return Ok(Vec::new());
}
let simd = execution != Execution::Scalar;
let fill = |(i, out): (usize, &mut [T])| -> Result<(), KernelError> {
for (j, slot) in out.iter_mut().enumerate() {
*slot = select(evaluate(&rows[i], &cols[j], simd).map_err(|e| e.at(i, j))?);
}
Ok(())
};
let mut data = vec![T::default(); nrows * ncols];
if execution == Execution::Parallel {
data.par_chunks_mut(ncols).enumerate().try_for_each(fill)?;
} else {
data.chunks_mut(ncols).enumerate().try_for_each(fill)?;
}
Ok(data)
}
pub fn partial_inductance_matrix(filaments: &[Filament]) -> Result<DMatrix<f64>, KernelError> {
let upper = evaluate_upper(filaments, |m| m.value)?;
let n = filaments.len();
Ok(DMatrix::from_fn(n, n, |i, j| {
let (lo, hi) = (i.min(j), i.max(j));
upper[lo][hi - lo]
}))
}
pub fn partial_inductance_matrix_detailed(
filaments: &[Filament],
) -> Result<MutualBatch, KernelError> {
let upper = evaluate_upper(filaments, |m| (m.value, m.resolved))?;
let n = filaments.len();
let mut unresolved_pairs = Vec::new();
for (i, row) in upper.iter().enumerate() {
for (offset, &(_, resolved)) in row.iter().enumerate() {
if !resolved {
let j = i + offset;
unresolved_pairs.push((i, j));
if i != j {
unresolved_pairs.push((j, i));
}
}
}
}
unresolved_pairs.sort_unstable();
Ok(MutualBatch {
values: DMatrix::from_fn(n, n, |i, j| {
let (lo, hi) = (i.min(j), i.max(j));
upper[lo][hi - lo].0
}),
unresolved_pairs,
})
}
pub fn partial_inductance_matrix_masked(
filaments: &[Filament],
mask: &PairMask,
) -> Result<DMatrix<f64>, KernelError> {
partial_inductance_matrix_masked_with(filaments, mask, |a, b| {
evaluate(a, b, true).map(|m| m.value)
})
}
pub fn partial_inductance_matrix_masked_with<K>(
filaments: &[Filament],
mask: &PairMask,
kernel: K,
) -> Result<DMatrix<f64>, KernelError>
where
K: Fn(&Filament, &Filament) -> Result<f64, KernelError> + Sync,
{
let n = filaments.len();
let upper: Vec<Vec<f64>> = (0..n)
.into_par_iter()
.map(|i| {
(i..n)
.map(|j| {
if mask.keeps(i, j) {
kernel(&filaments[i], &filaments[j]).map_err(|e| e.at(i, j))
} else {
Ok(0.0)
}
})
.collect()
})
.collect::<Result<_, KernelError>>()?;
Ok(DMatrix::from_fn(n, n, |i, j| {
let (lo, hi) = (i.min(j), i.max(j));
upper[lo][hi - lo]
}))
}
fn evaluate_upper<T: Send>(
filaments: &[Filament],
select: impl Fn(Mutual) -> T + Sync,
) -> Result<Vec<Vec<T>>, KernelError> {
let n = filaments.len();
(0..n)
.into_par_iter()
.map(|i| {
(i..n)
.map(|j| {
evaluate(&filaments[i], &filaments[j], true)
.map(&select)
.map_err(|e| e.at(i, j))
})
.collect()
})
.collect()
}