@@ -9,9 +9,9 @@ use crate::region::RegionID;
99use crate :: simulation:: CommodityPrices ;
1010use crate :: time_slice:: { TimeSliceID , TimeSliceInfo } ;
1111use crate :: units:: { Capacity , Dimensionless , Flow , FlowPerCapacity } ;
12- use anyhow:: { Result , ensure} ;
12+ use anyhow:: { Result , bail , ensure} ;
1313use indexmap:: IndexMap ;
14- use itertools:: chain;
14+ use itertools:: { chain, iproduct } ;
1515use log:: debug;
1616use 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