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 crate::units::UnitType;
9use highs::RowProblem as Problem;
10
11pub struct KeysWithOffset<T> {
13 offset: usize,
14 keys: Vec<T>,
15}
16
17impl<T> KeysWithOffset<T> {
18 pub fn zip_duals<'a, U>(&'a self, duals: &'a [f64]) -> impl Iterator<Item = (&'a T, U)>
20 where
21 U: UnitType,
22 {
23 assert!(
24 self.offset + self.keys.len() <= duals.len(),
25 "Bad constraint keys: dual rows out of range"
26 );
27
28 self.keys
29 .iter()
30 .zip(duals[self.offset..].iter().copied().map(U::new))
31 }
32}
33
34pub type CommodityBalanceKeys = KeysWithOffset<(CommodityID, RegionID, TimeSliceSelection)>;
36
37pub type ActivityKeys = KeysWithOffset<(AssetRef, TimeSliceID)>;
39
40pub struct ConstraintKeys {
42 pub commodity_balance_keys: CommodityBalanceKeys,
44 pub activity_keys: ActivityKeys,
46}
47
48pub fn add_asset_constraints<'a, I>(
66 problem: &mut Problem,
67 variables: &VariableMap,
68 model: &'a Model,
69 assets: &I,
70 commodities: &'a [CommodityID],
71 year: u32,
72) -> ConstraintKeys
73where
74 I: Iterator<Item = &'a AssetRef> + Clone + 'a,
75{
76 let commodity_balance_keys =
77 add_commodity_balance_constraints(problem, variables, model, assets, commodities, year);
78
79 let activity_keys = add_activity_constraints(problem, variables);
80
81 ConstraintKeys {
83 commodity_balance_keys,
84 activity_keys,
85 }
86}
87
88fn add_commodity_balance_constraints<'a, I>(
96 problem: &mut Problem,
97 variables: &VariableMap,
98 model: &'a Model,
99 assets: &I,
100 commodities: &'a [CommodityID],
101 year: u32,
102) -> CommodityBalanceKeys
103where
104 I: Iterator<Item = &'a AssetRef> + Clone + 'a,
105{
106 let offset = problem.num_rows();
108
109 let mut keys = Vec::new();
110 let mut terms = Vec::new();
111 for commodity_id in commodities {
112 let commodity = &model.commodities[commodity_id];
113 if !matches!(
114 commodity.kind,
115 CommodityType::SupplyEqualsDemand | CommodityType::ServiceDemand
116 ) {
117 continue;
118 }
119
120 for region_id in model.iter_regions() {
121 for ts_selection in model
122 .time_slice_info
123 .iter_selections_at_level(commodity.time_slice_level)
124 {
125 for (asset, flow) in assets
126 .clone()
127 .filter_region(region_id)
128 .flows_for_commodity(commodity_id)
129 {
130 for (time_slice, _) in ts_selection.iter(&model.time_slice_info) {
133 let var = variables.get_asset_var(asset, time_slice);
134 terms.push((var, flow.coeff.value()));
135 }
136 }
137
138 if terms.is_empty() {
142 continue;
143 }
144
145 if !variables.unmet_demand_var_idx.is_empty() {
147 for (time_slice, _) in ts_selection.iter(&model.time_slice_info) {
148 let var =
149 variables.get_unmet_demand_var(commodity_id, region_id, time_slice);
150 terms.push((var, 1.0));
151 }
152 }
153
154 let min = if commodity.kind == CommodityType::ServiceDemand {
157 commodity
158 .demand
159 .get(&(region_id.clone(), year, ts_selection.clone()))
160 .unwrap()
161 .value()
162 } else {
163 0.0
164 };
165 problem.add_row(min.., terms.drain(..));
166 keys.push((
167 commodity_id.clone(),
168 region_id.clone(),
169 ts_selection.clone(),
170 ));
171 }
172 }
173 }
174
175 CommodityBalanceKeys { offset, keys }
176}
177
178fn add_activity_constraints(problem: &mut Problem, variables: &VariableMap) -> ActivityKeys {
187 let offset = problem.num_rows();
189
190 let mut keys = Vec::new();
191 for (asset, time_slice, var) in variables.iter_asset_vars() {
192 let limits = asset.get_activity_limits(time_slice);
193 let limits = limits.start().value()..=limits.end().value();
194
195 problem.add_row(limits, [(var, 1.0)]);
196 keys.push((asset.clone(), time_slice.clone()));
197 }
198
199 ActivityKeys { offset, keys }
200}