muse2/simulation/investment/appraisal/
optimisation.rs1use super::DemandMap;
3use super::coefficients::CoefficientsMap;
4use super::constraints::{
5 add_activity_constraints, add_capacity_constraint, add_demand_constraints,
6};
7use crate::asset::AssetRef;
8use crate::commodity::Commodity;
9use crate::simulation::optimisation::solve_optimal;
10use crate::time_slice::{TimeSliceID, TimeSliceInfo};
11use crate::units::{Activity, Capacity, Flow};
12use anyhow::Result;
13use highs::{RowProblem as Problem, Sense};
14use indexmap::IndexMap;
15
16pub type Variable = highs::Col;
18
19struct VariableMap {
21 capacity_var: Variable,
23 activity_vars: IndexMap<TimeSliceID, Variable>,
25 unmet_demand_vars: IndexMap<TimeSliceID, Variable>,
27}
28
29pub struct ResultsMap {
31 pub capacity: Capacity,
33 pub activity: IndexMap<TimeSliceID, Activity>,
35 pub unmet_demand: DemandMap,
37}
38
39fn add_variables(problem: &mut Problem, cost_coefficients: &CoefficientsMap) -> VariableMap {
41 let capacity_var = problem.add_column(cost_coefficients.capacity_coefficient.value(), 0.0..);
43
44 let mut activity_vars = IndexMap::new();
46 for (time_slice, cost) in cost_coefficients.activity_coefficients.iter() {
47 let var = problem.add_column(cost.value(), 0.0..);
48 activity_vars.insert(time_slice.clone(), var);
49 }
50
51 let mut unmet_demand_vars = IndexMap::new();
54 for time_slice in cost_coefficients.activity_coefficients.keys() {
55 let var = problem.add_column(cost_coefficients.unmet_demand_coefficient.value(), 0.0..);
56 unmet_demand_vars.insert(time_slice.clone(), var);
57 }
58
59 VariableMap {
60 capacity_var,
61 activity_vars,
62 unmet_demand_vars,
63 }
64}
65
66fn add_constraints(
68 problem: &mut Problem,
69 asset: &AssetRef,
70 max_capacity: Option<Capacity>,
71 commodity: &Commodity,
72 variables: &VariableMap,
73 demand: &DemandMap,
74 time_slice_info: &TimeSliceInfo,
75) {
76 add_capacity_constraint(problem, asset, max_capacity, variables.capacity_var);
77 add_activity_constraints(
78 problem,
79 asset,
80 variables.capacity_var,
81 &variables.activity_vars,
82 );
83 add_demand_constraints(
84 problem,
85 asset,
86 commodity,
87 time_slice_info,
88 demand,
89 &variables.activity_vars,
90 &variables.unmet_demand_vars,
91 );
92}
93
94pub fn perform_optimisation(
98 asset: &AssetRef,
99 max_capacity: Option<Capacity>,
100 commodity: &Commodity,
101 coefficients: &CoefficientsMap,
102 demand: &DemandMap,
103 time_slice_info: &TimeSliceInfo,
104 sense: Sense,
105) -> Result<ResultsMap> {
106 let mut problem = Problem::default();
108
109 let variables = add_variables(&mut problem, coefficients);
111
112 add_constraints(
114 &mut problem,
115 asset,
116 max_capacity,
117 commodity,
118 &variables,
119 demand,
120 time_slice_info,
121 );
122
123 let solution = solve_optimal(problem.optimise(sense))?.get_solution();
125 let solution_values = solution.columns();
126 Ok(ResultsMap {
127 capacity: Capacity::new(solution_values[0]),
128 activity: variables
129 .activity_vars
130 .keys()
131 .zip(solution_values[1..].iter())
132 .map(|(time_slice, &value)| (time_slice.clone(), Activity::new(value)))
133 .collect(),
134 unmet_demand: variables
135 .unmet_demand_vars
136 .keys()
137 .zip(solution_values[variables.activity_vars.len() + 1..].iter())
138 .map(|(time_slice, &value)| (time_slice.clone(), Flow::new(value)))
139 .collect(),
140 })
141}