use std::collections::VecDeque;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::geometry::{Geometry, NodeId, SegmentDef};
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Port {
pub positive: NodeId,
pub negative: NodeId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
impl Port {
pub const fn new(positive: NodeId, negative: NodeId) -> Self {
Self {
positive,
negative,
name: None,
}
}
#[must_use]
pub fn named(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
}
#[derive(Clone, Debug, PartialEq, Eq, Error)]
pub enum MeshError {
#[error("at least one port is required")]
NoPorts,
#[error("port {port} refers to node {node}, but the geometry has {node_count} nodes")]
UnknownPortNode {
port: usize,
node: usize,
node_count: usize,
},
#[error("port {port} has the same node ({node}) as both terminals")]
DegeneratePort {
port: usize,
node: usize,
},
#[error(
"port {port}: nodes {positive} and {negative} are not connected by any chain of segments"
)]
PortNotConnected {
port: usize,
positive: usize,
negative: usize,
},
#[error("got filament counts for {got} segments, but the geometry has {expected}")]
SegmentCountMismatch {
expected: usize,
got: usize,
},
#[error("segment {segment} has no filaments")]
EmptySegment {
segment: usize,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct MeshMatrix {
rows: Vec<Vec<(usize, f64)>>,
branch_count: usize,
internal_count: usize,
}
impl MeshMatrix {
pub fn build(
geometry: &Geometry,
filaments_per_segment: &[usize],
ports: &[Port],
) -> Result<Self, MeshError> {
let defs = geometry.segment_defs();
if filaments_per_segment.len() != defs.len() {
return Err(MeshError::SegmentCountMismatch {
expected: defs.len(),
got: filaments_per_segment.len(),
});
}
if let Some(segment) = filaments_per_segment.iter().position(|&n| n == 0) {
return Err(MeshError::EmptySegment { segment });
}
if ports.is_empty() {
return Err(MeshError::NoPorts);
}
let first_branch: Vec<usize> = filaments_per_segment
.iter()
.scan(0, |next, &n| {
let first = *next;
*next += n;
Some(first)
})
.collect();
let branch_count = filaments_per_segment.iter().sum();
let mut rows = Vec::new();
for (&first, &n) in first_branch.iter().zip(filaments_per_segment) {
rows.extend((1..n).map(|k| vec![(first + k, 1.0), (first, -1.0)]));
}
let forest = Forest::grow(geometry.nodes().len(), defs);
for (s, def) in defs.iter().enumerate() {
if !forest.in_tree[s] {
let mut row = vec![(s, 1.0)];
row.extend(forest.path(def.b.0, def.a.0));
rows.push(row);
}
}
let internal_count = rows.len();
let node_count = geometry.nodes().len();
for (port, p) in ports.iter().enumerate() {
let (positive, negative) = (p.positive.0, p.negative.0);
if let Some(&node) = [positive, negative].iter().find(|&&n| n >= node_count) {
return Err(MeshError::UnknownPortNode {
port,
node,
node_count,
});
}
if positive == negative {
return Err(MeshError::DegeneratePort {
port,
node: positive,
});
}
match (forest.component[positive], forest.component[negative]) {
(Some(a), Some(b)) if a == b => rows.push(forest.path(positive, negative)),
_ => {
return Err(MeshError::PortNotConnected {
port,
positive,
negative,
})
}
}
}
for row in &mut rows[internal_count - forest.link_count()..] {
for entry in row {
entry.0 = first_branch[entry.0];
}
}
Ok(Self {
rows,
branch_count,
internal_count,
})
}
pub fn loop_count(&self) -> usize {
self.rows.len()
}
pub fn internal_count(&self) -> usize {
self.internal_count
}
pub fn port_count(&self) -> usize {
self.rows.len() - self.internal_count
}
pub fn branch_count(&self) -> usize {
self.branch_count
}
pub fn row(&self, index: usize) -> &[(usize, f64)] {
&self.rows[index]
}
}
struct Forest {
parent: Vec<Option<(usize, usize, bool)>>,
depth: Vec<usize>,
component: Vec<Option<usize>>,
in_tree: Vec<bool>,
}
impl Forest {
fn grow(node_count: usize, defs: &[SegmentDef]) -> Self {
let mut adjacency = vec![Vec::new(); node_count];
for (s, def) in defs.iter().enumerate() {
adjacency[def.a.0].push((def.b.0, s, true));
adjacency[def.b.0].push((def.a.0, s, false));
}
let mut forest = Self {
parent: vec![None; node_count],
depth: vec![0; node_count],
component: vec![None; node_count],
in_tree: vec![false; defs.len()],
};
let mut components = 0;
for root in 0..node_count {
if forest.component[root].is_some() || adjacency[root].is_empty() {
continue;
}
forest.component[root] = Some(components);
let mut queue = VecDeque::from([root]);
while let Some(node) = queue.pop_front() {
for &(next, segment, forwards) in &adjacency[node] {
if forest.component[next].is_none() {
forest.component[next] = Some(components);
forest.parent[next] = Some((node, segment, forwards));
forest.depth[next] = forest.depth[node] + 1;
forest.in_tree[segment] = true;
queue.push_back(next);
}
}
}
components += 1;
}
forest
}
fn link_count(&self) -> usize {
self.in_tree.iter().filter(|&&t| !t).count()
}
fn path(&self, from: usize, to: usize) -> Vec<(usize, f64)> {
let (mut up, mut down) = (Vec::new(), Vec::new());
let (mut x, mut y) = (from, to);
while x != y {
if self.depth[x] >= self.depth[y] {
let (parent, segment, forwards) = self.parent[x].expect("same component");
up.push((segment, if forwards { -1.0 } else { 1.0 }));
x = parent;
} else {
let (parent, segment, forwards) = self.parent[y].expect("same component");
down.push((segment, if forwards { 1.0 } else { -1.0 }));
y = parent;
}
}
up.extend(down.into_iter().rev());
up
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Node;
use nalgebra::DMatrix;
const COPPER: f64 = 5.8e7;
fn geometry(points: &[[f64; 3]], segments: &[(usize, usize)]) -> Geometry {
let mut geometry = Geometry::new();
for &point in points {
geometry.add_node(Node::from(point)).unwrap();
}
for &(a, b) in segments {
geometry
.add_segment(SegmentDef::new(NodeId(a), NodeId(b), 1e-4, 1e-4, COPPER))
.unwrap();
}
geometry
}
fn ring_with_stub_and_floating_bar() -> Geometry {
geometry(
&[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 2.0, 0.0],
[5.0, 0.0, 0.0],
[6.0, 0.0, 0.0],
],
&[(0, 1), (2, 1), (2, 3), (0, 3), (3, 4), (5, 6)],
)
}
fn node_divergence(
geometry: &Geometry,
per_segment: &[usize],
row: &[(usize, f64)],
) -> Vec<f64> {
let owner: Vec<usize> = per_segment
.iter()
.enumerate()
.flat_map(|(s, &n)| std::iter::repeat_n(s, n))
.collect();
let mut divergence = vec![0.0; geometry.nodes().len()];
for &(branch, sign) in row {
let def = geometry.segment_defs()[owner[branch]];
divergence[def.a.0] += sign;
divergence[def.b.0] -= sign;
}
divergence
}
fn dense(mesh: &MeshMatrix) -> DMatrix<f64> {
let mut dense = DMatrix::zeros(mesh.loop_count(), mesh.branch_count());
for i in 0..mesh.loop_count() {
for &(branch, sign) in mesh.row(i) {
assert_eq!(dense[(i, branch)], 0.0, "loop {i} repeats branch {branch}");
dense[(i, branch)] = sign;
}
}
dense
}
#[test]
fn loops_are_closed_independent_and_complete() {
let geometry = ring_with_stub_and_floating_bar();
let per_segment = [3, 1, 2, 4, 2, 3];
let ports = [
Port::new(NodeId(1), NodeId(4)),
Port::new(NodeId(3), NodeId(0)),
];
let mesh = MeshMatrix::build(&geometry, &per_segment, &ports).unwrap();
let branches: usize = per_segment.iter().sum();
assert_eq!(mesh.branch_count(), branches);
assert_eq!(mesh.internal_count(), branches - 7 + 2);
assert_eq!(mesh.port_count(), 2);
assert_eq!(mesh.loop_count(), mesh.internal_count() + 2);
for i in 0..mesh.internal_count() {
let divergence = node_divergence(&geometry, &per_segment, mesh.row(i));
assert!(
divergence.iter().all(|&d| d == 0.0),
"loop {i} is not closed"
);
}
for (p, port) in ports.iter().enumerate() {
let row = mesh.row(mesh.internal_count() + p);
let mut expected = vec![0.0; 7];
expected[port.positive.0] = 1.0;
expected[port.negative.0] = -1.0;
assert_eq!(node_divergence(&geometry, &per_segment, row), expected);
}
let rank = dense(&mesh).rank(1e-9);
assert_eq!(rank, mesh.loop_count(), "the loops are not independent");
}
#[test]
fn single_filament_chain_has_only_the_port_loop() {
let geometry = geometry(
&[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0]],
&[(0, 1), (2, 1)],
);
let mesh =
MeshMatrix::build(&geometry, &[1, 1], &[Port::new(NodeId(0), NodeId(2))]).unwrap();
assert_eq!(mesh.internal_count(), 0);
assert_eq!(mesh.row(0), &[(0, 1.0), (1, -1.0)]);
let reversed =
MeshMatrix::build(&geometry, &[1, 1], &[Port::new(NodeId(2), NodeId(0))]).unwrap();
assert_eq!(reversed.row(0), &[(1, 1.0), (0, -1.0)]);
}
#[test]
fn bundle_loops_pair_each_filament_with_the_first() {
let geometry = geometry(&[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]], &[(0, 1)]);
let mesh = MeshMatrix::build(&geometry, &[3], &[Port::new(NodeId(0), NodeId(1))]).unwrap();
assert_eq!(mesh.internal_count(), 2);
assert_eq!(mesh.row(0), &[(1, 1.0), (0, -1.0)]);
assert_eq!(mesh.row(1), &[(2, 1.0), (0, -1.0)]);
assert_eq!(mesh.row(2), &[(0, 1.0)]);
}
#[test]
fn construction_is_deterministic() {
let geometry = ring_with_stub_and_floating_bar();
let ports = [Port::new(NodeId(0), NodeId(2)).named("across")];
let first = MeshMatrix::build(&geometry, &[2; 6], &ports).unwrap();
assert_eq!(
first,
MeshMatrix::build(&geometry, &[2; 6], &ports).unwrap()
);
}
#[test]
fn invalid_ports_and_counts_are_rejected() {
let geometry = ring_with_stub_and_floating_bar();
let build = |counts: &[usize], ports: &[Port]| MeshMatrix::build(&geometry, counts, ports);
let ok = [1; 6];
assert_eq!(build(&ok, &[]), Err(MeshError::NoPorts));
assert_eq!(
build(&ok, &[Port::new(NodeId(0), NodeId(7))]),
Err(MeshError::UnknownPortNode {
port: 0,
node: 7,
node_count: 7
})
);
assert_eq!(
build(
&ok,
&[
Port::new(NodeId(0), NodeId(1)),
Port::new(NodeId(2), NodeId(2))
]
),
Err(MeshError::DegeneratePort { port: 1, node: 2 })
);
assert_eq!(
build(&ok, &[Port::new(NodeId(0), NodeId(5))]),
Err(MeshError::PortNotConnected {
port: 0,
positive: 0,
negative: 5
})
);
assert_eq!(
build(&[1; 5], &[Port::new(NodeId(0), NodeId(1))]),
Err(MeshError::SegmentCountMismatch {
expected: 6,
got: 5
})
);
assert_eq!(
build(&[1, 1, 0, 1, 1, 1], &[Port::new(NodeId(0), NodeId(1))]),
Err(MeshError::EmptySegment { segment: 2 })
);
}
#[test]
fn a_node_without_segments_cannot_carry_a_port() {
let geometry = geometry(
&[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [9.0, 9.0, 9.0]],
&[(0, 1)],
);
assert_eq!(
MeshMatrix::build(&geometry, &[1], &[Port::new(NodeId(0), NodeId(2))]),
Err(MeshError::PortNotConnected {
port: 0,
positive: 0,
negative: 2
})
);
}
}