mod balanced;
mod multiconductor;
pub use balanced::{BALANCED_STATE_QUANTITIES, BalancedOperatingPoints, BalancedStateBuilder};
pub use multiconductor::{MulticonductorOperatingPoints, MulticonductorStateBuilder};
#[derive(Clone, Debug)]
pub struct OperatingPoint<N> {
pub(crate) network: N,
pub(crate) columns: SharedColumns,
pub(crate) index: usize,
}
impl<N> OperatingPoint<N> {
pub fn network(&self) -> &N {
&self.network
}
fn value(&self, quantity: &'static str, identity: &str) -> Option<f64> {
self.columns
.quantities
.get(quantity)?
.value(self.index, identity)
}
fn stated(&self, quantity: &'static str) -> bool {
self.columns.quantities.contains_key(quantity)
}
pub fn identity_order(&self, quantity: &str) -> Option<impl ExactSizeIterator<Item = &str>> {
Some(self.columns.quantities.get(quantity)?.layout.order())
}
pub fn quantity_values(&self, quantity: &str) -> Option<Vec<f64>> {
let entry = self.columns.quantities.get(quantity)?;
let mut scratch = Vec::new();
Some(
entry
.storage
.row(self.index, entry.layout.len(), &mut scratch)
.to_vec(),
)
}
}
use std::collections::HashMap;
use std::sync::Arc;
use powerio_core::Error;
use crate::diagnostics::codes;
#[derive(Debug, Default)]
pub(crate) struct QuantityLayout {
index: HashMap<Box<str>, u32>,
order: Vec<Box<str>>,
}
impl QuantityLayout {
pub(crate) fn from_order(
quantity: &'static str,
order: impl IntoIterator<Item = String>,
) -> Result<Self, Error> {
let mut layout = Self::default();
for identity in order {
let column = u32::try_from(layout.order.len()).map_err(|_| {
Error::new(
&codes::BUILD_STATE_SHAPE_MISMATCH,
format!("{quantity}: more than u32::MAX elements"),
)
})?;
if layout
.index
.insert(identity.clone().into_boxed_str(), column)
.is_some()
{
return Err(Error::new(
&codes::BUILD_STATE_IDENTITY_UNKNOWN,
format!("{quantity}: duplicate element identity `{identity}`"),
));
}
layout.order.push(identity.into_boxed_str());
}
Ok(layout)
}
pub(crate) fn len(&self) -> usize {
self.order.len()
}
pub(crate) fn column(&self, identity: &str) -> Option<usize> {
self.index.get(identity).map(|column| *column as usize)
}
pub(crate) fn order(&self) -> impl ExactSizeIterator<Item = &str> {
self.order.iter().map(AsRef::as_ref)
}
}
pub(crate) type PointChanges = Box<[(u32, f64)]>;
#[derive(Debug)]
pub(crate) enum QuantityStorage {
Dense {
values: Box<[f64]>,
},
Sparse {
base: Box<[f64]>,
changes: Box<[PointChanges]>,
},
}
impl QuantityStorage {
pub(crate) fn value(&self, point: usize, column: usize, width: usize) -> f64 {
match self {
Self::Dense { values } => values[point * width + column],
Self::Sparse { base, changes } => {
let column32 = column as u32;
match changes[point].binary_search_by_key(&column32, |(c, _)| *c) {
Ok(found) => changes[point][found].1,
Err(_) => base[column],
}
}
}
}
pub(crate) fn row<'a>(
&'a self,
point: usize,
width: usize,
scratch: &'a mut Vec<f64>,
) -> &'a [f64] {
match self {
Self::Dense { values } => &values[point * width..(point + 1) * width],
Self::Sparse { base, changes } => {
scratch.clear();
scratch.extend_from_slice(base);
for (column, value) in &changes[point] {
scratch[*column as usize] = *value;
}
scratch
}
}
}
}
#[derive(Debug)]
pub(crate) struct Quantity {
pub(crate) layout: QuantityLayout,
pub(crate) storage: QuantityStorage,
}
impl Quantity {
pub(crate) fn value(&self, point: usize, identity: &str) -> Option<f64> {
let column = self.layout.column(identity)?;
Some(self.storage.value(point, column, self.layout.len()))
}
}
#[derive(Debug)]
pub(crate) struct StateColumns {
pub(crate) quantities: HashMap<&'static str, Quantity>,
}
pub(crate) type SharedColumns = Arc<StateColumns>;
pub(crate) fn dense_quantity(
quantity: &'static str,
layout: QuantityLayout,
point_count: usize,
values: Vec<f64>,
) -> Result<Quantity, Error> {
let expected = point_count.checked_mul(layout.len()).ok_or_else(|| {
Error::new(
&codes::BUILD_STATE_SHAPE_MISMATCH,
format!(
"{quantity}: {point_count} points by {} elements exceeds addressable memory",
layout.len()
),
)
})?;
if values.len() != expected {
return Err(Error::new(
&codes::BUILD_STATE_SHAPE_MISMATCH,
format!(
"{quantity}: {} values supplied; {point_count} points by {} elements needs {expected}",
values.len(),
layout.len()
),
));
}
Ok(Quantity {
layout,
storage: QuantityStorage::Dense {
values: values.into_boxed_slice(),
},
})
}
pub(crate) fn sparse_quantity(
quantity: &'static str,
layout: QuantityLayout,
point_count: usize,
base: Vec<f64>,
changes: Vec<Vec<(String, f64)>>,
) -> Result<Quantity, Error> {
if base.len() != layout.len() {
return Err(Error::new(
&codes::BUILD_STATE_SHAPE_MISMATCH,
format!(
"{quantity}: base row has {} values; the layout has {} elements",
base.len(),
layout.len()
),
));
}
if changes.len() != point_count {
return Err(Error::new(
&codes::BUILD_STATE_SHAPE_MISMATCH,
format!(
"{quantity}: {} change sets supplied for {point_count} points",
changes.len()
),
));
}
let mut resolved = Vec::with_capacity(point_count);
for point in changes {
let mut row: Vec<(u32, f64)> = Vec::with_capacity(point.len());
for (identity, value) in point {
let Some(column) = layout.column(&identity) else {
return Err(Error::new(
&codes::BUILD_STATE_IDENTITY_UNKNOWN,
format!("{quantity}: unknown element identity `{identity}`"),
));
};
row.push((column as u32, value));
}
row.sort_unstable_by_key(|(column, _)| *column);
row.dedup_by_key(|(column, _)| *column);
resolved.push(row.into_boxed_slice());
}
Ok(Quantity {
layout,
storage: QuantityStorage::Sparse {
base: base.into_boxed_slice(),
changes: resolved.into_boxed_slice(),
},
})
}
pub(crate) fn row_identity(uid: Option<&str>, table: &str, row: usize) -> String {
match uid {
Some(uid) => uid.to_owned(),
None => format!("{table}:{row}"),
}
}