|
1 | 1 | //! Code for adding constraints to the dispatch optimisation problem. |
2 | 2 | use super::VariableMap; |
3 | 3 | use crate::asset::{AssetPool, AssetRef}; |
4 | | -use crate::commodity::CommodityID; |
| 4 | +use crate::commodity::{CommodityID, CommodityType}; |
5 | 5 | use crate::model::Model; |
6 | 6 | use crate::region::RegionID; |
7 | 7 | use crate::time_slice::{TimeSliceID, TimeSliceInfo, TimeSliceSelection}; |
@@ -85,18 +85,57 @@ pub fn add_asset_constraints( |
85 | 85 | /// [1]: https://energysystemsmodellinglab.github.io/MUSE_2.0/dispatch_optimisation.html#commodity-balance-constraints |
86 | 86 | fn add_commodity_balance_constraints( |
87 | 87 | problem: &mut Problem, |
88 | | - _variables: &VariableMap, |
89 | | - _model: &Model, |
90 | | - _assets: &AssetPool, |
91 | | - _year: u32, |
| 88 | + variables: &VariableMap, |
| 89 | + model: &Model, |
| 90 | + assets: &AssetPool, |
| 91 | + year: u32, |
92 | 92 | ) -> CommodityBalanceKeys { |
93 | 93 | // Row offset in problem. This line **must** come before we add more constraints. |
94 | 94 | let offset = problem.num_rows(); |
95 | 95 |
|
96 | | - let keys = Vec::new(); |
97 | | - |
98 | | - // **TODO:** Add commodity balance constraints: |
99 | | - // https://github.com/EnergySystemsModellingLab/MUSE_2.0/issues/577 |
| 96 | + let mut keys = Vec::new(); |
| 97 | + let mut terms = Vec::new(); |
| 98 | + for (commodity_id, commodity) in model.commodities.iter() { |
| 99 | + if !matches!( |
| 100 | + commodity.kind, |
| 101 | + CommodityType::SupplyEqualsDemand | CommodityType::ServiceDemand |
| 102 | + ) { |
| 103 | + continue; |
| 104 | + } |
| 105 | + |
| 106 | + for region_id in model.iter_regions() { |
| 107 | + for ts_selection in model |
| 108 | + .time_slice_info |
| 109 | + .iter_selections_at_level(commodity.time_slice_level) |
| 110 | + { |
| 111 | + for (asset, flow) in assets.iter_for_region_and_commodity(region_id, commodity_id) { |
| 112 | + // If the commodity has a time slice level of season/annual, the constraint will |
| 113 | + // cover multiple time slices |
| 114 | + for (time_slice, _) in ts_selection.iter(&model.time_slice_info) { |
| 115 | + let var = variables.get(asset, time_slice); |
| 116 | + terms.push((var, flow.coeff)); |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + // Add constraint. For SED commodities, the RHS is zero and for SVD commodities it |
| 121 | + // is the exogenous demand supplied by the user. |
| 122 | + let rhs = if commodity.kind == CommodityType::ServiceDemand { |
| 123 | + *commodity |
| 124 | + .demand |
| 125 | + .get(&(region_id.clone(), year, ts_selection.clone())) |
| 126 | + .unwrap() |
| 127 | + } else { |
| 128 | + 0.0 |
| 129 | + }; |
| 130 | + problem.add_row(rhs..=rhs, terms.drain(..)); |
| 131 | + keys.push(( |
| 132 | + commodity_id.clone(), |
| 133 | + region_id.clone(), |
| 134 | + ts_selection.clone(), |
| 135 | + )) |
| 136 | + } |
| 137 | + } |
| 138 | + } |
100 | 139 |
|
101 | 140 | CommodityBalanceKeys { offset, keys } |
102 | 141 | } |
|
0 commit comments