use csx::primitives::BoxShape;
use csx::properties::{Excitation, LumpedElement, ProbeBox, ProbeType};
use csx::{ContinuousStructure, Primitive, Property, PropertyId, Scalar};
use crate::axis::Axis;
use crate::post::{Complex64, PortResult};
use crate::sim::Results;
use crate::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PortProperties {
pub element: (String, PropertyId),
pub excitation: Option<(String, PropertyId)>,
pub voltage: (String, PropertyId),
pub current: (String, PropertyId),
}
#[derive(Debug, Clone, PartialEq)]
pub struct LumpedPort {
number: u32,
resistance: f64,
start: [f64; 3],
stop: [f64; 3],
axis: Axis,
amplitude: Option<f64>,
delay: f64,
priority: i32,
props: Option<PortProperties>,
}
impl LumpedPort {
pub fn new(number: u32, r: f64, start: [f64; 3], stop: [f64; 3], axis: Axis) -> Self {
Self {
number,
resistance: r,
start,
stop,
axis,
amplitude: None,
delay: 0.0,
priority: 0,
props: None,
}
}
pub fn excite(mut self, amplitude: f64) -> Self {
self.amplitude = Some(amplitude);
self
}
pub fn delay(mut self, delay: f64) -> Self {
self.delay = delay;
self
}
pub fn priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub fn number(&self) -> u32 {
self.number
}
pub fn resistance(&self) -> f64 {
self.resistance
}
pub fn is_excited(&self) -> bool {
self.amplitude.is_some()
}
pub fn properties(&self) -> Option<&PortProperties> {
self.props.as_ref()
}
pub fn add_to(mut self, model: &mut ContinuousStructure) -> Result<Self> {
if self.props.is_some() {
return Err(Error::Invalid(format!(
"port {} was already added",
self.number
)));
}
if !(self.resistance.is_finite() && self.resistance > 0.0) {
return Err(Error::Invalid(format!(
"port resistance {} must be finite and > 0",
self.resistance
)));
}
if self.start.iter().chain(&self.stop).any(|v| !v.is_finite())
|| !self.delay.is_finite()
|| self.amplitude.is_some_and(|a| !a.is_finite())
{
return Err(Error::Invalid("port values must be finite".into()));
}
let a = self.axis.index();
let span = self.stop[a] - self.start[a];
let length = span.abs() * model.grid.delta_unit;
if !(length > 0.0 && length.is_finite()) {
return Err(Error::Invalid(format!(
"port {} has zero length along {}",
self.number, self.axis
)));
}
let n = self.number;
let names = [
format!("port_resist_{n}"),
format!("port_excite_{n}"),
format!("port_ut{n}"),
format!("port_it{n}"),
];
if let Some(dup) = names.iter().find(|s| model.property_by_name(s).is_some()) {
return Err(Error::Invalid(format!(
"the model already has a property named {dup:?}"
)));
}
let [resist, excite, ut, it] = names;
let csx_axis = csx::Axis::from_index(a).expect("axis index in 0..3");
let prim = |s: [f64; 3], t: [f64; 3]| {
Primitive::new(BoxShape::new(s, t)).with_priority(self.priority)
};
let (start, stop) = (self.start, self.stop);
let el = LumpedElement::resistor(csx_axis, self.resistance);
let el_id = model.add_property(Property::lumped_element(resist.clone(), el));
model.add_primitive(el_id, prim(start, stop))?;
let excitation = match self.amplitude {
Some(amp) => {
let mut v = [0.0; 3];
v[a] = span.signum() * amp / length;
let mut ex = Excitation::new(v).with_number(n);
ex.delay = Scalar::new(self.delay);
let id = model.add_property(Property::excitation(excite.clone(), ex));
model.add_primitive(id, prim(start, stop))?;
Some((excite, id))
}
None => None,
};
let mut mid = [0.0; 3];
for d in 0..3 {
mid[d] = 0.5 * (start[d] + stop[d]);
if d != a {
let (lo, hi) = (start[d].min(stop[d]), start[d].max(stop[d]));
let near = model
.grid
.lines(d)
.iter()
.copied()
.filter(|x| (lo..=hi).contains(x))
.min_by(|x, y| (x - mid[d]).abs().total_cmp(&(y - mid[d]).abs()));
if let Some(x) = near {
mid[d] = x;
}
}
}
let (mut vs, mut vt) = (mid, mid);
vs[a] = start[a];
vt[a] = stop[a];
let mut vp = ProbeBox::new(ProbeType::Voltage);
vp.number = n;
let v_id = model.add_property(Property::probe(ut.clone(), vp));
model.add_primitive(v_id, prim(vs, vt))?;
let mut ip = ProbeBox::new(ProbeType::Current)
.with_weight(-1.0)
.with_norm_dir(csx_axis);
ip.number = n;
let i_id = model.add_property(Property::probe(it.clone(), ip));
model.add_primitive(i_id, prim(start, stop))?;
self.props = Some(PortProperties {
element: (resist, el_id),
excitation,
voltage: (ut, v_id),
current: (it, i_id),
});
Ok(self)
}
pub fn analyze(&self, results: &Results, freqs: &[f64]) -> Result<PortResult> {
self.analyze_with(results, freqs, Complex64::new(self.resistance, 0.0))
}
pub fn analyze_with(
&self,
results: &Results,
freqs: &[f64],
z_ref: Complex64,
) -> Result<PortResult> {
let p = self
.props
.as_ref()
.ok_or_else(|| Error::Invalid(format!("port {} was not added", self.number)))?;
let get = |name: &str| {
results
.probe(name)
.ok_or_else(|| Error::Invalid(format!("no probe {name:?} in the results")))
};
let v = get(&p.voltage.0)?.dft(freqs)?;
let el = results.lumped_current(&p.element.0).ok_or_else(|| {
Error::Invalid(format!(
"no lumped element {:?} in the results",
p.element.0
))
})?;
let a = self.axis.index();
let sign = if self.stop[a] >= self.start[a] {
-1.0
} else {
1.0
};
let i = el.dft(freqs)?.into_iter().map(|x| x * sign).collect();
PortResult::new(freqs.to_vec(), v, i, z_ref)
}
}