Skip to content

Commit a56bb7a

Browse files
committed
Refactor to create select_assets_for_commodity
1 parent e8b963a commit a56bb7a

2 files changed

Lines changed: 114 additions & 58 deletions

File tree

src/commodity.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ use crate::region::RegionID;
44
use crate::time_slice::{TimeSliceID, TimeSliceLevel, TimeSliceSelection};
55
use crate::units::{Flow, MoneyPerFlow};
66
use indexmap::IndexMap;
7+
use itertools::Itertools;
78
use serde::Deserialize;
89
use serde_string_enum::DeserializeLabeledStringEnum;
910
use std::collections::HashMap;
11+
use std::fmt::Display;
1012
use std::rc::Rc;
1113

1214
define_id_type! {CommodityID}
@@ -60,6 +62,25 @@ pub enum InvestmentSet {
6062
Cycle(Vec<CommodityID>),
6163
}
6264

65+
impl InvestmentSet {
66+
/// Returns an iterator over the commodity IDs in this investment set
67+
pub fn iter(&self) -> impl Iterator<Item = &CommodityID> {
68+
match self {
69+
InvestmentSet::Single(id) => std::slice::from_ref(id).iter(),
70+
InvestmentSet::Cycle(ids) => ids.iter(),
71+
}
72+
}
73+
}
74+
75+
impl Display for InvestmentSet {
76+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77+
match self {
78+
InvestmentSet::Single(id) => write!(f, "{id}"),
79+
InvestmentSet::Cycle(ids) => write!(f, "[{}]", ids.iter().join(", ")),
80+
}
81+
}
82+
}
83+
6384
/// Type of balance for application of cost
6485
#[derive(PartialEq, Clone, Debug, DeserializeLabeledStringEnum)]
6586
pub enum BalanceType {

src/simulation/investment.rs

Lines changed: 93 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use crate::region::RegionID;
99
use crate::simulation::CommodityPrices;
1010
use crate::time_slice::{TimeSliceID, TimeSliceInfo};
1111
use crate::units::{Capacity, Dimensionless, Flow, FlowPerCapacity};
12-
use anyhow::{Result, ensure};
12+
use anyhow::{Result, bail, ensure};
1313
use indexmap::IndexMap;
14-
use itertools::chain;
14+
use itertools::{chain, iproduct};
1515
use log::debug;
1616
use std::collections::HashMap;
1717

@@ -60,69 +60,42 @@ pub fn perform_agent_investment(
6060
// Iterate over investment sets in the investment order
6161
let mut seen_commodities = Vec::new();
6262
for investment_set in investment_order {
63-
let commodity_id = match investment_set {
64-
InvestmentSet::Single(commodity_id) => commodity_id.clone(),
65-
InvestmentSet::Cycle(commodities) => anyhow::bail!(
66-
"Investment cycles are not yet supported. Found cycle for commodities: {commodities:?}"
67-
),
63+
// Select assets for the commodity(/ies) of interest
64+
let selected_assets = match investment_set {
65+
InvestmentSet::Single(commodity_id) => {
66+
let commodity = &model.commodities[commodity_id];
67+
select_assets_for_commodity(
68+
model,
69+
commodity,
70+
region_id,
71+
year,
72+
&demand,
73+
existing_assets,
74+
prices,
75+
writer,
76+
)?
77+
}
78+
InvestmentSet::Cycle(_) => {
79+
bail!(
80+
"Investment cycles are not yet supported. Found cycle for commodities: {investment_set}"
81+
);
82+
}
6883
};
6984

70-
seen_commodities.push(commodity_id.clone());
71-
let commodity = &model.commodities[&commodity_id];
85+
// Update our list of seen commodities
86+
for commodity_id in investment_set.iter() {
87+
seen_commodities.push(commodity_id.clone());
88+
}
7289

7390
// Remove prices for already-seen commodities. Commodities which are produced by at
7491
// least one asset in the dispatch run will have prices produced endogenously (via the
7592
// commodity balance constraints), but commodities for which investment has not yet been
7693
// performed will, by definition, not have any producers. For these, we provide prices
7794
// from the previous dispatch run otherwise they will appear to be free to the model.
78-
for time_slice in model.time_slice_info.iter_ids() {
79-
external_prices.remove(&commodity_id, region_id, time_slice);
80-
}
81-
82-
// List of assets selected/retained for this region/commodity
83-
let mut selected_assets = Vec::new();
84-
85-
for (agent, commodity_portion) in
86-
get_responsible_agents(model.agents.values(), &commodity_id, region_id, year)
95+
for (commodity_id, time_slice) in
96+
iproduct!(investment_set.iter(), model.time_slice_info.iter_ids())
8797
{
88-
debug!(
89-
"Running investment for agent '{}' with commodity '{}' in region '{}'",
90-
&agent.id, commodity_id, region_id
91-
);
92-
93-
// Get demand portion for this commodity for this agent in this region/year
94-
let demand_portion_for_commodity = get_demand_portion_for_commodity(
95-
&model.time_slice_info,
96-
&demand,
97-
&commodity_id,
98-
region_id,
99-
commodity_portion,
100-
);
101-
102-
// Existing and candidate assets from which to choose
103-
let opt_assets = get_asset_options(
104-
&model.time_slice_info,
105-
existing_assets,
106-
&demand_portion_for_commodity,
107-
agent,
108-
commodity,
109-
region_id,
110-
year,
111-
)
112-
.collect();
113-
114-
// Choose assets from among existing pool and candidates
115-
let best_assets = select_best_assets(
116-
model,
117-
opt_assets,
118-
commodity,
119-
agent,
120-
prices,
121-
demand_portion_for_commodity,
122-
year,
123-
writer,
124-
)?;
125-
selected_assets.extend(best_assets);
98+
external_prices.remove(commodity_id, region_id, time_slice);
12699
}
127100

128101
// If no assets have been selected for this region/commodity, skip dispatch optimisation
@@ -139,7 +112,7 @@ pub fn perform_agent_investment(
139112
// **TODO**: presumably we only need to do this for selected_assets, as assets added in
140113
// previous iterations should not change
141114
debug!(
142-
"Running post-investment dispatch for commodity '{commodity_id}' in region '{region_id}'"
115+
"Running post-investment dispatch for '{investment_set}' in region '{region_id}'"
143116
);
144117

145118
// As upstream commodities by definition will not yet have producers, we explicitly set
@@ -148,7 +121,7 @@ pub fn perform_agent_investment(
148121
.with_commodity_subset(&seen_commodities)
149122
.with_input_prices(&external_prices)
150123
.run(
151-
&format!("post {commodity_id}/{region_id} investment"),
124+
&format!("post {investment_set}/{region_id} investment"),
152125
writer,
153126
)?;
154127

@@ -160,6 +133,68 @@ pub fn perform_agent_investment(
160133
Ok(all_selected_assets)
161134
}
162135

136+
/// Select assets for a single commodity in a given region and year
137+
///
138+
/// Returns a list of assets that are selected for investment for this commodity in this region and
139+
/// year.
140+
#[allow(clippy::too_many_arguments)]
141+
fn select_assets_for_commodity(
142+
model: &Model,
143+
commodity: &Commodity,
144+
region_id: &RegionID,
145+
year: u32,
146+
demand: &AllDemandMap,
147+
existing_assets: &[AssetRef],
148+
prices: &CommodityPrices,
149+
writer: &mut DataWriter,
150+
) -> Result<Vec<AssetRef>> {
151+
let mut selected_assets = Vec::new();
152+
for (agent, commodity_portion) in
153+
get_responsible_agents(model.agents.values(), &commodity.id, region_id, year)
154+
{
155+
debug!(
156+
"Running investment for agent '{}' with commodity '{}' in region '{}'",
157+
&agent.id, commodity.id, region_id
158+
);
159+
160+
// Get demand portion for this commodity for this agent in this region/year
161+
let demand_portion_for_commodity = get_demand_portion_for_commodity(
162+
&model.time_slice_info,
163+
demand,
164+
&commodity.id,
165+
region_id,
166+
commodity_portion,
167+
);
168+
169+
// Existing and candidate assets from which to choose
170+
let opt_assets = get_asset_options(
171+
&model.time_slice_info,
172+
existing_assets,
173+
&demand_portion_for_commodity,
174+
agent,
175+
commodity,
176+
region_id,
177+
year,
178+
)
179+
.collect();
180+
181+
// Choose assets from among existing pool and candidates
182+
let best_assets = select_best_assets(
183+
model,
184+
opt_assets,
185+
commodity,
186+
agent,
187+
prices,
188+
demand_portion_for_commodity,
189+
year,
190+
writer,
191+
)?;
192+
selected_assets.extend(best_assets);
193+
}
194+
195+
Ok(selected_assets)
196+
}
197+
163198
/// Flatten the preset commodity demands for a given year into a map of commodity, region and
164199
/// time slice to demand.
165200
///

0 commit comments

Comments
 (0)