1use crate::graph;
2use crate::initial_condition;
3use crate::scenario;
4use crate::sddp;
5use crate::subproblem;
6use crate::system;
7use rand_distr::{LogNormal, Normal};
8use serde::Deserialize;
9use serde_json;
10use std::fs;
11
12#[derive(Deserialize)]
13pub struct Config {
14 pub num_iterations: usize,
15 pub num_forward_passes: usize,
16 pub num_simulation_scenarios: usize,
17 pub seed: u64,
18}
19
20pub fn read_config_input(filepath: &str) -> Config {
21 let contents =
22 fs::read_to_string(filepath).expect("Error while reading config file");
23 let parsed: Config = serde_json::from_str(&contents).unwrap();
24 parsed
25}
26
27#[derive(Deserialize)]
28pub struct BusInput {
29 pub id: usize,
30 pub deficit_cost: f64,
31}
32
33#[derive(Deserialize)]
34pub struct LineInput {
35 pub id: usize,
36 pub source_bus_id: usize,
37 pub target_bus_id: usize,
38 pub direct_capacity: f64,
39 pub reverse_capacity: f64,
40 pub exchange_penalty: f64,
41}
42
43#[derive(Deserialize)]
44pub struct ThermalInput {
45 pub id: usize,
46 pub bus_id: usize,
47 pub cost: f64,
48 pub min_generation: f64,
49 pub max_generation: f64,
50}
51
52#[derive(Deserialize)]
53pub struct HydroInput {
54 pub id: usize,
55 pub downstream_hydro_id: Option<usize>,
56 pub bus_id: usize,
57 pub productivity: f64,
58 pub min_storage: f64,
59 pub max_storage: f64,
60 pub min_turbined_flow: f64,
61 pub max_turbined_flow: f64,
62 pub spillage_penalty: f64,
63}
64
65#[derive(Deserialize)]
66pub struct SystemInput {
67 pub buses: Vec<BusInput>,
68 pub lines: Vec<LineInput>,
69 pub thermals: Vec<ThermalInput>,
70 pub hydros: Vec<HydroInput>,
71}
72
73pub fn read_system_input(filepath: &str) -> SystemInput {
74 let contents =
75 fs::read_to_string(filepath).expect("Error while reading config file");
76 let parsed: SystemInput = serde_json::from_str(&contents).unwrap();
77 parsed
78}
79
80fn validate_id_range(ids: &[usize], elem_name: &str) {
81 let num_elements = ids.len();
82 for elem_id in 0..num_elements {
83 if ids.iter().find(|id| **id == elem_id).is_none() {
84 panic!("ID {} not found for {}", elem_id, elem_name);
85 }
86 }
87}
88
89fn validate_entity_count(ids: &[usize], count: usize, elem_name: &str) {
90 let entity_count = ids.len();
91 if entity_count != count {
92 panic!(
93 "Error matching recourse for {}: {} != {}",
94 elem_name, entity_count, count
95 );
96 }
97}
98
99impl SystemInput {
100 pub fn build_sddp_system(&self) -> system::System {
101 let buses_ids: Vec<usize> = self.buses.iter().map(|b| b.id).collect();
103 let lines_ids: Vec<usize> = self.lines.iter().map(|b| b.id).collect();
104 let thermals_ids: Vec<usize> =
105 self.thermals.iter().map(|b| b.id).collect();
106 let hydros_ids: Vec<usize> = self.hydros.iter().map(|b| b.id).collect();
107 validate_id_range(&buses_ids, "buses");
108 validate_id_range(&lines_ids, "lines");
109 validate_id_range(&thermals_ids, "thermals");
110 validate_id_range(&hydros_ids, "hydros");
111
112 let num_buses = buses_ids.len();
113 let mut buses = Vec::<system::Bus>::with_capacity(num_buses);
114 for id in 0..num_buses {
115 let bus = self.buses.iter().find(|b| b.id == id).unwrap();
116 buses.push(system::Bus::new(id, bus.deficit_cost));
117 }
118
119 let num_lines = lines_ids.len();
120 let mut lines = Vec::<system::Line>::with_capacity(num_lines);
121 for id in 0..num_lines {
122 let line = self.lines.iter().find(|l| l.id == id).unwrap();
123 lines.push(system::Line::new(
124 id,
125 line.source_bus_id,
126 line.target_bus_id,
127 line.direct_capacity,
128 line.reverse_capacity,
129 line.exchange_penalty,
130 ));
131 }
132
133 let num_thermals = thermals_ids.len();
134 let mut thermals = Vec::<system::Thermal>::with_capacity(num_thermals);
135 for id in 0..num_thermals {
136 let thermal = self.thermals.iter().find(|t| t.id == id).unwrap();
137 thermals.push(system::Thermal::new(
138 id,
139 thermal.bus_id,
140 thermal.cost,
141 thermal.min_generation,
142 thermal.max_generation,
143 ));
144 }
145
146 let num_hydros = hydros_ids.len();
147 let mut hydros = Vec::<system::Hydro>::with_capacity(num_hydros);
148 for id in 0..num_hydros {
149 let hydro = self.hydros.iter().find(|h| h.id == id).unwrap();
150 hydros.push(system::Hydro::new(
151 id,
152 hydro.downstream_hydro_id,
153 hydro.bus_id,
154 hydro.productivity,
155 hydro.min_storage,
156 hydro.max_storage,
157 hydro.min_turbined_flow,
158 hydro.max_turbined_flow,
159 hydro.spillage_penalty,
160 ));
161 }
162
163 system::System::new(buses, lines, thermals, hydros)
164 }
165}
166
167#[derive(Deserialize)]
168pub struct GraphNodeInput {
169 pub id: usize,
170 pub stage_id: usize,
171 pub season_id: usize,
172 pub start_date: String,
173 pub end_date: String,
174 pub risk_measure: String,
175 pub load_stochastic_process: String,
176 pub inflow_stochastic_process: String,
177 pub state_variables: String,
178}
179
180#[derive(Deserialize)]
181pub struct GraphEdgeInput {
182 pub source_id: usize,
183 pub target_id: usize,
184 pub probability: f64,
185 pub discount_rate: f64,
186}
187
188#[derive(Deserialize)]
189pub struct GraphInput {
190 pub nodes: Vec<GraphNodeInput>,
191 pub edges: Vec<GraphEdgeInput>,
192}
193
194pub fn read_graph_input(filepath: &str) -> GraphInput {
195 let contents =
196 fs::read_to_string(filepath).expect("Error while reading graph file");
197 let parsed: GraphInput = serde_json::from_str(&contents).unwrap();
198 parsed
199}
200
201impl GraphInput {
202 fn add_sddp_study_period_to_graph(
203 &self,
204 graph: &mut graph::DirectedGraph<sddp::NodeData>,
205 system_input: &SystemInput,
206 ) -> Result<(), String> {
207 for node_input in self.nodes.iter() {
209 let r = graph.add_node(sddp::NodeData::new(
210 node_input.id as isize,
211 node_input.stage_id,
212 node_input.season_id,
213 &node_input.start_date,
214 &node_input.end_date,
215 subproblem::StudyPeriodKind::Study,
216 system_input.build_sddp_system(),
217 &node_input.risk_measure,
218 &node_input.load_stochastic_process,
219 &node_input.inflow_stochastic_process,
220 &node_input.state_variables,
221 )?);
222 if r.is_err() {
223 panic!("Error while building graph in node {}", node_input.id);
224 }
225 }
226 for edge_input in self.edges.iter() {
227 let source_id = graph
228 .get_node_id_with(|node_data| {
229 node_data.id == edge_input.source_id as isize
230 })
231 .expect(&format!(
232 "Error adding edge {} -> {}",
233 edge_input.source_id, edge_input.target_id
234 ));
235 let target_id = graph
236 .get_node_id_with(|node_data| {
237 node_data.id == edge_input.target_id as isize
238 })
239 .unwrap();
240 let r = graph.add_edge(source_id, target_id);
241 if r.is_err() {
242 panic!(
243 "Error while building graph in edge {} -> {}",
244 edge_input.source_id, edge_input.target_id
245 );
246 }
247 }
248 Ok(())
249 }
250
251 fn add_sddp_pre_study_period_to_graph(
252 &self,
253 graph: &mut graph::DirectedGraph<sddp::NodeData>,
254 system_input: &SystemInput,
255 ) -> Result<(), String> {
256 let initial_condition_node_id = graph
257 .add_node(sddp::NodeData::new(
258 -1,
259 0,
260 0,
261 "1970-01-01T00:00:00Z",
262 "1970-01-01T00:00:00Z",
263 subproblem::StudyPeriodKind::PreStudy,
264 system_input.build_sddp_system(),
265 "expectation",
266 "naive",
267 "naive",
268 "storage",
269 )?)
270 .unwrap();
271 graph
272 .add_edge(initial_condition_node_id, self.nodes.get(0).unwrap().id)
273 .unwrap();
274 Ok(())
275 }
276
277 pub fn build_sddp_graph(
278 &self,
279 system_input: &SystemInput,
280 ) -> Result<graph::DirectedGraph<sddp::NodeData>, String> {
281 let mut g = graph::DirectedGraph::<sddp::NodeData>::new();
282
283 self.add_sddp_study_period_to_graph(&mut g, system_input)?;
284 self.add_sddp_pre_study_period_to_graph(&mut g, system_input)?;
285 Ok(g)
286 }
287}
288
289#[derive(Deserialize)]
290pub struct InitialStorage {
291 pub hydro_id: usize,
292 pub value: f64,
293}
294
295#[derive(Deserialize)]
296pub struct PastInflow {
297 pub hydro_id: usize,
298 pub lag: usize,
299 pub value: f64,
300}
301
302#[derive(Deserialize)]
303pub struct InitialConditionInput {
304 pub storage: Vec<InitialStorage>,
305 pub inflow: Vec<PastInflow>,
306}
307
308#[derive(Deserialize)]
309pub struct NormalParams {
310 pub mu: f64,
311 pub sigma: f64,
312}
313
314#[derive(Deserialize)]
315pub struct LoadDistribution {
316 pub bus_id: usize,
317 pub normal: NormalParams,
318}
319
320#[derive(Deserialize)]
321pub struct LognormalParams {
322 pub mu: f64,
323 pub sigma: f64,
324}
325
326#[derive(Deserialize)]
327pub struct InflowDistribution {
328 pub hydro_id: usize,
329 pub lognormal: LognormalParams,
330}
331
332#[derive(Deserialize)]
333pub struct UncertaintyDistributions {
334 pub load: Vec<LoadDistribution>,
335 pub inflow: Vec<InflowDistribution>,
336}
337
338#[derive(Deserialize)]
339pub struct SeasonalUncertaintyInput {
340 pub season_id: usize,
341 pub num_branchings: usize,
342 pub distributions: UncertaintyDistributions,
343}
344
345#[derive(Deserialize)]
346pub struct Recourse {
347 pub initial_condition: InitialConditionInput,
348 pub uncertainties: Vec<SeasonalUncertaintyInput>,
349}
350
351pub fn read_recourse_input(filepath: &str) -> Recourse {
352 let contents = fs::read_to_string(filepath)
353 .expect("Error while reading recourse file");
354 let parsed: Recourse = serde_json::from_str(&contents).unwrap();
355 parsed
356}
357
358impl Recourse {
359 pub fn build_sddp_initial_condition(
360 &self,
361 ) -> initial_condition::InitialCondition {
362 let initial_condition_hydro_ids: Vec<usize> = self
363 .initial_condition
364 .storage
365 .iter()
366 .map(|s| s.hydro_id)
367 .collect();
368 validate_id_range(&initial_condition_hydro_ids, "initial storages");
369 let num_hydros = initial_condition_hydro_ids.len();
370 let mut storage = Vec::<f64>::with_capacity(num_hydros);
371 for id in 0..num_hydros {
372 let s = self
373 .initial_condition
374 .storage
375 .iter()
376 .find(|s| s.hydro_id == id)
377 .unwrap();
378 storage.push(s.value);
379 }
380 initial_condition::InitialCondition::new(storage, vec![])
381 }
382
383 pub fn generate_sddp_noises(
384 &self,
385 g: &graph::DirectedGraph<sddp::NodeData>,
386 seed: u64,
387 ) -> scenario::SAA {
388 let mut scenario_generator = scenario::NoiseGenerator::new();
389
390 for node_id in 0..g.node_count() {
391 let node = g.get_node(node_id).unwrap();
392 let num_buses = node.data.system.meta.buses_count;
393 let num_hydros = node.data.system.meta.hydros_count;
394 let node_uncertainties = self
395 .uncertainties
396 .iter()
397 .find(|s| s.season_id == node.data.season_id);
398 match node_uncertainties {
399 Some(node_uncertainties) => {
400 let scenario_bus_ids: Vec<usize> = node_uncertainties
401 .distributions
402 .load
403 .iter()
404 .map(|s| s.bus_id)
405 .collect();
406 validate_id_range(&scenario_bus_ids, "load distributions");
407 validate_entity_count(
408 scenario_bus_ids.as_slice(),
409 num_buses,
410 "bus loads",
411 );
412 let scenario_hydro_ids: Vec<usize> = node_uncertainties
413 .distributions
414 .inflow
415 .iter()
416 .map(|s| s.hydro_id)
417 .collect();
418 validate_id_range(
419 &scenario_hydro_ids,
420 "inflow distributions",
421 );
422 validate_entity_count(
423 scenario_hydro_ids.as_slice(),
424 num_hydros,
425 "hydro inflows",
426 );
427 let mut load_distributions =
428 Vec::<Normal<f64>>::with_capacity(num_buses);
429 let mut inflow_distributions =
430 Vec::<LogNormal<f64>>::with_capacity(num_hydros);
431 for id in 0..num_buses {
432 let load_distribution = node_uncertainties
433 .distributions
434 .load
435 .iter()
436 .find(|s| s.bus_id == id)
437 .unwrap();
438 load_distributions.push(
439 Normal::new(
440 load_distribution.normal.mu,
441 load_distribution.normal.sigma,
442 )
443 .unwrap(),
444 );
445 }
446 for id in 0..num_hydros {
447 let inflow_distribution = node_uncertainties
448 .distributions
449 .inflow
450 .iter()
451 .find(|s| s.hydro_id == id)
452 .unwrap();
453 inflow_distributions.push(
454 LogNormal::new(
455 inflow_distribution.lognormal.mu,
456 inflow_distribution.lognormal.sigma,
457 )
458 .unwrap(),
459 );
460 }
461 scenario_generator.add_node_generator(
462 load_distributions,
463 inflow_distributions,
464 node_uncertainties.num_branchings,
465 );
466 }
467 None => panic!(
468 "Could not find load distributions for node {}",
469 node.id
470 ),
471 }
472 }
473 scenario_generator.generate(seed)
474 }
475}
476
477pub struct Input {
478 pub config: Config,
479 pub system: SystemInput,
480 pub graph: GraphInput,
481 pub recourse: Recourse,
482}
483
484impl Input {
485 pub fn build(path: &str) -> Self {
486 let config = read_config_input(&(path.to_owned() + "/config.json"));
487 let system = read_system_input(&(path.to_owned() + "/system.json"));
488 let graph = read_graph_input(&(path.to_owned() + "/graph.json"));
489 let recourse =
490 read_recourse_input(&(path.to_owned() + "/recourse.json"));
491 return Self {
492 config,
493 system,
494 graph,
495 recourse,
496 };
497 }
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503
504 #[test]
505 fn test_read_config() {
506 let filepath = "example/config.json";
507 let config = read_config_input(filepath);
508 assert_eq!(config.num_iterations, 32);
509 assert_eq!(config.num_simulation_scenarios, 128);
510 }
511
512 #[test]
513 fn test_read_system() {
514 let filepath = "example/system.json";
515 let system = read_system_input(filepath);
516 assert_eq!(system.buses.len(), 1);
517 assert_eq!(system.lines.len(), 0);
518 assert_eq!(system.thermals.len(), 2);
519 assert_eq!(system.hydros.len(), 1);
520 }
521
522 #[test]
523 fn test_build_sddp_system() {
524 let filepath = "example/system.json";
525 let system = read_system_input(filepath);
526 system.build_sddp_system();
527 }
528
529 #[test]
530 fn test_read_recourse() {
531 let filepath = "example/recourse.json";
532 let recourse = read_recourse_input(filepath);
533 assert_eq!(recourse.initial_condition.storage.len(), 1);
534 assert_eq!(recourse.uncertainties.len(), 12);
535 }
536
537 #[test]
538 fn test_read_input() {
539 let path = "example";
540 let input = Input::build(path);
541 assert_eq!(input.config.num_iterations, 32);
542 }
543}