1use crate::asset::{Asset, AssetPool, AssetRef};
3use crate::model::Model;
4use crate::output::DataWriter;
5use crate::process::ProcessMap;
6use crate::simulation::prices::{Prices, calculate_prices};
7use crate::timeit::DispatchTimer;
8use crate::units::Capacity;
9use anyhow::{Context, Result};
10use context_manager;
11use log::info;
12use std::path::Path;
13use std::rc::Rc;
14
15pub mod optimisation;
16use optimisation::{DispatchRun, FlowMap};
17pub mod investment;
18use investment::perform_agent_investment;
19pub mod market;
20pub mod prices;
21pub use prices::PriceMap;
22
23pub fn run(model: &Model, output_path: &Path, debug_model: bool) -> Result<()> {
31 let mut writer = DataWriter::create(output_path, &model.model_path, debug_model)?;
32 let mut user_assets = model.user_assets.clone();
33 let mut asset_pool = AssetPool::new(); let mut year_iter = model.iter_years().peekable();
37 let year = year_iter.next().unwrap(); info!("Milestone year: {year}");
40
41 let new_assets = asset_pool.commission_new(year, &mut user_assets);
43
44 writer.write_assets(new_assets)?;
46 writer.write_asset_capacities(year, &asset_pool)?;
47
48 let next_year = year_iter.peek().copied();
50 let mut candidates = candidate_assets_for_next_year(
51 &model.processes,
52 next_year,
53 model.parameters.candidate_asset_capacity,
54 );
55
56 info!("Running dispatch optimisation...");
58 let (mut prices, flow_map) =
59 run_dispatch_for_year(model, &asset_pool, &candidates, year, &mut writer)?;
60
61 writer.write_flows(year, &flow_map)?;
63 writer.write_prices(year, &prices.market)?;
64
65 while let Some(year) = year_iter.next() {
66 info!("Milestone year: {year}");
67
68 asset_pool.decommission_old(year);
70
71 let mut new_assets = asset_pool.commission_new(year, &mut user_assets).to_vec();
73
74 let existing_assets = asset_pool.take();
76
77 let mut ironing_out_iter = 0;
79 let selected_assets: Vec<AssetRef> = loop {
80 writer.set_debug_context(format!("ironing out iteration {ironing_out_iter}"));
82
83 info!("Running agent investment...");
85 let selected_assets =
86 perform_agent_investment(model, year, &existing_assets, &prices, &mut writer)
87 .context("Agent investment failed")?;
88
89 info!("Running dispatch optimisation...");
91 let (new_prices, ..) =
92 run_dispatch_for_year(model, &selected_assets, &candidates, year, &mut writer)?;
93
94 let prices_stable = prices.market.within_tolerance_weighted(
96 &new_prices.market,
97 model.parameters.price_tolerance,
98 &model.time_slice_info,
99 );
100
101 prices = new_prices;
103
104 writer.clear_debug_context();
106
107 if prices_stable {
109 info!("Prices converged after {} iterations", ironing_out_iter + 1);
110 break selected_assets;
111 }
112
113 ironing_out_iter += 1;
115 if ironing_out_iter == model.parameters.max_ironing_out_iterations {
116 info!(
117 "Max ironing out iterations ({}) reached",
118 model.parameters.max_ironing_out_iterations
119 );
120 break selected_assets;
121 }
122 };
123
124 let newly_selected = asset_pool.extend(selected_assets);
126 new_assets.extend_from_slice(newly_selected);
127
128 asset_pool.mothball_unretained(existing_assets, year);
130 asset_pool.decommission_mothballed(year, model.parameters.mothball_years);
131
132 writer.write_assets(&new_assets)?;
134 writer.write_asset_capacities(year, &asset_pool)?;
135
136 let next_year = year_iter.peek().copied();
138 candidates = candidate_assets_for_next_year(
139 &model.processes,
140 next_year,
141 model.parameters.candidate_asset_capacity,
142 );
143
144 info!("Running final dispatch optimisation for year {year}...");
146 let (new_prices, flow_map) =
147 run_dispatch_for_year(model, &asset_pool, &candidates, year, &mut writer)?;
148
149 writer.write_flows(year, &flow_map)?;
151 writer.write_prices(year, &new_prices.market)?;
152
153 prices = new_prices;
155 }
156
157 writer.flush()?;
158
159 Ok(())
160}
161
162#[context_manager::wrap(DispatchTimer)]
170fn run_dispatch_for_year(
171 model: &Model,
172 assets: &[AssetRef],
173 candidates: &[AssetRef],
174 year: u32,
175 writer: &mut DataWriter,
176) -> Result<(Prices, FlowMap)> {
177 debug_assert!(assets.iter().all(|asset| !asset.is_candidate()));
179 debug_assert!(candidates.iter().all(|asset| asset.is_candidate()));
180
181 let assets_vec: Vec<AssetRef>;
183 let assets = if assets.iter().any(|asset| asset.has_any_mothballed_units()) {
184 assets_vec = assets
185 .iter()
186 .cloned()
187 .filter_map(|asset| {
188 let num_units = asset.get_num_nonmothballed_units();
191 (num_units > 0).then(|| asset.with_subset_of_units(num_units))
192 })
193 .collect();
194 &assets_vec
195 } else {
196 assets
197 };
198
199 let (solution_existing, flow_map) = if assets.is_empty() {
202 (None, FlowMap::default())
203 } else {
204 let solution =
205 DispatchRun::new(model, assets, year).run("final without candidates", writer)?;
206 let flow_map = solution.create_flow_map();
207 (Some(solution), flow_map)
208 };
209
210 let solution_with_candidates = if candidates.is_empty() {
213 None
214 } else {
215 Some(
216 DispatchRun::new(model, assets, year)
217 .with_candidates(candidates)
218 .run("final with candidates", writer)?,
219 )
220 };
221
222 let prices = match (
226 solution_existing.as_ref(),
227 solution_with_candidates.as_ref(),
228 ) {
229 (None, None) => Prices::default(),
230 (Some(existing), None) => calculate_prices(model, existing, existing, year)?,
231 (Some(existing), Some(with_candidates)) => {
232 calculate_prices(model, existing, with_candidates, year)?
233 }
234 (None, Some(with_candidates)) => {
235 calculate_prices(model, with_candidates, with_candidates, year)?
236 }
237 };
238
239 Ok((prices, flow_map))
240}
241
242fn candidate_assets_for_next_year(
244 processes: &ProcessMap,
245 next_year: Option<u32>,
246 candidate_asset_capacity: Capacity,
247) -> Vec<AssetRef> {
248 let mut candidates = Vec::new();
249 let Some(next_year) = next_year else {
250 return candidates;
251 };
252
253 for process in processes
254 .values()
255 .filter(move |process| process.active_for_year(next_year))
256 {
257 for region_id in &process.regions {
258 candidates.push(
259 Asset::new_candidate_for_dispatch(
260 Rc::clone(process),
261 region_id.clone(),
262 candidate_asset_capacity,
263 next_year,
264 )
265 .unwrap()
266 .into(),
267 );
268 }
269 }
270
271 candidates
272}