Skip to content

Commit 35b7bfe

Browse files
authored
Merge pull request #611 from EnergySystemsModellingLab/commodity-flows
Add code for getting commodity flows
2 parents 0cd3aa1 + dd25139 commit 35b7bfe

4 files changed

Lines changed: 60 additions & 43 deletions

File tree

src/output.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::asset::{Asset, AssetRef};
44
use crate::commodity::CommodityID;
55
use crate::process::ProcessID;
66
use crate::region::RegionID;
7-
use crate::simulation::optimisation::Solution;
7+
use crate::simulation::optimisation::{FlowMap, Solution};
88
use crate::simulation::CommodityPrices;
99
use crate::time_slice::TimeSliceID;
1010
use anyhow::{Context, Result};
@@ -269,16 +269,13 @@ impl DataWriter {
269269
}
270270

271271
/// Write commodity flows to a CSV file
272-
pub fn write_flows<'a, I>(&mut self, milestone_year: u32, flows: I) -> Result<()>
273-
where
274-
I: Iterator<Item = (&'a AssetRef, &'a CommodityID, &'a TimeSliceID, f64)>,
275-
{
276-
for (asset, commodity_id, time_slice, flow) in flows {
272+
pub fn write_flows(&mut self, milestone_year: u32, flow_map: &FlowMap) -> Result<()> {
273+
for ((asset, commodity_id, time_slice), flow) in flow_map {
277274
let asset_row = AssetRow::new(milestone_year, asset);
278275
let flow_row = CommodityFlowRow {
279276
commodity_id: commodity_id.clone(),
280277
time_slice: time_slice.clone(),
281-
flow,
278+
flow: *flow,
282279
};
283280
self.flows_writer.serialize((asset_row, flow_row))?;
284281
}
@@ -330,6 +327,7 @@ mod tests {
330327
use crate::asset::AssetPool;
331328
use crate::fixture::{assets, commodity_id, region_id, time_slice};
332329
use crate::time_slice::TimeSliceID;
330+
use indexmap::indexmap;
333331
use itertools::{assert_equal, Itertools};
334332
use rstest::rstest;
335333
use std::iter;
@@ -362,15 +360,15 @@ mod tests {
362360
fn test_write_flows(assets: AssetPool, commodity_id: CommodityID, time_slice: TimeSliceID) {
363361
let milestone_year = 2020;
364362
let asset = assets.iter().next().unwrap();
365-
let flow_item = (asset, &commodity_id, &time_slice, 42.0);
363+
let flow_map = indexmap! {
364+
(asset.clone(), commodity_id.clone(), time_slice.clone()) => 42.0
365+
};
366366

367367
// Write a flow
368368
let dir = tempdir().unwrap();
369369
{
370370
let mut writer = DataWriter::create(dir.path(), false).unwrap();
371-
writer
372-
.write_flows(milestone_year, iter::once(flow_item))
373-
.unwrap();
371+
writer.write_flows(milestone_year, &flow_map).unwrap();
374372
writer.flush().unwrap();
375373
}
376374

src/simulation.rs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,17 @@ pub fn run(
2929
) -> Result<()> {
3030
let mut writer = DataWriter::create(output_path, debug_model)?;
3131

32-
let mut opt_results = None; // all results of dispatch optimisation
33-
for year in model.iter_years() {
34-
info!("Milestone year: {year}");
35-
36-
// Assets that have been decommissioned cannot be selected by agents
37-
assets.decommission_old(year);
32+
// Iterate over milestone years
33+
let mut year_iter = model.iter_years();
34+
let mut year = year_iter.next().unwrap(); // NB: There will be at least one year
3835

39-
// NB: Agent investment is not carried out in first milestone year
40-
if let Some((solution, prices)) = opt_results {
41-
perform_agent_investment(&model, &solution, &prices, &mut assets);
36+
// There shouldn't be assets already commissioned, but let's do this just in case
37+
assets.decommission_old(year);
4238

43-
// **TODO:** Remove this when we implement at least some of the agent investment code
44-
// See: https://github.com/EnergySystemsModellingLab/MUSE_2.0/issues/304
45-
error!("Agent investment is not yet implemented. Exiting...");
46-
return Ok(());
47-
}
39+
// **TODO:** Remove annotation when the loop actually loops
40+
#[allow(clippy::never_loop)]
41+
loop {
42+
info!("Milestone year: {year}");
4843

4944
// Newly commissioned assets will be included in optimisation for at least one milestone
5045
// year before agents have the option of decommissioning them
@@ -56,14 +51,31 @@ pub fn run(
5651

5752
// Dispatch optimisation
5853
let solution = perform_dispatch_optimisation(&model, &assets, year)?;
54+
let flow_map = solution.create_flow_map();
5955
let prices = CommodityPrices::from_model_and_solution(&model, &solution, &assets);
6056

6157
// Write result of dispatch optimisation to file
6258
writer.write_debug_info(year, &solution)?;
63-
writer.write_flows(year, solution.iter_commodity_flows_for_assets())?;
59+
writer.write_flows(year, &flow_map)?;
6460
writer.write_prices(year, &prices)?;
6561

66-
opt_results = Some((solution, prices));
62+
if let Some(next_year) = year_iter.next() {
63+
year = next_year;
64+
65+
// NB: Agent investment is not carried out in first milestone year
66+
perform_agent_investment(&model, &flow_map, &prices, &mut assets);
67+
68+
// Decommission assets whose lifetime has passed
69+
assets.decommission_old(year);
70+
} else {
71+
// No more milestone years. Simulation is finished.
72+
break;
73+
}
74+
75+
// **TODO:** Remove this when we implement at least some of the agent investment code
76+
// See: https://github.com/EnergySystemsModellingLab/MUSE_2.0/issues/304
77+
error!("Agent investment is not yet implemented. Exiting...");
78+
break;
6779
}
6880

6981
writer.flush()?;

src/simulation/investment.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Code for performing agent investment.
2-
use super::optimisation::Solution;
2+
use super::optimisation::FlowMap;
33
use super::CommodityPrices;
44
use crate::asset::AssetPool;
55
use crate::model::Model;
@@ -10,12 +10,12 @@ use log::info;
1010
/// # Arguments
1111
///
1212
/// * `model` - The model
13-
/// * `solution` - The solution to the dispatch optimisation
13+
/// * `flow_map` - Map of commodity flows
1414
/// * `prices` - Commodity prices
1515
/// * `assets` - The asset pool
1616
pub fn perform_agent_investment(
1717
_model: &Model,
18-
_solution: &Solution,
18+
_flow_map: &FlowMap,
1919
_prices: &CommodityPrices,
2020
assets: &mut AssetPool,
2121
) {

src/simulation/optimisation.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ use indexmap::IndexMap;
1313
mod constraints;
1414
use constraints::{add_asset_constraints, ConstraintKeys};
1515

16+
/// A map of commodity flows calculated during the optimisation
17+
pub type FlowMap = IndexMap<(AssetRef, CommodityID, TimeSliceID), f64>;
18+
1619
/// A decision variable in the optimisation
1720
///
1821
/// Note that this type does **not** include the value of the variable; it just refers to a
@@ -48,26 +51,30 @@ impl VariableMap {
4851
/// The solution to the dispatch optimisation problem
4952
pub struct Solution<'a> {
5053
solution: highs::Solution,
51-
_variables: VariableMap,
54+
variables: VariableMap,
5255
time_slice_info: &'a TimeSliceInfo,
5356
constraint_keys: ConstraintKeys,
5457
}
5558

5659
impl Solution<'_> {
57-
/// Iterate over the newly calculated commodity flows for assets.
60+
/// Create a map of commodity flows for each asset's coeffs at every time slice.
5861
///
5962
/// Note that this only includes commodity flows which relate to assets, so not every commodity
6063
/// in the simulation will necessarily be represented.
61-
///
62-
/// # Returns
63-
///
64-
/// An iterator of tuples containing an asset ID, commodity, time slice and flow.
65-
pub fn iter_commodity_flows_for_assets(
66-
&self,
67-
) -> impl Iterator<Item = (&AssetRef, &CommodityID, &TimeSliceID, f64)> {
68-
// **TODO:** Need to calculate flows by multiplying coeffs by asset activity:
69-
// https://github.com/EnergySystemsModellingLab/MUSE_2.0/issues/593
70-
std::iter::empty()
64+
pub fn create_flow_map(&self) -> FlowMap {
65+
// The decision variables represent assets' activity levels, not commodity flows. We
66+
// multiply this value by the flow coeffs to get commodity flows.
67+
let mut flows = FlowMap::new();
68+
for ((asset, time_slice), activity) in self.variables.0.keys().zip(self.solution.columns())
69+
{
70+
for flow in asset.iter_flows() {
71+
let flow_key = (asset.clone(), flow.commodity.id.clone(), time_slice.clone());
72+
let flow_value = activity * flow.coeff;
73+
flows.insert(flow_key, flow_value);
74+
}
75+
}
76+
77+
flows
7178
}
7279

7380
/// Keys and dual values for commodity balance constraints.
@@ -139,7 +146,7 @@ pub fn perform_dispatch_optimisation<'a>(
139146
match solution.status() {
140147
HighsModelStatus::Optimal => Ok(Solution {
141148
solution: solution.get_solution(),
142-
_variables: variables,
149+
variables,
143150
time_slice_info: &model.time_slice_info,
144151
constraint_keys,
145152
}),

0 commit comments

Comments
 (0)