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(model: &Model, output_path: &Path, debug_model: bool) -> Result<()> {
28 let mut writer = DataWriter::create(output_path, &model.model_path, debug_model)?;
29 let mut user_assets = model.user_assets.clone();
30 let mut asset_pool = AssetPool::new(); let mut year_iter = model.iter_years().peekable();
34 let year = year_iter.next().unwrap(); info!("Milestone year: {year}");
37
38 let new_assets = asset_pool.commission_new(year, &mut user_assets);
40
41 writer.write_assets(new_assets.iter())?;
43 writer.write_asset_capacities(year, asset_pool.iter())?;
44
45 let next_year = year_iter.peek().copied();
47 let mut candidates = candidate_assets_for_next_year(
48 &model.processes,
49 next_year,
50 model.parameters.candidate_asset_capacity,
51 );
52
53 info!("Running dispatch optimisation...");
55 let (flow_map, mut prices) =
56 run_dispatch_for_year(model, asset_pool.as_slice(), &candidates, year, &mut writer)?;
57
58 writer.write_flows(year, &flow_map)?;
60 writer.write_prices(year, &prices)?;
61
62 while let Some(year) = year_iter.next() {
63 info!("Milestone year: {year}");
64
65 asset_pool.decommission_old(year);
67
68 let new_user_assets = asset_pool.commission_new(year, &mut user_assets).to_vec();
70
71 let existing_assets = asset_pool.take();
73
74 let mut ironing_out_iter = 0;
76 let selected_assets: Vec<AssetRef> = loop {
77 writer.set_debug_context(format!("ironing out iteration {ironing_out_iter}"));
79
80 info!("Running agent investment...");
82 let selected_assets =
83 perform_agent_investment(model, year, &existing_assets, &prices, &mut writer)
84 .context("Agent investment failed")?;
85
86 info!("Running dispatch optimisation...");
88 let (_flow_map, new_prices) =
89 run_dispatch_for_year(model, &selected_assets, &candidates, year, &mut writer)?;
90
91 let prices_stable = prices.within_tolerance_weighted(
93 &new_prices,
94 model.parameters.price_tolerance,
95 &model.time_slice_info,
96 );
97
98 prices = new_prices;
100
101 writer.clear_debug_context();
103
104 if prices_stable {
106 info!("Prices converged after {} iterations", ironing_out_iter + 1);
107 break selected_assets;
108 }
109
110 ironing_out_iter += 1;
112 if ironing_out_iter == model.parameters.max_ironing_out_iterations {
113 info!(
114 "Max ironing out iterations ({}) reached",
115 model.parameters.max_ironing_out_iterations
116 );
117 break selected_assets;
118 }
119 };
120
121 let newly_commissioned = asset_pool.extend(selected_assets).to_vec();
123
124 asset_pool.mothball_unretained(existing_assets, year);
126 asset_pool.decommission_mothballed(year, model.parameters.mothball_years);
127
128 writer.write_assets(new_user_assets.iter().chain(newly_commissioned.iter()))?;
130 writer.write_asset_capacities(year, asset_pool.iter())?;
131
132 let next_year = year_iter.peek().copied();
134 candidates = candidate_assets_for_next_year(
135 &model.processes,
136 next_year,
137 model.parameters.candidate_asset_capacity,
138 );
139
140 info!("Running final dispatch optimisation for year {year}...");
142 let (flow_map, new_prices) =
143 run_dispatch_for_year(model, asset_pool.as_slice(), &candidates, year, &mut writer)?;
144
145 writer.write_flows(year, &flow_map)?;
147 writer.write_prices(year, &new_prices)?;
148
149 prices = new_prices;
151 }
152
153 writer.flush()?;
154
155 Ok(())
156}
157
158fn run_dispatch_for_year(
160 model: &Model,
161 assets: &[AssetRef],
162 candidates: &[AssetRef],
163 year: u32,
164 writer: &mut DataWriter,
165) -> Result<(FlowMap, CommodityPrices)> {
166 let (solution_existing, flow_map) = (!assets.is_empty())
169 .then(|| -> Result<_> {
170 let solution =
171 DispatchRun::new(model, assets, year).run("final without candidates", writer)?;
172 let flow_map = solution.create_flow_map();
173
174 Ok((Some(solution), flow_map))
175 })
176 .transpose()?
177 .unwrap_or_default();
178
179 let solution_for_prices = (!candidates.is_empty())
182 .then(|| {
183 DispatchRun::new(model, assets, year)
184 .with_candidates(candidates)
185 .run("final with candidates", writer)
186 })
187 .transpose()?
188 .or(solution_existing);
189
190 let prices = solution_for_prices
193 .map(|solution| calculate_prices(model, &solution, year))
194 .transpose()?
195 .unwrap_or_default();
196
197 Ok((flow_map, prices))
198}
199
200fn candidate_assets_for_next_year(
202 processes: &ProcessMap,
203 next_year: Option<u32>,
204 candidate_asset_capacity: Capacity,
205) -> Vec<AssetRef> {
206 let mut candidates = Vec::new();
207 let Some(next_year) = next_year else {
208 return candidates;
209 };
210
211 for process in processes
212 .values()
213 .filter(move |process| process.active_for_year(next_year))
214 {
215 for region_id in &process.regions {
216 candidates.push(
217 Asset::new_candidate_for_dispatch(
218 Rc::clone(process),
219 region_id.clone(),
220 candidate_asset_capacity,
221 next_year,
222 )
223 .unwrap()
224 .into(),
225 );
226 }
227 }
228
229 candidates
230}