use crate::entities::{DeficitSegment, HydroPenalties};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GlobalPenaltyDefaults {
pub bus_deficit_segments: Vec<DeficitSegment>,
pub bus_excess_cost: f64,
pub line_exchange_cost: f64,
pub hydro: HydroPenalties,
pub ncs_curtailment_cost: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HydroPenaltyOverrides {
pub spillage_cost: Option<f64>,
pub diversion_cost: Option<f64>,
pub fpha_turbined_cost: Option<f64>,
pub storage_violation_below_cost: Option<f64>,
pub filling_target_violation_cost: Option<f64>,
pub turbined_violation_below_cost: Option<f64>,
pub outflow_violation_below_cost: Option<f64>,
pub outflow_violation_above_cost: Option<f64>,
pub generation_violation_below_cost: Option<f64>,
pub evaporation_violation_cost: Option<f64>,
pub water_withdrawal_violation_cost: Option<f64>,
}
#[must_use]
pub fn resolve_bus_deficit_segments(
entity_deficit_segments: &Option<Vec<DeficitSegment>>,
global: &GlobalPenaltyDefaults,
) -> Vec<DeficitSegment> {
entity_deficit_segments
.clone()
.unwrap_or_else(|| global.bus_deficit_segments.clone())
}
#[must_use]
pub fn resolve_bus_excess_cost(
entity_excess_cost: Option<f64>,
global: &GlobalPenaltyDefaults,
) -> f64 {
entity_excess_cost.unwrap_or(global.bus_excess_cost)
}
#[must_use]
pub fn resolve_line_exchange_cost(
entity_exchange_cost: Option<f64>,
global: &GlobalPenaltyDefaults,
) -> f64 {
entity_exchange_cost.unwrap_or(global.line_exchange_cost)
}
#[must_use]
pub fn resolve_hydro_penalties(
entity_overrides: &Option<HydroPenaltyOverrides>,
global: &GlobalPenaltyDefaults,
) -> HydroPenalties {
let g = &global.hydro;
match entity_overrides {
None => *g,
Some(ov) => HydroPenalties {
spillage_cost: ov.spillage_cost.unwrap_or(g.spillage_cost),
diversion_cost: ov.diversion_cost.unwrap_or(g.diversion_cost),
fpha_turbined_cost: ov.fpha_turbined_cost.unwrap_or(g.fpha_turbined_cost),
storage_violation_below_cost: ov
.storage_violation_below_cost
.unwrap_or(g.storage_violation_below_cost),
filling_target_violation_cost: ov
.filling_target_violation_cost
.unwrap_or(g.filling_target_violation_cost),
turbined_violation_below_cost: ov
.turbined_violation_below_cost
.unwrap_or(g.turbined_violation_below_cost),
outflow_violation_below_cost: ov
.outflow_violation_below_cost
.unwrap_or(g.outflow_violation_below_cost),
outflow_violation_above_cost: ov
.outflow_violation_above_cost
.unwrap_or(g.outflow_violation_above_cost),
generation_violation_below_cost: ov
.generation_violation_below_cost
.unwrap_or(g.generation_violation_below_cost),
evaporation_violation_cost: ov
.evaporation_violation_cost
.unwrap_or(g.evaporation_violation_cost),
water_withdrawal_violation_cost: ov
.water_withdrawal_violation_cost
.unwrap_or(g.water_withdrawal_violation_cost),
},
}
}
#[must_use]
pub fn resolve_ncs_curtailment_cost(
entity_curtailment_cost: Option<f64>,
global: &GlobalPenaltyDefaults,
) -> f64 {
entity_curtailment_cost.unwrap_or(global.ncs_curtailment_cost)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_global() -> GlobalPenaltyDefaults {
GlobalPenaltyDefaults {
bus_deficit_segments: vec![
DeficitSegment {
depth_mw: Some(100.0),
cost_per_mwh: 500.0,
},
DeficitSegment {
depth_mw: None,
cost_per_mwh: 5000.0,
},
],
bus_excess_cost: 100.0,
line_exchange_cost: 5.0,
hydro: HydroPenalties {
spillage_cost: 0.01,
diversion_cost: 0.02,
fpha_turbined_cost: 0.03,
storage_violation_below_cost: 1.0,
filling_target_violation_cost: 2.0,
turbined_violation_below_cost: 3.0,
outflow_violation_below_cost: 4.0,
outflow_violation_above_cost: 5.0,
generation_violation_below_cost: 6.0,
evaporation_violation_cost: 7.0,
water_withdrawal_violation_cost: 8.0,
},
ncs_curtailment_cost: 50.0,
}
}
#[test]
fn test_resolve_bus_excess_cost_global() {
let global = make_global();
let result = resolve_bus_excess_cost(None, &global);
assert!((result - 100.0).abs() < f64::EPSILON);
}
#[test]
fn test_resolve_bus_excess_cost_override() {
let global = make_global();
let result = resolve_bus_excess_cost(Some(250.0), &global);
assert!((result - 250.0).abs() < f64::EPSILON);
}
#[test]
fn test_resolve_bus_deficit_segments_global() {
let global = make_global();
let result = resolve_bus_deficit_segments(&None, &global);
assert_eq!(result, global.bus_deficit_segments);
assert_eq!(result.len(), 2);
assert!((result[0].cost_per_mwh - 500.0).abs() < f64::EPSILON);
}
#[test]
fn test_resolve_bus_deficit_segments_override() {
let global = make_global();
let override_segments = vec![DeficitSegment {
depth_mw: None,
cost_per_mwh: 9999.0,
}];
let result = resolve_bus_deficit_segments(&Some(override_segments.clone()), &global);
assert_eq!(result, override_segments);
assert_eq!(result.len(), 1);
assert!((result[0].cost_per_mwh - 9999.0).abs() < f64::EPSILON);
}
#[test]
fn test_resolve_line_exchange_cost_global() {
let global = make_global();
let result = resolve_line_exchange_cost(None, &global);
assert!((result - 5.0).abs() < f64::EPSILON);
}
#[test]
fn test_resolve_line_exchange_cost_override() {
let global = make_global();
let result = resolve_line_exchange_cost(Some(12.0), &global);
assert!((result - 12.0).abs() < f64::EPSILON);
}
#[test]
fn test_resolve_hydro_penalties_all_global() {
let global = make_global();
let result = resolve_hydro_penalties(&None, &global);
assert_eq!(result, global.hydro);
}
#[test]
fn test_resolve_hydro_penalties_partial_override() {
let global = make_global();
let overrides = HydroPenaltyOverrides {
spillage_cost: Some(0.05),
..Default::default()
};
let result = resolve_hydro_penalties(&Some(overrides), &global);
assert!((result.spillage_cost - 0.05).abs() < f64::EPSILON);
assert!((result.diversion_cost - 0.02).abs() < f64::EPSILON);
assert!((result.fpha_turbined_cost - 0.03).abs() < f64::EPSILON);
assert!((result.storage_violation_below_cost - 1.0).abs() < f64::EPSILON);
assert!((result.filling_target_violation_cost - 2.0).abs() < f64::EPSILON);
assert!((result.turbined_violation_below_cost - 3.0).abs() < f64::EPSILON);
assert!((result.outflow_violation_below_cost - 4.0).abs() < f64::EPSILON);
assert!((result.outflow_violation_above_cost - 5.0).abs() < f64::EPSILON);
assert!((result.generation_violation_below_cost - 6.0).abs() < f64::EPSILON);
assert!((result.evaporation_violation_cost - 7.0).abs() < f64::EPSILON);
assert!((result.water_withdrawal_violation_cost - 8.0).abs() < f64::EPSILON);
}
#[test]
fn test_resolve_hydro_penalties_all_override() {
let global = make_global();
let overrides = HydroPenaltyOverrides {
spillage_cost: Some(10.0),
diversion_cost: Some(20.0),
fpha_turbined_cost: Some(30.0),
storage_violation_below_cost: Some(40.0),
filling_target_violation_cost: Some(50.0),
turbined_violation_below_cost: Some(60.0),
outflow_violation_below_cost: Some(70.0),
outflow_violation_above_cost: Some(80.0),
generation_violation_below_cost: Some(90.0),
evaporation_violation_cost: Some(100.0),
water_withdrawal_violation_cost: Some(110.0),
};
let result = resolve_hydro_penalties(&Some(overrides), &global);
assert!((result.spillage_cost - 10.0).abs() < f64::EPSILON);
assert!((result.diversion_cost - 20.0).abs() < f64::EPSILON);
assert!((result.fpha_turbined_cost - 30.0).abs() < f64::EPSILON);
assert!((result.storage_violation_below_cost - 40.0).abs() < f64::EPSILON);
assert!((result.filling_target_violation_cost - 50.0).abs() < f64::EPSILON);
assert!((result.turbined_violation_below_cost - 60.0).abs() < f64::EPSILON);
assert!((result.outflow_violation_below_cost - 70.0).abs() < f64::EPSILON);
assert!((result.outflow_violation_above_cost - 80.0).abs() < f64::EPSILON);
assert!((result.generation_violation_below_cost - 90.0).abs() < f64::EPSILON);
assert!((result.evaporation_violation_cost - 100.0).abs() < f64::EPSILON);
assert!((result.water_withdrawal_violation_cost - 110.0).abs() < f64::EPSILON);
}
#[test]
fn test_resolve_hydro_penalties_default_overrides_equals_global() {
let global = make_global();
let result = resolve_hydro_penalties(&Some(HydroPenaltyOverrides::default()), &global);
assert_eq!(result, global.hydro);
}
#[test]
fn test_resolve_ncs_curtailment_cost_global() {
let global = make_global();
let result = resolve_ncs_curtailment_cost(None, &global);
assert!((result - 50.0).abs() < f64::EPSILON);
}
#[test]
fn test_resolve_ncs_curtailment_cost_override() {
let global = make_global();
let result = resolve_ncs_curtailment_cost(Some(75.0), &global);
assert!((result - 75.0).abs() < f64::EPSILON);
}
}