powers_rs/
subproblem.rs

1use crate::fcf;
2use crate::risk_measure;
3use crate::scenario;
4use crate::solver;
5use crate::state;
6use crate::stochastic_process;
7use crate::system;
8use std::sync::{Arc, Mutex};
9
10/// Helper function for removing the future cost term from the stage objective,
11/// a.k.a the `alpha` term, or the epigraphical variable, assuming the objective
12/// function is:
13///
14/// c^T x + `alpha`
15fn get_current_stage_objective(
16    total_stage_objective: f64,
17    solution: &solver::Solution,
18) -> f64 {
19    let future_objective = solution.colvalue.last().unwrap();
20    total_stage_objective - future_objective
21}
22
23/// Helper function for setting the same default solver options on
24/// every solved problem.
25fn set_default_solver_options(model: &mut solver::Model) {
26    model.set_option("presolve", "off");
27    model.set_option("solver", "simplex");
28    model.set_option("parallel", "off");
29    model.set_option("threads", 1);
30    model.set_option("primal_feasibility_tolerance", 1e-7);
31    model.set_option("dual_feasibility_tolerance", 1e-7);
32    model.set_option("time_limit", 300);
33}
34
35/// Helper function for setting the solver options when retrying a solve
36fn set_first_retry_solver_options(model: &mut solver::Model) {
37    model.set_option("primal_feasibility_tolerance", 1e-6);
38    model.set_option("dual_feasibility_tolerance", 1e-6);
39}
40
41/// Helper function for setting the solver options when retrying a solve
42fn set_second_retry_solver_options(model: &mut solver::Model) {
43    model.set_option("primal_feasibility_tolerance", 1e-5);
44    model.set_option("dual_feasibility_tolerance", 1e-5);
45}
46
47/// Helper function for setting the solver options when retrying a solve
48fn set_third_retry_solver_options(model: &mut solver::Model) {
49    model.set_option("simplex_strategy", 4);
50}
51
52/// Helper function for setting the solver options when retrying a solve
53fn set_final_retry_solver_options(model: &mut solver::Model) {
54    model.set_option("presolve", "on");
55    model.set_option("solver", "ipm");
56    model.set_option("primal_feasibility_tolerance", 1e-7);
57    model.set_option("dual_feasibility_tolerance", 1e-7);
58}
59
60/// Helper function for setting the solver options when retrying a solve
61fn set_retry_solver_options(model: &mut solver::Model, retry: usize) {
62    match retry {
63        1 => set_first_retry_solver_options(model),
64        2 => set_second_retry_solver_options(model),
65        3 => set_third_retry_solver_options(model),
66        4 => set_final_retry_solver_options(model),
67        _ => set_default_solver_options(model),
68    }
69}
70
71/// Helper accessor for indexing desired variables in each subproblem
72#[derive(Clone)]
73pub struct Variables {
74    pub deficit: Vec<usize>,
75    pub direct_exchange: Vec<usize>,
76    pub reverse_exchange: Vec<usize>,
77    pub thermal_gen: Vec<usize>,
78    pub turbined_flow: Vec<usize>,
79    pub spillage: Vec<usize>,
80    pub stored_volume: Vec<usize>,
81    pub inflow: Vec<usize>,
82    pub inflow_process: Vec<Vec<usize>>,
83    pub alpha: usize,
84}
85
86impl Variables {
87    pub fn with_capacity(system: &system::System) -> Self {
88        Self {
89            deficit: Vec::<usize>::with_capacity(system.meta.buses_count),
90            direct_exchange: Vec::<usize>::with_capacity(
91                system.meta.lines_count,
92            ),
93            reverse_exchange: Vec::<usize>::with_capacity(
94                system.meta.lines_count,
95            ),
96            thermal_gen: Vec::<usize>::with_capacity(
97                system.meta.thermals_count,
98            ),
99            turbined_flow: Vec::<usize>::with_capacity(system.meta.buses_count),
100            spillage: Vec::<usize>::with_capacity(system.meta.hydros_count),
101            stored_volume: Vec::<usize>::with_capacity(
102                system.meta.hydros_count,
103            ),
104            inflow: Vec::<usize>::with_capacity(system.meta.hydros_count),
105            inflow_process: Vec::<Vec<usize>>::with_capacity(
106                system.meta.hydros_count,
107            ),
108            alpha: 0,
109        }
110    }
111}
112
113/// Helper accessor for indexing desired variables in each subproblem
114#[derive(Clone)]
115pub struct Constraints {
116    pub load_balance: Vec<usize>,
117    pub hydro_balance: Vec<usize>,
118    pub inflow_process: Vec<Vec<usize>>,
119}
120
121impl Constraints {
122    pub fn with_capacity(system: &system::System) -> Self {
123        Self {
124            load_balance: Vec::<usize>::with_capacity(system.meta.buses_count),
125            hydro_balance: Vec::<usize>::with_capacity(
126                system.meta.hydros_count,
127            ),
128            inflow_process: Vec::<Vec<usize>>::with_capacity(
129                system.meta.hydros_count,
130            ),
131        }
132    }
133}
134
135/// A subproblem that contains a solver model and is associated to a single
136/// node in the computing graph
137
138#[derive(Clone)]
139pub struct Subproblem {
140    pub model: Option<solver::Model>,
141    pub state: Box<dyn state::State>,
142    pub variables: Variables,
143    pub constraints: Constraints,
144}
145
146impl Subproblem {
147    pub fn new(
148        system: &system::System,
149        state_choice: &str,
150        load_stochastic_process: &Box<
151            dyn stochastic_process::StochasticProcess,
152        >,
153        inflow_stochastic_process: &Box<
154            dyn stochastic_process::StochasticProcess,
155        >,
156    ) -> Self {
157        let state = state::factory(
158            state_choice,
159            system,
160            load_stochastic_process,
161            inflow_stochastic_process,
162        );
163        let mut pb = solver::Problem::new();
164        let variables = Subproblem::add_variables_to_subproblem(
165            &mut pb,
166            system,
167            &state,
168            load_stochastic_process,
169            inflow_stochastic_process,
170        );
171        let constraints = Subproblem::add_constraints_to_subproblem(
172            &mut pb,
173            &variables,
174            system,
175            &state,
176            load_stochastic_process,
177            inflow_stochastic_process,
178        );
179        Self::add_offset_to_subproblem(&mut pb, system);
180
181        let mut model = pb.optimise(solver::Sense::Minimise);
182        set_retry_solver_options(&mut model, 0);
183
184        Self {
185            model: Some(model),
186            state,
187            variables,
188            constraints,
189        }
190    }
191
192    fn add_variables_to_subproblem(
193        pb: &mut solver::Problem,
194        system: &system::System,
195        state: &Box<dyn state::State>,
196        load_stochastic_process: &Box<
197            dyn stochastic_process::StochasticProcess,
198        >,
199        inflow_stochastic_process: &Box<
200            dyn stochastic_process::StochasticProcess,
201        >,
202    ) -> Variables {
203        let deficit: Vec<usize> = system
204            .buses
205            .iter()
206            .map(|bus| pb.add_column(bus.deficit_cost, 0.0..))
207            .collect();
208        let direct_exchange: Vec<usize> = system
209            .lines
210            .iter()
211            .map(|line| {
212                pb.add_column(line.exchange_penalty, 0.0..line.direct_capacity)
213            })
214            .collect();
215        let reverse_exchange: Vec<usize> = system
216            .lines
217            .iter()
218            .map(|line| {
219                pb.add_column(line.exchange_penalty, 0.0..line.reverse_capacity)
220            })
221            .collect();
222        let thermal_gen: Vec<usize> = system
223            .thermals
224            .iter()
225            .map(|thermal| {
226                pb.add_column(
227                    thermal.cost,
228                    0.0..(thermal.max_generation - thermal.min_generation),
229                )
230            })
231            .collect();
232        let turbined_flow: Vec<usize> = system
233            .hydros
234            .iter()
235            .map(|hydro| {
236                pb.add_column(
237                    0.0,
238                    hydro.min_turbined_flow..hydro.max_turbined_flow,
239                )
240            })
241            .collect();
242        let spillage: Vec<usize> = system
243            .hydros
244            .iter()
245            .map(|hydro| pb.add_column(hydro.spillage_penalty, 0.0..))
246            .collect();
247        let stored_volume: Vec<usize> = system
248            .hydros
249            .iter()
250            .map(|hydro| {
251                pb.add_column(0.0, hydro.min_storage..hydro.max_storage)
252            })
253            .collect();
254        let inflow: Vec<usize> = system
255            .hydros
256            .iter()
257            .map(|_hydro| pb.add_column(0.0, 0.0..))
258            .collect();
259
260        // Adds inflow as variables, bounded at 0, which will be fixed in runtime
261        let inflow_process = state.add_variables_to_subproblem(
262            pb,
263            &load_stochastic_process,
264            &inflow_stochastic_process,
265        );
266
267        let alpha = pb.add_column(1.0, 0.0..);
268
269        Variables {
270            deficit,
271            direct_exchange,
272            reverse_exchange,
273            thermal_gen,
274            turbined_flow,
275            spillage,
276            stored_volume,
277            inflow,
278            inflow_process,
279            alpha,
280        }
281    }
282
283    fn add_constraints_to_subproblem(
284        pb: &mut solver::Problem,
285        variables: &Variables,
286        system: &system::System,
287        state: &Box<dyn state::State>,
288        load_stochastic_process: &Box<
289            dyn stochastic_process::StochasticProcess,
290        >,
291        inflow_stochastic_process: &Box<
292            dyn stochastic_process::StochasticProcess,
293        >,
294    ) -> Constraints {
295        // Adds load balance with 0.0 as RHS
296        let mut load_balance: Vec<usize> = vec![0; system.meta.buses_count];
297        for bus in system.buses.iter() {
298            let mut factors = vec![(variables.deficit[bus.id], 1.0)];
299            for thermal_id in bus.thermal_ids.iter() {
300                factors.push((variables.thermal_gen[*thermal_id], 1.0));
301            }
302            for hydro_id in bus.hydro_ids.iter() {
303                factors.push((
304                    variables.turbined_flow[*hydro_id],
305                    system.hydros.get(*hydro_id).unwrap().productivity,
306                ));
307            }
308            for line_id in bus.source_line_ids.iter() {
309                factors.push((variables.reverse_exchange[*line_id], 1.0));
310                factors.push((variables.direct_exchange[*line_id], -1.0));
311            }
312            for line_id in bus.target_line_ids.iter() {
313                factors.push((variables.direct_exchange[*line_id], 1.0));
314                factors.push((variables.reverse_exchange[*line_id], -1.0));
315            }
316            load_balance[bus.id] = pb.add_row(0.0..0.0, &factors);
317        }
318
319        // Adds hydro balance with 0.0 as RHS
320        let mut hydro_balance: Vec<usize> = vec![0; system.meta.hydros_count];
321        for hydro in system.hydros.iter() {
322            let mut factors: Vec<(usize, f64)> = vec![
323                (variables.stored_volume[hydro.id], 1.0),
324                (variables.turbined_flow[hydro.id], 1.0),
325                (variables.spillage[hydro.id], 1.0),
326                (variables.inflow[hydro.id], -1.0),
327            ];
328            for upstream_hydro_id in hydro.upstream_hydro_ids.iter() {
329                factors
330                    .push((variables.turbined_flow[*upstream_hydro_id], -1.0));
331                factors.push((variables.spillage[*upstream_hydro_id], -1.0));
332            }
333            hydro_balance[hydro.id] = pb.add_row(0.0..0.0, &factors);
334        }
335
336        // Adds inflow process as variables, bounded at 0, which will be fixed in runtime
337        let inflow_process = state.add_constraints_to_subproblem(
338            pb,
339            variables,
340            load_stochastic_process,
341            inflow_stochastic_process,
342        );
343
344        Constraints {
345            load_balance,
346            hydro_balance,
347            inflow_process,
348        }
349    }
350
351    fn add_offset_to_subproblem(
352        pb: &mut solver::Problem,
353        system: &system::System,
354    ) {
355        let mut offset = 0.0;
356        for thermal in system.thermals.iter() {
357            offset += thermal.cost * thermal.min_generation;
358        }
359        pb.offset = offset;
360    }
361
362    fn set_load_balance_rhs(&mut self, loads: &[f64]) {
363        if let Some(model) = self.model.as_mut() {
364            for (index, row) in self.constraints.load_balance.iter().enumerate()
365            {
366                model.change_rows_bounds(*row, loads[index], loads[index]);
367            }
368        }
369    }
370
371    fn set_hydro_balance_rhs(&mut self, initial_storages: &[f64]) {
372        if let Some(model) = self.model.as_mut() {
373            for (index, row) in
374                self.constraints.hydro_balance.iter().enumerate()
375            {
376                model.change_rows_bounds(
377                    *row,
378                    initial_storages[index],
379                    initial_storages[index],
380                );
381            }
382        }
383    }
384
385    pub fn update_with_current_trajectory(
386        &mut self,
387        realizations: Vec<&Realization>,
388    ) {
389        let realization = realizations.last().unwrap();
390        self.set_hydro_balance_rhs(&realization.final_storage);
391    }
392
393    pub fn update_with_current_realization(
394        &mut self,
395        realization: &Realization,
396    ) {
397        self.state.update_with_current_realization(realization);
398    }
399
400    pub fn compute_new_cut(
401        &self,
402        forward_trajectory: &[&Realization],
403        branching_realizations: &Vec<Realization>,
404        risk_measure: &Box<dyn risk_measure::RiskMeasure>,
405    ) -> fcf::CutStatePair {
406        // this only works when all nodes have the same state definition??
407        let mut visited_state = self.state.clone();
408        let cut = visited_state.compute_new_cut(
409            risk_measure,
410            forward_trajectory,
411            branching_realizations,
412        );
413        fcf::CutStatePair::new(cut, visited_state)
414    }
415
416    pub fn add_cut_and_evaluate_cut_selection(
417        &mut self,
418        cut_state_pair: fcf::CutStatePair,
419        future_cost_function: Arc<Mutex<fcf::FutureCostFunction>>,
420    ) {
421        let mut cut = cut_state_pair.cut;
422        let mut visited_state = cut_state_pair.state;
423
424        if let Some(model) = self.model.as_mut() {
425            self.state.add_cut_constraint_to_model(
426                &mut cut,
427                &self.variables,
428                model,
429            );
430        }
431        let mut fcf = future_cost_function.lock().unwrap();
432        cut.id = fcf.cut_pool.total_cut_count;
433        fcf.update_cut_pool_on_add(cut.id);
434        fcf.eval_new_cut_domination(&mut cut);
435
436        fcf.add_cut(cut);
437
438        // Obtains returning cut ids, based on cut selection
439        let returning_cut_ids =
440            fcf.update_old_cuts_domination(&mut visited_state);
441
442        fcf.add_state(visited_state);
443
444        // Obtains removing cut ids, based on cut selection
445        let mut removing_cut_ids = Vec::<usize>::new();
446        for cut in fcf.cut_pool.pool.iter_mut() {
447            if (cut.non_dominated_state_count <= 0) && cut.active {
448                removing_cut_ids.push(cut.id);
449            }
450        }
451
452        // Returns cuts to model
453        for cut_id in returning_cut_ids.iter() {
454            let cut = fcf.cut_pool.pool.get_mut(*cut_id).unwrap();
455            if let Some(model) = self.model.as_mut() {
456                self.state.add_cut_constraint_to_model(
457                    cut,
458                    &self.variables,
459                    model,
460                );
461            }
462            fcf.update_cut_pool_on_return(*cut_id);
463        }
464
465        // Removes cuts from model
466        for cut_id in removing_cut_ids.iter() {
467            let cut_index = fcf.get_active_cut_index_by_id(*cut_id);
468            let row_index = self.first_cut_row_index() + cut_index;
469            if let Some(model) = self.model.as_mut() {
470                model.delete_row(row_index).unwrap();
471            }
472            fcf.update_cut_pool_on_remove(*cut_id, cut_index);
473        }
474    }
475
476    fn set_uncertainties<'a>(
477        &mut self,
478        bus_loads: &[f64],
479        hydros_inflow: &[f64],
480    ) {
481        self.set_load_balance_rhs(bus_loads);
482        if let Some(model) = self.model.as_mut() {
483            self.state.set_inflows_in_subproblem(
484                model,
485                &self.constraints,
486                hydros_inflow,
487            );
488        }
489    }
490
491    fn retry_solve(&mut self) {
492        let mut retry: usize = 0;
493        if let Some(model) = self.model.as_mut() {
494            loop {
495                if retry > 4 {
496                    panic!("Error while solving model");
497                }
498                model.solve();
499
500                match model.status() {
501                    solver::HighsModelStatus::Optimal => {
502                        if retry != 0 {
503                            set_default_solver_options(model);
504                        }
505                        return;
506                    }
507                    solver::HighsModelStatus::Infeasible => {
508                        retry += 1;
509                        set_retry_solver_options(model, retry);
510                    }
511                    _ => panic!("Error while solving model"),
512                }
513            }
514        }
515    }
516
517    fn first_cut_row_index(&self) -> usize {
518        self.constraints
519            .inflow_process
520            .last()
521            .unwrap()
522            .last()
523            .unwrap()
524            + 1
525    }
526
527    pub fn realize_uncertainties(
528        &mut self,
529        noises: &scenario::SampledBranchingNoises,
530        load_stochastic_process: &Box<
531            dyn stochastic_process::StochasticProcess,
532        >,
533        inflow_stochastic_process: &Box<
534            dyn stochastic_process::StochasticProcess,
535        >,
536        realization_container: &mut Realization,
537    ) -> Result<(), String> {
538        let load = load_stochastic_process.realize(noises.get_load_noises());
539        let inflow_noises =
540            inflow_stochastic_process.realize(noises.get_inflow_noises());
541
542        self.set_uncertainties(load, inflow_noises);
543
544        self.retry_solve();
545
546        match &self.model {
547            Some(model) => match model.status() {
548                solver::HighsModelStatus::Optimal => {
549                    let mut solution = model.get_solution();
550                    self.slice_solution_rows_to_problem_constraints(
551                        &mut solution,
552                    );
553
554                    // basis
555                    realization_container.basis.clone_from(&model.get_basis());
556
557                    // costs
558                    realization_container.total_stage_objective =
559                        model.get_objective_value();
560                    realization_container.current_stage_objective =
561                        get_current_stage_objective(
562                            realization_container.total_stage_objective,
563                            &solution,
564                        );
565
566                    // bus results
567                    self.get_deficit_from_solution(
568                        &solution,
569                        realization_container,
570                    );
571                    self.get_marginal_cost_from_solution(
572                        &solution,
573                        realization_container,
574                    );
575                    // line results
576                    self.get_net_exchange_from_solution(
577                        &solution,
578                        realization_container,
579                    );
580                    // thermal results
581                    self.get_thermal_gen_from_solution(
582                        &solution,
583                        realization_container,
584                    );
585                    // hydro results
586                    self.get_inflow_from_solution(
587                        &solution,
588                        realization_container,
589                    );
590                    self.get_final_storage_from_solution(
591                        &solution,
592                        realization_container,
593                    );
594                    self.get_turbined_flow_from_solution(
595                        &solution,
596                        realization_container,
597                    );
598                    self.get_spillage_from_solution(
599                        &solution,
600                        realization_container,
601                    );
602                    self.get_water_values_from_solution(
603                        &solution,
604                        realization_container,
605                    );
606
607                    model.clear_solver();
608                    Ok(())
609                }
610                _ => Err(format!("Error while solving subproblem: {:?}", model.status())),
611            },
612            None => Err("Error while solving subproblem: Model is None".to_string()),
613        }
614    }
615
616    fn get_deficit_from_solution(
617        &self,
618        solution: &solver::Solution,
619        realization_container: &mut Realization,
620    ) {
621        let first = *self.variables.deficit.first().unwrap();
622        let last = *self.variables.deficit.last().unwrap() + 1;
623        realization_container
624            .deficit
625            .clone_from_slice(&solution.colvalue[first..last]);
626    }
627
628    fn get_net_exchange_from_solution(
629        &self,
630        solution: &solver::Solution,
631        realization_container: &mut Realization,
632    ) {
633        if !self.variables.direct_exchange.is_empty() {
634            let direct_first = *self.variables.direct_exchange.first().unwrap();
635            let direct_last =
636                *self.variables.direct_exchange.last().unwrap() + 1;
637            let reverse_first =
638                *self.variables.reverse_exchange.first().unwrap();
639            let reverse_last =
640                *self.variables.reverse_exchange.last().unwrap() + 1;
641            realization_container.exchange.clone_from_slice(
642                &solution.colvalue[direct_first..direct_last],
643            );
644            realization_container
645                .exchange
646                .iter_mut()
647                .zip(&solution.colvalue[reverse_first..reverse_last])
648                .for_each(|(direct, reverse)| *direct -= *reverse);
649        }
650    }
651
652    fn get_thermal_gen_from_solution(
653        &self,
654        solution: &solver::Solution,
655        realization_container: &mut Realization,
656    ) {
657        if !self.variables.thermal_gen.is_empty() {
658            let first = *self.variables.thermal_gen.first().unwrap();
659            let last = *self.variables.thermal_gen.last().unwrap() + 1;
660            realization_container
661                .thermal_generation
662                .clone_from_slice(&solution.colvalue[first..last]);
663        }
664    }
665
666    fn get_spillage_from_solution(
667        &self,
668        solution: &solver::Solution,
669        realization_container: &mut Realization,
670    ) {
671        let first = *self.variables.spillage.first().unwrap();
672        let last = *self.variables.spillage.last().unwrap() + 1;
673        realization_container
674            .spillage
675            .clone_from_slice(&solution.colvalue[first..last]);
676    }
677
678    fn get_turbined_flow_from_solution(
679        &self,
680        solution: &solver::Solution,
681        realization_container: &mut Realization,
682    ) {
683        let first = *self.variables.turbined_flow.first().unwrap();
684        let last = *self.variables.turbined_flow.last().unwrap() + 1;
685        realization_container
686            .turbined_flow
687            .clone_from_slice(&solution.colvalue[first..last]);
688    }
689
690    fn get_final_storage_from_solution(
691        &self,
692        solution: &solver::Solution,
693        realization_container: &mut Realization,
694    ) {
695        let first = *self.variables.stored_volume.first().unwrap();
696        let last = *self.variables.stored_volume.last().unwrap() + 1;
697        realization_container
698            .final_storage
699            .clone_from_slice(&solution.colvalue[first..last]);
700    }
701
702    fn get_inflow_from_solution(
703        &self,
704        solution: &solver::Solution,
705        realization_container: &mut Realization,
706    ) {
707        let first = *self.variables.inflow.first().unwrap();
708        let last = *self.variables.inflow.last().unwrap() + 1;
709        realization_container
710            .inflow
711            .clone_from_slice(&solution.colvalue[first..last]);
712    }
713
714    fn get_water_values_from_solution(
715        &self,
716        solution: &solver::Solution,
717        realization_container: &mut Realization,
718    ) {
719        let first = *self.constraints.hydro_balance.first().unwrap();
720        let last = *self.constraints.hydro_balance.last().unwrap() + 1;
721        realization_container
722            .water_value
723            .clone_from_slice(&solution.rowdual[first..last]);
724    }
725
726    fn get_marginal_cost_from_solution(
727        &self,
728        solution: &solver::Solution,
729        realization_container: &mut Realization,
730    ) {
731        let first = *self.constraints.load_balance.first().unwrap();
732        let last = *self.constraints.load_balance.last().unwrap() + 1;
733        realization_container
734            .marginal_cost
735            .clone_from_slice(&solution.rowdual[first..last]);
736    }
737
738    fn slice_solution_rows_to_problem_constraints(
739        &self,
740        solution: &mut solver::Solution,
741    ) {
742        let end = *self
743            .constraints
744            .inflow_process
745            .last()
746            .unwrap()
747            .last()
748            .unwrap()
749            + 1;
750        solution.rowvalue.truncate(end);
751        solution.rowdual.truncate(end);
752    }
753}
754
755#[derive(Debug, PartialEq, Clone)]
756pub enum StudyPeriodKind {
757    PreStudy,
758    Study,
759    PostStudy,
760}
761
762#[derive(Debug, Clone)]
763pub struct Realization {
764    pub kind: StudyPeriodKind,
765    pub loads: Vec<f64>,
766    pub deficit: Vec<f64>,
767    pub exchange: Vec<f64>,
768    pub inflow: Vec<f64>,
769    pub turbined_flow: Vec<f64>,
770    pub spillage: Vec<f64>,
771    pub thermal_generation: Vec<f64>,
772    pub water_value: Vec<f64>,
773    pub marginal_cost: Vec<f64>,
774    pub current_stage_objective: f64,
775    pub total_stage_objective: f64,
776    pub final_storage: Vec<f64>,
777    pub basis: solver::Basis,
778}
779
780impl Realization {
781    pub fn new(
782        loads: Vec<f64>,
783        deficit: Vec<f64>,
784        exchange: Vec<f64>,
785        inflow: Vec<f64>,
786        turbined_flow: Vec<f64>,
787        spillage: Vec<f64>,
788        thermal_generation: Vec<f64>,
789        water_value: Vec<f64>,
790        marginal_cost: Vec<f64>,
791        current_stage_objective: f64,
792        total_stage_objective: f64,
793        final_storage: Vec<f64>,
794        basis: solver::Basis,
795    ) -> Self {
796        Self {
797            kind: StudyPeriodKind::Study,
798            loads,
799            deficit,
800            exchange,
801            inflow,
802            turbined_flow,
803            spillage,
804            thermal_generation,
805            water_value,
806            marginal_cost,
807            current_stage_objective,
808            total_stage_objective,
809            final_storage,
810            basis,
811        }
812    }
813
814    pub fn with_capacity(
815        kind: &StudyPeriodKind,
816        system: &system::System,
817    ) -> Self {
818        Self {
819            kind: kind.clone(),
820            loads: vec![0.0; system.meta.buses_count],
821            deficit: vec![0.0; system.meta.buses_count],
822            exchange: vec![0.0; system.meta.lines_count],
823            inflow: vec![0.0; system.meta.hydros_count],
824            turbined_flow: vec![0.0; system.meta.hydros_count],
825            spillage: vec![0.0; system.meta.hydros_count],
826            thermal_generation: vec![0.0; system.meta.thermals_count],
827            water_value: vec![0.0; system.meta.hydros_count],
828            marginal_cost: vec![0.0; system.meta.buses_count],
829            current_stage_objective: 0.0,
830            total_stage_objective: 0.0,
831            final_storage: vec![0.0; system.meta.hydros_count],
832            basis: solver::Basis::new(),
833        }
834    }
835}
836
837impl Default for Realization {
838    fn default() -> Self {
839        Self {
840            kind: StudyPeriodKind::Study,
841            loads: vec![],
842            deficit: vec![],
843            exchange: vec![],
844            inflow: vec![],
845            turbined_flow: vec![],
846            spillage: vec![],
847            thermal_generation: vec![],
848            water_value: vec![],
849            marginal_cost: vec![],
850            current_stage_objective: 0.0,
851            total_stage_objective: 0.0,
852            final_storage: vec![],
853            basis: solver::Basis::new(),
854        }
855    }
856}
857
858#[cfg(test)]
859mod tests {
860
861    use super::*;
862
863    #[test]
864    fn test_create_subproblem_with_default_system() {
865        let system = system::System::default();
866        let load_stochastic_process = stochastic_process::factory("naive");
867        let inflow_stochastic_process = stochastic_process::factory("naive");
868        let subproblem = Subproblem::new(
869            &system,
870            "storage",
871            &load_stochastic_process,
872            &inflow_stochastic_process,
873        );
874        assert_eq!(subproblem.variables.deficit.len(), 1);
875        assert_eq!(subproblem.variables.direct_exchange.len(), 0);
876        assert_eq!(subproblem.variables.reverse_exchange.len(), 0);
877        assert_eq!(subproblem.variables.thermal_gen.len(), 2);
878        assert_eq!(subproblem.variables.turbined_flow.len(), 1);
879        assert_eq!(subproblem.variables.spillage.len(), 1);
880        assert_eq!(subproblem.variables.stored_volume.len(), 1);
881        assert_eq!(subproblem.variables.inflow.len(), 1);
882    }
883
884    #[test]
885    fn test_solve_subproblem_with_default_system() {
886        let system = system::System::default();
887        let load_stochastic_process = stochastic_process::factory("naive");
888        let inflow_stochastic_process = stochastic_process::factory("naive");
889        let mut subproblem = Subproblem::new(
890            &system,
891            "storage",
892            &load_stochastic_process,
893            &inflow_stochastic_process,
894        );
895        let inflow = [0.0];
896        let initial_storage = [83.333];
897        let load = [50.0];
898
899        subproblem.set_hydro_balance_rhs(&initial_storage);
900        subproblem.set_uncertainties(&load, &inflow);
901
902        if let Some(mut model) = subproblem.model {
903            model.solve();
904            assert_eq!(model.status(), solver::HighsModelStatus::Optimal);
905        }
906    }
907
908    #[test]
909    fn test_get_solution_cost_with_default_system() {
910        let system = system::System::default();
911        let load_stochastic_process = stochastic_process::factory("naive");
912        let inflow_stochastic_process = stochastic_process::factory("naive");
913        let mut subproblem = Subproblem::new(
914            &system,
915            "storage",
916            &load_stochastic_process,
917            &inflow_stochastic_process,
918        );
919        let inflow = [0.0];
920        let initial_storage = [23.333];
921        let load = [50.0];
922
923        subproblem.set_hydro_balance_rhs(&initial_storage);
924
925        subproblem.set_uncertainties(&load, &inflow);
926
927        if let Some(mut model) = subproblem.model {
928            model.solve();
929            assert_eq!(model.get_objective_value(), 191.67000000000002);
930        }
931    }
932}