use super::Engine;
use super::fields::Fields;
use super::lumped::{LumpedBox, LumpedElement, Source};
use super::operator::{Hooks, Operator, OperatorHandle, StepContext};
use crate::axis::Axis;
use crate::post::{Complex64, PortResult, dft};
use crate::waveform::Waveform;
use crate::{Error, Result};
#[derive(Debug)]
pub struct LumpedPort {
region: LumpedBox,
element: LumpedElement,
resistance: f64,
edges: Vec<[usize; 3]>,
dt: f64,
first_step: Option<u64>,
v: Vec<f64>,
i: Vec<f64>,
}
impl LumpedPort {
pub fn new(region: LumpedBox, r: f64) -> Self {
Self {
region,
element: LumpedElement::resistor(region, r).record_current(),
resistance: r,
edges: Vec::new(),
dt: 0.0,
first_step: None,
v: Vec::new(),
i: Vec::new(),
}
}
pub fn excite(mut self, v: impl Fn(f64) -> f64 + Send + 'static) -> Self {
self.element.set_source(Some(Source::Fn(Box::new(v))));
self
}
pub fn excite_waveform(mut self, waveform: Waveform, amplitude: f64) -> Self {
self.element
.set_source(Some(Source::Waveform(waveform, amplitude)));
self
}
pub fn region(&self) -> &LumpedBox {
&self.region
}
pub fn resistance(&self) -> f64 {
self.resistance
}
pub fn add_to(mut self, engine: &mut Engine) -> Result<OperatorHandle<LumpedPort>> {
let region = self.region;
region.check_fits(engine.grid().nodes())?;
self.element.install(engine)?;
self.dt = engine.dt();
self.edges = region.edges();
Ok(engine.add_operator(self))
}
pub fn dt(&self) -> f64 {
self.dt
}
pub fn voltage(&self) -> &[f64] {
&self.v
}
pub fn current(&self) -> &[f64] {
&self.i
}
pub fn voltage_t0(&self) -> f64 {
(self.first_step.unwrap_or(0) as f64 + 1.0) * self.dt
}
pub fn current_t0(&self) -> f64 {
(self.first_step.unwrap_or(0) as f64 + 0.5) * self.dt
}
pub fn voltage_times(&self) -> Vec<f64> {
let t0 = self.voltage_t0();
(0..self.v.len()).map(|n| t0 + n as f64 * self.dt).collect()
}
pub fn current_times(&self) -> Vec<f64> {
let t0 = self.current_t0();
(0..self.i.len()).map(|n| t0 + n as f64 * self.dt).collect()
}
pub fn analyze(&self, freqs: &[f64]) -> Result<PortResult> {
self.analyze_with(freqs, Complex64::new(self.resistance, 0.0))
}
pub fn analyze_with(&self, freqs: &[f64], z_ref: Complex64) -> Result<PortResult> {
if self.v.is_empty() {
return Err(Error::Invalid("port has no samples yet".into()));
}
let v = dft(self.voltage_t0(), self.dt, &self.v, freqs);
let i = dft(self.current_t0(), self.dt, &self.i, freqs);
PortResult::new(freqs.to_vec(), v, i, z_ref)
}
}
pub(crate) fn loop_entries(
axis: Axis,
lo: [usize; 3],
hi: [usize; 3],
layer: usize,
) -> Vec<(Axis, [usize; 3], f64)> {
let (ab, ac) = axis.others();
let (a, b, c) = (axis.index(), ab.index(), ac.index());
let node = |pb: usize, pc: usize| {
let mut p = [0; 3];
p[a] = layer;
p[b] = pb;
p[c] = pc;
p
};
let mut v = Vec::new();
for pc in lo[c]..=hi[c] {
v.push((ac, node(hi[b], pc), -1.0));
if lo[b] > 0 {
v.push((ac, node(lo[b] - 1, pc), 1.0));
}
}
for pb in lo[b]..=hi[b] {
v.push((ab, node(pb, hi[c]), 1.0));
if lo[c] > 0 {
v.push((ab, node(pb, lo[c] - 1), -1.0));
}
}
v
}
impl Operator for LumpedPort {
fn hooks(&self) -> Hooks {
Hooks::PRE_E | Hooks::POST_E
}
fn energy(&self) -> f64 {
self.element.branch_energy()
}
fn pre_e(&mut self, fields: &mut Fields<'_>, _ctx: &StepContext<'_>) {
self.element.pre_e_impl(fields);
}
fn post_e(&mut self, fields: &mut Fields<'_>, ctx: &StepContext<'_>) {
if self.first_step.is_none() {
self.first_step = Some(ctx.step);
}
self.element.post_e_impl(fields, ctx);
let i = self.element.current().last().copied().unwrap_or(0.0);
self.i.push(-i);
let axis = self.region.axis();
let n = self.region.columns() as f64;
let s: f64 = self
.edges
.iter()
.map(|&p| fields.u.get(axis, p) as f64)
.sum();
self.v.push(s / n);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mid_loop(region: &LumpedBox) -> Vec<(Axis, [usize; 3], f64)> {
let a = region.axis().index();
let layer = region.lo()[a] + (region.series_edges() - 1) / 2;
loop_entries(region.axis(), region.lo(), region.hi(), layer)
}
#[test]
fn loop_single_column() {
let r = LumpedBox::new(Axis::Z, [3, 4, 1], [3, 4, 4]).unwrap();
let l = mid_loop(&r);
let want = vec![
(Axis::Y, [3, 4, 2], -1.0),
(Axis::Y, [2, 4, 2], 1.0),
(Axis::X, [3, 4, 2], 1.0),
(Axis::X, [3, 3, 2], -1.0),
];
assert_eq!(l, want);
let r = LumpedBox::new(Axis::X, [0, 0, 0], [2, 1, 0]).unwrap();
let l = mid_loop(&r);
assert_eq!(
l,
vec![
(Axis::Z, [0, 1, 0], -1.0),
(Axis::Y, [0, 0, 0], 1.0),
(Axis::Y, [0, 1, 0], 1.0),
]
);
}
}