use crate::asset::{Asset, AssetPool, AssetRef};
use crate::model::Model;
use crate::output::DataWriter;
use crate::process::ProcessMap;
use crate::simulation::prices::calculate_prices;
use crate::units::Capacity;
use anyhow::{Context, Result};
use log::info;
use std::path::Path;
use std::rc::Rc;
pub mod optimisation;
use optimisation::{DispatchRun, FlowMap};
pub mod investment;
use investment::perform_agent_investment;
pub mod prices;
pub use prices::CommodityPrices;
pub fn run(model: &Model, output_path: &Path, debug_model: bool) -> Result<()> {
let mut writer = DataWriter::create(output_path, &model.model_path, debug_model)?;
let mut user_assets = model.user_assets.clone();
let mut asset_pool = AssetPool::new(); let mut decommissioned = Vec::new();
let mut year_iter = model.iter_years().peekable();
let year = year_iter.next().unwrap();
info!("Milestone year: {year}");
asset_pool.commission_new(year, &mut user_assets);
writer.write_assets(asset_pool.iter())?;
let next_year = year_iter.peek().copied();
let mut candidates = candidate_assets_for_next_year(
&model.processes,
next_year,
model.parameters.candidate_asset_capacity,
);
info!("Running dispatch optimisation...");
let (flow_map, mut prices) =
run_dispatch_for_year(model, asset_pool.as_slice(), &candidates, year, &mut writer)?;
writer.write_flows(year, &flow_map)?;
writer.write_prices(year, &prices)?;
while let Some(year) = year_iter.next() {
info!("Milestone year: {year}");
asset_pool.decommission_old(year, &mut decommissioned);
asset_pool.commission_new(year, &mut user_assets);
let existing_assets = asset_pool.take();
let mut ironing_out_iter = 0;
let selected_assets: Vec<AssetRef> = loop {
writer.set_debug_context(format!("ironing out iteration {ironing_out_iter}"));
info!("Running agent investment...");
let selected_assets =
perform_agent_investment(model, year, &existing_assets, &prices, &mut writer)
.context("Agent investment failed")?;
info!("Running dispatch optimisation...");
let (_flow_map, new_prices) =
run_dispatch_for_year(model, &selected_assets, &candidates, year, &mut writer)?;
let prices_stable = prices.within_tolerance_weighted(
&new_prices,
model.parameters.price_tolerance,
&model.time_slice_info,
);
prices = new_prices;
writer.clear_debug_context();
if prices_stable {
info!("Prices converged after {} iterations", ironing_out_iter + 1);
break selected_assets;
}
ironing_out_iter += 1;
if ironing_out_iter == model.parameters.max_ironing_out_iterations {
info!(
"Max ironing out iterations ({}) reached",
model.parameters.max_ironing_out_iterations
);
break selected_assets;
}
};
asset_pool.extend(selected_assets);
asset_pool.mothball_unretained(existing_assets, year);
asset_pool.decommission_mothballed(
year,
model.parameters.mothball_years,
&mut decommissioned,
);
writer.write_assets(decommissioned.iter().chain(asset_pool.iter()))?;
let next_year = year_iter.peek().copied();
candidates = candidate_assets_for_next_year(
&model.processes,
next_year,
model.parameters.candidate_asset_capacity,
);
info!("Running final dispatch optimisation for year {year}...");
let (flow_map, new_prices) =
run_dispatch_for_year(model, asset_pool.as_slice(), &candidates, year, &mut writer)?;
writer.write_flows(year, &flow_map)?;
writer.write_prices(year, &new_prices)?;
prices = new_prices;
}
writer.flush()?;
Ok(())
}
fn run_dispatch_for_year(
model: &Model,
assets: &[AssetRef],
candidates: &[AssetRef],
year: u32,
writer: &mut DataWriter,
) -> Result<(FlowMap, CommodityPrices)> {
let (solution_existing, flow_map) = (!assets.is_empty())
.then(|| -> Result<_> {
let solution =
DispatchRun::new(model, assets, year).run("final without candidates", writer)?;
let flow_map = solution.create_flow_map();
Ok((Some(solution), flow_map))
})
.transpose()?
.unwrap_or_default();
let solution_for_prices = (!candidates.is_empty())
.then(|| {
DispatchRun::new(model, assets, year)
.with_candidates(candidates)
.run("final with candidates", writer)
})
.transpose()?
.or(solution_existing);
let prices = solution_for_prices
.map(|solution| calculate_prices(model, &solution, year))
.transpose()?
.unwrap_or_default();
Ok((flow_map, prices))
}
fn candidate_assets_for_next_year(
processes: &ProcessMap,
next_year: Option<u32>,
candidate_asset_capacity: Capacity,
) -> Vec<AssetRef> {
let mut candidates = Vec::new();
let Some(next_year) = next_year else {
return candidates;
};
for process in processes
.values()
.filter(move |process| process.active_for_year(next_year))
{
for region_id in &process.regions {
candidates.push(
Asset::new_candidate_for_dispatch(
Rc::clone(process),
region_id.clone(),
candidate_asset_capacity,
next_year,
)
.unwrap()
.into(),
);
}
}
candidates
}