use faer::sparse::linalg::solvers::{Lu, SymbolicLu};
use faer::sparse::{SparseColMat, Triplet};
use num_complex::Complex64 as c64;
use super::netlist::{Netlist, NetlistError};
use super::{Component, Fidelity, Parameter, Port, Provenance, SMatrix, Spectrum};
use crate::units::Wavelength;
use crate::{Error, Result};
#[derive(Clone, Copy, Debug)]
enum Entry {
One,
Block {
instance: usize,
q: usize,
p: usize,
diagonal: bool,
},
}
pub(super) struct Solved {
pub(super) triplets: Vec<Triplet<usize, usize, c64>>,
pub(super) lu: Lu<usize, c64>,
pub(super) b: faer::Mat<c64>,
pub(super) s: SMatrix,
}
#[derive(Clone, Debug)]
pub struct Circuit {
netlist: Netlist,
pub(super) offsets: Vec<usize>,
pub(super) parameter_offsets: Vec<usize>,
owner: Vec<(usize, usize)>,
pub(super) partner: Vec<Option<usize>>,
pub(super) external: Vec<usize>,
ports: Vec<Port>,
parameters: Vec<Parameter>,
values: Vec<f64>,
entries: Vec<(usize, usize, Entry)>,
symbolic: SymbolicLu<usize>,
}
impl Netlist {
pub fn compile(&self) -> Result<Circuit> {
self.validate()?;
let instances = self.instances();
let mut offsets = Vec::with_capacity(instances.len());
let mut parameter_offsets = Vec::with_capacity(instances.len());
let mut owner = Vec::new();
let mut parameters = Vec::new();
let mut values = Vec::new();
for (i, inst) in instances.iter().enumerate() {
offsets.push(owner.len());
parameter_offsets.push(parameters.len());
owner.extend((0..inst.component.ports().len()).map(|p| (i, p)));
for (p, &value) in inst.component.parameters().iter().zip(&inst.values) {
parameters.push(Parameter {
name: format!("{}.{}", inst.name, p.name),
default: value,
..p.clone()
});
}
values.extend_from_slice(&inst.values);
}
let n = owner.len();
let number = |r| -> Result<usize> {
let (i, p) = self.resolve(r)?;
Ok(offsets[i] + p)
};
let mut partner = vec![None; n];
for (a, b) in self.connections() {
let (a, b) = (number(a)?, number(b)?);
partner[a] = Some(b);
partner[b] = Some(a);
}
let mut external = Vec::new();
let mut ports = Vec::new();
for (name, r) in self.external() {
let g = number(r)?;
external.push(g);
let (i, p) = owner[g];
ports.push(Port {
name: name.clone(),
mode: instances[i].component.ports()[p].mode.clone(),
});
}
let mut entries = Vec::new();
for (j, &partner) in partner.iter().enumerate() {
match partner {
None => entries.push((j, j, Entry::One)),
Some(m) => {
let (instance, p) = owner[m];
let rows = offsets[instance]
..offsets[instance] + instances[instance].component.ports().len();
if !rows.contains(&j) {
entries.push((j, j, Entry::One));
}
for row in rows {
entries.push((
row,
j,
Entry::Block {
instance,
q: row - offsets[instance],
p,
diagonal: row == j,
},
));
}
}
}
}
let pattern: Vec<Triplet<usize, usize, c64>> = entries
.iter()
.map(|&(row, col, _)| Triplet::new(row, col, c64::new(1.0, 0.0)))
.collect();
let matrix = SparseColMat::<usize, c64>::try_new_from_triplets(n, n, &pattern)
.map_err(|e| Error::invalid("circuit", format!("can't assemble its system: {e:?}")))?;
let symbolic = SymbolicLu::try_new(matrix.symbolic())
.map_err(|e| Error::invalid("circuit", format!("can't analyse its system: {e:?}")))?;
Ok(Circuit {
netlist: self.clone(),
offsets,
parameter_offsets,
owner,
partner,
external,
ports,
parameters,
values,
entries,
symbolic,
})
}
}
impl Circuit {
pub fn netlist(&self) -> &Netlist {
&self.netlist
}
pub fn values(&self) -> &[f64] {
&self.values
}
pub fn set(&mut self, instance: &str, parameter: &str, value: f64) -> Result<()> {
self.netlist.set(instance, parameter, value)?;
let i = self.netlist.index(instance)?;
let inst = &self.netlist.instances()[i];
let start = self.parameter_offsets[i];
self.values[start..start + inst.values.len()].copy_from_slice(&inst.values);
for (k, &v) in inst.values.iter().enumerate() {
self.parameters[start + k].default = v;
}
Ok(())
}
pub fn s_matrix(&self, wavelength: Wavelength) -> Result<SMatrix> {
self.solve(wavelength, &self.values)
}
pub fn s_matrix_with(&self, wavelength: Wavelength, values: &[f64]) -> Result<SMatrix> {
self.solve(wavelength, values)
}
pub fn spectrum(&self, wavelengths: &[Wavelength]) -> Result<Spectrum> {
Spectrum::of(self, wavelengths, &self.values)
}
fn blocks(&self, wavelength: Wavelength, values: &[f64]) -> Result<Vec<SMatrix>> {
if values.len() != self.parameters.len() {
return Err(Error::invalid(
"circuit",
format!(
"has {} parameters, got {} values",
self.parameters.len(),
values.len()
),
));
}
self.netlist
.instances()
.iter()
.enumerate()
.map(|(i, inst)| {
let start = self.parameter_offsets[i];
let own = &values[start..start + inst.values.len()];
let s = inst.component.s_matrix(wavelength, own)?;
let ports = inst.component.ports().len();
if s.size() != ports {
return Err(NetlistError::SizeMismatch {
instance: inst.name.clone(),
ports,
size: s.size(),
}
.into());
}
Ok(s)
})
.collect()
}
fn solve(&self, wavelength: Wavelength, values: &[f64]) -> Result<SMatrix> {
if self.external.is_empty() {
self.blocks(wavelength, values)?;
return Ok(SMatrix::zeros(0));
}
Ok(self.solved(wavelength, values)?.s)
}
pub(super) fn solved(&self, wavelength: Wavelength, values: &[f64]) -> Result<Solved> {
use faer::linalg::solvers::Solve;
let blocks = self.blocks(wavelength, values)?;
let n = self.owner.len();
let k = self.external.len();
let one = c64::new(1.0, 0.0);
let triplets: Vec<Triplet<usize, usize, c64>> = self
.entries
.iter()
.map(|&(row, col, entry)| {
let value = match entry {
Entry::One => one,
Entry::Block {
instance,
q,
p,
diagonal,
} => {
let s = -blocks[instance][(q, p)];
if diagonal { one + s } else { s }
}
};
Triplet::new(row, col, value)
})
.collect();
let singular = || {
Error::invalid(
"circuit",
format!("its system is singular at {wavelength}: a lossless resonance"),
)
};
let matrix = SparseColMat::<usize, c64>::try_new_from_triplets(n, n, &triplets)
.map_err(|e| Error::invalid("circuit", format!("can't assemble its system: {e:?}")))?;
let lu = Lu::try_new_with_symbolic(self.symbolic.clone(), matrix.as_ref())
.map_err(|_| singular())?;
let mut rhs = faer::Mat::<c64>::zeros(n, k);
for (c, &g) in self.external.iter().enumerate() {
let (instance, p) = self.owner[g];
let start = self.offsets[instance];
for q in 0..blocks[instance].size() {
rhs[(start + q, c)] = blocks[instance][(q, p)];
}
}
let mut b = lu.solve(&rhs);
let mut residual = rhs.clone();
for t in &triplets {
for c in 0..k {
residual[(t.row, c)] -= t.val * b[(t.col, c)];
}
}
let correction = lu.solve(&residual);
b += &correction;
let s = SMatrix::from_fn(k, |q, p| b[(self.external[q], p)]);
if s.values.iter().any(|v| !v.is_finite()) {
return Err(singular());
}
Ok(Solved { triplets, lu, b, s })
}
pub fn s_matrix_by_growth(&self, wavelength: Wavelength) -> Result<SMatrix> {
let blocks = self.blocks(wavelength, &self.values)?;
let n = self.owner.len();
let mut open: Vec<usize> = (0..n).collect();
let mut s = vec![vec![c64::new(0.0, 0.0); n]; n];
for (g, &(instance, p)) in self.owner.iter().enumerate() {
let start = self.offsets[instance];
for q in 0..blocks[instance].size() {
s[start + q][g] = blocks[instance][(q, p)];
}
}
let one = c64::new(1.0, 0.0);
for (a, b) in self.netlist.connections() {
let position = |r| -> Result<usize> {
let (i, p) = self.netlist.resolve(r)?;
let g = self.offsets[i] + p;
open.iter()
.position(|&o| o == g)
.ok_or_else(|| Error::invalid("circuit", format!("{r} is joined twice")))
};
let (k, l) = (position(a)?, position(b)?);
let (skk, skl, slk, sll) = (s[k][k], s[k][l], s[l][k], s[l][l]);
let denominator = (one - skl) * (one - slk) - skk * sll;
if denominator.norm() == 0.0 {
return Err(Error::invalid(
"circuit",
format!("sub-network growth divides by zero at {wavelength}"),
));
}
let keep: Vec<usize> = (0..open.len()).filter(|&i| i != k && i != l).collect();
let grown: Vec<Vec<c64>> = keep
.iter()
.map(|&i| {
keep.iter()
.map(|&j| {
let (sik, sil, skj, slj) = (s[i][k], s[i][l], s[k][j], s[l][j]);
s[i][j]
+ (sil * skj * (one - slk)
+ sil * skk * slj
+ sik * slj * (one - skl)
+ sik * sll * skj)
/ denominator
})
.collect()
})
.collect();
s = grown;
open = keep.iter().map(|&i| open[i]).collect();
}
let at = self
.external
.iter()
.map(|g| {
open.iter()
.position(|o| o == g)
.ok_or_else(|| Error::invalid("circuit", "an external port was joined"))
})
.collect::<Result<Vec<usize>>>()?;
Ok(SMatrix::from_fn(self.external.len(), |q, p| {
s[at[q]][at[p]]
}))
}
}
impl Component for Circuit {
fn kind(&self) -> &str {
"circuit"
}
fn ports(&self) -> &[Port] {
&self.ports
}
fn parameters(&self) -> &[Parameter] {
&self.parameters
}
fn s_matrix(&self, wavelength: Wavelength, values: &[f64]) -> Result<SMatrix> {
self.solve(wavelength, values)
}
fn provenance(&self) -> Provenance {
let fidelities = self
.netlist
.instances()
.iter()
.map(|i| i.component.provenance().fidelity);
let rank = |f: Fidelity| match f {
Fidelity::Analytic => 0,
Fidelity::Compact => 1,
Fidelity::TwoD => 2,
Fidelity::ThreeD => 3,
Fidelity::Measured => 4,
};
let fidelity = fidelities
.min_by_key(|&f| rank(f))
.unwrap_or(Fidelity::Analytic);
Provenance {
fidelity,
source: format!(
"a circuit of {} instances, solved as one sparse system",
self.netlist.instances().len()
),
error: None,
validity: None,
}
}
fn reciprocal(&self) -> bool {
self.netlist
.instances()
.iter()
.all(|i| i.component.reciprocal())
}
fn derivatives(&self, wavelength: Wavelength, values: &[f64]) -> Result<Option<Vec<SMatrix>>> {
Ok(Some(self.jacobian(wavelength, values)?.1))
}
}