use std::collections::BTreeMap;
use crate::EntityId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SamplingScheme {
InSample,
External,
Historical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ExternalSelectionMode {
Random,
Sequential,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ScenarioSource {
pub sampling_scheme: SamplingScheme,
pub seed: Option<i64>,
pub selection_mode: Option<ExternalSelectionMode>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct InflowModel {
pub hydro_id: EntityId,
pub stage_id: i32,
pub mean_m3s: f64,
pub std_m3s: f64,
pub ar_coefficients: Vec<f64>,
pub residual_std_ratio: f64,
}
impl InflowModel {
#[must_use]
pub fn ar_order(&self) -> usize {
self.ar_coefficients.len()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LoadModel {
pub bus_id: EntityId,
pub stage_id: i32,
pub mean_mw: f64,
pub std_mw: f64,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NcsModel {
pub ncs_id: EntityId,
pub stage_id: i32,
pub mean: f64,
pub std: f64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CorrelationEntity {
pub entity_type: String,
pub id: EntityId,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CorrelationGroup {
pub name: String,
pub entities: Vec<CorrelationEntity>,
pub matrix: Vec<Vec<f64>>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CorrelationProfile {
pub groups: Vec<CorrelationGroup>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CorrelationScheduleEntry {
pub stage_id: i32,
pub profile_name: String,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CorrelationModel {
pub method: String,
pub profiles: BTreeMap<String, CorrelationProfile>,
pub schedule: Vec<CorrelationScheduleEntry>,
}
impl Default for ScenarioSource {
fn default() -> Self {
Self {
sampling_scheme: SamplingScheme::InSample,
seed: None,
selection_mode: None,
}
}
}
impl Default for CorrelationModel {
fn default() -> Self {
Self {
method: "cholesky".to_string(),
profiles: BTreeMap::new(),
schedule: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::{
CorrelationEntity, CorrelationGroup, CorrelationModel, CorrelationProfile,
CorrelationScheduleEntry, InflowModel, NcsModel, SamplingScheme,
};
#[cfg(feature = "serde")]
use super::{ExternalSelectionMode, ScenarioSource};
use crate::EntityId;
#[test]
fn test_inflow_model_construction() {
let model = InflowModel {
hydro_id: EntityId(7),
stage_id: 11,
mean_m3s: 250.0,
std_m3s: 55.0,
ar_coefficients: vec![0.5, 0.2, 0.1],
residual_std_ratio: 0.85,
};
assert_eq!(model.hydro_id, EntityId(7));
assert_eq!(model.stage_id, 11);
assert_eq!(model.mean_m3s, 250.0);
assert_eq!(model.std_m3s, 55.0);
assert_eq!(model.ar_order(), 3);
assert_eq!(model.ar_coefficients, vec![0.5, 0.2, 0.1]);
assert_eq!(model.ar_coefficients.len(), model.ar_order());
assert!((model.residual_std_ratio - 0.85).abs() < f64::EPSILON);
}
#[test]
fn test_inflow_model_ar_order_method() {
let white_noise = InflowModel {
hydro_id: EntityId(1),
stage_id: 0,
mean_m3s: 100.0,
std_m3s: 10.0,
ar_coefficients: vec![],
residual_std_ratio: 1.0,
};
assert_eq!(white_noise.ar_order(), 0);
let par2 = InflowModel {
hydro_id: EntityId(2),
stage_id: 1,
mean_m3s: 200.0,
std_m3s: 20.0,
ar_coefficients: vec![0.45, 0.22],
residual_std_ratio: 0.85,
};
assert_eq!(par2.ar_order(), 2);
}
#[test]
fn test_correlation_model_construction() {
let make_profile = |entity_ids: &[i32]| {
let entities: Vec<CorrelationEntity> = entity_ids
.iter()
.map(|&id| CorrelationEntity {
entity_type: "inflow".to_string(),
id: EntityId(id),
})
.collect();
let n = entities.len();
let matrix: Vec<Vec<f64>> = (0..n)
.map(|i| (0..n).map(|j| if i == j { 1.0 } else { 0.0 }).collect())
.collect();
CorrelationProfile {
groups: vec![CorrelationGroup {
name: "group_a".to_string(),
entities,
matrix,
}],
}
};
let mut profiles = BTreeMap::new();
profiles.insert("wet".to_string(), make_profile(&[1, 2, 3]));
profiles.insert("dry".to_string(), make_profile(&[1, 2]));
let model = CorrelationModel {
method: "cholesky".to_string(),
profiles,
schedule: vec![
CorrelationScheduleEntry {
stage_id: 0,
profile_name: "wet".to_string(),
},
CorrelationScheduleEntry {
stage_id: 6,
profile_name: "dry".to_string(),
},
],
};
assert_eq!(model.profiles.len(), 2);
let mut profile_iter = model.profiles.keys();
assert_eq!(profile_iter.next().unwrap(), "dry");
assert_eq!(profile_iter.next().unwrap(), "wet");
assert!(model.profiles.contains_key("wet"));
assert!(model.profiles.contains_key("dry"));
let wet = &model.profiles["wet"];
assert_eq!(wet.groups[0].matrix.len(), 3);
let dry = &model.profiles["dry"];
assert_eq!(dry.groups[0].matrix.len(), 2);
assert_eq!(model.schedule.len(), 2);
assert_eq!(model.schedule[0].profile_name, "wet");
assert_eq!(model.schedule[1].profile_name, "dry");
}
#[test]
fn test_sampling_scheme_copy() {
let original = SamplingScheme::InSample;
let copied = original;
assert_eq!(original, copied);
let original_ext = SamplingScheme::External;
let copied_ext = original_ext;
assert_eq!(original_ext, copied_ext);
let original_hist = SamplingScheme::Historical;
let copied_hist = original_hist;
assert_eq!(original_hist, copied_hist);
}
#[cfg(feature = "serde")]
#[test]
fn test_scenario_source_serde_roundtrip() {
let source = ScenarioSource {
sampling_scheme: SamplingScheme::InSample,
seed: Some(12345),
selection_mode: None,
};
let json = serde_json::to_string(&source).unwrap();
let deserialized: ScenarioSource = serde_json::from_str(&json).unwrap();
assert_eq!(source, deserialized);
let source_ext = ScenarioSource {
sampling_scheme: SamplingScheme::External,
seed: Some(99),
selection_mode: Some(ExternalSelectionMode::Sequential),
};
let json_ext = serde_json::to_string(&source_ext).unwrap();
let deserialized_ext: ScenarioSource = serde_json::from_str(&json_ext).unwrap();
assert_eq!(source_ext, deserialized_ext);
let source_hist = ScenarioSource {
sampling_scheme: SamplingScheme::Historical,
seed: None,
selection_mode: None,
};
let json_hist = serde_json::to_string(&source_hist).unwrap();
let deserialized_hist: ScenarioSource = serde_json::from_str(&json_hist).unwrap();
assert_eq!(source_hist, deserialized_hist);
}
#[cfg(feature = "serde")]
#[test]
fn test_inflow_model_serde_roundtrip() {
let model = InflowModel {
hydro_id: EntityId(3),
stage_id: 0,
mean_m3s: 150.0,
std_m3s: 30.0,
ar_coefficients: vec![0.45, 0.22],
residual_std_ratio: 0.85,
};
let json = serde_json::to_string(&model).unwrap();
let deserialized: InflowModel = serde_json::from_str(&json).unwrap();
assert_eq!(model, deserialized);
assert!((deserialized.residual_std_ratio - 0.85).abs() < f64::EPSILON);
}
#[test]
fn test_ncs_model_construction() {
let model = NcsModel {
ncs_id: EntityId(3),
stage_id: 0,
mean: 0.5,
std: 0.1,
};
assert_eq!(model.ncs_id, EntityId(3));
assert_eq!(model.stage_id, 0);
assert_eq!(model.mean, 0.5);
assert_eq!(model.std, 0.1);
}
#[cfg(feature = "serde")]
#[test]
fn test_ncs_model_serde_roundtrip() {
let model = NcsModel {
ncs_id: EntityId(5),
stage_id: 2,
mean: 0.75,
std: 0.15,
};
let json = serde_json::to_string(&model).unwrap();
let deserialized: NcsModel = serde_json::from_str(&json).unwrap();
assert_eq!(model, deserialized);
}
#[test]
fn test_correlation_model_identity_matrix_access() {
let identity = vec![
vec![1.0, 0.0, 0.0],
vec![0.0, 1.0, 0.0],
vec![0.0, 0.0, 1.0],
];
let mut profiles = BTreeMap::new();
profiles.insert(
"default".to_string(),
CorrelationProfile {
groups: vec![CorrelationGroup {
name: "all_hydros".to_string(),
entities: vec![
CorrelationEntity {
entity_type: "inflow".to_string(),
id: EntityId(1),
},
CorrelationEntity {
entity_type: "inflow".to_string(),
id: EntityId(2),
},
CorrelationEntity {
entity_type: "inflow".to_string(),
id: EntityId(3),
},
],
matrix: identity,
}],
},
);
let model = CorrelationModel {
method: "cholesky".to_string(),
profiles,
schedule: vec![],
};
assert_eq!(model.profiles["default"].groups[0].matrix.len(), 3);
}
}