#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationCostResult {
pub stage_id: u32,
pub block_id: Option<u32>,
pub total_cost: f64,
pub immediate_cost: f64,
pub future_cost: f64,
pub discount_factor: f64,
pub thermal_cost: f64,
pub contract_cost: f64,
pub deficit_cost: f64,
pub excess_cost: f64,
pub storage_violation_cost: f64,
pub filling_target_cost: f64,
pub hydro_violation_cost: f64,
pub inflow_penalty_cost: f64,
pub generic_violation_cost: f64,
pub spillage_cost: f64,
pub fpha_turbined_cost: f64,
pub curtailment_cost: f64,
pub exchange_cost: f64,
pub pumping_cost: f64,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationHydroResult {
pub stage_id: u32,
pub block_id: Option<u32>,
pub hydro_id: i32,
pub turbined_m3s: f64,
pub spillage_m3s: f64,
pub evaporation_m3s: Option<f64>,
pub diverted_inflow_m3s: Option<f64>,
pub diverted_outflow_m3s: Option<f64>,
pub incremental_inflow_m3s: f64,
pub inflow_m3s: f64,
pub storage_initial_hm3: f64,
pub storage_final_hm3: f64,
pub generation_mw: f64,
pub productivity_mw_per_m3s: Option<f64>,
pub spillage_cost: f64,
pub water_value_per_hm3: f64,
pub storage_binding_code: i8,
pub operative_state_code: i8,
pub turbined_slack_m3s: f64,
pub outflow_slack_below_m3s: f64,
pub outflow_slack_above_m3s: f64,
pub generation_slack_mw: f64,
pub storage_violation_below_hm3: f64,
pub filling_target_violation_hm3: f64,
pub evaporation_violation_m3s: f64,
pub inflow_nonnegativity_slack_m3s: f64,
pub water_withdrawal_violation_m3s: f64,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationThermalResult {
pub stage_id: u32,
pub block_id: Option<u32>,
pub thermal_id: i32,
pub generation_mw: f64,
pub generation_cost: f64,
pub is_gnl: bool,
pub gnl_committed_mw: Option<f64>,
pub gnl_decision_mw: Option<f64>,
pub operative_state_code: i8,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationExchangeResult {
pub stage_id: u32,
pub block_id: Option<u32>,
pub line_id: i32,
pub direct_flow_mw: f64,
pub reverse_flow_mw: f64,
pub exchange_cost: f64,
pub operative_state_code: i8,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationBusResult {
pub stage_id: u32,
pub block_id: Option<u32>,
pub bus_id: i32,
pub load_mw: f64,
pub deficit_mw: f64,
pub excess_mw: f64,
pub spot_price: f64,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationPumpingResult {
pub stage_id: u32,
pub block_id: Option<u32>,
pub pumping_station_id: i32,
pub pumped_flow_m3s: f64,
pub power_consumption_mw: f64,
pub pumping_cost: f64,
pub operative_state_code: i8,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationContractResult {
pub stage_id: u32,
pub block_id: Option<u32>,
pub contract_id: i32,
pub power_mw: f64,
pub price_per_mwh: f64,
pub total_cost: f64,
pub operative_state_code: i8,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationNonControllableResult {
pub stage_id: u32,
pub block_id: Option<u32>,
pub non_controllable_id: i32,
pub generation_mw: f64,
pub available_mw: f64,
pub curtailment_mw: f64,
pub curtailment_cost: f64,
pub operative_state_code: i8,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationInflowLagResult {
pub stage_id: u32,
pub hydro_id: i32,
pub lag_index: u32,
pub inflow_m3s: f64,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationGenericViolationResult {
pub stage_id: u32,
pub block_id: Option<u32>,
pub constraint_id: i32,
pub slack_value: f64,
pub slack_cost: f64,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationStageResult {
pub stage_id: u32,
pub costs: Vec<SimulationCostResult>,
pub hydros: Vec<SimulationHydroResult>,
pub thermals: Vec<SimulationThermalResult>,
pub exchanges: Vec<SimulationExchangeResult>,
pub buses: Vec<SimulationBusResult>,
pub pumping_stations: Vec<SimulationPumpingResult>,
pub contracts: Vec<SimulationContractResult>,
pub non_controllables: Vec<SimulationNonControllableResult>,
pub inflow_lags: Vec<SimulationInflowLagResult>,
pub generic_violations: Vec<SimulationGenericViolationResult>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ScenarioCategoryCosts {
pub resource_cost: f64,
pub recourse_cost: f64,
pub violation_cost: f64,
pub regularization_cost: f64,
pub imputed_cost: f64,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct SimulationScenarioResult {
pub scenario_id: u32,
pub total_cost: f64,
pub per_category_costs: ScenarioCategoryCosts,
pub stages: Vec<SimulationStageResult>,
}
#[derive(Debug)]
pub struct SimulationSummary {
pub mean_cost: f64,
pub std_cost: f64,
pub min_cost: f64,
pub max_cost: f64,
pub cvar: f64,
pub cvar_alpha: f64,
pub category_stats: Vec<CategoryCostStats>,
pub deficit_frequency: f64,
pub total_deficit_mwh: f64,
pub total_spillage_mwh: f64,
pub n_scenarios: u32,
pub stage_stats: Option<Vec<StageSummaryStats>>,
}
#[derive(Debug)]
pub struct CategoryCostStats {
pub category: String,
pub mean: f64,
pub max: f64,
pub frequency: f64,
}
#[derive(Debug)]
pub struct StageSummaryStats {
pub stage_id: u32,
pub mean_cost: f64,
pub mean_storage_hm3: f64,
pub mean_generation_mw: f64,
}
const _: fn() = || {
fn assert_send<T: Send>() {}
assert_send::<SimulationScenarioResult>();
};
#[cfg(test)]
mod tests {
use super::{
CategoryCostStats, ScenarioCategoryCosts, SimulationBusResult, SimulationContractResult,
SimulationCostResult, SimulationExchangeResult, SimulationGenericViolationResult,
SimulationHydroResult, SimulationInflowLagResult, SimulationNonControllableResult,
SimulationPumpingResult, SimulationScenarioResult, SimulationStageResult,
SimulationSummary, SimulationThermalResult, StageSummaryStats,
};
#[test]
fn cost_result_construction_all_fields() {
let r = SimulationCostResult {
stage_id: 0,
block_id: Some(0),
total_cost: 1000.0,
immediate_cost: 800.0,
future_cost: 200.0,
discount_factor: 0.95,
thermal_cost: 500.0,
contract_cost: 100.0,
deficit_cost: 50.0,
excess_cost: 10.0,
storage_violation_cost: 20.0,
filling_target_cost: 30.0,
hydro_violation_cost: 5.0,
inflow_penalty_cost: 3.0,
generic_violation_cost: 2.0,
spillage_cost: 1.0,
fpha_turbined_cost: 4.0,
curtailment_cost: 7.0,
exchange_cost: 8.0,
pumping_cost: 60.0,
};
assert_eq!(r.stage_id, 0);
assert_eq!(r.block_id, Some(0));
assert_eq!(r.total_cost, 1000.0);
assert_eq!(r.immediate_cost, 800.0);
assert_eq!(r.future_cost, 200.0);
assert_eq!(r.discount_factor, 0.95);
assert_eq!(r.thermal_cost, 500.0);
assert_eq!(r.contract_cost, 100.0);
assert_eq!(r.deficit_cost, 50.0);
assert_eq!(r.excess_cost, 10.0);
assert_eq!(r.storage_violation_cost, 20.0);
assert_eq!(r.filling_target_cost, 30.0);
assert_eq!(r.hydro_violation_cost, 5.0);
assert_eq!(r.inflow_penalty_cost, 3.0);
assert_eq!(r.generic_violation_cost, 2.0);
assert_eq!(r.spillage_cost, 1.0);
assert_eq!(r.fpha_turbined_cost, 4.0);
assert_eq!(r.curtailment_cost, 7.0);
assert_eq!(r.exchange_cost, 8.0);
assert_eq!(r.pumping_cost, 60.0);
}
#[test]
fn hydro_result_optional_fields() {
let r = SimulationHydroResult {
stage_id: 1,
block_id: Some(0),
hydro_id: 5,
turbined_m3s: 100.0,
spillage_m3s: 0.0,
evaporation_m3s: None,
diverted_inflow_m3s: None,
diverted_outflow_m3s: None,
incremental_inflow_m3s: 200.0,
inflow_m3s: 200.0,
storage_initial_hm3: 500.0,
storage_final_hm3: 480.0,
generation_mw: 50.0,
productivity_mw_per_m3s: Some(0.5),
spillage_cost: 0.0,
water_value_per_hm3: 10.0,
storage_binding_code: 0,
operative_state_code: 1,
turbined_slack_m3s: 0.0,
outflow_slack_below_m3s: 0.0,
outflow_slack_above_m3s: 0.0,
generation_slack_mw: 0.0,
storage_violation_below_hm3: 0.0,
filling_target_violation_hm3: 0.0,
evaporation_violation_m3s: 0.0,
inflow_nonnegativity_slack_m3s: 0.0,
water_withdrawal_violation_m3s: 0.0,
};
assert_eq!(r.hydro_id, 5);
assert_eq!(r.turbined_m3s, 100.0);
assert_eq!(r.evaporation_m3s, None);
assert_eq!(r.diverted_inflow_m3s, None);
assert_eq!(r.diverted_outflow_m3s, None);
assert_eq!(r.productivity_mw_per_m3s, Some(0.5));
}
#[test]
fn thermal_result_gnl_fields_nullable() {
let gnl = SimulationThermalResult {
stage_id: 2,
block_id: Some(1),
thermal_id: 10,
generation_mw: 200.0,
generation_cost: 5000.0,
is_gnl: true,
gnl_committed_mw: Some(250.0),
gnl_decision_mw: Some(200.0),
operative_state_code: 1,
};
assert!(gnl.is_gnl);
assert_eq!(gnl.gnl_committed_mw, Some(250.0));
assert_eq!(gnl.gnl_decision_mw, Some(200.0));
let non_gnl = SimulationThermalResult {
stage_id: 2,
block_id: Some(1),
thermal_id: 11,
generation_mw: 100.0,
generation_cost: 3000.0,
is_gnl: false,
gnl_committed_mw: None,
gnl_decision_mw: None,
operative_state_code: 1,
};
assert!(!non_gnl.is_gnl);
assert_eq!(non_gnl.gnl_committed_mw, None);
assert_eq!(non_gnl.gnl_decision_mw, None);
}
#[test]
fn exchange_result_construction() {
let r = SimulationExchangeResult {
stage_id: 0,
block_id: Some(0),
line_id: 3,
direct_flow_mw: 150.0,
reverse_flow_mw: 0.0,
exchange_cost: 10.0,
operative_state_code: 1,
};
assert_eq!(r.stage_id, 0);
assert_eq!(r.block_id, Some(0));
assert_eq!(r.line_id, 3);
assert_eq!(r.direct_flow_mw, 150.0);
assert_eq!(r.reverse_flow_mw, 0.0);
assert_eq!(r.exchange_cost, 10.0);
assert_eq!(r.operative_state_code, 1);
}
#[test]
fn bus_result_construction() {
let r = SimulationBusResult {
stage_id: 0,
block_id: Some(0),
bus_id: 1,
load_mw: 300.0,
deficit_mw: 0.0,
excess_mw: 0.0,
spot_price: 120.0,
};
assert_eq!(r.stage_id, 0);
assert_eq!(r.block_id, Some(0));
assert_eq!(r.bus_id, 1);
assert_eq!(r.load_mw, 300.0);
assert_eq!(r.deficit_mw, 0.0);
assert_eq!(r.excess_mw, 0.0);
assert_eq!(r.spot_price, 120.0);
}
#[test]
fn pumping_result_construction() {
let r = SimulationPumpingResult {
stage_id: 0,
block_id: Some(0),
pumping_station_id: 2,
pumped_flow_m3s: 50.0,
power_consumption_mw: 25.0,
pumping_cost: 500.0,
operative_state_code: 1,
};
assert_eq!(r.stage_id, 0);
assert_eq!(r.block_id, Some(0));
assert_eq!(r.pumping_station_id, 2);
assert_eq!(r.pumped_flow_m3s, 50.0);
assert_eq!(r.power_consumption_mw, 25.0);
assert_eq!(r.pumping_cost, 500.0);
assert_eq!(r.operative_state_code, 1);
}
#[test]
fn contract_result_construction() {
let r = SimulationContractResult {
stage_id: 0,
block_id: Some(0),
contract_id: 7,
power_mw: 80.0,
price_per_mwh: 200.0,
total_cost: 16000.0,
operative_state_code: 1,
};
assert_eq!(r.stage_id, 0);
assert_eq!(r.block_id, Some(0));
assert_eq!(r.contract_id, 7);
assert_eq!(r.power_mw, 80.0);
assert_eq!(r.price_per_mwh, 200.0);
assert_eq!(r.total_cost, 16000.0);
assert_eq!(r.operative_state_code, 1);
}
#[test]
fn non_controllable_result_construction() {
let r = SimulationNonControllableResult {
stage_id: 0,
block_id: Some(0),
non_controllable_id: 4,
generation_mw: 60.0,
available_mw: 80.0,
curtailment_mw: 20.0,
curtailment_cost: 200.0,
operative_state_code: 1,
};
assert_eq!(r.stage_id, 0);
assert_eq!(r.block_id, Some(0));
assert_eq!(r.non_controllable_id, 4);
assert_eq!(r.generation_mw, 60.0);
assert_eq!(r.available_mw, 80.0);
assert_eq!(r.curtailment_mw, 20.0);
assert_eq!(r.curtailment_cost, 200.0);
assert_eq!(r.operative_state_code, 1);
}
#[test]
fn inflow_lag_result_construction() {
let r = SimulationInflowLagResult {
stage_id: 5,
hydro_id: 2,
lag_index: 1,
inflow_m3s: 350.0,
};
assert_eq!(r.stage_id, 5);
assert_eq!(r.hydro_id, 2);
assert_eq!(r.lag_index, 1);
assert_eq!(r.inflow_m3s, 350.0);
}
#[test]
fn generic_violation_result_construction() {
let r = SimulationGenericViolationResult {
stage_id: 3,
block_id: Some(2),
constraint_id: 15,
slack_value: 5.0,
slack_cost: 50.0,
};
assert_eq!(r.stage_id, 3);
assert_eq!(r.block_id, Some(2));
assert_eq!(r.constraint_id, 15);
assert_eq!(r.slack_value, 5.0);
assert_eq!(r.slack_cost, 50.0);
}
#[test]
fn stage_result_empty_optional_vecs() {
let stage = SimulationStageResult {
stage_id: 0,
costs: vec![],
hydros: vec![],
thermals: vec![],
exchanges: vec![],
buses: vec![],
pumping_stations: vec![],
contracts: vec![],
non_controllables: vec![],
inflow_lags: vec![],
generic_violations: vec![],
};
assert!(stage.pumping_stations.is_empty());
assert!(stage.contracts.is_empty());
assert!(stage.non_controllables.is_empty());
assert!(stage.inflow_lags.is_empty());
assert!(stage.generic_violations.is_empty());
}
#[test]
fn scenario_result_is_send() {
fn assert_send<T: Send>() {}
assert_send::<SimulationScenarioResult>();
}
#[test]
fn scenario_result_with_multiple_stages() {
let stages: Vec<SimulationStageResult> = (0..12)
.map(|i| SimulationStageResult {
stage_id: i,
costs: vec![],
hydros: vec![],
thermals: vec![],
exchanges: vec![],
buses: vec![],
pumping_stations: vec![],
contracts: vec![],
non_controllables: vec![],
inflow_lags: vec![],
generic_violations: vec![],
})
.collect();
let result = SimulationScenarioResult {
scenario_id: 42,
total_cost: 1_000_000.0,
per_category_costs: ScenarioCategoryCosts {
resource_cost: 600_000.0,
recourse_cost: 100_000.0,
violation_cost: 50_000.0,
regularization_cost: 200_000.0,
imputed_cost: 50_000.0,
},
stages,
};
assert_eq!(result.scenario_id, 42);
assert_eq!(result.stages.len(), 12);
}
#[test]
fn category_costs_construction() {
let c = ScenarioCategoryCosts {
resource_cost: 1.0,
recourse_cost: 2.0,
violation_cost: 3.0,
regularization_cost: 4.0,
imputed_cost: 5.0,
};
assert_eq!(c.resource_cost, 1.0);
assert_eq!(c.recourse_cost, 2.0);
assert_eq!(c.violation_cost, 3.0);
assert_eq!(c.regularization_cost, 4.0);
assert_eq!(c.imputed_cost, 5.0);
}
#[test]
fn category_cost_stats_construction() {
let stats = CategoryCostStats {
category: "recourse".to_string(),
mean: 500.0,
max: 2000.0,
frequency: 0.15,
};
assert_eq!(stats.category, "recourse");
assert_eq!(stats.mean, 500.0);
assert_eq!(stats.max, 2000.0);
assert_eq!(stats.frequency, 0.15);
}
#[test]
fn stage_summary_stats_construction() {
let stats = StageSummaryStats {
stage_id: 3,
mean_cost: 100_000.0,
mean_storage_hm3: 750.0,
mean_generation_mw: 1200.0,
};
assert_eq!(stats.stage_id, 3);
assert_eq!(stats.mean_cost, 100_000.0);
assert_eq!(stats.mean_storage_hm3, 750.0);
assert_eq!(stats.mean_generation_mw, 1200.0);
}
#[test]
fn simulation_summary_construction() {
let category_stats: Vec<CategoryCostStats> = (0_i32..5)
.map(|i| CategoryCostStats {
category: format!("cat_{i}"),
mean: f64::from(i) * 100.0,
max: f64::from(i) * 500.0,
frequency: 0.1 * f64::from(i),
})
.collect();
let stage_stats: Vec<StageSummaryStats> = (0_u32..12)
.map(|i| StageSummaryStats {
stage_id: i,
mean_cost: f64::from(i) * 1000.0,
mean_storage_hm3: 800.0,
mean_generation_mw: 1500.0,
})
.collect();
let summary = SimulationSummary {
mean_cost: 1_500_000.0,
std_cost: 200_000.0,
min_cost: 900_000.0,
max_cost: 2_100_000.0,
cvar: 1_900_000.0,
cvar_alpha: 0.95,
category_stats,
deficit_frequency: 0.08,
total_deficit_mwh: 12_500.0,
total_spillage_mwh: 3_200.0,
n_scenarios: 2000,
stage_stats: Some(stage_stats),
};
assert_eq!(summary.mean_cost, 1_500_000.0);
assert_eq!(summary.std_cost, 200_000.0);
assert_eq!(summary.min_cost, 900_000.0);
assert_eq!(summary.max_cost, 2_100_000.0);
assert_eq!(summary.cvar, 1_900_000.0);
assert_eq!(summary.cvar_alpha, 0.95);
assert_eq!(summary.category_stats.len(), 5);
assert_eq!(summary.deficit_frequency, 0.08);
assert_eq!(summary.total_deficit_mwh, 12_500.0);
assert_eq!(summary.total_spillage_mwh, 3_200.0);
assert_eq!(summary.n_scenarios, 2000);
assert!(summary.stage_stats.is_some());
assert_eq!(summary.stage_stats.as_ref().unwrap().len(), 12);
}
#[test]
fn simulation_summary_optional_stage_stats() {
let summary_none = SimulationSummary {
mean_cost: 0.0,
std_cost: 0.0,
min_cost: 0.0,
max_cost: 0.0,
cvar: 0.0,
cvar_alpha: 0.95,
category_stats: vec![],
deficit_frequency: 0.0,
total_deficit_mwh: 0.0,
total_spillage_mwh: 0.0,
n_scenarios: 100,
stage_stats: None,
};
assert!(summary_none.stage_stats.is_none());
let summary_some = SimulationSummary {
mean_cost: 0.0,
std_cost: 0.0,
min_cost: 0.0,
max_cost: 0.0,
cvar: 0.0,
cvar_alpha: 0.95,
category_stats: vec![],
deficit_frequency: 0.0,
total_deficit_mwh: 0.0,
total_spillage_mwh: 0.0,
n_scenarios: 100,
stage_stats: Some(vec![StageSummaryStats {
stage_id: 0,
mean_cost: 0.0,
mean_storage_hm3: 0.0,
mean_generation_mw: 0.0,
}]),
};
assert!(summary_some.stage_stats.is_some());
assert_eq!(summary_some.stage_stats.unwrap().len(), 1);
}
}