use crate::dae::DAE;
use crate::kernel::{Domain, ExprId, ExprPool};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Port {
pub name: String,
pub potential: ExprId,
pub flow: ExprId,
}
impl Port {
pub fn new(name: &str, pool: &ExprPool) -> Self {
let potential = pool.symbol(format!("{name}.v"), Domain::Real);
let flow = pool.symbol(format!("{name}.i"), Domain::Real);
Port {
name: name.to_string(),
potential,
flow,
}
}
}
#[derive(Clone, Debug)]
pub struct Component {
pub name: String,
pub ports: Vec<Port>,
pub equations: Vec<ExprId>,
pub internal_vars: Vec<ExprId>,
pub internal_derivs: Vec<ExprId>,
}
impl Component {
pub fn new(name: &str) -> Self {
Component {
name: name.to_string(),
ports: vec![],
equations: vec![],
internal_vars: vec![],
internal_derivs: vec![],
}
}
pub fn add_port(mut self, port: Port) -> Self {
self.ports.push(port);
self
}
pub fn add_equation(mut self, eq: ExprId) -> Self {
self.equations.push(eq);
self
}
pub fn add_internal_var(mut self, var: ExprId, deriv: ExprId) -> Self {
self.internal_vars.push(var);
self.internal_derivs.push(deriv);
self
}
pub fn port(&self, name: &str) -> Option<&Port> {
self.ports.iter().find(|p| p.name == name)
}
}
#[derive(Clone, Debug)]
struct Connection {
port_a: Port,
port_b: Port,
}
#[derive(Clone, Debug, Default)]
pub struct System {
components: Vec<Component>,
connections: Vec<Connection>,
}
impl System {
pub fn new() -> Self {
System::default()
}
pub fn add_component(&mut self, component: Component) {
self.components.push(component);
}
pub fn connect(&mut self, port_a: &Port, port_b: &Port) {
self.connections.push(Connection {
port_a: port_a.clone(),
port_b: port_b.clone(),
});
}
pub fn flatten(&self, time_var: ExprId, pool: &ExprPool) -> DAE {
let mut equations: Vec<ExprId> = Vec::new();
let mut variables: Vec<ExprId> = Vec::new();
let mut derivatives: Vec<ExprId> = Vec::new();
for comp in &self.components {
equations.extend_from_slice(&comp.equations);
for port in &comp.ports {
add_var_if_new(port.potential, pool, &mut variables, &mut derivatives);
add_var_if_new(port.flow, pool, &mut variables, &mut derivatives);
}
for (&var, &deriv) in comp.internal_vars.iter().zip(comp.internal_derivs.iter()) {
if !variables.contains(&var) {
variables.push(var);
derivatives.push(deriv);
}
}
}
let neg_one = pool.integer(-1_i32);
for conn in &self.connections {
let neg_b = pool.mul(vec![neg_one, conn.port_b.potential]);
let pot_eq = pool.add(vec![conn.port_a.potential, neg_b]);
equations.push(pot_eq);
let flow_eq = pool.add(vec![conn.port_a.flow, conn.port_b.flow]);
equations.push(flow_eq);
}
DAE::new(equations, variables, derivatives, time_var)
}
}
fn add_var_if_new(
var: ExprId,
pool: &ExprPool,
variables: &mut Vec<ExprId>,
derivatives: &mut Vec<ExprId>,
) {
if variables.contains(&var) {
return;
}
let deriv_name = pool.with(var, |d| match d {
crate::kernel::ExprData::Symbol { name, .. } => format!("d{name}/dt"),
_ => "d?/dt".to_string(),
});
let deriv = pool.symbol(&deriv_name, Domain::Real);
variables.push(var);
derivatives.push(deriv);
}
pub fn resistor(name: &str, resistance: ExprId, pool: &ExprPool) -> Component {
let port_p = Port::new(&format!("{name}.p"), pool);
let port_n = Port::new(&format!("{name}.n"), pool);
let v = pool.add(vec![
port_p.potential,
pool.mul(vec![pool.integer(-1_i32), port_n.potential]),
]);
let i = port_p.flow;
let ri = pool.mul(vec![resistance, i]);
let eq = pool.add(vec![v, pool.mul(vec![pool.integer(-1_i32), ri])]);
Component::new(name)
.add_port(port_p)
.add_port(port_n)
.add_equation(eq)
}
pub fn capacitor(name: &str, capacitance: ExprId, pool: &ExprPool) -> Component {
let port_p = Port::new(&format!("{name}.p"), pool);
let port_n = Port::new(&format!("{name}.n"), pool);
let v_name = format!("{name}.vc");
let v = pool.symbol(&v_name, Domain::Real);
let dv_name = format!("d{v_name}/dt");
let dv = pool.symbol(&dv_name, Domain::Real);
let i = port_p.flow;
let c_dv = pool.mul(vec![capacitance, dv]);
let eq = pool.add(vec![c_dv, pool.mul(vec![pool.integer(-1_i32), i])]);
let neg_vn = pool.mul(vec![pool.integer(-1_i32), port_n.potential]);
let v_def = pool.add(vec![
v,
pool.mul(vec![
pool.integer(-1_i32),
pool.add(vec![port_p.potential, neg_vn]),
]),
]);
Component::new(name)
.add_port(port_p)
.add_port(port_n)
.add_equation(eq)
.add_equation(v_def)
.add_internal_var(v, dv)
}
pub fn voltage_source(name: &str, voltage: ExprId, pool: &ExprPool) -> Component {
let port_p = Port::new(&format!("{name}.p"), pool);
let port_n = Port::new(&format!("{name}.n"), pool);
let neg_v = pool.mul(vec![pool.integer(-1_i32), port_n.potential]);
let diff_v = pool.add(vec![port_p.potential, neg_v]);
let neg_voltage = pool.mul(vec![pool.integer(-1_i32), voltage]);
let eq = pool.add(vec![diff_v, neg_voltage]);
Component::new(name)
.add_port(port_p)
.add_port(port_n)
.add_equation(eq)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::ExprPool;
fn p() -> ExprPool {
ExprPool::new()
}
#[test]
fn port_creation() {
let pool = p();
let port = Port::new("pin1", &pool);
assert_eq!(port.name, "pin1");
assert_ne!(port.potential, port.flow);
}
#[test]
fn resistor_has_one_equation() {
let pool = p();
let r_val = pool.symbol("R", crate::kernel::Domain::Real);
let comp = resistor("R1", r_val, &pool);
assert_eq!(comp.equations.len(), 1);
assert_eq!(comp.ports.len(), 2);
}
#[test]
fn capacitor_has_two_equations() {
let pool = p();
let c_val = pool.symbol("C", crate::kernel::Domain::Real);
let comp = capacitor("C1", c_val, &pool);
assert_eq!(comp.equations.len(), 2);
assert_eq!(comp.internal_vars.len(), 1);
}
#[test]
fn system_flatten_rc_circuit() {
let pool = p();
let r_val = pool.symbol("R", crate::kernel::Domain::Real);
let c_val = pool.symbol("C", crate::kernel::Domain::Real);
let v_src = pool.symbol("Vs", crate::kernel::Domain::Real);
let t = pool.symbol("t", crate::kernel::Domain::Real);
let res = resistor("R1", r_val, &pool);
let cap = capacitor("C1", c_val, &pool);
let src = voltage_source("V1", v_src, &pool);
let src_p = src.port("V1.p").unwrap().clone();
let src_n = src.port("V1.n").unwrap().clone();
let res_p = res.port("R1.p").unwrap().clone();
let res_n = res.port("R1.n").unwrap().clone();
let cap_p = cap.port("C1.p").unwrap().clone();
let cap_n = cap.port("C1.n").unwrap().clone();
let mut sys = System::new();
sys.add_component(src);
sys.add_component(res);
sys.add_component(cap);
sys.connect(&src_p, &res_p);
sys.connect(&res_n, &cap_p);
sys.connect(&cap_n, &src_n);
let dae = sys.flatten(t, &pool);
assert!(dae.n_equations() >= 3); assert!(dae.n_variables() >= 4); }
#[test]
fn connect_generates_two_equations() {
let pool = p();
let t = pool.symbol("t", crate::kernel::Domain::Real);
let r_val = pool.symbol("R", crate::kernel::Domain::Real);
let comp1 = resistor("R1", r_val, &pool);
let comp2 = resistor("R2", r_val, &pool);
let port_a = comp1.port("R1.n").unwrap().clone();
let port_b = comp2.port("R2.p").unwrap().clone();
let mut sys = System::new();
sys.add_component(comp1);
sys.add_component(comp2);
sys.connect(&port_a, &port_b);
let dae = sys.flatten(t, &pool);
assert_eq!(dae.n_equations(), 4);
}
}