Skip to content

Commit ba0cf9b

Browse files
committed
Store row offsets with keys
This makes things cleaner and safer.
1 parent 7779d41 commit ba0cf9b

2 files changed

Lines changed: 63 additions & 99 deletions

File tree

src/simulation/optimisation.rs

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -78,52 +78,41 @@ impl Solution<'_> {
7878
})
7979
}
8080

81-
/// Returns constraint keys along with their corresponding dual values
82-
fn zip_duals<'a, I, T>(&'a self, keys: I, offset: usize) -> impl Iterator<Item = (T, f64)> + 'a
83-
where
84-
I: Iterator<Item = T> + 'a,
85-
{
86-
keys.zip(self.solution.dual_rows()[offset..].iter().copied())
87-
}
88-
8981
/// Keys and dual values for commodity balance constraints.
9082
pub fn iter_commodity_balance_duals(
9183
&self,
9284
) -> impl Iterator<Item = (&CommodityID, &RegionID, &TimeSliceID, f64)> {
9385
// Each commodity balance constraint applies to a particular time slice
9486
// selection (depending on time slice level). Where this covers multiple timeslices,
9587
// we return the same dual for each individual timeslice.
96-
self.zip_duals(
97-
self.constraint_keys.commodity_balance_keys.iter(),
98-
self.constraint_keys.commodity_balance_keys_offset(),
99-
)
100-
.flat_map(|((commodity_id, region_id, ts_selection), price)| {
101-
self.time_slice_info
102-
.iter_selection(ts_selection)
103-
.map(move |(ts, _)| (commodity_id, region_id, ts, price))
104-
})
88+
self.constraint_keys
89+
.commodity_balance_keys
90+
.zip_duals(self.solution.dual_rows())
91+
.flat_map(|((commodity_id, region_id, ts_selection), price)| {
92+
self.time_slice_info
93+
.iter_selection(ts_selection)
94+
.map(move |(ts, _)| (commodity_id, region_id, ts, price))
95+
})
10596
}
10697

10798
/// Keys and dual values for capacity constraints.
10899
pub fn iter_capacity_duals(&self) -> impl Iterator<Item = (AssetID, &TimeSliceID, f64)> {
109-
self.zip_duals(
110-
self.constraint_keys.capacity_keys.iter(),
111-
self.constraint_keys.capacity_keys_offset(),
112-
)
113-
.map(|((asset_id, time_slice), dual)| (*asset_id, time_slice, dual))
100+
self.constraint_keys
101+
.capacity_keys
102+
.zip_duals(self.solution.dual_rows())
103+
.map(|((asset_id, time_slice), dual)| (*asset_id, time_slice, dual))
114104
}
115105

116106
/// Keys and dual values for fixed asset constraints.
117107
pub fn iter_fixed_asset_duals(
118108
&self,
119109
) -> impl Iterator<Item = (AssetID, &CommodityID, &TimeSliceID, f64)> {
120-
self.zip_duals(
121-
self.constraint_keys.fixed_asset_keys.iter(),
122-
self.constraint_keys.fixed_asset_keys_offset(),
123-
)
124-
.map(|((asset_id, commodity_id, time_slice), dual)| {
125-
(*asset_id, commodity_id, time_slice, dual)
126-
})
110+
self.constraint_keys
111+
.fixed_asset_keys
112+
.zip_duals(self.solution.dual_rows())
113+
.map(|((asset_id, commodity_id, time_slice), dual)| {
114+
(*asset_id, commodity_id, time_slice, dual)
115+
})
127116
}
128117
}
129118

src/simulation/optimisation/constraints.rs

Lines changed: 45 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Code for adding constraints to the dispatch optimisation problem.
2+
use super::VariableMap;
23
use crate::asset::{AssetID, AssetPool};
34
use crate::commodity::{CommodityID, CommodityType};
45
use crate::model::Model;
@@ -7,42 +8,41 @@ use crate::time_slice::{TimeSliceID, TimeSliceInfo, TimeSliceSelection};
78
use highs::RowProblem as Problem;
89
use 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
2239
pub 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

Comments
 (0)