use std::collections::HashMap;
use std::hash::BuildHasher;
use cobre_core::{EntityId, VariableRef};
use crate::hydro_models::{ProductionModelSet, ResolvedProductionModel};
use crate::indexer::StageIndexer;
pub(crate) struct EntityPositionMaps<'a, S: BuildHasher = std::hash::RandomState> {
pub hydro: &'a HashMap<EntityId, usize, S>,
pub thermal: &'a HashMap<EntityId, usize, S>,
pub bus: &'a HashMap<EntityId, usize, S>,
pub line: &'a HashMap<EntityId, usize, S>,
}
#[must_use]
pub(crate) fn resolve_variable_ref<S: BuildHasher>(
var_ref: &VariableRef,
block_idx: usize,
n_blks: usize,
stage_idx: usize,
indexer: &StageIndexer,
production_models: &ProductionModelSet,
positions: &EntityPositionMaps<'_, S>,
) -> Vec<(usize, f64)> {
let hydro_pos = positions.hydro;
let thermal_pos = positions.thermal;
let bus_pos = positions.bus;
let line_pos = positions.line;
match var_ref {
VariableRef::HydroStorage { hydro_id } => {
resolve_hydro_storage(*hydro_id, indexer, hydro_pos)
}
VariableRef::HydroEvaporation { hydro_id } => {
resolve_hydro_evaporation(*hydro_id, indexer, hydro_pos)
}
VariableRef::HydroTurbined { hydro_id, block_id } => resolve_block_variable(
*hydro_id,
*block_id,
block_idx,
n_blks,
indexer.turbine.start,
hydro_pos,
1.0,
),
VariableRef::HydroSpillage { hydro_id, block_id } => resolve_block_variable(
*hydro_id,
*block_id,
block_idx,
n_blks,
indexer.spillage.start,
hydro_pos,
1.0,
),
VariableRef::HydroOutflow { hydro_id, block_id } => {
resolve_hydro_outflow(*hydro_id, *block_id, block_idx, n_blks, indexer, hydro_pos)
}
VariableRef::HydroGeneration { hydro_id, block_id } => resolve_hydro_generation(
*hydro_id,
*block_id,
block_idx,
n_blks,
stage_idx,
indexer,
production_models,
hydro_pos,
),
VariableRef::ThermalGeneration {
thermal_id,
block_id,
} => resolve_block_variable(
*thermal_id,
*block_id,
block_idx,
n_blks,
indexer.thermal.start,
thermal_pos,
1.0,
),
VariableRef::LineDirect { line_id, block_id } => resolve_block_variable(
*line_id,
*block_id,
block_idx,
n_blks,
indexer.line_fwd.start,
line_pos,
1.0,
),
VariableRef::LineReverse { line_id, block_id } => resolve_block_variable(
*line_id,
*block_id,
block_idx,
n_blks,
indexer.line_rev.start,
line_pos,
1.0,
),
VariableRef::LineExchange { line_id, block_id } => {
resolve_line_exchange(*line_id, *block_id, block_idx, n_blks, indexer, line_pos)
}
VariableRef::BusDeficit { bus_id, block_id } => {
resolve_bus_deficit(*bus_id, *block_id, block_idx, n_blks, indexer, bus_pos)
}
VariableRef::BusExcess { bus_id, block_id } => resolve_block_variable(
*bus_id,
*block_id,
block_idx,
n_blks,
indexer.excess.start,
bus_pos,
1.0,
),
VariableRef::HydroDiversion { hydro_id, block_id } => resolve_block_variable(
*hydro_id,
*block_id,
block_idx,
n_blks,
indexer.diversion.start,
hydro_pos,
1.0,
),
VariableRef::HydroWithdrawal { .. }
| VariableRef::PumpingFlow { .. }
| VariableRef::PumpingPower { .. }
| VariableRef::ContractImport { .. }
| VariableRef::ContractExport { .. }
| VariableRef::NonControllableGeneration { .. }
| VariableRef::NonControllableCurtailment { .. } => vec![],
}
}
fn resolve_hydro_storage<S: BuildHasher>(
hydro_id: EntityId,
indexer: &StageIndexer,
hydro_pos: &HashMap<EntityId, usize, S>,
) -> Vec<(usize, f64)> {
if let Some(&pos) = hydro_pos.get(&hydro_id) {
vec![(indexer.storage.start + pos, 1.0)]
} else {
vec![]
}
}
fn resolve_hydro_evaporation<S: BuildHasher>(
hydro_id: EntityId,
indexer: &StageIndexer,
hydro_pos: &HashMap<EntityId, usize, S>,
) -> Vec<(usize, f64)> {
if let Some(&sys_pos) = hydro_pos.get(&hydro_id) {
if let Some(local_idx) = indexer
.evap_hydro_indices
.iter()
.position(|&p| p == sys_pos)
{
let q_ev_col = indexer.evap_indices[local_idx].q_ev_col;
vec![(q_ev_col, 1.0)]
} else {
vec![]
}
} else {
vec![]
}
}
fn resolve_hydro_outflow<S: BuildHasher>(
hydro_id: EntityId,
block_id: Option<usize>,
block_idx: usize,
n_blks: usize,
indexer: &StageIndexer,
hydro_pos: &HashMap<EntityId, usize, S>,
) -> Vec<(usize, f64)> {
let mut result = Vec::with_capacity(2);
result.extend(resolve_block_variable(
hydro_id,
block_id,
block_idx,
n_blks,
indexer.turbine.start,
hydro_pos,
1.0,
));
result.extend(resolve_block_variable(
hydro_id,
block_id,
block_idx,
n_blks,
indexer.spillage.start,
hydro_pos,
1.0,
));
result
}
fn resolve_hydro_generation<S: BuildHasher>(
hydro_id: EntityId,
block_id: Option<usize>,
block_idx: usize,
n_blks: usize,
stage_idx: usize,
indexer: &StageIndexer,
production_models: &ProductionModelSet,
hydro_pos: &HashMap<EntityId, usize, S>,
) -> Vec<(usize, f64)> {
let Some(&sys_pos) = hydro_pos.get(&hydro_id) else {
return vec![];
};
match production_models.model(sys_pos, stage_idx) {
ResolvedProductionModel::Fpha { .. } => {
if let Some(fpha_local_idx) = indexer
.fpha_hydro_indices
.iter()
.position(|&p| p == sys_pos)
{
let effective_blk = block_id.unwrap_or(block_idx);
let col = indexer.generation.start + fpha_local_idx * n_blks + effective_blk;
vec![(col, 1.0)]
} else {
vec![]
}
}
ResolvedProductionModel::ConstantProductivity { productivity } => {
resolve_block_variable(
hydro_id,
block_id,
block_idx,
n_blks,
indexer.turbine.start,
hydro_pos,
*productivity,
)
}
}
}
fn resolve_line_exchange<S: BuildHasher>(
line_id: EntityId,
block_id: Option<usize>,
block_idx: usize,
n_blks: usize,
indexer: &StageIndexer,
line_pos: &HashMap<EntityId, usize, S>,
) -> Vec<(usize, f64)> {
if let Some(&pos) = line_pos.get(&line_id) {
let effective_blk = block_id.unwrap_or(block_idx);
let fwd_col = indexer.line_fwd.start + pos * n_blks + effective_blk;
let rev_col = indexer.line_rev.start + pos * n_blks + effective_blk;
vec![(fwd_col, 1.0), (rev_col, -1.0)]
} else {
vec![]
}
}
fn resolve_bus_deficit<S: BuildHasher>(
bus_id: EntityId,
block_id: Option<usize>,
block_idx: usize,
n_blks: usize,
indexer: &StageIndexer,
bus_pos: &HashMap<EntityId, usize, S>,
) -> Vec<(usize, f64)> {
if let Some(&b_pos) = bus_pos.get(&bus_id) {
let effective_blk = block_id.unwrap_or(block_idx);
let s = indexer.max_deficit_segments;
let base = indexer.deficit.start + b_pos * s * n_blks + effective_blk;
(0..s).map(|seg| (base + seg * n_blks, 1.0)).collect()
} else {
vec![]
}
}
fn resolve_block_variable<S: BuildHasher>(
entity_id: EntityId,
ref_block_id: Option<usize>,
current_block_idx: usize,
n_blks: usize,
col_start: usize,
pos_map: &HashMap<EntityId, usize, S>,
multiplier: f64,
) -> Vec<(usize, f64)> {
if let Some(&pos) = pos_map.get(&entity_id) {
let effective_blk = ref_block_id.unwrap_or(current_block_idx);
vec![(col_start + pos * n_blks + effective_blk, multiplier)]
} else {
vec![]
}
}
#[cfg(test)]
#[allow(
clippy::doc_markdown,
clippy::too_many_arguments,
clippy::identity_op,
clippy::erasing_op
)]
mod tests {
use std::collections::HashMap;
use cobre_core::{EntityId, VariableRef};
use super::resolve_variable_ref;
use crate::hydro_models::{FphaPlane, ProductionModelSet, ResolvedProductionModel};
use crate::indexer::StageIndexer;
fn make_indexer() -> StageIndexer {
StageIndexer::with_equipment_and_evaporation(
&crate::indexer::EquipmentCounts {
hydro_count: 4,
max_par_order: 0,
n_thermals: 2,
n_lines: 1,
n_buses: 2,
n_blks: 3,
has_inflow_penalty: false,
max_deficit_segments: 2,
},
&crate::indexer::FphaColumnLayout {
hydro_indices: vec![0, 2],
planes_per_hydro: vec![3, 3],
},
&crate::indexer::EvapConfig {
hydro_indices: vec![],
},
)
}
fn make_production_models() -> ProductionModelSet {
let fpha_plane = FphaPlane {
intercept: 0.0,
gamma_v: 0.1,
gamma_q: 0.5,
gamma_s: 0.0,
};
let fpha_model = || ResolvedProductionModel::Fpha {
planes: vec![fpha_plane],
};
let models: Vec<Vec<ResolvedProductionModel>> = vec![
vec![fpha_model(), fpha_model()], vec![
ResolvedProductionModel::ConstantProductivity { productivity: 2.5 },
ResolvedProductionModel::ConstantProductivity { productivity: 2.5 },
], vec![fpha_model(), fpha_model()], vec![
ResolvedProductionModel::ConstantProductivity { productivity: 1.0 },
ResolvedProductionModel::ConstantProductivity { productivity: 1.0 },
], ];
ProductionModelSet::new(models, 4, 2)
}
fn make_hydro_pos() -> HashMap<EntityId, usize> {
[
(EntityId(10), 0),
(EntityId(20), 1),
(EntityId(30), 2),
(EntityId(40), 3),
]
.into_iter()
.collect()
}
fn make_thermal_pos() -> HashMap<EntityId, usize> {
[(EntityId(5), 0), (EntityId(6), 1)].into_iter().collect()
}
fn make_bus_pos() -> HashMap<EntityId, usize> {
[(EntityId(100), 0), (EntityId(200), 1)]
.into_iter()
.collect()
}
fn make_line_pos() -> HashMap<EntityId, usize> {
[(EntityId(50), 0)].into_iter().collect()
}
fn call(
var_ref: VariableRef,
block_idx: usize,
indexer: &StageIndexer,
production_models: &ProductionModelSet,
hydro_pos: &HashMap<EntityId, usize>,
thermal_pos: &HashMap<EntityId, usize>,
bus_pos: &HashMap<EntityId, usize>,
line_pos: &HashMap<EntityId, usize>,
) -> Vec<(usize, f64)> {
let positions = super::EntityPositionMaps {
hydro: hydro_pos,
thermal: thermal_pos,
bus: bus_pos,
line: line_pos,
};
resolve_variable_ref(
&var_ref,
block_idx,
indexer.n_blks,
0, indexer,
production_models,
&positions,
)
}
#[test]
fn thermal_generation_block_id_none_at_block_1() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::ThermalGeneration {
thermal_id: EntityId(5),
block_id: None,
},
1, &indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(49 + 0 * 3 + 1, 1.0)]);
}
#[test]
fn thermal_generation_block_id_some_at_block_2() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::ThermalGeneration {
thermal_id: EntityId(5),
block_id: Some(2),
},
2,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(49 + 0 * 3 + 2, 1.0)]);
}
#[test]
fn thermal_generation_second_thermal() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::ThermalGeneration {
thermal_id: EntityId(6),
block_id: None,
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(49 + 1 * 3 + 0, 1.0)]);
}
#[test]
fn hydro_storage_stage_level_ignores_block() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
for block_idx in [0, 1, 2] {
let result = call(
VariableRef::HydroStorage {
hydro_id: EntityId(10),
},
block_idx,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(0, 1.0)], "block_idx={block_idx}");
}
let result2 = call(
VariableRef::HydroStorage {
hydro_id: EntityId(30),
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result2, vec![(2, 1.0)]);
}
#[test]
fn hydro_outflow_expands_to_turbine_and_spillage() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::HydroOutflow {
hydro_id: EntityId(40),
block_id: None,
},
0, &indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
let turbine_col = 13 + 3 * 3 + 0; let spillage_col = 25 + 3 * 3 + 0; assert_eq!(result.len(), 2);
assert_eq!(result[0], (turbine_col, 1.0));
assert_eq!(result[1], (spillage_col, 1.0));
}
#[test]
fn hydro_outflow_block_id_some_uses_explicit_block() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::HydroOutflow {
hydro_id: EntityId(10),
block_id: Some(1),
},
0, &indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(13 + 0 * 3 + 1, 1.0), (25 + 0 * 3 + 1, 1.0)]);
}
#[test]
fn hydro_generation_constant_productivity_maps_to_turbine() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::HydroGeneration {
hydro_id: EntityId(20),
block_id: None,
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(13 + 1 * 3 + 0, 2.5)]);
}
#[test]
fn hydro_generation_fpha_maps_to_generation_column() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::HydroGeneration {
hydro_id: EntityId(10),
block_id: None,
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(79 + 0 * 3 + 0, 1.0)]);
}
#[test]
fn hydro_generation_fpha_second_hydro_block_2() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::HydroGeneration {
hydro_id: EntityId(30),
block_id: None,
},
2,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(79 + 1 * 3 + 2, 1.0)]);
}
#[test]
fn hydro_evaporation_maps_to_q_ev_col() {
let evap_indexer = StageIndexer::with_equipment_and_evaporation(
&crate::indexer::EquipmentCounts {
hydro_count: 2,
max_par_order: 0,
n_thermals: 0,
n_lines: 0,
n_buses: 1,
n_blks: 1,
has_inflow_penalty: false,
max_deficit_segments: 1,
},
&crate::indexer::FphaColumnLayout {
hydro_indices: vec![],
planes_per_hydro: vec![],
},
&crate::indexer::EvapConfig {
hydro_indices: vec![0],
},
);
let prod_models = ProductionModelSet::new(
vec![
vec![ResolvedProductionModel::ConstantProductivity { productivity: 1.0 }],
vec![ResolvedProductionModel::ConstantProductivity { productivity: 1.0 }],
],
2,
1,
);
let hpos: HashMap<EntityId, usize> =
[(EntityId(10), 0), (EntityId(20), 1)].into_iter().collect();
let tpos: HashMap<EntityId, usize> = HashMap::new();
let bpos: HashMap<EntityId, usize> = [(EntityId(100), 0)].into_iter().collect();
let lpos: HashMap<EntityId, usize> = HashMap::new();
let positions = super::EntityPositionMaps {
hydro: &hpos,
thermal: &tpos,
bus: &bpos,
line: &lpos,
};
let result = resolve_variable_ref(
&VariableRef::HydroEvaporation {
hydro_id: EntityId(10),
},
0,
1, 0, &evap_indexer,
&prod_models,
&positions,
);
assert_eq!(result, vec![(15, 1.0)]);
}
#[test]
fn hydro_evaporation_no_evap_model_returns_empty() {
let evap_indexer = StageIndexer::with_equipment_and_evaporation(
&crate::indexer::EquipmentCounts {
hydro_count: 2,
max_par_order: 0,
n_thermals: 0,
n_lines: 0,
n_buses: 1,
n_blks: 1,
has_inflow_penalty: false,
max_deficit_segments: 1,
},
&crate::indexer::FphaColumnLayout {
hydro_indices: vec![],
planes_per_hydro: vec![],
},
&crate::indexer::EvapConfig {
hydro_indices: vec![0],
},
);
let prod_models = ProductionModelSet::new(
vec![
vec![ResolvedProductionModel::ConstantProductivity { productivity: 1.0 }],
vec![ResolvedProductionModel::ConstantProductivity { productivity: 1.0 }],
],
2,
1,
);
let hpos: HashMap<EntityId, usize> =
[(EntityId(10), 0), (EntityId(20), 1)].into_iter().collect();
let tpos: HashMap<EntityId, usize> = HashMap::new();
let bpos: HashMap<EntityId, usize> = [(EntityId(100), 0)].into_iter().collect();
let lpos: HashMap<EntityId, usize> = HashMap::new();
let positions = super::EntityPositionMaps {
hydro: &hpos,
thermal: &tpos,
bus: &bpos,
line: &lpos,
};
let result = resolve_variable_ref(
&VariableRef::HydroEvaporation {
hydro_id: EntityId(20),
},
0,
1,
0,
&evap_indexer,
&prod_models,
&positions,
);
assert!(result.is_empty());
}
#[test]
fn pumping_flow_returns_empty() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::PumpingFlow {
station_id: EntityId(1),
block_id: None,
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert!(result.is_empty());
}
#[test]
fn pumping_power_returns_empty() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::PumpingPower {
station_id: EntityId(1),
block_id: None,
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert!(result.is_empty());
}
#[test]
fn contract_import_returns_empty() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::ContractImport {
contract_id: EntityId(99),
block_id: None,
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert!(result.is_empty());
}
#[test]
fn non_controllable_generation_returns_empty() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::NonControllableGeneration {
source_id: EntityId(7),
block_id: None,
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert!(result.is_empty());
}
#[test]
fn missing_entity_id_returns_empty() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::ThermalGeneration {
thermal_id: EntityId(999),
block_id: None,
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert!(result.is_empty());
}
#[test]
fn bus_deficit_returns_one_entry_per_segment() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::BusDeficit {
bus_id: EntityId(100),
block_id: None,
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result.len(), 2);
assert_eq!(result[0], (61, 1.0));
assert_eq!(result[1], (64, 1.0));
}
#[test]
fn bus_deficit_second_bus_block_1() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::BusDeficit {
bus_id: EntityId(200),
block_id: None,
},
1,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result.len(), 2);
assert_eq!(result[0], (68, 1.0));
assert_eq!(result[1], (71, 1.0));
}
#[test]
fn bus_excess_maps_to_excess_column() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::BusExcess {
bus_id: EntityId(100),
block_id: None,
},
2,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(73 + 0 * 3 + 2, 1.0)]);
}
#[test]
fn line_direct_maps_to_fwd_column() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::LineDirect {
line_id: EntityId(50),
block_id: None,
},
1,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(55 + 0 * 3 + 1, 1.0)]);
}
#[test]
fn line_reverse_maps_to_rev_column() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::LineReverse {
line_id: EntityId(50),
block_id: None,
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(58, 1.0)]);
}
#[test]
fn line_exchange_maps_to_fwd_and_rev_columns() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::LineExchange {
line_id: EntityId(50),
block_id: None,
},
1,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(56, 1.0), (59, -1.0)]);
}
#[test]
fn line_exchange_with_explicit_block() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::LineExchange {
line_id: EntityId(50),
block_id: Some(0),
},
2,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(55, 1.0), (58, -1.0)]);
}
#[test]
fn line_exchange_unknown_id_returns_empty() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::LineExchange {
line_id: EntityId(999),
block_id: None,
},
0,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert!(result.is_empty());
}
#[test]
fn hydro_turbined_maps_to_turbine_column() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::HydroTurbined {
hydro_id: EntityId(20),
block_id: None,
},
2,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(13 + 1 * 3 + 2, 1.0)]);
}
#[test]
fn hydro_spillage_maps_to_spillage_column() {
let indexer = make_indexer();
let prod = make_production_models();
let hpos = make_hydro_pos();
let tpos = make_thermal_pos();
let bpos = make_bus_pos();
let lpos = make_line_pos();
let result = call(
VariableRef::HydroSpillage {
hydro_id: EntityId(40),
block_id: None,
},
1,
&indexer,
&prod,
&hpos,
&tpos,
&bpos,
&lpos,
);
assert_eq!(result, vec![(25 + 3 * 3 + 1, 1.0)]);
}
}