#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ResolvedLoadFactors {
factors: Vec<f64>,
n_stages: usize,
max_blocks: usize,
}
impl ResolvedLoadFactors {
#[must_use]
pub fn empty() -> Self {
Self {
factors: Vec::new(),
n_stages: 0,
max_blocks: 0,
}
}
#[must_use]
pub fn new(n_buses: usize, n_stages: usize, max_blocks: usize) -> Self {
Self {
factors: vec![1.0; n_buses * n_stages * max_blocks],
n_stages,
max_blocks,
}
}
pub fn set(&mut self, bus_idx: usize, stage_idx: usize, block_idx: usize, value: f64) {
let idx = (bus_idx * self.n_stages + stage_idx) * self.max_blocks + block_idx;
self.factors[idx] = value;
}
#[inline]
#[must_use]
pub fn factor(&self, bus_idx: usize, stage_idx: usize, block_idx: usize) -> f64 {
if self.factors.is_empty() {
return 1.0;
}
let idx = (bus_idx * self.n_stages + stage_idx) * self.max_blocks + block_idx;
self.factors.get(idx).copied().unwrap_or(1.0)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ResolvedExchangeFactors {
data: Vec<(f64, f64)>,
n_stages: usize,
max_blocks: usize,
}
impl ResolvedExchangeFactors {
#[must_use]
pub fn empty() -> Self {
Self {
data: Vec::new(),
n_stages: 0,
max_blocks: 0,
}
}
#[must_use]
pub fn new(n_lines: usize, n_stages: usize, max_blocks: usize) -> Self {
Self {
data: vec![(1.0, 1.0); n_lines * n_stages * max_blocks],
n_stages,
max_blocks,
}
}
pub fn set(
&mut self,
line_idx: usize,
stage_idx: usize,
block_idx: usize,
direct_factor: f64,
reverse_factor: f64,
) {
let idx = (line_idx * self.n_stages + stage_idx) * self.max_blocks + block_idx;
self.data[idx] = (direct_factor, reverse_factor);
}
#[inline]
#[must_use]
pub fn factors(&self, line_idx: usize, stage_idx: usize, block_idx: usize) -> (f64, f64) {
if self.data.is_empty() {
return (1.0, 1.0);
}
let idx = (line_idx * self.n_stages + stage_idx) * self.max_blocks + block_idx;
self.data.get(idx).copied().unwrap_or((1.0, 1.0))
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ResolvedNcsBounds {
data: Vec<f64>,
n_stages: usize,
}
impl ResolvedNcsBounds {
#[must_use]
pub fn empty() -> Self {
Self {
data: Vec::new(),
n_stages: 0,
}
}
#[must_use]
pub fn new(n_ncs: usize, n_stages: usize, default_mw: &[f64]) -> Self {
assert!(
default_mw.len() == n_ncs,
"default_mw length ({}) must equal n_ncs ({n_ncs})",
default_mw.len()
);
let mut data = vec![0.0; n_ncs * n_stages];
for (ncs_idx, &mw) in default_mw.iter().enumerate() {
for stage_idx in 0..n_stages {
data[ncs_idx * n_stages + stage_idx] = mw;
}
}
Self { data, n_stages }
}
pub fn set(&mut self, ncs_idx: usize, stage_idx: usize, value: f64) {
let idx = ncs_idx * self.n_stages + stage_idx;
self.data[idx] = value;
}
#[inline]
#[must_use]
pub fn available_generation(&self, ncs_idx: usize, stage_idx: usize) -> f64 {
if self.data.is_empty() {
return 0.0;
}
let idx = ncs_idx * self.n_stages + stage_idx;
self.data.get(idx).copied().unwrap_or(0.0)
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ResolvedNcsFactors {
factors: Vec<f64>,
n_stages: usize,
max_blocks: usize,
}
impl ResolvedNcsFactors {
#[must_use]
pub fn empty() -> Self {
Self {
factors: Vec::new(),
n_stages: 0,
max_blocks: 0,
}
}
#[must_use]
pub fn new(n_ncs: usize, n_stages: usize, max_blocks: usize) -> Self {
Self {
factors: vec![1.0; n_ncs * n_stages * max_blocks],
n_stages,
max_blocks,
}
}
pub fn set(&mut self, ncs_idx: usize, stage_idx: usize, block_idx: usize, value: f64) {
let idx = (ncs_idx * self.n_stages + stage_idx) * self.max_blocks + block_idx;
self.factors[idx] = value;
}
#[inline]
#[must_use]
pub fn factor(&self, ncs_idx: usize, stage_idx: usize, block_idx: usize) -> f64 {
if self.factors.is_empty() {
return 1.0;
}
let idx = (ncs_idx * self.n_stages + stage_idx) * self.max_blocks + block_idx;
self.factors.get(idx).copied().unwrap_or(1.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_factors_empty_returns_one() {
let t = ResolvedLoadFactors::empty();
assert!((t.factor(0, 0, 0) - 1.0).abs() < f64::EPSILON);
assert!((t.factor(5, 3, 2) - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_load_factors_new_default_is_one() {
let t = ResolvedLoadFactors::new(2, 1, 3);
for bus in 0..2 {
for blk in 0..3 {
assert!(
(t.factor(bus, 0, blk) - 1.0).abs() < f64::EPSILON,
"expected 1.0 at ({bus}, 0, {blk})"
);
}
}
}
#[test]
fn test_load_factors_set_and_get() {
let mut t = ResolvedLoadFactors::new(2, 1, 3);
t.set(0, 0, 0, 0.85);
t.set(0, 0, 1, 1.15);
assert!((t.factor(0, 0, 0) - 0.85).abs() < 1e-10);
assert!((t.factor(0, 0, 1) - 1.15).abs() < 1e-10);
assert!((t.factor(0, 0, 2) - 1.0).abs() < f64::EPSILON);
assert!((t.factor(1, 0, 0) - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_load_factors_out_of_bounds_returns_one() {
let t = ResolvedLoadFactors::new(1, 1, 2);
assert!((t.factor(5, 0, 0) - 1.0).abs() < f64::EPSILON);
assert!((t.factor(0, 0, 99) - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_exchange_factors_empty_returns_one_one() {
let t = ResolvedExchangeFactors::empty();
assert_eq!(t.factors(0, 0, 0), (1.0, 1.0));
assert_eq!(t.factors(5, 3, 2), (1.0, 1.0));
}
#[test]
fn test_exchange_factors_new_default_is_one_one() {
let t = ResolvedExchangeFactors::new(1, 1, 2);
assert_eq!(t.factors(0, 0, 0), (1.0, 1.0));
assert_eq!(t.factors(0, 0, 1), (1.0, 1.0));
}
#[test]
fn test_exchange_factors_set_and_get() {
let mut t = ResolvedExchangeFactors::new(1, 1, 2);
t.set(0, 0, 0, 0.9, 0.85);
assert_eq!(t.factors(0, 0, 0), (0.9, 0.85));
assert_eq!(t.factors(0, 0, 1), (1.0, 1.0));
}
#[test]
fn test_exchange_factors_out_of_bounds_returns_default() {
let t = ResolvedExchangeFactors::new(1, 1, 1);
assert_eq!(t.factors(5, 0, 0), (1.0, 1.0));
}
#[test]
fn test_ncs_bounds_empty_is_empty() {
let t = ResolvedNcsBounds::empty();
assert!(t.is_empty());
assert!((t.available_generation(0, 0) - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_ncs_bounds_new_uses_defaults() {
let t = ResolvedNcsBounds::new(2, 3, &[100.0, 200.0]);
assert!(!t.is_empty());
assert!((t.available_generation(0, 0) - 100.0).abs() < f64::EPSILON);
assert!((t.available_generation(0, 2) - 100.0).abs() < f64::EPSILON);
assert!((t.available_generation(1, 0) - 200.0).abs() < f64::EPSILON);
assert!((t.available_generation(1, 2) - 200.0).abs() < f64::EPSILON);
}
#[test]
fn test_ncs_bounds_set_and_get() {
let mut t = ResolvedNcsBounds::new(2, 3, &[100.0, 200.0]);
t.set(0, 1, 50.0);
assert!((t.available_generation(0, 1) - 50.0).abs() < f64::EPSILON);
assert!((t.available_generation(0, 0) - 100.0).abs() < f64::EPSILON);
assert!((t.available_generation(1, 0) - 200.0).abs() < f64::EPSILON);
}
#[test]
fn test_ncs_bounds_out_of_bounds_returns_zero() {
let t = ResolvedNcsBounds::new(1, 1, &[100.0]);
assert!((t.available_generation(5, 0) - 0.0).abs() < f64::EPSILON);
assert!((t.available_generation(0, 99) - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_ncs_factors_empty_returns_one() {
let t = ResolvedNcsFactors::empty();
assert!((t.factor(0, 0, 0) - 1.0).abs() < f64::EPSILON);
assert!((t.factor(5, 3, 2) - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_ncs_factors_new_default_is_one() {
let t = ResolvedNcsFactors::new(2, 1, 3);
for ncs in 0..2 {
for blk in 0..3 {
assert!(
(t.factor(ncs, 0, blk) - 1.0).abs() < f64::EPSILON,
"factor({ncs}, 0, {blk}) should be 1.0"
);
}
}
}
#[test]
fn test_ncs_factors_set_and_get() {
let mut t = ResolvedNcsFactors::new(2, 1, 3);
t.set(0, 0, 1, 0.8);
assert!((t.factor(0, 0, 1) - 0.8).abs() < 1e-10);
assert!((t.factor(0, 0, 0) - 1.0).abs() < f64::EPSILON);
assert!((t.factor(1, 0, 0) - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_ncs_factors_out_of_bounds_returns_one() {
let t = ResolvedNcsFactors::new(1, 1, 2);
assert!((t.factor(5, 0, 0) - 1.0).abs() < f64::EPSILON);
assert!((t.factor(0, 0, 99) - 1.0).abs() < f64::EPSILON);
}
}