use cobre_core::System;
use crate::LoadError;
pub fn serialize_system(system: &System) -> Result<Vec<u8>, LoadError> {
postcard::to_allocvec(system)
.map_err(|e| LoadError::parse("<broadcast>", format!("postcard serialization failed: {e}")))
}
pub fn deserialize_system(bytes: &[u8]) -> Result<System, LoadError> {
let mut system: System = postcard::from_bytes(bytes).map_err(|e| {
LoadError::parse(
"<broadcast>",
format!("postcard deserialization failed: {e}"),
)
})?;
system.rebuild_indices();
Ok(system)
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use cobre_core::{
Bus, DeficitSegment, EntityId, Hydro, HydroGenerationModel, HydroPenalties, SystemBuilder,
Thermal, ThermalCostSegment,
};
fn minimal_bus(id: i32) -> Bus {
Bus {
id: EntityId(id),
name: format!("Bus {id}"),
deficit_segments: vec![DeficitSegment {
depth_mw: None,
cost_per_mwh: 500.0,
}],
excess_cost: 0.0,
}
}
fn minimal_thermal(id: i32, bus_id: i32) -> Thermal {
Thermal {
id: EntityId(id),
name: format!("Thermal {id}"),
bus_id: EntityId(bus_id),
entry_stage_id: None,
exit_stage_id: None,
cost_segments: vec![ThermalCostSegment {
capacity_mw: 100.0,
cost_per_mwh: 50.0,
}],
min_generation_mw: 0.0,
max_generation_mw: 100.0,
gnl_config: None,
}
}
fn zero_hydro_penalties() -> HydroPenalties {
HydroPenalties {
spillage_cost: 0.0,
diversion_cost: 0.0,
fpha_turbined_cost: 0.0,
storage_violation_below_cost: 0.0,
filling_target_violation_cost: 0.0,
turbined_violation_below_cost: 0.0,
outflow_violation_below_cost: 0.0,
outflow_violation_above_cost: 0.0,
generation_violation_below_cost: 0.0,
evaporation_violation_cost: 0.0,
water_withdrawal_violation_cost: 0.0,
}
}
fn minimal_hydro(id: i32, bus_id: i32) -> Hydro {
Hydro {
id: EntityId(id),
name: format!("Hydro {id}"),
bus_id: EntityId(bus_id),
downstream_id: None,
entry_stage_id: None,
exit_stage_id: None,
min_storage_hm3: 0.0,
max_storage_hm3: 1000.0,
min_outflow_m3s: 0.0,
max_outflow_m3s: None,
generation_model: HydroGenerationModel::ConstantProductivity {
productivity_mw_per_m3s: 1.0,
},
min_turbined_m3s: 0.0,
max_turbined_m3s: 200.0,
min_generation_mw: 0.0,
max_generation_mw: 200.0,
tailrace: None,
hydraulic_losses: None,
efficiency: None,
evaporation_coefficients_mm: None,
diversion: None,
filling: None,
penalties: zero_hydro_penalties(),
}
}
#[test]
fn test_round_trip_minimal_system() {
let bus = minimal_bus(1);
let system = SystemBuilder::new().buses(vec![bus]).build().unwrap();
let bytes = serialize_system(&system).unwrap();
assert!(!bytes.is_empty());
let restored = deserialize_system(&bytes).unwrap();
assert_eq!(restored.n_buses(), system.n_buses());
assert!(restored.bus(EntityId(1)).is_some());
}
#[test]
fn test_round_trip_populated_system() {
let buses = vec![minimal_bus(1), minimal_bus(2)];
let thermals = vec![minimal_thermal(1, 1), minimal_thermal(2, 2)];
let hydros = vec![minimal_hydro(1, 1)];
let system = SystemBuilder::new()
.buses(buses)
.thermals(thermals)
.hydros(hydros)
.build()
.unwrap();
let bytes = serialize_system(&system).unwrap();
let restored = deserialize_system(&bytes).unwrap();
assert_eq!(restored.n_buses(), system.n_buses());
assert_eq!(restored.n_thermals(), system.n_thermals());
assert_eq!(restored.n_hydros(), system.n_hydros());
assert!(restored.bus(EntityId(1)).is_some());
assert!(restored.bus(EntityId(2)).is_some());
assert!(restored.thermal(EntityId(1)).is_some());
assert!(restored.thermal(EntityId(2)).is_some());
assert!(restored.hydro(EntityId(1)).is_some());
assert_eq!(restored, system);
}
#[test]
fn test_deserialize_corrupted_bytes() {
let result = deserialize_system(&[0u8; 4]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("<broadcast>"));
assert!(matches!(err, LoadError::ParseError { .. }));
}
#[test]
fn test_deserialize_empty_bytes() {
let result = deserialize_system(&[]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, LoadError::ParseError { .. }));
assert!(err.to_string().contains("<broadcast>"));
}
#[test]
fn test_serialized_size_reasonable() {
let bus = minimal_bus(1);
let system = SystemBuilder::new().buses(vec![bus]).build().unwrap();
let bytes = serialize_system(&system).unwrap();
assert!(bytes.len() < 1024);
}
}