powers_rs/
input.rs

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        // ensure valid id ranges (0..)
102        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        // Build study graph
208        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}