11//! Code for adding constraints to the dispatch optimisation problem.
22use super :: VariableMap ;
33use crate :: asset:: { AssetPool , AssetRef } ;
4- use crate :: commodity:: CommodityID ;
4+ use crate :: commodity:: { CommodityID , CommodityType } ;
55use crate :: model:: Model ;
66use crate :: region:: RegionID ;
77use crate :: time_slice:: { TimeSliceID , TimeSliceInfo , TimeSliceSelection } ;
@@ -28,34 +28,33 @@ impl<T> KeysWithOffset<T> {
2828/// Indicates the commodity ID and time slice selection covered by each commodity balance constraint
2929pub type CommodityBalanceKeys = KeysWithOffset < ( CommodityID , RegionID , TimeSliceSelection ) > ;
3030
31- /// Indicates the asset ID and time slice covered by each capacity constraint
32- pub type CapacityKeys = KeysWithOffset < ( AssetRef , TimeSliceID ) > ;
31+ /// Indicates the asset ID and time slice covered by each activity constraint
32+ pub type ActivityKeys = KeysWithOffset < ( AssetRef , TimeSliceID ) > ;
3333
3434/// The keys for different constraints
3535pub struct ConstraintKeys {
3636 /// Keys for commodity balance constraints
3737 pub commodity_balance_keys : CommodityBalanceKeys ,
38- /// Keys for capacity constraints
39- pub capacity_keys : CapacityKeys ,
38+ /// Keys for activity constraints
39+ pub activity_keys : ActivityKeys ,
4040}
4141
4242/// Add asset-level constraints
4343///
4444/// Note: the ordering of constraints is important, as the dual values of the constraints must later
4545/// be retrieved to calculate commodity prices.
4646///
47- /// # Arguments:
47+ /// # Arguments
4848///
4949/// * `problem` - The optimisation problem
5050/// * `variables` - The variables in the problem
5151/// * `model` - The model
5252/// * `assets` - The asset pool
5353/// * `year` - Current milestone year
5454///
55- /// # Returns:
55+ /// # Returns
5656///
57- /// * A vector of keys for commodity balance constraints
58- /// * A vector of keys for capacity constraints
57+ /// Keys for the different constraints.
5958pub fn add_asset_constraints (
6059 problem : & mut Problem ,
6160 variables : & VariableMap ,
@@ -67,12 +66,12 @@ pub fn add_asset_constraints(
6766 add_commodity_balance_constraints ( problem, variables, model, assets, year) ;
6867
6968 let capacity_keys =
70- add_asset_capacity_constraints ( problem, variables, assets , & model. time_slice_info ) ;
69+ add_activity_constraints ( problem, variables, & model. time_slice_info , assets ) ;
7170
7271 // Return constraint keys
7372 ConstraintKeys {
7473 commodity_balance_keys,
75- capacity_keys,
74+ activity_keys : capacity_keys,
7675 }
7776}
7877
@@ -85,44 +84,84 @@ pub fn add_asset_constraints(
8584/// [1]: https://energysystemsmodellinglab.github.io/MUSE_2.0/dispatch_optimisation.html#commodity-balance-constraints
8685fn add_commodity_balance_constraints (
8786 problem : & mut Problem ,
88- _variables : & VariableMap ,
89- _model : & Model ,
90- _assets : & AssetPool ,
91- _year : u32 ,
87+ variables : & VariableMap ,
88+ model : & Model ,
89+ assets : & AssetPool ,
90+ year : u32 ,
9291) -> CommodityBalanceKeys {
9392 // Row offset in problem. This line **must** come before we add more constraints.
9493 let offset = problem. num_rows ( ) ;
9594
96- let keys = Vec :: new ( ) ;
97-
98- // **TODO:** Add commodity balance constraints:
99- // https://github.com/EnergySystemsModellingLab/MUSE_2.0/issues/577
95+ let mut keys = Vec :: new ( ) ;
96+ let mut terms = Vec :: new ( ) ;
97+ for ( commodity_id, commodity) in model. commodities . iter ( ) {
98+ if !matches ! (
99+ commodity. kind,
100+ CommodityType :: SupplyEqualsDemand | CommodityType :: ServiceDemand
101+ ) {
102+ continue ;
103+ }
104+
105+ for region_id in model. iter_regions ( ) {
106+ for ts_selection in model
107+ . time_slice_info
108+ . iter_selections_at_level ( commodity. time_slice_level )
109+ {
110+ for ( asset, flow) in assets. iter_for_region_and_commodity ( region_id, commodity_id) {
111+ // If the commodity has a time slice level of season/annual, the constraint will
112+ // cover multiple time slices
113+ for ( time_slice, _) in ts_selection. iter ( & model. time_slice_info ) {
114+ let var = variables. get ( asset, time_slice) ;
115+ terms. push ( ( var, flow. coeff ) ) ;
116+ }
117+ }
118+
119+ // Add constraint. For SED commodities, the RHS is zero and for SVD commodities it
120+ // is the exogenous demand supplied by the user.
121+ let rhs = if commodity. kind == CommodityType :: ServiceDemand {
122+ * commodity
123+ . demand
124+ . get ( & ( region_id. clone ( ) , year, ts_selection. clone ( ) ) )
125+ . unwrap ( )
126+ } else {
127+ 0.0
128+ } ;
129+ problem. add_row ( rhs..=rhs, terms. drain ( ..) ) ;
130+ keys. push ( (
131+ commodity_id. clone ( ) ,
132+ region_id. clone ( ) ,
133+ ts_selection. clone ( ) ,
134+ ) )
135+ }
136+ }
137+ }
100138
101139 CommodityBalanceKeys { offset, keys }
102140}
103141
104- /// Add asset-level capacity and availability constraints .
142+ /// Add constraints on the activity of different assets .
105143///
106- /// For every asset at every time slice, the sum of the commodity flows for assets must not exceed
107- /// the capacity limits, which are a product of the annual capacity, time slice length and process
108- /// availability.
109- ///
110- /// See description in [the dispatch optimisation documentation][1].
111- ///
112- /// [1]: https://energysystemsmodellinglab.github.io/MUSE_2.0/dispatch_optimisation.html#asset-level-capacity-and-availability-constraints
113- fn add_asset_capacity_constraints (
144+ /// This ensures that assets do not exceed their specified capacity and availability for each time
145+ /// slice.
146+ fn add_activity_constraints (
114147 problem : & mut Problem ,
115- _variables : & VariableMap ,
116- _assets : & AssetPool ,
117- _time_slice_info : & TimeSliceInfo ,
118- ) -> CapacityKeys {
148+ variables : & VariableMap ,
149+ time_slice_info : & TimeSliceInfo ,
150+ assets : & AssetPool ,
151+ ) -> ActivityKeys {
119152 // Row offset in problem. This line **must** come before we add more constraints.
120153 let offset = problem. num_rows ( ) ;
121154
122- let keys = Vec :: new ( ) ;
155+ let mut keys = Vec :: new ( ) ;
156+ for asset in assets. iter ( ) {
157+ for time_slice in time_slice_info. iter_ids ( ) {
158+ let var = variables. get ( asset, time_slice) ;
159+ let limits = asset. get_activity_limits ( time_slice) ;
123160
124- // **TODO:** Add capacity/availability constraints:
125- // https://github.com/EnergySystemsModellingLab/MUSE_2.0/issues/579
161+ problem. add_row ( limits, [ ( var, 1.0 ) ] ) ;
162+ keys. push ( ( asset. clone ( ) , time_slice. clone ( ) ) )
163+ }
164+ }
126165
127- CapacityKeys { offset, keys }
166+ ActivityKeys { offset, keys }
128167}
0 commit comments