muse2/simulation/investment/appraisal/
constraints.rs1use super::DemandMap;
3use super::optimisation::Variable;
4use crate::asset::{AssetCapacity, AssetRef, AssetState};
5use crate::commodity::Commodity;
6use crate::time_slice::{TimeSliceID, TimeSliceInfo};
7use crate::units::Flow;
8use highs::RowProblem as Problem;
9use indexmap::IndexMap;
10
11pub fn add_capacity_constraint(
17 problem: &mut Problem,
18 asset: &AssetRef,
19 max_capacity: AssetCapacity,
20 capacity_var: Variable,
21) {
22 let capacity_limit = match max_capacity {
23 AssetCapacity::Continuous(cap) => cap.value(),
24 AssetCapacity::Discrete(units, _) => units as f64,
25 };
26
27 let bounds = match asset.state() {
28 AssetState::Commissioned { .. } => {
29 capacity_limit..=capacity_limit
31 }
32 AssetState::Candidate => {
33 0.0..=capacity_limit
35 }
36 _ => panic!(
37 "add_capacity_constraint should only be called with Commissioned or Candidate assets"
38 ),
39 };
40 problem.add_row(bounds, [(capacity_var, 1.0)]);
41}
42
43pub fn add_activity_constraints(
54 problem: &mut Problem,
55 asset: &AssetRef,
56 capacity_var: Variable,
57 activity_vars: &IndexMap<TimeSliceID, Variable>,
58 time_slice_info: &TimeSliceInfo,
59) {
60 match asset.state() {
61 AssetState::Commissioned { .. } => {
62 add_activity_constraints_for_existing(problem, asset, activity_vars, time_slice_info);
63 }
64 AssetState::Candidate => {
65 add_activity_constraints_for_candidate(
66 problem,
67 asset,
68 capacity_var,
69 activity_vars,
70 time_slice_info,
71 );
72 }
73 _ => panic!(
74 "add_activity_constraints should only be called with Commissioned or Candidate assets"
75 ),
76 }
77}
78
79fn add_activity_constraints_for_existing(
80 problem: &mut Problem,
81 asset: &AssetRef,
82 activity_vars: &IndexMap<TimeSliceID, Variable>,
83 time_slice_info: &TimeSliceInfo,
84) {
85 for (ts_selection, limits) in asset.iter_activity_limits() {
86 let limits = limits.start().value()..=limits.end().value();
87
88 let terms = ts_selection
90 .iter(time_slice_info)
91 .map(|(time_slice, _)| (*activity_vars.get(time_slice).unwrap(), 1.0))
92 .collect::<Vec<_>>();
93
94 problem.add_row(limits, &terms);
96 }
97}
98
99fn add_activity_constraints_for_candidate(
100 problem: &mut Problem,
101 asset: &AssetRef,
102 capacity_var: Variable,
103 activity_vars: &IndexMap<TimeSliceID, Variable>,
104 time_slice_info: &TimeSliceInfo,
105) {
106 for (ts_selection, limits) in asset.iter_activity_per_capacity_limits() {
107 let mut upper_limit = limits.end().value();
108 let mut lower_limit = limits.start().value();
109
110 if let AssetCapacity::Discrete(_, unit_size) = asset.capacity() {
113 upper_limit *= unit_size.value();
114 lower_limit *= unit_size.value();
115 }
116
117 let mut terms_upper = vec![(capacity_var, -upper_limit)];
120 let mut terms_lower = vec![(capacity_var, -lower_limit)];
121 for (time_slice, _) in ts_selection.iter(time_slice_info) {
122 let var = *activity_vars.get(time_slice).unwrap();
123 terms_upper.push((var, 1.0));
124 terms_lower.push((var, 1.0));
125 }
126
127 problem.add_row(..=0.0, &terms_upper);
129
130 problem.add_row(0.0.., &terms_lower);
132 }
133}
134
135pub fn add_demand_constraints(
142 problem: &mut Problem,
143 asset: &AssetRef,
144 commodity: &Commodity,
145 time_slice_info: &TimeSliceInfo,
146 demand: &DemandMap,
147 activity_vars: &IndexMap<TimeSliceID, Variable>,
148 unmet_demand_vars: &IndexMap<TimeSliceID, Variable>,
149) {
150 for ts_selection in time_slice_info.iter_selections_at_level(commodity.time_slice_level) {
151 let mut demand_for_ts_selection = Flow(0.0);
152 let mut terms = Vec::new();
153 for (time_slice, _) in ts_selection.iter(time_slice_info) {
154 demand_for_ts_selection += demand[time_slice];
155 let flow_coeff = asset.get_flow(&commodity.id).unwrap().coeff;
156 terms.push((activity_vars[time_slice], flow_coeff.value()));
157 terms.push((unmet_demand_vars[time_slice], 1.0));
158 }
159 problem.add_row(
160 demand_for_ts_selection.value()..=demand_for_ts_selection.value(),
161 terms,
162 );
163 }
164}