1use crate::asset::{Asset, AssetPool, AssetRef};
3use crate::model::Model;
4use crate::output::DataWriter;
5use crate::process::ProcessMap;
6use crate::simulation::prices::calculate_prices;
7use crate::units::Capacity;
8use anyhow::{Context, Result};
9use log::info;
10use std::path::Path;
11use std::rc::Rc;
12
13pub mod optimisation;
14use optimisation::{DispatchRun, FlowMap};
15pub mod investment;
16use investment::perform_agent_investment;
17pub mod prices;
18pub use prices::CommodityPrices;
19
20pub fn run(
29 model: &Model,
30 mut assets: AssetPool,
31 output_path: &Path,
32 debug_model: bool,
33) -> Result<()> {
34 let mut writer = DataWriter::create(output_path, &model.model_path, debug_model)?;
35
36 let mut year_iter = model.iter_years().peekable();
38 let year = year_iter.next().unwrap(); info!("Milestone year: {year}");
41
42 assets.update_for_year(year);
44
45 writer.write_assets(assets.iter_all())?;
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 (flow_map, mut prices) =
59 run_dispatch_for_year(model, assets.as_slice(), &candidates, year, &mut writer)?;
60
61 writer.write_flows(year, &flow_map)?;
63 writer.write_prices(year, &prices)?;
64
65 while let Some(year) = year_iter.next() {
66 info!("Milestone year: {year}");
67
68 assets.update_for_year(year);
72
73 let existing_assets = assets.take();
75
76 let mut ironing_out_iter = 0;
78 let selected_assets: Vec<AssetRef> = loop {
79 writer.set_debug_context(format!("ironing out iteration {ironing_out_iter}"));
81
82 info!("Running agent investment...");
84 let selected_assets =
85 perform_agent_investment(model, year, &existing_assets, &prices, &mut writer)
86 .context("Agent investment failed")?;
87
88 let mut all_candidates = candidates.clone();
91 all_candidates.extend(
92 existing_assets
93 .iter()
94 .filter(|asset| !selected_assets.contains(asset))
95 .map(|asset| {
96 let mut asset = Asset::new_candidate_from_commissioned(asset);
97 asset.set_capacity(model.parameters.candidate_asset_capacity);
98 asset.into()
99 }),
100 );
101
102 info!("Running dispatch optimisation...");
104 let (_flow_map, new_prices) =
105 run_dispatch_for_year(model, &selected_assets, &all_candidates, year, &mut writer)?;
106
107 let prices_stable = prices.within_tolerance_weighted(
109 &new_prices,
110 model.parameters.price_tolerance,
111 &model.time_slice_info,
112 );
113
114 prices = new_prices;
116
117 writer.clear_debug_context();
119
120 if prices_stable {
122 info!("Prices converged after {} iterations", ironing_out_iter + 1);
123 break selected_assets;
124 }
125
126 ironing_out_iter += 1;
128 if ironing_out_iter == model.parameters.max_ironing_out_iterations {
129 info!(
130 "Max ironing out iterations ({}) reached",
131 model.parameters.max_ironing_out_iterations
132 );
133 break selected_assets;
134 }
135 };
136
137 assets.extend(selected_assets);
139
140 assets.decommission_if_not_active(existing_assets, year);
142
143 writer.write_assets(assets.iter_all())?;
145
146 let next_year = year_iter.peek().copied();
148 candidates = candidate_assets_for_next_year(
149 &model.processes,
150 next_year,
151 model.parameters.candidate_asset_capacity,
152 );
153
154 info!("Running final dispatch optimisation for year {year}...");
156 let (flow_map, new_prices) =
157 run_dispatch_for_year(model, assets.as_slice(), &candidates, year, &mut writer)?;
158
159 writer.write_flows(year, &flow_map)?;
161 writer.write_prices(year, &new_prices)?;
162
163 prices = new_prices;
165 }
166
167 writer.flush()?;
168
169 Ok(())
170}
171
172fn run_dispatch_for_year(
174 model: &Model,
175 assets: &[AssetRef],
176 candidates: &[AssetRef],
177 year: u32,
178 writer: &mut DataWriter,
179) -> Result<(FlowMap, CommodityPrices)> {
180 let (solution_existing, flow_map) = (!assets.is_empty())
183 .then(|| -> Result<_> {
184 let solution =
185 DispatchRun::new(model, assets, year).run("final without candidates", writer)?;
186 let flow_map = solution.create_flow_map();
187
188 Ok((Some(solution), flow_map))
189 })
190 .transpose()?
191 .unwrap_or_default();
192
193 let solution_for_prices = (!candidates.is_empty())
196 .then(|| {
197 DispatchRun::new(model, assets, year)
198 .with_candidates(candidates)
199 .run("final with candidates", writer)
200 })
201 .transpose()?
202 .or(solution_existing);
203
204 let prices = solution_for_prices
207 .map(|solution| calculate_prices(model, &solution))
208 .unwrap_or_default();
209
210 Ok((flow_map, prices))
211}
212
213fn candidate_assets_for_next_year(
215 processes: &ProcessMap,
216 next_year: Option<u32>,
217 candidate_asset_capacity: Capacity,
218) -> Vec<AssetRef> {
219 let mut candidates = Vec::new();
220 let Some(next_year) = next_year else {
221 return candidates;
222 };
223
224 for process in processes
225 .values()
226 .filter(move |process| process.active_for_year(next_year))
227 {
228 for region_id in &process.regions {
229 candidates.push(
230 Asset::new_candidate(
231 Rc::clone(process),
232 region_id.clone(),
233 candidate_asset_capacity,
234 next_year,
235 )
236 .unwrap()
237 .into(),
238 );
239 }
240 }
241
242 candidates
243}