muse2/simulation/optimisation/
constraints.rs1use super::VariableMap;
3use crate::asset::{AssetIterator, AssetRef};
4use crate::commodity::{CommodityID, CommodityType};
5use crate::model::Model;
6use crate::region::RegionID;
7use crate::time_slice::{TimeSliceID, TimeSliceSelection};
8use highs::RowProblem as Problem;
9
10pub struct KeysWithOffset<T> {
12 offset: usize,
13 keys: Vec<T>,
14}
15
16impl<T> KeysWithOffset<T> {
17 pub fn zip_duals<'a>(&'a self, duals: &'a [f64]) -> impl Iterator<Item = (&'a T, f64)> {
19 assert!(
20 self.offset + self.keys.len() <= duals.len(),
21 "Bad constraint keys: dual rows out of range"
22 );
23
24 self.keys.iter().zip(duals[self.offset..].iter().copied())
25 }
26}
27
28pub type CommodityBalanceKeys = KeysWithOffset<(CommodityID, RegionID, TimeSliceSelection)>;
30
31pub type ActivityKeys = KeysWithOffset<(AssetRef, TimeSliceID)>;
33
34pub struct ConstraintKeys {
36 pub commodity_balance_keys: CommodityBalanceKeys,
38 pub activity_keys: ActivityKeys,
40}
41
42pub fn add_asset_constraints<'a, I>(
59 problem: &mut Problem,
60 variables: &VariableMap,
61 model: &'a Model,
62 assets: I,
63 year: u32,
64) -> ConstraintKeys
65where
66 I: Iterator<Item = &'a AssetRef> + Clone + 'a,
67{
68 let commodity_balance_keys =
69 add_commodity_balance_constraints(problem, variables, model, assets.clone(), year);
70
71 let activity_keys = add_activity_constraints(problem, variables);
72
73 ConstraintKeys {
75 commodity_balance_keys,
76 activity_keys,
77 }
78}
79
80fn add_commodity_balance_constraints<'a, I>(
88 problem: &mut Problem,
89 variables: &VariableMap,
90 model: &'a Model,
91 assets: I,
92 year: u32,
93) -> CommodityBalanceKeys
94where
95 I: Iterator<Item = &'a AssetRef> + Clone + 'a,
96{
97 let offset = problem.num_rows();
99
100 let mut keys = Vec::new();
101 let mut terms = Vec::new();
102 for (commodity_id, commodity) in model.commodities.iter() {
103 if !matches!(
104 commodity.kind,
105 CommodityType::SupplyEqualsDemand | CommodityType::ServiceDemand
106 ) {
107 continue;
108 }
109
110 for region_id in model.iter_regions() {
111 for ts_selection in model
112 .time_slice_info
113 .iter_selections_at_level(commodity.time_slice_level)
114 {
115 for (asset, flow) in assets
116 .clone()
117 .filter_region(region_id)
118 .flows_for_commodity(commodity_id)
119 {
120 for (time_slice, _) in ts_selection.iter(&model.time_slice_info) {
123 let var = variables.get(asset, time_slice);
124 terms.push((var, flow.coeff.value()));
125 }
126 }
127
128 let rhs = if commodity.kind == CommodityType::ServiceDemand {
131 commodity
132 .demand
133 .get(&(region_id.clone(), year, ts_selection.clone()))
134 .unwrap()
135 .value()
136 } else {
137 0.0
138 };
139 problem.add_row(rhs..=rhs, terms.drain(..));
140 keys.push((
141 commodity_id.clone(),
142 region_id.clone(),
143 ts_selection.clone(),
144 ))
145 }
146 }
147 }
148
149 CommodityBalanceKeys { offset, keys }
150}
151
152fn add_activity_constraints(problem: &mut Problem, variables: &VariableMap) -> ActivityKeys {
157 let offset = problem.num_rows();
159
160 let mut keys = Vec::new();
161 for (asset, time_slice, var) in variables.iter() {
162 let limits = asset.get_activity_limits(time_slice);
163 let limits = limits.start().value()..=limits.end().value();
164
165 problem.add_row(limits, [(var, 1.0)]);
166 keys.push((asset.clone(), time_slice.clone()))
167 }
168
169 ActivityKeys { offset, keys }
170}