11//! Code for adding constraints to the dispatch optimisation problem.
2+ use super :: VariableMap ;
23use crate :: asset:: { AssetID , AssetPool } ;
34use crate :: commodity:: { CommodityID , CommodityType } ;
45use crate :: model:: Model ;
@@ -7,42 +8,41 @@ use crate::time_slice::{TimeSliceID, TimeSliceInfo, TimeSliceSelection};
78use highs:: RowProblem as Problem ;
89use std:: rc:: Rc ;
910
10- use super :: VariableMap ;
11+ /// Corresponding variables for a constraint along with the row offset in the solution
12+ pub struct KeysWithOffset < T > {
13+ offset : usize ,
14+ keys : Vec < T > ,
15+ }
16+
17+ impl < T > KeysWithOffset < T > {
18+ /// Zip the keys with the corresponding dual values in the solution, accounting for the offset
19+ pub fn zip_duals < ' a > ( & ' a self , duals : & ' a [ f64 ] ) -> impl Iterator < Item = ( & ' a T , f64 ) > {
20+ assert ! (
21+ self . offset + self . keys. len( ) <= duals. len( ) ,
22+ "Bad constraint keys: dual rows out of range"
23+ ) ;
24+
25+ self . keys . iter ( ) . zip ( duals[ self . offset ..] . iter ( ) . copied ( ) )
26+ }
27+ }
1128
1229/// Indicates the commodity ID and time slice selection covered by each commodity balance constraint
13- pub type CommodityBalanceConstraintKeys = Vec < ( CommodityID , RegionID , TimeSliceSelection ) > ;
30+ pub type CommodityBalanceKeys = KeysWithOffset < ( CommodityID , RegionID , TimeSliceSelection ) > ;
1431
1532/// Indicates the asset ID and time slice covered by each capacity constraint
16- pub type CapacityConstraintKeys = Vec < ( AssetID , TimeSliceID ) > ;
33+ pub type CapacityKeys = KeysWithOffset < ( AssetID , TimeSliceID ) > ;
1734
1835/// Indicates the asset ID, commodity ID and time slice for each fixed asset constraint
19- pub type FixedAssetConstraintKeys = Vec < ( AssetID , CommodityID , TimeSliceID ) > ;
36+ pub type FixedAssetKeys = KeysWithOffset < ( AssetID , CommodityID , TimeSliceID ) > ;
2037
2138/// The keys for different constraints
2239pub struct ConstraintKeys {
2340 /// Keys for commodity balance constraints
24- pub commodity_balance_keys : CommodityBalanceConstraintKeys ,
41+ pub commodity_balance_keys : CommodityBalanceKeys ,
2542 /// Keys for capacity constraints
26- pub capacity_keys : CapacityConstraintKeys ,
43+ pub capacity_keys : CapacityKeys ,
2744 /// Keys for fixed asset constraints
28- pub fixed_asset_keys : FixedAssetConstraintKeys ,
29- }
30-
31- impl ConstraintKeys {
32- /// Start offset for commodity balance constraints
33- pub fn commodity_balance_keys_offset ( & self ) -> usize {
34- 0
35- }
36-
37- /// Start offset for capacity constraints
38- pub fn capacity_keys_offset ( & self ) -> usize {
39- self . commodity_balance_keys . len ( )
40- }
41-
42- /// Start offset for capacity constraints
43- pub fn fixed_asset_keys_offset ( & self ) -> usize {
44- self . capacity_keys_offset ( ) + self . capacity_keys . len ( )
45- }
45+ pub fixed_asset_keys : FixedAssetKeys ,
4646}
4747
4848/// Add asset-level constraints
@@ -72,27 +72,16 @@ pub fn add_asset_constraints(
7272 let commodity_balance_keys =
7373 add_commodity_balance_constraints ( problem, variables, model, assets, year) ;
7474
75- let capacity_keys = add_asset_capacity_constraints (
76- problem,
77- variables,
78- assets,
79- & model. time_slice_info ,
80- & commodity_balance_keys,
81- ) ;
75+ let capacity_keys =
76+ add_asset_capacity_constraints ( problem, variables, assets, & model. time_slice_info ) ;
8277
8378 // **TODO**: Currently it's safe to assume all process flows are non-flexible, as we enforce
8479 // this when reading data in. Once we've added support for flexible process flows, we will
8580 // need to add different constraints for assets with flexible and non-flexible flows.
8681 //
8782 // See: https://github.com/EnergySystemsModellingLab/MUSE_2.0/issues/360
88- let fixed_asset_keys = add_fixed_asset_constraints (
89- problem,
90- variables,
91- assets,
92- & model. time_slice_info ,
93- & commodity_balance_keys,
94- & capacity_keys,
95- ) ;
83+ let fixed_asset_keys =
84+ add_fixed_asset_constraints ( problem, variables, assets, & model. time_slice_info ) ;
9685
9786 // Return constraint keys
9887 ConstraintKeys {
@@ -115,16 +104,12 @@ fn add_commodity_balance_constraints(
115104 model : & Model ,
116105 assets : & AssetPool ,
117106 year : u32 ,
118- ) -> CommodityBalanceConstraintKeys {
119- // Sanity check: we rely on the first n values of the dual row values corresponding to the
120- // commodity constraints, so these must be the first rows
121- assert ! (
122- problem. num_rows( ) == 0 ,
123- "Commodity balance constraints must be added before other constraints"
124- ) ;
107+ ) -> CommodityBalanceKeys {
108+ // Row offset in problem. This line **must** come before we add more constraints.
109+ let offset = problem. num_rows ( ) ;
125110
126111 let mut terms = Vec :: new ( ) ;
127- let mut keys = CommodityBalanceConstraintKeys :: new ( ) ;
112+ let mut keys = Vec :: new ( ) ;
128113 for commodity in model. commodities . values ( ) {
129114 if commodity. kind != CommodityType :: SupplyEqualsDemand
130115 && commodity. kind != CommodityType :: ServiceDemand
@@ -183,7 +168,7 @@ fn add_commodity_balance_constraints(
183168 }
184169 }
185170
186- keys
171+ CommodityBalanceKeys { offset , keys }
187172}
188173
189174/// Add asset-level capacity and availability constraints.
@@ -200,17 +185,12 @@ fn add_asset_capacity_constraints(
200185 variables : & VariableMap ,
201186 assets : & AssetPool ,
202187 time_slice_info : & TimeSliceInfo ,
203- commodity_balance_keys : & CommodityBalanceConstraintKeys ,
204- ) -> CapacityConstraintKeys {
205- // Sanity check: we rely on the dual rows corresponding to the capacity constraints being
206- // immediately after the commodity balance constraints in `problem`.
207- assert ! (
208- problem. num_rows( ) == commodity_balance_keys. len( ) ,
209- "Capacity constraints must be added immediately after commodity constraints."
210- ) ;
188+ ) -> CapacityKeys {
189+ // Row offset in problem. This line **must** come before we add more constraints.
190+ let offset = problem. num_rows ( ) ;
211191
212192 let mut terms = Vec :: new ( ) ;
213- let mut keys = CapacityConstraintKeys :: new ( ) ;
193+ let mut keys = Vec :: new ( ) ;
214194 for asset in assets. iter ( ) {
215195 for time_slice in time_slice_info. iter_ids ( ) {
216196 let mut is_input = false ; // NB: there will be at least one PAC
@@ -234,7 +214,8 @@ fn add_asset_capacity_constraints(
234214 keys. push ( ( asset. id , time_slice. clone ( ) ) ) ;
235215 }
236216 }
237- keys
217+
218+ CapacityKeys { offset, keys }
238219}
239220
240221/// Add constraints for non-flexible assets.
@@ -249,17 +230,11 @@ fn add_fixed_asset_constraints(
249230 variables : & VariableMap ,
250231 assets : & AssetPool ,
251232 time_slice_info : & TimeSliceInfo ,
252- commodity_balance_keys : & CommodityBalanceConstraintKeys ,
253- capacity_keys : & CapacityConstraintKeys ,
254- ) -> FixedAssetConstraintKeys {
255- // Sanity check: we rely on the dual rows corresponding to these constraints being
256- // immediately after the commodity balance and capacity constraints in `problem`.
257- assert ! (
258- problem. num_rows( ) == commodity_balance_keys. len( ) + capacity_keys. len( ) ,
259- "Fixed asset constraints must be added immediately after commodity constraints."
260- ) ;
261-
262- let mut keys = FixedAssetConstraintKeys :: new ( ) ;
233+ ) -> FixedAssetKeys {
234+ // Row offset in problem. This line **must** come before we add more constraints.
235+ let offset = problem. num_rows ( ) ;
236+
237+ let mut keys = Vec :: new ( ) ;
263238 for asset in assets. iter ( ) {
264239 // Get first PAC. unwrap is safe because all processes have at least one PAC.
265240 let pac1 = asset. iter_pacs ( ) . next ( ) . unwrap ( ) ;
@@ -283,5 +258,5 @@ fn add_fixed_asset_constraints(
283258 }
284259 }
285260
286- keys
261+ FixedAssetKeys { offset , keys }
287262}
0 commit comments