use crate::indexer::StageIndexer;
#[derive(Debug, Clone)]
pub struct PatchBuffer {
pub indices: Vec<usize>,
pub lower: Vec<f64>,
pub upper: Vec<f64>,
pub col_indices: Vec<usize>,
pub col_lower: Vec<f64>,
pub col_upper: Vec<f64>,
hydro_count: usize,
max_par_order: usize,
load_bus_count: usize,
max_blocks: usize,
n_anticipated: usize,
k_max: usize,
active_load_patches: usize,
active_z_inflow_patches: usize,
}
impl PatchBuffer {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
hydro_count: usize,
max_par_order: usize,
n_load_buses: usize,
max_blocks: usize,
n_anticipated: usize,
k_max: usize,
) -> Self {
let capacity = hydro_count + n_load_buses * max_blocks + hydro_count;
let n_ant_state = n_anticipated * k_max;
let col_capacity = hydro_count * (1 + max_par_order) + n_ant_state;
Self {
indices: vec![0; capacity],
lower: vec![0.0; capacity],
upper: vec![0.0; capacity],
col_indices: vec![0; col_capacity],
col_lower: vec![0.0; col_capacity],
col_upper: vec![0.0; col_capacity],
hydro_count,
max_par_order,
load_bus_count: n_load_buses,
max_blocks,
n_anticipated,
k_max,
active_load_patches: 0,
active_z_inflow_patches: 0,
}
}
pub fn fill_forward_patches(
&mut self,
indexer: &StageIndexer,
state: &[f64],
noise: &[f64],
base_row: usize,
_row_scale: &[f64],
) {
debug_assert_eq!(
state.len(),
indexer.n_state,
"state slice length {got} != n_state {expected}",
got = state.len(),
expected = indexer.n_state,
);
debug_assert!(
noise.len() == indexer.hydro_count || noise.is_empty(),
"noise slice length {got} must equal hydro_count {expected} or be empty",
got = noise.len(),
expected = indexer.hydro_count,
);
for (h, &nv) in noise.iter().enumerate() {
self.indices[h] = ar_dynamics_row_offset(base_row, h);
self.lower[h] = nv;
self.upper[h] = nv;
}
}
pub fn fill_col_state_patches(
&mut self,
indexer: &StageIndexer,
state: &[f64],
col_scale: &[f64],
) {
debug_assert_eq!(
state.len(),
indexer.n_state,
"state slice length {got} != n_state {expected}",
got = state.len(),
expected = indexer.n_state,
);
let n = self.hydro_count;
let l = self.max_par_order;
let storage_in_start = indexer.storage_in.start;
for (h, &sv) in state[..n].iter().enumerate() {
let col = storage_in_start + h;
let scaled = if col_scale.is_empty() {
sv
} else {
sv / col_scale[col]
};
self.col_indices[h] = col;
self.col_lower[h] = scaled;
self.col_upper[h] = scaled;
}
let inflow_lags_start = indexer.inflow_lags.start;
for lag in 0..l {
for h in 0..n {
let slot = n + lag * n + h;
let col = inflow_lags_start + lag * n + h;
let sv = state[slot];
let scaled = if col_scale.is_empty() {
sv
} else {
sv / col_scale[col]
};
self.col_indices[slot] = col;
self.col_lower[slot] = scaled;
self.col_upper[slot] = scaled;
}
}
let cat6_start = n * (1 + l);
self.fill_anticipated_state_col_patches(indexer, state, col_scale, cat6_start);
}
fn fill_anticipated_state_col_patches(
&mut self,
indexer: &StageIndexer,
state: &[f64],
col_scale: &[f64],
cat6_start: usize,
) {
let n_ant = self.n_anticipated;
let k = self.k_max;
let ant_state_col_start = indexer.anticipated_state.start;
for slot in 0..k {
for plant in 0..n_ant {
let off = slot * n_ant + plant;
let buf_slot = cat6_start + off;
let col = ant_state_col_start + off;
let sv = state[col];
let scaled = if col_scale.is_empty() {
sv
} else {
sv / col_scale[col]
};
self.col_indices[buf_slot] = col;
self.col_lower[buf_slot] = scaled;
self.col_upper[buf_slot] = scaled;
}
}
}
pub fn fill_load_patches(
&mut self,
load_row_start: usize,
n_blocks: usize,
load_rhs: &[f64],
bus_positions: &[usize],
row_scale: &[f64],
) {
debug_assert_eq!(
load_rhs.len(),
self.load_bus_count * n_blocks,
"load_rhs length {got} != load_bus_count*n_blocks {expected}",
got = load_rhs.len(),
expected = self.load_bus_count * n_blocks,
);
debug_assert_eq!(
bus_positions.len(),
self.load_bus_count,
"bus_positions length {got} != load_bus_count {expected}",
got = bus_positions.len(),
expected = self.load_bus_count,
);
debug_assert!(
n_blocks <= self.max_blocks,
"n_blocks {n_blocks} exceeds max_blocks {mb}",
mb = self.max_blocks,
);
let cat4_start = self.hydro_count;
let mut slot = cat4_start;
for (i, &bus_pos) in bus_positions.iter().enumerate() {
for blk in 0..n_blocks {
let row = load_row_start + bus_pos * n_blocks + blk;
let rhs = load_rhs[i * n_blocks + blk];
let scaled = if row_scale.is_empty() {
rhs
} else {
rhs * row_scale[row]
};
self.indices[slot] = row;
self.lower[slot] = scaled;
self.upper[slot] = scaled;
slot += 1;
}
}
self.active_load_patches = self.load_bus_count * n_blocks;
}
pub fn fill_z_inflow_patches(
&mut self,
z_inflow_row_start: usize,
z_inflow_rhs: &[f64],
row_scale: &[f64],
) {
let n = self.hydro_count;
if n == 0 || z_inflow_rhs.is_empty() {
self.active_z_inflow_patches = 0;
return;
}
let cat5_start = self.hydro_count + self.active_load_patches;
for (h, &rhs) in z_inflow_rhs.iter().enumerate().take(n) {
let slot = cat5_start + h;
let row = z_inflow_row_start + h;
let scaled = if row_scale.is_empty() {
rhs
} else {
rhs * row_scale[row]
};
self.indices[slot] = row;
self.lower[slot] = scaled;
self.upper[slot] = scaled;
}
self.active_z_inflow_patches = n;
}
#[must_use]
#[inline]
pub fn forward_patch_count(&self) -> usize {
self.hydro_count + self.active_load_patches + self.active_z_inflow_patches
}
#[must_use]
#[inline]
pub fn state_col_patch_count(&self) -> usize {
self.hydro_count * (1 + self.max_par_order) + self.n_anticipated * self.k_max
}
}
#[must_use]
#[inline]
pub fn ar_dynamics_row_offset(base_row: usize, hydro_index: usize) -> usize {
base_row + hydro_index
}
#[cfg(test)]
#[allow(
clippy::doc_markdown,
clippy::too_many_lines,
clippy::cast_sign_loss,
clippy::cast_possible_truncation
)]
mod tests {
use super::{PatchBuffer, ar_dynamics_row_offset};
use crate::indexer::StageIndexer;
fn idx(n: usize, l: usize) -> StageIndexer {
StageIndexer::new(n, l)
}
#[test]
fn new_3_2_sizes_to_6() {
let buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
assert_eq!(buf.indices.len(), 6);
assert_eq!(buf.lower.len(), 6);
assert_eq!(buf.upper.len(), 6);
}
#[test]
fn row_buffer_capacity_after_phase1() {
let buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
assert_eq!(buf.indices.len(), 6);
assert_eq!(buf.lower.len(), 6);
assert_eq!(buf.upper.len(), 6);
}
#[test]
fn col_buffer_capacity_3_2_sizes_to_9() {
let buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
assert_eq!(buf.col_indices.len(), 9);
assert_eq!(buf.col_lower.len(), 9);
assert_eq!(buf.col_upper.len(), 9);
}
#[test]
fn col_buffer_capacity_with_anticipated() {
let buf = PatchBuffer::new(0, 0, 0, 0, 1, 2);
assert_eq!(buf.col_indices.len(), 2);
assert_eq!(buf.col_lower.len(), 2);
assert_eq!(buf.col_upper.len(), 2);
}
#[test]
fn col_buffer_capacity_combined_state_and_anticipated() {
let buf = PatchBuffer::new(3, 2, 0, 0, 2, 3);
assert_eq!(buf.col_indices.len(), 15);
assert_eq!(buf.col_lower.len(), 15);
assert_eq!(buf.col_upper.len(), 15);
}
#[test]
fn col_buffer_capacity_zero_hydros() {
let buf = PatchBuffer::new(0, 0, 0, 0, 0, 0);
assert_eq!(buf.col_indices.len(), 0);
assert_eq!(buf.col_lower.len(), 0);
assert_eq!(buf.col_upper.len(), 0);
}
#[test]
fn col_buffer_capacity_production_scale() {
let buf = PatchBuffer::new(160, 12, 0, 0, 0, 0);
assert_eq!(buf.col_indices.len(), 2080);
assert_eq!(buf.col_lower.len(), 2080);
assert_eq!(buf.col_upper.len(), 2080);
}
#[test]
fn state_col_patch_count_returns_n_times_one_plus_l() {
let buf = PatchBuffer::new(3, 2, 0, 0, 1, 2);
assert_eq!(buf.state_col_patch_count(), 11);
}
#[test]
fn col_buffer_zero_initialised() {
let buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
assert_eq!(buf.col_indices.len(), 9);
assert!(
buf.col_indices.iter().all(|&v| v == 0),
"col_indices not zero-initialised"
);
assert!(
buf.col_lower.iter().all(|&v| v == 0.0),
"col_lower not zero-initialised"
);
assert!(
buf.col_upper.iter().all(|&v| v == 0.0),
"col_upper not zero-initialised"
);
}
#[test]
fn new_160_12_sizes_to_320() {
let buf = PatchBuffer::new(160, 12, 0, 0, 0, 0);
assert_eq!(buf.indices.len(), 320);
assert_eq!(buf.lower.len(), 320);
assert_eq!(buf.upper.len(), 320);
}
#[test]
fn new_zero_lags_sizes_to_2n() {
let buf = PatchBuffer::new(5, 0, 0, 0, 0, 0);
assert_eq!(buf.indices.len(), 10); }
#[test]
fn new_zero_hydros_sizes_to_zero() {
let buf = PatchBuffer::new(0, 0, 0, 0, 0, 0);
assert_eq!(buf.indices.len(), 0);
}
#[test]
fn forward_patch_count_without_z_inflow_fill() {
let buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
assert_eq!(buf.forward_patch_count(), 3);
}
#[test]
fn ar_dynamics_row_offset_adds_base_plus_hydro() {
assert_eq!(ar_dynamics_row_offset(100, 0), 100);
assert_eq!(ar_dynamics_row_offset(100, 1), 101);
assert_eq!(ar_dynamics_row_offset(100, 2), 102);
}
#[test]
fn ar_dynamics_row_offset_zero_base() {
assert_eq!(ar_dynamics_row_offset(0, 7), 7);
}
#[test]
fn fill_forward_patches_writes_only_noise() {
let mut buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
let state = [10.0, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let noise = [0.1, 0.2, 0.3];
buf.fill_forward_patches(&idx(3, 2), &state, &noise, 50, &[]);
assert_eq!(buf.indices[0], 50);
assert_eq!(buf.indices[1], 51);
assert_eq!(buf.indices[2], 52);
assert_eq!(buf.lower[0], 0.1);
assert_eq!(buf.upper[0], 0.1);
assert_eq!(buf.lower[1], 0.2);
assert_eq!(buf.upper[1], 0.2);
assert_eq!(buf.lower[2], 0.3);
assert_eq!(buf.upper[2], 0.3);
}
#[test]
fn fill_forward_patches_all_equality_constraints() {
let mut buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
let state = [10.0, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let noise = [0.1, 0.2, 0.3];
buf.fill_forward_patches(&idx(3, 2), &state, &noise, 50, &[]);
for i in 0..buf.forward_patch_count() {
assert_eq!(
buf.lower[i],
buf.upper[i],
"patch {i}: lower {lo} != upper {up}",
lo = buf.lower[i],
up = buf.upper[i],
);
}
}
#[test]
fn forward_patches_zero_lags_only_noise() {
let n = 2;
let mut buf = PatchBuffer::new(n, 0, 0, 0, 0, 0);
let state = [5.0, 7.0];
let noise = [0.5, 0.6];
buf.fill_forward_patches(&idx(n, 0), &state, &noise, 10, &[]);
assert_eq!(buf.forward_patch_count(), 2);
assert_eq!(buf.indices[0], 10); assert_eq!(buf.lower[0], 0.5);
assert_eq!(buf.indices[1], 11); assert_eq!(buf.lower[1], 0.6);
}
#[test]
fn production_scale_forward_patch_count() {
let buf = PatchBuffer::new(160, 12, 0, 0, 0, 0);
assert_eq!(buf.forward_patch_count(), 160);
assert_eq!(buf.indices.len(), 320);
}
#[test]
#[allow(clippy::cast_precision_loss)] fn production_scale_fill_forward_patches_smoke() {
let n = 160;
let l = 12;
let mut buf = PatchBuffer::new(n, l, 0, 0, 0, 0);
let n_state = n * (1 + l);
let state: Vec<f64> = (0..n_state).map(|i| i as f64).collect();
let noise: Vec<f64> = (0..n).map(|h| h as f64 * 0.01).collect();
buf.fill_forward_patches(&StageIndexer::new(n, l), &state, &noise, 500, &[]);
assert_eq!(buf.indices[0], 500); assert_eq!(buf.lower[0], 0.0); assert_eq!(buf.indices[159], 659); assert_eq!(buf.lower[159], 159.0 * 0.01);
for i in 0..buf.forward_patch_count() {
assert_eq!(buf.lower[i], buf.upper[i], "patch {i} not equality");
}
}
#[test]
fn clone_and_debug() {
let buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
let cloned = buf.clone();
assert_eq!(cloned.indices.len(), buf.indices.len());
let s = format!("{buf:?}");
assert!(s.contains("PatchBuffer"));
}
#[test]
fn new_with_load_allocates_correct_capacity() {
let buf = PatchBuffer::new(2, 1, 1, 3, 0, 0);
assert_eq!(buf.indices.len(), 7);
assert_eq!(buf.lower.len(), 7);
assert_eq!(buf.upper.len(), 7);
}
#[test]
fn fill_load_patches_correct_indices() {
let mut buf = PatchBuffer::new(0, 0, 2, 2, 0, 0);
let load_rhs = [300.0_f64, 280.0, 500.0, 450.0];
let bus_positions = [0_usize, 1];
buf.fill_load_patches(100, 2, &load_rhs, &bus_positions, &[]);
assert_eq!(buf.indices[0], 100); assert_eq!(buf.indices[1], 101); assert_eq!(buf.indices[2], 102); assert_eq!(buf.indices[3], 103); }
#[test]
fn fill_load_patches_correct_values() {
let mut buf = PatchBuffer::new(0, 0, 2, 2, 0, 0);
let load_rhs = [300.0_f64, 280.0, 500.0, 450.0];
let bus_positions = [0_usize, 1];
buf.fill_load_patches(100, 2, &load_rhs, &bus_positions, &[]);
assert_eq!(buf.lower[0], 300.0);
assert_eq!(buf.upper[0], 300.0);
assert_eq!(buf.lower[1], 280.0);
assert_eq!(buf.upper[1], 280.0);
assert_eq!(buf.lower[2], 500.0);
assert_eq!(buf.upper[2], 500.0);
assert_eq!(buf.lower[3], 450.0);
assert_eq!(buf.upper[3], 450.0);
}
#[test]
fn fill_load_patches_equality_constraints() {
let mut buf = PatchBuffer::new(3, 2, 2, 3, 0, 0);
let state = [10.0, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let noise = [0.1, 0.2, 0.3];
buf.fill_forward_patches(&idx(3, 2), &state, &noise, 50, &[]);
let load_rhs = [100.0_f64, 90.0, 80.0, 200.0, 190.0, 180.0];
let bus_positions = [0_usize, 1];
buf.fill_load_patches(20, 3, &load_rhs, &bus_positions, &[]);
let count = buf.forward_patch_count();
for i in 0..count {
assert_eq!(
buf.lower[i],
buf.upper[i],
"patch {i}: lower {lo} != upper {up}",
lo = buf.lower[i],
up = buf.upper[i],
);
}
}
#[test]
fn forward_patch_count_includes_load() {
let mut buf = PatchBuffer::new(3, 2, 2, 3, 0, 0);
let state = [10.0, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let noise = [0.1, 0.2, 0.3];
buf.fill_forward_patches(&idx(3, 2), &state, &noise, 50, &[]);
let load_rhs = [100.0_f64, 90.0, 80.0, 200.0, 190.0, 180.0];
let bus_positions = [0_usize, 1];
buf.fill_load_patches(20, 3, &load_rhs, &bus_positions, &[]);
assert_eq!(buf.forward_patch_count(), 9); }
#[test]
fn zero_load_buses_no_category4() {
let mut buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
let state = [10.0, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let noise = [0.1, 0.2, 0.3];
buf.fill_forward_patches(&idx(3, 2), &state, &noise, 50, &[]);
assert_eq!(buf.forward_patch_count(), 3);
}
#[test]
fn fill_forward_patches_zero_hydros_zero_noise_patches() {
use crate::indexer::{EquipmentCounts, EvapConfig, FphaColumnLayout};
let indexer = StageIndexer::with_equipment_and_evaporation(
&EquipmentCounts {
hydro_count: 0,
max_par_order: 0,
n_thermals: 0,
n_lines: 0,
n_buses: 1,
n_blks: 1,
has_inflow_penalty: false,
max_deficit_segments: 1,
n_anticipated: 1,
k_max: 2,
anticipated_lead_stages: vec![2],
anticipated_thermal_indices: vec![0],
},
&FphaColumnLayout {
hydro_indices: vec![],
planes_per_hydro: vec![],
},
&EvapConfig {
hydro_indices: vec![],
},
);
let mut state = vec![0.0_f64; indexer.n_state];
state[indexer.anticipated_state.start] = 7.0;
state[indexer.anticipated_state.start + 1] = 11.0;
let mut buf = PatchBuffer::new(0, 0, 0, 0, 1, 2);
assert_eq!(
buf.forward_patch_count(),
0,
"forward_patch_count before fill"
);
buf.fill_forward_patches(&indexer, &state, &[], 0, &[]);
assert_eq!(
buf.forward_patch_count(),
0,
"forward_patch_count after fill"
);
}
#[test]
fn fill_forward_patches_no_anticipated_noise_at_slot_zero() {
let n = 3;
let l = 2;
let mut buf = PatchBuffer::new(n, l, 0, 0, 0, 0);
let state = [10.0, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let noise = [0.1, 0.2, 0.3];
buf.fill_forward_patches(&idx(n, l), &state, &noise, 50, &[]);
assert_eq!(buf.forward_patch_count(), 3);
assert_eq!(buf.indices[0], 50); assert_eq!(buf.lower[0], 0.1);
assert_eq!(buf.indices[1], 51);
assert_eq!(buf.lower[1], 0.2);
assert_eq!(buf.indices[2], 52);
assert_eq!(buf.lower[2], 0.3);
}
fn idx_augmented_3_2() -> StageIndexer {
use crate::indexer::{EquipmentCounts, EvapConfig, FphaColumnLayout};
StageIndexer::with_equipment_and_evaporation(
&EquipmentCounts {
hydro_count: 3,
max_par_order: 2,
n_thermals: 0,
n_lines: 0,
n_buses: 1,
n_blks: 1,
has_inflow_penalty: false,
max_deficit_segments: 1,
n_anticipated: 0,
k_max: 0,
anticipated_lead_stages: vec![],
anticipated_thermal_indices: vec![],
},
&FphaColumnLayout {
hydro_indices: vec![],
planes_per_hydro: vec![],
},
&EvapConfig {
hydro_indices: vec![],
},
)
}
#[test]
fn fill_col_state_patches_category1_indices() {
let indexer = idx_augmented_3_2();
let state = [10.0_f64, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
buf.fill_col_state_patches(&indexer, &state, &[]);
let s = indexer.storage_in.start;
assert_eq!(buf.col_indices[0], s);
assert_eq!(buf.col_indices[1], s + 1);
assert_eq!(buf.col_indices[2], s + 2);
}
#[test]
fn fill_col_state_patches_category1_values() {
let indexer = idx_augmented_3_2();
let state = [10.0_f64, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
buf.fill_col_state_patches(&indexer, &state, &[]);
assert_eq!(buf.col_lower[0], 10.0);
assert_eq!(buf.col_upper[0], 10.0);
assert_eq!(buf.col_lower[1], 20.0);
assert_eq!(buf.col_upper[1], 20.0);
assert_eq!(buf.col_lower[2], 30.0);
assert_eq!(buf.col_upper[2], 30.0);
}
#[test]
fn fill_col_state_patches_category2_indices_and_values() {
let indexer = idx_augmented_3_2();
let state = [10.0_f64, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
buf.fill_col_state_patches(&indexer, &state, &[]);
let il = indexer.inflow_lags.start;
assert_eq!(buf.col_indices[3], il);
assert_eq!(buf.col_indices[4], il + 1);
assert_eq!(buf.col_indices[5], il + 2);
assert_eq!(buf.col_indices[6], il + 3);
assert_eq!(buf.col_indices[7], il + 4);
assert_eq!(buf.col_indices[8], il + 5);
assert_eq!(buf.col_lower[3], 1.0);
assert_eq!(buf.col_upper[3], 1.0);
assert_eq!(buf.col_lower[6], 4.0);
assert_eq!(buf.col_upper[6], 4.0);
assert_eq!(buf.col_lower[8], 6.0);
assert_eq!(buf.col_upper[8], 6.0);
}
#[test]
fn fill_col_state_patches_anticipated_category6() {
use crate::indexer::{EquipmentCounts, EvapConfig, FphaColumnLayout};
let indexer = StageIndexer::with_equipment_and_evaporation(
&EquipmentCounts {
hydro_count: 0,
max_par_order: 0,
n_thermals: 0,
n_lines: 0,
n_buses: 1,
n_blks: 1,
has_inflow_penalty: false,
max_deficit_segments: 1,
n_anticipated: 1,
k_max: 2,
anticipated_lead_stages: vec![2],
anticipated_thermal_indices: vec![0],
},
&FphaColumnLayout {
hydro_indices: vec![],
planes_per_hydro: vec![],
},
&EvapConfig {
hydro_indices: vec![],
},
);
let ant_start = indexer.anticipated_state.start;
let mut state = vec![0.0_f64; indexer.n_state];
state[ant_start] = 7.0;
state[ant_start + 1] = 11.0;
let mut buf = PatchBuffer::new(0, 0, 0, 0, 1, 2);
buf.fill_col_state_patches(&indexer, &state, &[]);
assert_eq!(buf.col_indices[0], ant_start);
assert_eq!(buf.col_indices[1], ant_start + 1);
assert_eq!(buf.col_lower[0], 7.0);
assert_eq!(buf.col_upper[0], 7.0);
assert_eq!(buf.col_lower[1], 11.0);
assert_eq!(buf.col_upper[1], 11.0);
}
#[test]
fn fill_col_state_patches_equality_constraints() {
let indexer = idx_augmented_3_2();
let state = [10.0_f64, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
buf.fill_col_state_patches(&indexer, &state, &[]);
let count = buf.state_col_patch_count();
for i in 0..count {
assert_eq!(
buf.col_lower[i],
buf.col_upper[i],
"col patch {i}: lower {lo} != upper {up}",
lo = buf.col_lower[i],
up = buf.col_upper[i],
);
}
}
#[test]
fn fill_col_state_patches_unscaled_with_col_scale() {
let indexer = idx_augmented_3_2();
let state = [10.0_f64, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
let ncols = indexer.anticipated_state.end.max(indexer.storage_in.end);
let mut col_scale = vec![1.0_f64; ncols];
let s = indexer.storage_in.start;
col_scale[s] = 2.0;
col_scale[s + 1] = 2.0;
col_scale[s + 2] = 2.0;
buf.fill_col_state_patches(&indexer, &state, &col_scale);
assert_eq!(buf.col_lower[0], 5.0);
assert_eq!(buf.col_upper[0], 5.0);
assert_eq!(buf.col_lower[1], 10.0);
assert_eq!(buf.col_upper[1], 10.0);
assert_eq!(buf.col_lower[2], 15.0);
assert_eq!(buf.col_upper[2], 15.0);
}
#[test]
fn fill_col_state_patches_zero_anticipated_collapses_correctly() {
let indexer = idx_augmented_3_2();
let state = [10.0_f64, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
buf.fill_col_state_patches(&indexer, &state, &[]);
assert_eq!(buf.state_col_patch_count(), 9);
assert_eq!(buf.col_indices.len(), 9);
}
#[test]
fn row_buffer_unchanged_after_fill_col_state_patches() {
let indexer = idx_augmented_3_2();
let state = [10.0_f64, 20.0, 30.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut buf = PatchBuffer::new(3, 2, 0, 0, 0, 0);
buf.fill_col_state_patches(&indexer, &state, &[]);
assert!(
buf.indices.iter().all(|&v| v == 0),
"row indices modified by fill_col_state_patches"
);
assert!(
buf.lower.iter().all(|&v| v == 0.0),
"row lower modified by fill_col_state_patches"
);
assert!(
buf.upper.iter().all(|&v| v == 0.0),
"row upper modified by fill_col_state_patches"
);
}
}