use num_complex::Complex;
use crate::network::YBusSparse;
use super::models::{DynamicModel, ModelJacobian};
#[derive(Clone, Debug)]
pub struct DaeLayout {
pub n_bus: usize,
pub n_diff: usize,
pub dev_offset: Vec<usize>,
pub dev_len: Vec<usize>,
pub dev_bus: Vec<usize>,
}
impl DaeLayout {
pub fn new(dev_len: &[usize], dev_bus: &[usize], n_bus: usize) -> Self {
let mut dev_offset = Vec::with_capacity(dev_len.len());
let mut acc = 0usize;
for &len in dev_len {
dev_offset.push(acc);
acc += len;
}
Self {
n_bus,
n_diff: acc,
dev_offset,
dev_len: dev_len.to_vec(),
dev_bus: dev_bus.to_vec(),
}
}
pub fn n(&self) -> usize {
self.n_diff + 2 * self.n_bus
}
pub fn v_re(&self, i: usize) -> usize {
self.n_diff + 2 * i
}
pub fn v_im(&self, i: usize) -> usize {
self.n_diff + 2 * i + 1
}
pub fn n_devices(&self) -> usize {
self.dev_len.len()
}
}
#[derive(Clone, Debug)]
pub struct DaePattern {
layout: DaeLayout,
rows: Vec<u32>,
cols: Vec<u32>,
dfdx_slot: Vec<usize>,
dfdv_slot: Vec<usize>,
didx_slot: Vec<usize>,
net_row_slot: Vec<usize>,
net_diag_slot: Vec<usize>,
fixed: Vec<bool>,
nnz: usize,
}
impl DaePattern {
pub fn analyze(layout: DaeLayout, ybus: &YBusSparse, fixed: &[usize]) -> Self {
let n_dev = layout.n_devices();
let mut rows: Vec<u32> = Vec::new();
let mut cols: Vec<u32> = Vec::new();
let push = |r: usize, c: usize, rows: &mut Vec<u32>, cols: &mut Vec<u32>| {
rows.push(r as u32);
cols.push(c as u32);
};
let mut dfdx_slot = Vec::with_capacity(n_dev);
for d in 0..n_dev {
dfdx_slot.push(rows.len());
let (off, len) = (layout.dev_offset[d], layout.dev_len[d]);
for i in 0..len {
for j in 0..len {
push(off + i, off + j, &mut rows, &mut cols);
}
}
}
let mut dfdv_slot = Vec::with_capacity(n_dev);
for d in 0..n_dev {
dfdv_slot.push(rows.len());
let (off, len, bus) = (layout.dev_offset[d], layout.dev_len[d], layout.dev_bus[d]);
for i in 0..len {
push(off + i, layout.v_re(bus), &mut rows, &mut cols);
push(off + i, layout.v_im(bus), &mut rows, &mut cols);
}
}
let mut didx_slot = Vec::with_capacity(n_dev);
for d in 0..n_dev {
didx_slot.push(rows.len());
let (off, len, bus) = (layout.dev_offset[d], layout.dev_len[d], layout.dev_bus[d]);
for j in 0..len {
push(layout.v_re(bus), off + j, &mut rows, &mut cols);
}
for j in 0..len {
push(layout.v_im(bus), off + j, &mut rows, &mut cols);
}
}
let mut net_row_slot = Vec::with_capacity(layout.n_bus);
let mut net_diag_slot = vec![usize::MAX; layout.n_bus];
for i in 0..layout.n_bus {
net_row_slot.push(rows.len());
for &(j, _) in ybus.row(i) {
if i == j {
net_diag_slot[i] = rows.len();
}
push(layout.v_re(i), layout.v_re(j), &mut rows, &mut cols);
push(layout.v_re(i), layout.v_im(j), &mut rows, &mut cols);
push(layout.v_im(i), layout.v_re(j), &mut rows, &mut cols);
push(layout.v_im(i), layout.v_im(j), &mut rows, &mut cols);
}
}
let mut is_fixed = vec![false; layout.n_bus];
for &b in fixed {
is_fixed[b] = true;
}
let nnz = rows.len();
Self {
layout,
rows,
cols,
dfdx_slot,
dfdv_slot,
didx_slot,
net_row_slot,
net_diag_slot,
fixed: is_fixed,
nnz,
}
}
pub fn layout(&self) -> &DaeLayout {
&self.layout
}
pub fn nnz(&self) -> usize {
self.nnz
}
pub fn is_fixed(&self, bus: usize) -> bool {
self.fixed[bus]
}
pub fn to_triplets(&self, values: &[f64]) -> Vec<(usize, usize, f64)> {
(0..self.nnz)
.map(|k| (self.rows[k] as usize, self.cols[k] as usize, values[k]))
.collect()
}
pub fn bus_without_diagonal(&self) -> Option<usize> {
(0..self.layout.n_bus).find(|&i| !self.fixed[i] && self.net_diag_slot[i] == usize::MAX)
}
#[allow(clippy::too_many_arguments)]
pub fn fill(
&self,
models: &[Box<dyn DynamicModel>],
x: &[f64],
v: &[Complex<f64>],
ha: f64,
ybus: &YBusSparse,
scratch: &mut [ModelJacobian],
values: &mut Vec<f64>,
) {
values.clear();
values.resize(self.nnz, 0.0);
for (d, model) in models.iter().enumerate() {
let (off, len, bus) = (
self.layout.dev_offset[d],
self.layout.dev_len[d],
self.layout.dev_bus[d],
);
let jac = &mut scratch[d];
jac.clear();
model.jacobian(&x[off..off + len], v[bus], jac);
let base = self.dfdx_slot[d];
for i in 0..len {
for j in 0..len {
let identity = if i == j { 1.0 } else { 0.0 };
values[base + i * len + j] = identity - ha * jac.dfdx[i * len + j];
}
}
let base = self.dfdv_slot[d];
for i in 0..len {
values[base + i * 2] = -ha * jac.dfdv[i * 2];
values[base + i * 2 + 1] = -ha * jac.dfdv[i * 2 + 1];
}
let base = self.didx_slot[d];
if !self.fixed[bus] {
for j in 0..len {
values[base + j] = -jac.didx[j];
values[base + len + j] = -jac.didx[len + j];
}
}
}
for i in 0..self.layout.n_bus {
let mut slot = self.net_row_slot[i];
if self.fixed[i] {
for &(j, _) in ybus.row(i) {
let one = if i == j { 1.0 } else { 0.0 };
values[slot] = one;
values[slot + 1] = 0.0;
values[slot + 2] = 0.0;
values[slot + 3] = one;
slot += 4;
}
continue;
}
for &(_, y) in ybus.row(i) {
values[slot] = y.re;
values[slot + 1] = -y.im;
values[slot + 2] = y.im;
values[slot + 3] = y.re;
slot += 4;
}
}
for (d, _) in models.iter().enumerate() {
let bus = self.layout.dev_bus[d];
if self.fixed[bus] {
continue;
}
let diag = self.net_diag_slot[bus];
if diag == usize::MAX {
continue;
}
let didv = scratch[d].didv;
values[diag] -= didv[0];
values[diag + 1] -= didv[1];
values[diag + 2] -= didv[2];
values[diag + 3] -= didv[3];
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn residual(
pattern: &DaePattern,
models: &[Box<dyn DynamicModel>],
x0: &[f64],
f0: &[f64],
x: &[f64],
v: &[Complex<f64>],
v_fixed: &[Complex<f64>],
ha: f64,
hb: f64,
ybus: &YBusSparse,
f_scratch: &mut [f64],
out: &mut [f64],
) {
let layout = &pattern.layout;
for (d, model) in models.iter().enumerate() {
let (off, len, bus) = (layout.dev_offset[d], layout.dev_len[d], layout.dev_bus[d]);
model.derivatives(&x[off..off + len], v[bus], &mut f_scratch[off..off + len]);
for i in off..off + len {
out[i] = x[i] - x0[i] - ha * f_scratch[i] - hb * f0[i];
}
}
for i in 0..layout.n_bus {
if pattern.fixed[i] {
out[layout.v_re(i)] = v[i].re - v_fixed[i].re;
out[layout.v_im(i)] = v[i].im - v_fixed[i].im;
continue;
}
let mut acc = Complex::new(0.0, 0.0);
for &(j, y) in ybus.row(i) {
acc += y * v[j];
}
out[layout.v_re(i)] = acc.re;
out[layout.v_im(i)] = acc.im;
}
for (d, model) in models.iter().enumerate() {
let (off, len, bus) = (layout.dev_offset[d], layout.dev_len[d], layout.dev_bus[d]);
if pattern.fixed[bus] {
continue;
}
let inj = model.injection(&x[off..off + len], v[bus]);
out[layout.v_re(bus)] -= inj.re;
out[layout.v_im(bus)] -= inj.im;
}
}