1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0
//! Pumped hydro storage unit model.
use serde::{Deserialize, Serialize};
use crate::network::GeneratorRef;
/// Pumped hydro storage unit — a synchronous machine overlay.
///
/// The solver sees the underlying Generator via a stable generator reference.
/// This struct
/// carries the hydro-specific constraints that the dispatch engine needs:
/// reservoir dynamics, mode transitions, head dependence, penstock limits.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PumpedHydroUnit {
/// Unit name.
pub name: String,
/// Stable reference to the synchronous machine the solver sees.
pub generator: GeneratorRef,
// --- operating modes ---
/// Fixed-speed (pump is constant-P) vs variable-speed (pump is dispatchable).
pub variable_speed: bool,
/// Fixed-speed pump draw (MW). Ignored if variable_speed.
pub pump_mw_fixed: f64,
/// Variable-speed min pump power (MW).
pub pump_mw_min: Option<f64>,
/// Variable-speed max pump power (MW).
pub pump_mw_max: Option<f64>,
/// Minutes to switch between generate and pump mode (through zero).
pub mode_transition_min: f64,
/// Can operate as synchronous condenser (P=0, Q available).
pub condenser_capable: bool,
/// (low_mw, high_mw) around zero where operation is impossible.
pub forbidden_zone: Option<(f64, f64)>,
// --- reservoir / energy ---
/// Upper reservoir capacity in energy-equivalent MWh.
pub upper_reservoir_mwh: f64,
/// Lower reservoir capacity (f64::MAX if river/ocean).
pub lower_reservoir_mwh: f64,
/// Initial state of charge (MWh).
pub soc_initial_mwh: f64,
/// Minimum state of charge (MWh).
pub soc_min_mwh: f64,
/// Maximum state of charge (MWh).
pub soc_max_mwh: f64,
/// Generation-mode efficiency (typically 0.87-0.93).
pub efficiency_generate: f64,
/// Pump-mode efficiency (typically 0.85-0.92).
pub efficiency_pump: f64,
// --- head dependence ---
/// Piecewise-linear (soc_mwh, pmax_mw) — capacity vs reservoir level.
pub head_curve: Vec<(f64, f64)>,
// --- hydraulic constraints ---
/// Number of reversible units at this plant.
pub n_units: u32,
/// Total hydraulic throughput limit across all units (MW).
pub shared_penstock_mw_max: Option<f64>,
/// Environmental minimum generation (MW, downstream flow).
pub min_release_mw: f64,
/// Hydraulic ramp limit (MW/min, fish/flood protection).
pub ramp_rate_mw_per_min: Option<f64>,
// --- startup ---
/// Minutes to synchronize in generate mode.
pub startup_time_gen_min: f64,
/// Minutes to synchronize in pump mode.
pub startup_time_pump_min: f64,
/// Cost per start ($).
pub startup_cost: f64,
// --- market ---
/// Reserve offers keyed by product ID (generic reserve model).
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reserve_offers: Vec<crate::market::reserve::ReserveOffer>,
/// Custom qualification flags for reserve products.
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
pub qualifications: crate::market::reserve::QualificationMap,
}
impl PumpedHydroUnit {
/// Construct a pumped hydro unit with sensible defaults.
pub fn new(name: String, generator: GeneratorRef, capacity_mwh: f64) -> Self {
Self {
name,
generator,
variable_speed: false,
pump_mw_fixed: 0.0,
pump_mw_min: None,
pump_mw_max: None,
mode_transition_min: 5.0,
condenser_capable: false,
forbidden_zone: None,
upper_reservoir_mwh: capacity_mwh,
lower_reservoir_mwh: f64::MAX,
soc_initial_mwh: 0.5 * capacity_mwh,
soc_min_mwh: 0.0,
soc_max_mwh: capacity_mwh,
efficiency_generate: 0.90,
efficiency_pump: 0.87,
head_curve: Vec::new(),
n_units: 1,
shared_penstock_mw_max: None,
min_release_mw: 0.0,
ramp_rate_mw_per_min: None,
startup_time_gen_min: 5.0,
startup_time_pump_min: 10.0,
startup_cost: 0.0,
reserve_offers: Vec::new(),
qualifications: std::collections::HashMap::new(),
}
}
}