diff --git a/Cargo.lock b/Cargo.lock index 3833e7bb9..d010bb763 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1082,6 +1082,7 @@ dependencies = [ "ordered-float", "petgraph", "platform-info", + "rayon", "rstest", "serde", "similar", diff --git a/Cargo.toml b/Cargo.toml index 7fcfd6954..87a946d19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ dirs = "6.0.0" edit = "0.1.5" erased-serde = "0.4.10" context_manager = "0.1.3" +rayon = "1.12.0" [dev-dependencies] assert_cmd = "2.2.2" diff --git a/benches/assets.rs b/benches/assets.rs index 852c33343..3baeac175 100644 --- a/benches/assets.rs +++ b/benches/assets.rs @@ -16,8 +16,9 @@ use muse2::simulation::market::{ }; use muse2::simulation::optimisation::DispatchRun; use muse2::simulation::prices::{Prices, calculate_prices}; +use rayon::ThreadPoolBuilder; use std::hint::black_box; -use std::rc::Rc; +use std::sync::Arc; use std::time::Duration; use tempfile::TempDir; @@ -43,8 +44,8 @@ fn load_bench_model() -> (Model, DataWriter, TempDir, TempDir) { let example_dir = TempDir::new().expect("Failed to create temp dir for example"); let model_path = example_dir.path().join(EXAMPLE_NAME); Example::from_name(EXAMPLE_NAME) - .expect("Invalid example name") - .extract(&model_path) + .and_then(|example| example.extract(&model_path)) + .expect("Failed to extract example"); let model = load_model(&model_path).expect("Failed to load model"); let output_dir = TempDir::new().expect("Failed to create temp dir for output"); @@ -114,11 +115,11 @@ fn calculate_seed_prices( /// /// Each synthetic process is a copy of one of `templates`, but given a unique ID so that it is /// treated as a distinct candidate technology. -fn build_synthetic_processes(templates: &[Rc], n: usize) -> Vec> { +fn build_synthetic_processes(templates: &[Arc], n: usize) -> Vec> { (0..n) .map(|i| { let template = &templates[i % templates.len()]; - Rc::new(Process { + Arc::new(Process { id: ProcessID::new(&format!("{}#{i}", template.id)), ..(**template).clone() }) @@ -128,6 +129,11 @@ fn build_synthetic_processes(templates: &[Rc], n: usize) -> Vec> = agent + let templates: Vec> = agent .iter_search_space(region_id, &commodity.id, YEAR) .cloned() .collect(); - let mut group = c.benchmark_group("select_best_assets"); - group - .noise_threshold(0.05) - .sample_size(20) - .measurement_time(Duration::from_secs(3)); - - for n in N_TECHNOLOGIES_RANGE { - // Give the agent a synthetic search space of `n` competing technologies for this market - let mut agent = agent.clone(); - agent.search_space.insert( - (commodity.id.clone(), region_id.clone(), YEAR), - Rc::new(build_synthetic_processes(&templates, n)), - ); - - let opt_assets: Vec = get_asset_options( - &existing_assets, - &demand, - &agent, - commodity, - region_id, - YEAR, - model.parameters.capacity_limit_factor, - ) - .collect(); - let investment_limits = - collect_investment_limits_for_candidates(&opt_assets, commodity_portion); - - group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, _| { - b.iter_batched( - || { - ( - opt_assets.clone(), - investment_limits.clone(), - demand.clone(), - ) - }, - |(opt_assets, investment_limits, demand)| { - select_best_assets( - black_box(&model), - opt_assets, - investment_limits, - black_box(commodity), - black_box(&agent), - black_box(region_id), - black_box(&prices), - demand, - black_box(YEAR), - &mut writer, - ) - .expect("select_best_assets failed") - }, - BatchSize::SmallInput, + // Single-thread pool used to run `select_best_assets` sequentially for comparison. + // Because `select_best_assets` uses `par_iter` internally, installing this pool makes it + // degenerate to serial execution while keeping all other code paths identical. + let sequential_pool = ThreadPoolBuilder::new() + .num_threads(1) + .build() + .expect("Failed to build sequential thread pool"); + + for (group_name, use_parallel) in &[("parallel", true), ("sequential", false)] { + let mut group = c.benchmark_group(format!("select_best_assets/{group_name}")); + group + .noise_threshold(0.05) + .sample_size(20) + .measurement_time(Duration::from_secs(3)); + + for n in N_TECHNOLOGIES_RANGE { + // Give the agent a synthetic search space of `n` competing technologies + let mut agent = agent.clone(); + agent.search_space.insert( + (commodity.id.clone(), region_id.clone(), YEAR), + Arc::new(build_synthetic_processes(&templates, n)), ); - }); + + let opt_assets: Vec = get_asset_options( + &existing_assets, + &demand, + &agent, + commodity, + region_id, + YEAR, + model.parameters.capacity_limit_factor, + ) + .collect(); + let investment_limits = + collect_investment_limits_for_candidates(&opt_assets, commodity_portion); + + group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, _| { + b.iter_batched( + || { + ( + opt_assets.clone(), + investment_limits.clone(), + demand.clone(), + ) + }, + |(opt_assets, investment_limits, demand)| { + let run = || { + select_best_assets( + black_box(&model), + opt_assets, + investment_limits, + black_box(commodity), + black_box(&agent), + black_box(region_id), + black_box(&prices), + demand, + black_box(YEAR), + &mut writer, + ) + .expect("select_best_assets failed") + }; + if *use_parallel { + run() + } else { + sequential_pool.install(run) + } + }, + BatchSize::SmallInput, + ); + }); + } + group.finish(); } - group.finish(); } criterion_group!(benches, criterion_benchmark); diff --git a/src/agent.rs b/src/agent.rs index 5d5c42fac..db5e6d851 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -8,7 +8,7 @@ use crate::units::Dimensionless; use indexmap::{IndexMap, IndexSet}; use serde::Deserialize; use std::collections::HashMap; -use std::rc::Rc; +use std::sync::Arc; define_id_type! {AgentID, "agent ID"} @@ -19,7 +19,7 @@ pub type AgentMap = IndexMap; pub type AgentCommodityPortionsMap = HashMap<(CommodityID, u32), Dimensionless>; /// A map for the agent's search space, keyed by commodity, region, and year -pub type AgentSearchSpaceMap = HashMap<(CommodityID, RegionID, u32), Rc>>>; +pub type AgentSearchSpaceMap = HashMap<(CommodityID, RegionID, u32), Arc>>>; /// A map of objectives for an agent, keyed by year. /// @@ -59,7 +59,7 @@ impl Agent { region_id: &RegionID, commodity_id: &CommodityID, year: u32, - ) -> impl Iterator> { + ) -> impl Iterator> { self.search_space[&(commodity_id.clone(), region_id.clone(), year)].iter() } } diff --git a/src/asset.rs b/src/asset.rs index c3a57ecb7..466622dd5 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -16,12 +16,12 @@ use anyhow::{Context, Result, ensure}; use indexmap::IndexMap; use log::debug; use serde::{Deserialize, Serialize}; -use std::cell::Cell; use std::cmp::Ordering; use std::hash::{Hash, Hasher}; use std::iter; use std::ops::RangeInclusive; -use std::rc::Rc; +use std::sync::Arc; +use std::sync::Mutex; mod capacity; pub use capacity::AssetCapacity; @@ -106,32 +106,47 @@ pub enum AssetState { } /// An asset controlled by an agent. -#[derive(Clone)] pub struct Asset { /// The status of the asset state: AssetState, /// The [`Process`] that this asset corresponds to - process: Rc, + process: Arc, /// Activity limits for this asset - activity_limits: Rc, + activity_limits: Arc, /// The commodity flows for this asset - flows: Rc>, + flows: Arc>, /// The [`ProcessParameter`] corresponding to the asset's region and commission year - process_parameter: Rc, + process_parameter: Arc, /// The region in which the asset is located region_id: RegionID, /// Capacity of asset (for candidates this is a hypothetical capacity which may be altered) - capacity: Cell, + capacity: Mutex, /// The year the asset was/will be commissioned commission_year: u32, /// The maximum year that the asset could be decommissioned max_decommission_year: u32, } +impl Clone for Asset { + fn clone(&self) -> Self { + Self { + state: self.state.clone(), + process: Arc::clone(&self.process), + activity_limits: Arc::clone(&self.activity_limits), + flows: Arc::clone(&self.flows), + process_parameter: Arc::clone(&self.process_parameter), + region_id: self.region_id.clone(), + capacity: Mutex::new(*self.capacity.lock().expect("capacity lock poisoned")), + commission_year: self.commission_year, + max_decommission_year: self.max_decommission_year, + } + } +} + impl Asset { /// Create a new candidate asset pub fn new_candidate( - process: Rc, + process: Arc, region_id: RegionID, capacity: Capacity, commission_year: u32, @@ -153,7 +168,7 @@ impl Asset { /// `candidate_asset_capacity`, regardless of whether the underlying process is divisible or /// not. pub fn new_candidate_for_dispatch( - process: Rc, + process: Arc, region_id: RegionID, capacity: Capacity, commission_year: u32, @@ -185,7 +200,7 @@ impl Asset { #[cfg(test)] pub fn new_ready( agent_id: AgentID, - process: Rc, + process: Arc, region_id: RegionID, capacity: Capacity, commission_year: u32, @@ -211,7 +226,7 @@ impl Asset { #[cfg(test)] pub fn new_commissioned( agent_id: AgentID, - process: Rc, + process: Arc, region_id: RegionID, capacity: Capacity, commission_year: u32, @@ -235,7 +250,7 @@ impl Asset { /// Private helper to create an asset with the given state fn new_with_state( state: AssetState, - process: Rc, + process: Arc, region_id: RegionID, capacity: AssetCapacity, commission_year: u32, @@ -301,7 +316,7 @@ impl Asset { flows, process_parameter, region_id, - capacity: Cell::new(capacity), + capacity: Mutex::new(capacity), commission_year, max_decommission_year, }) @@ -727,7 +742,7 @@ impl Asset { /// Get the capacity for this asset pub fn capacity(&self) -> AssetCapacity { - self.capacity.get() + *self.capacity.lock().expect("capacity lock poisoned") } /// Get the total capacity for this asset @@ -747,9 +762,8 @@ impl Asset { ); self.capacity().assert_same_type(capacity); - // As `capacity` is a `Cell`, we don't actually need a `mut` ref to `self`, but allowing for - // changing the capacity of immutable refs would be potentially dangerous - self.capacity.set(capacity); + // We require a `&mut self` here to prevent accidental mutation through shared references. + *self.capacity.lock().expect("capacity lock poisoned") = capacity; } /// Increase the capacity for this asset (only for Candidate and Parent assets) @@ -766,26 +780,25 @@ impl Asset { "Capacity increase must be positive" ); - // As `capacity` is a `Cell`, we don't actually need a `mut` ref to `self`, but allowing for - // changing the capacity of immutable refs would be potentially dangerous - self.capacity.update(|c| c + capacity); + // We require a `&mut self` here to prevent accidental mutation through shared references. + let mut guard = self.capacity.lock().expect("capacity lock poisoned"); + *guard = *guard + capacity; } /// Decrease the unit count (number of units) of this asset by one. /// - /// Note that this method uses interior mutability so that we can operate on an immutable ref to - /// `self`. Accordingly, calling this method will result in a change in the capacity for all - /// `Rc` copies of the asset, which is potentially dangerous. This method is therefore private - /// and should **only** be used for the case where we want to decrease the unit count for parent - /// assets. + /// Note that this method uses interior mutability via a `Mutex` so that we can operate on an + /// immutable ref to `self`. Accordingly, calling this method will result in a change in the + /// capacity for all `Arc` copies of the asset, which is potentially dangerous. This method is + /// therefore private and should **only** be used for the case where we want to decrease the + /// unit count for parent assets. fn decrement_unit_count(&self) { - let AssetCapacity::Discrete(n_units, unit_size) = self.capacity() else { + let mut cap = self.capacity.lock().expect("capacity lock poisoned"); + let AssetCapacity::Discrete(n_units, unit_size) = *cap else { panic!("Cannot decrement unit count of non-divisible asset"); }; assert!(n_units > 0, "Unit count has dropped below zero"); - - self.capacity - .set(AssetCapacity::Discrete(n_units - 1, unit_size)); + *cap = AssetCapacity::Discrete(n_units - 1, unit_size); } /// Decommission this asset @@ -987,7 +1000,7 @@ impl UserAsset { /// Create a new [`UserAsset`] pub fn new( agent_id: AgentID, - process: Rc, + process: Arc, region_id: RegionID, capacity: Capacity, commission_year: u32, @@ -1037,12 +1050,12 @@ pub fn check_capacity_valid_for_asset(capacity: Capacity) -> Result<()> { /// otherwise using a combination of other fields which should be unique at all the relevant points /// in the simulation. #[derive(Clone, Debug, derive_more::Deref, derive_more::From, derive_more::Into)] -pub struct AssetRef(#[deref(forward)] Rc); +pub struct AssetRef(#[deref(forward)] Arc); impl AssetRef { /// Make a mutable reference to the underlying [`Asset`] pub fn make_mut(&mut self) -> &mut Asset { - Rc::make_mut(&mut self.0) + Arc::make_mut(&mut self.0) } /// Get a representation of this [`AssetRef`] that can be used for comparisons @@ -1090,7 +1103,7 @@ impl AssetRef { // Create a child of size `unit_size` let child = AssetRef::from(Asset { - capacity: Cell::new(AssetCapacity::Discrete(1, unit_size)), + capacity: Mutex::new(AssetCapacity::Discrete(1, unit_size)), ..Asset::clone(&self) }); @@ -1132,7 +1145,7 @@ impl AssetRef { match num_units.cmp(&max_num_units) { // Make a new Asset with fewer units Ordering::Less => Self::from(Asset { - capacity: Cell::new(AssetCapacity::Discrete(num_units, unit_size)), + capacity: Mutex::new(AssetCapacity::Discrete(num_units, unit_size)), ..Asset::clone(self) }), // Same number of units as self @@ -1146,7 +1159,7 @@ impl AssetRef { impl From for AssetRef { fn from(value: Asset) -> Self { - Self::from(Rc::new(value)) + Self::from(Arc::new(value)) } } @@ -1253,7 +1266,7 @@ mod tests { use float_cmp::assert_approx_eq; use indexmap::indexmap; use rstest::{fixture, rstest}; - use std::rc::Rc; + use std::sync::Arc; #[rstest] fn get_input_cost_from_prices_works( @@ -1263,20 +1276,20 @@ mod tests { time_slice: TimeSliceID, ) { // Update the process flows using the existing commodity fixture - let commodity_rc = Rc::new(svd_commodity); + let commodity_rc = Arc::new(svd_commodity); let process_flow = ProcessFlow { - commodity: Rc::clone(&commodity_rc), + commodity: Arc::clone(&commodity_rc), coeff: FlowPerActivity(-2.0), // Input kind: FlowType::Fixed, cost: MoneyPerFlow(0.0), }; let process_flows = indexmap! { commodity_rc.id.clone() => process_flow.clone() }; - let process_flows_map = process_flows_map(process.regions.clone(), Rc::new(process_flows)); + let process_flows_map = process_flows_map(process.regions.clone(), Arc::new(process_flows)); process.flows = process_flows_map; // Create asset - let asset = - Asset::new_candidate(Rc::new(process), region_id.clone(), Capacity(1.0), 2020).unwrap(); + let asset = Asset::new_candidate(Arc::new(process), region_id.clone(), Capacity(1.0), 2020) + .unwrap(); // Set input prices let mut input_prices = PriceMap::default(); @@ -1309,7 +1322,7 @@ mod tests { fn asset_with_activity_limits(process_with_activity_limits: Process) -> Asset { Asset::new_ready( "agent1".into(), - Rc::new(process_with_activity_limits), + Arc::new(process_with_activity_limits), "GBR".into(), Capacity(2.0), 2010, @@ -1414,7 +1427,7 @@ mod tests { let asset = AssetRef::from( Asset::new_ready( "agent1".into(), - Rc::new(process), + Arc::new(process), "GBR".into(), capacity, 2010, @@ -1498,7 +1511,7 @@ mod tests { assert_eq!(partial_parent.num_children(), Some(num_units)); assert_eq!(partial_parent.group_id(), parent.group_id()); assert_eq!(partial_parent.agent_id(), parent.agent_id()); - assert_eq!(Rc::ptr_eq(&partial_parent.0, &parent.0), expect_same_asset); + assert_eq!(Arc::ptr_eq(&partial_parent.0, &parent.0), expect_same_asset); assert_eq!(parent.capacity(), AssetCapacity::Discrete(3, Capacity(4.0))); } @@ -1599,11 +1612,11 @@ mod tests { // Set an addition limit of 3 for (region, year 2015) process.investment_constraints.insert( (region_id.clone(), 2015), - Rc::new(crate::process::ProcessInvestmentConstraint { + Arc::new(crate::process::ProcessInvestmentConstraint { addition_limit: Some(Capacity(3.0)), }), ); - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); // Create a candidate asset with commission year 2015 let asset = diff --git a/src/asset/pool.rs b/src/asset/pool.rs index 7d5e7d654..2aab4d4cb 100644 --- a/src/asset/pool.rs +++ b/src/asset/pool.rs @@ -200,7 +200,7 @@ mod tests { use itertools::{Itertools, assert_equal}; use rstest::{fixture, rstest}; use std::iter; - use std::rc::Rc; + use std::sync::Arc; #[fixture] fn user_assets(mut process: Process) -> Vec { @@ -215,12 +215,12 @@ mod tests { let process_parameter_map = process_parameter_map(process.regions.clone(), process_param); process.parameters = process_parameter_map; - let rc_process = Rc::new(process); + let rc_process = Arc::new(process); [2020, 2010] .map(|year| { UserAsset::new( "agent1".into(), - Rc::clone(&rc_process), + Arc::clone(&rc_process), "GBR".into(), Capacity(1.0), year, @@ -393,11 +393,11 @@ mod tests { let original_count = asset_pool.assets.len(); // Create new non-commissioned assets - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); let new_assets = vec![ Asset::new_ready( "agent2".into(), - Rc::clone(&process_rc), + Arc::clone(&process_rc), "GBR".into(), Capacity(1.5), 2015, @@ -406,7 +406,7 @@ mod tests { .into(), Asset::new_ready( "agent3".into(), - Rc::clone(&process_rc), + Arc::clone(&process_rc), "GBR".into(), Capacity(2.5), 2020, @@ -443,11 +443,11 @@ mod tests { // Create new non-commissioned assets process.unit_size = Some(Capacity(4.0)); - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); let new_assets: Vec = vec![ Asset::new_ready( "agent2".into(), - Rc::clone(&process_rc), + Arc::clone(&process_rc), "GBR".into(), Capacity(11.0), 2015, @@ -475,10 +475,10 @@ mod tests { // Add one ready divisible asset so extend() commissions multiple new children process.unit_size = Some(Capacity(4.0)); - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); let ready_divisible: AssetRef = Asset::new_ready( "agent_selected".into(), - Rc::clone(&process_rc), + Arc::clone(&process_rc), "GBR".into(), Capacity(11.0), 2020, @@ -549,11 +549,11 @@ mod tests { asset_pool.commission_new(2020, &mut user_assets); // Create new assets that would be out of order if added at the end - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); let new_assets = vec![ Asset::new_ready( "agent_high_id".into(), - Rc::clone(&process_rc), + Arc::clone(&process_rc), "GBR".into(), Capacity(1.0), 2010, @@ -562,7 +562,7 @@ mod tests { .into(), Asset::new_ready( "agent_low_id".into(), - Rc::clone(&process_rc), + Arc::clone(&process_rc), "GBR".into(), Capacity(1.0), 2015, @@ -605,11 +605,11 @@ mod tests { assert_eq!(asset_pool.next_id, 2); // Should be 2 after commissioning 2 assets // Create new non-commissioned assets - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); let new_assets = vec![ Asset::new_ready( "agent1".into(), - Rc::clone(&process_rc), + Arc::clone(&process_rc), "GBR".into(), Capacity(1.0), 2015, @@ -618,7 +618,7 @@ mod tests { .into(), Asset::new_ready( "agent2".into(), - Rc::clone(&process_rc), + Arc::clone(&process_rc), "GBR".into(), Capacity(1.0), 2020, diff --git a/src/commodity.rs b/src/commodity.rs index 80d8cb1a7..a7fa92edf 100644 --- a/src/commodity.rs +++ b/src/commodity.rs @@ -6,12 +6,12 @@ use crate::units::{Flow, MoneyPerFlow}; use indexmap::IndexMap; use serde::Deserialize; use std::collections::HashMap; -use std::rc::Rc; +use std::sync::Arc; define_id_type! {CommodityID, "commodity ID"} /// A map of [`Commodity`]s, keyed by commodity ID -pub type CommodityMap = IndexMap>; +pub type CommodityMap = IndexMap>; /// A map of [`MoneyPerFlow`]s, keyed by region ID, year and time slice ID for a specific levy pub type CommodityLevyMap = HashMap<(RegionID, u32, TimeSliceID), MoneyPerFlow>; diff --git a/src/fixture.rs b/src/fixture.rs index 7363820cf..02b98c8f2 100644 --- a/src/fixture.rs +++ b/src/fixture.rs @@ -30,7 +30,7 @@ use itertools::Itertools; use rstest::fixture; use std::collections::HashMap; use std::iter; -use std::rc::Rc; +use std::sync::Arc; /// Assert that an error with the given message occurs macro_rules! assert_error { @@ -215,7 +215,7 @@ pub fn asset_divisible(mut process: Process) -> Asset { process.unit_size = Some(Capacity(4.0)); Asset::new_ready( "agent1".into(), - Rc::new(process), + Arc::new(process), "GBR".into(), Capacity(11.0), 2010, @@ -248,7 +248,7 @@ pub fn process_parameter_map( region_ids: IndexSet, process_parameter: ProcessParameter, ) -> ProcessParameterMap { - let parameter = Rc::new(process_parameter); + let parameter = Arc::new(process_parameter); region_ids .into_iter() .cartesian_product(2010..=2020) @@ -271,28 +271,28 @@ pub fn process_activity_limits_map( region_ids .into_iter() .cartesian_product(2010..=2020) - .map(|(region_id, year)| ((region_id, year), Rc::new(process_activity_limits.clone()))) + .map(|(region_id, year)| ((region_id, year), Arc::new(process_activity_limits.clone()))) .collect() } #[fixture] /// Create an empty set of `ProcessInvestmentConstraints` for a given region/year -/// Returns a `HashMap` keyed by (`RegionID`, year) with empty Rc +/// Returns a `HashMap` keyed by (`RegionID`, year) with empty Arc pub fn process_investment_constraints() -> ProcessInvestmentConstraintsMap { HashMap::new() } #[fixture] /// Create an empty set of `ProcessFlows` for a given region/year -pub fn process_flows() -> Rc> { - Rc::new(IndexMap::new()) +pub fn process_flows() -> Arc> { + Arc::new(IndexMap::new()) } #[fixture] /// Create a `ProcessFlowsMap` with the provided flows for each region/year pub fn process_flows_map( region_ids: IndexSet, - process_flows: Rc>, + process_flows: Arc>, ) -> ProcessFlowsMap { region_ids .into_iter() @@ -406,7 +406,7 @@ pub fn appraisal_output(asset: Asset, time_slice: TimeSliceID) -> AppraisalOutpu let unmet_demand = indexmap! { time_slice.clone() => Flow(5.0) }; AppraisalOutput { asset: AssetRef::from(asset), - coefficients: Rc::new(ObjectiveCoefficients { + coefficients: Arc::new(ObjectiveCoefficients { activity_coefficients, market_costs, }), diff --git a/src/graph.rs b/src/graph.rs index 1f166ac77..def0ddb06 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -12,7 +12,7 @@ use std::collections::HashMap; use std::fs::File; use std::io::Write as IoWrite; use std::path::Path; -use std::rc::Rc; +use std::sync::Arc; pub mod investment; pub mod validate; @@ -61,7 +61,7 @@ pub enum GraphEdge { fn get_flow_for_year( process: &Process, target: (RegionID, u32), -) -> Option>> { +) -> Option>> { // If its already in the map, we return it if process.flows.contains_key(&target) { return process.flows.get(&target).cloned(); diff --git a/src/graph/investment.rs b/src/graph/investment.rs index 5d6ade42d..5f30ac1ec 100644 --- a/src/graph/investment.rs +++ b/src/graph/investment.rs @@ -504,7 +504,7 @@ mod tests { use crate::fixture::{sed_commodity, svd_commodity}; use petgraph::graph::Graph; use rstest::rstest; - use std::rc::Rc; + use std::sync::Arc; #[test] fn order_sccs_simple_cycle() { @@ -561,9 +561,9 @@ mod tests { // Create commodities map using fixtures let mut commodities = CommodityMap::new(); - commodities.insert("A".into(), Rc::new(sed_commodity.clone())); - commodities.insert("B".into(), Rc::new(sed_commodity)); - commodities.insert("C".into(), Rc::new(svd_commodity)); + commodities.insert("A".into(), Arc::new(sed_commodity.clone())); + commodities.insert("B".into(), Arc::new(sed_commodity)); + commodities.insert("C".into(), Arc::new(svd_commodity)); let graphs = IndexMap::from([(("GBR".into(), 2020), graph)]); let result = solve_investment_order_for_year(&graphs, &commodities, 2020); @@ -590,8 +590,8 @@ mod tests { // Create commodities map using fixtures let mut commodities = CommodityMap::new(); - commodities.insert("A".into(), Rc::new(sed_commodity.clone())); - commodities.insert("B".into(), Rc::new(sed_commodity)); + commodities.insert("A".into(), Arc::new(sed_commodity.clone())); + commodities.insert("B".into(), Arc::new(sed_commodity)); let graphs = IndexMap::from([(("GBR".into(), 2020), graph)]); let result = solve_investment_order_for_year(&graphs, &commodities, 2020); @@ -627,10 +627,10 @@ mod tests { // Create commodities map using fixtures let mut commodities = CommodityMap::new(); - commodities.insert("A".into(), Rc::new(sed_commodity.clone())); - commodities.insert("B".into(), Rc::new(sed_commodity.clone())); - commodities.insert("C".into(), Rc::new(sed_commodity)); - commodities.insert("D".into(), Rc::new(svd_commodity)); + commodities.insert("A".into(), Arc::new(sed_commodity.clone())); + commodities.insert("B".into(), Arc::new(sed_commodity.clone())); + commodities.insert("C".into(), Arc::new(sed_commodity)); + commodities.insert("D".into(), Arc::new(svd_commodity)); let graphs = IndexMap::from([(("GBR".into(), 2020), graph)]); let result = solve_investment_order_for_year(&graphs, &commodities, 2020); @@ -663,9 +663,9 @@ mod tests { // Create commodities map using fixtures let mut commodities = CommodityMap::new(); - commodities.insert("A".into(), Rc::new(sed_commodity.clone())); - commodities.insert("B".into(), Rc::new(sed_commodity)); - commodities.insert("C".into(), Rc::new(svd_commodity)); + commodities.insert("A".into(), Arc::new(sed_commodity.clone())); + commodities.insert("B".into(), Arc::new(sed_commodity)); + commodities.insert("C".into(), Arc::new(svd_commodity)); // Duplicate the graph over two regions let graphs = IndexMap::from([ diff --git a/src/graph/validate.rs b/src/graph/validate.rs index fd5f01c00..b750ae9db 100644 --- a/src/graph/validate.rs +++ b/src/graph/validate.rs @@ -237,7 +237,7 @@ mod tests { use crate::fixture::{assert_error, other_commodity, sed_commodity, svd_commodity}; use petgraph::graph::Graph; use rstest::rstest; - use std::rc::Rc; + use std::sync::Arc; #[rstest] fn validate_commodities_graph_works( @@ -249,9 +249,9 @@ mod tests { let mut commodities = CommodityMap::new(); // Add test commodities (all have DayNight time slice level) - commodities.insert("A".into(), Rc::new(other_commodity)); - commodities.insert("B".into(), Rc::new(sed_commodity)); - commodities.insert("C".into(), Rc::new(svd_commodity)); + commodities.insert("A".into(), Arc::new(other_commodity)); + commodities.insert("B".into(), Arc::new(sed_commodity)); + commodities.insert("C".into(), Arc::new(svd_commodity)); // Build valid graph: A(OTH) -> B(SED) -> C(SVD) ->D(DEMAND) let node_a = graph.add_node(GraphNode::Commodity("A".into())); @@ -276,9 +276,9 @@ mod tests { let mut commodities = CommodityMap::new(); // Add test commodities (all have DayNight time slice level) - commodities.insert("A".into(), Rc::new(svd_commodity)); - commodities.insert("B".into(), Rc::new(sed_commodity)); - commodities.insert("C".into(), Rc::new(other_commodity)); + commodities.insert("A".into(), Arc::new(svd_commodity)); + commodities.insert("B".into(), Arc::new(sed_commodity)); + commodities.insert("C".into(), Arc::new(other_commodity)); // Build invalid graph: C(OTH) -> A(SVD) -> B(SED) - SVD cannot be consumed let node_c = graph.add_node(GraphNode::Commodity("C".into())); @@ -300,7 +300,7 @@ mod tests { let mut commodities = CommodityMap::new(); // Add test commodities (all have DayNight time slice level) - commodities.insert("A".into(), Rc::new(svd_commodity)); + commodities.insert("A".into(), Arc::new(svd_commodity)); // Build invalid graph: A(SVD) -> B(DEMAND) - SVD must be produced let node_a = graph.add_node(GraphNode::Commodity("A".into())); @@ -320,8 +320,8 @@ mod tests { let mut commodities = CommodityMap::new(); // Add test commodities (all have DayNight time slice level) - commodities.insert("A".into(), Rc::new(sed_commodity.clone())); - commodities.insert("B".into(), Rc::new(sed_commodity)); + commodities.insert("A".into(), Arc::new(sed_commodity.clone())); + commodities.insert("B".into(), Arc::new(sed_commodity)); // Build invalid graph: B(SED) -> A(SED) let node_a = graph.add_node(GraphNode::Commodity("A".into())); @@ -344,9 +344,9 @@ mod tests { let mut commodities = CommodityMap::new(); // Add test commodities (all have DayNight time slice level) - commodities.insert("A".into(), Rc::new(other_commodity)); - commodities.insert("B".into(), Rc::new(sed_commodity.clone())); - commodities.insert("C".into(), Rc::new(sed_commodity)); + commodities.insert("A".into(), Arc::new(other_commodity)); + commodities.insert("B".into(), Arc::new(sed_commodity.clone())); + commodities.insert("C".into(), Arc::new(sed_commodity)); // Build invalid graph: B(SED) -> A(OTH) -> C(SED) let node_a = graph.add_node(GraphNode::Commodity("A".into())); diff --git a/src/id.rs b/src/id.rs index 52eaed069..9c0e06501 100644 --- a/src/id.rs +++ b/src/id.rs @@ -30,7 +30,7 @@ macro_rules! define_id_type { )] /// An ID type (e.g. `AgentID`, `CommodityID`, etc.) #[from(forward)] - pub struct $name(pub std::rc::Rc); + pub struct $name(pub std::sync::Arc); impl std::borrow::Borrow for $name { fn borrow(&self) -> &str { @@ -71,7 +71,7 @@ macro_rules! define_id_type { impl $name { /// Create a new ID from a string slice pub fn new(id: &str) -> Self { - $name(std::rc::Rc::from(id)) + $name(std::sync::Arc::from(id)) } } }; diff --git a/src/input/agent/commodity_portion.rs b/src/input/agent/commodity_portion.rs index 0dc59694e..ba42f8e1c 100644 --- a/src/input/agent/commodity_portion.rs +++ b/src/input/agent/commodity_portion.rs @@ -211,7 +211,7 @@ mod tests { }; use indexmap::IndexMap; use rstest::{fixture, rstest}; - use std::rc::Rc; + use std::sync::Arc; #[fixture] fn milestone_years() -> [u32; 1] { @@ -221,8 +221,8 @@ mod tests { #[fixture] fn commodities(svd_commodity: Commodity, other_commodity: Commodity) -> CommodityMap { IndexMap::from([ - ("commodity1".into(), Rc::new(svd_commodity)), - ("other_commodity".into(), Rc::new(other_commodity)), + ("commodity1".into(), Arc::new(svd_commodity)), + ("other_commodity".into(), Arc::new(other_commodity)), ]) } @@ -331,7 +331,7 @@ mod tests { sed_commodity: Commodity, ) { // Invalid case: SED commodity without associated commodity portions - commodities.insert(CommodityID::new("sed_commodity"), Rc::new(sed_commodity)); + commodities.insert(CommodityID::new("sed_commodity"), Arc::new(sed_commodity)); assert_error!( validate_agent_commodity_portions( &agent_commodity_portions, diff --git a/src/input/agent/search_space.rs b/src/input/agent/search_space.rs index 44236266d..6216b0072 100644 --- a/src/input/agent/search_space.rs +++ b/src/input/agent/search_space.rs @@ -11,11 +11,11 @@ use itertools::{Itertools, iproduct}; use serde::Deserialize; use std::collections::{HashMap, HashSet}; use std::path::Path; -use std::rc::Rc; +use std::sync::Arc; const AGENT_SEARCH_SPACES_FILE_NAME: &str = "agent_search_spaces.csv"; -type ProducersMap = HashMap<(CommodityID, RegionID, u32), Rc>>>; +type ProducersMap = HashMap<(CommodityID, RegionID, u32), Arc>>>; #[derive(PartialEq, Debug, Deserialize)] struct SearchSpaceEntry { @@ -85,7 +85,7 @@ fn for_each_year_in_search_space( mut f: F, ) -> Result<()> where - F: FnMut(CommodityID, RegionID, u32, Rc>>) -> Result<()>, + F: FnMut(CommodityID, RegionID, u32, Arc>>) -> Result<()>, { ensure!(!search_space.is_empty(), "No processes provided"); @@ -103,7 +103,7 @@ where } } else { // Check each process ID in turn - let search_space: Rc> = Rc::new( + let search_space: Arc> = Arc::new( search_space .split(';') .map(|process_id_str| { @@ -249,7 +249,7 @@ fn get_producers_map(agents: &AgentMap, processes: &ProcessMap) -> ProducersMap && process.regions.contains(region_id) }) .cloned(); - Rc::get_mut(vec).unwrap().extend(producers); + Arc::get_mut(vec).unwrap().extend(producers); } map @@ -272,13 +272,13 @@ mod tests { use std::iter; #[fixture] - fn process1(process: Process) -> Rc { - Rc::new(process) + fn process1(process: Process) -> Arc { + Arc::new(process) } #[fixture] - fn process2(process: Process) -> Rc { - Rc::new(Process { + fn process2(process: Process) -> Arc { + Arc::new(Process { id: "process2".into(), ..process }) @@ -316,14 +316,14 @@ mod tests { agent: Agent, commodity_id: CommodityID, region_id: RegionID, - process1: Rc, - process2: Rc, + process1: Arc, + process2: Arc, ) { let producers = hash_map! { - (commodity_id.clone(), region_id.clone(), 2020) => Rc::new(vec![process1.clone()]), - (commodity_id.clone(), region_id.clone(), 2030) => Rc::new(vec![process2.clone()]) + (commodity_id.clone(), region_id.clone(), 2020) => Arc::new(vec![process1.clone()]), + (commodity_id.clone(), region_id.clone(), 2030) => Arc::new(vec![process2.clone()]) }; - let mut calls: Vec<(u32, Rc>>)> = Vec::new(); + let mut calls: Vec<(u32, Arc>>)> = Vec::new(); for_each_year_in_search_space( "all", &agent, @@ -354,12 +354,12 @@ mod tests { processes: ProcessMap, ) { let process = processes.values().next().unwrap().clone(); - let value = Rc::new(vec![process.clone()]); + let value = Arc::new(vec![process.clone()]); let producers = hash_map! { (commodity_id.clone(), region_id.clone(), 2020) => value.clone(), (commodity_id.clone(), region_id.clone(), 2030) => value }; - let mut calls: Vec<(u32, Rc>>)> = Vec::new(); + let mut calls: Vec<(u32, Arc>>)> = Vec::new(); for_each_year_in_search_space( "process1", &agent, @@ -379,7 +379,7 @@ mod tests { assert_eq!(calls[0].1.len(), 1); assert_eq!(calls[0].1[0].id, process.id); // Both years receive the same Rc-wrapped search space - assert!(Rc::ptr_eq(&calls[0].1, &calls[1].1)); + assert!(Arc::ptr_eq(&calls[0].1, &calls[1].1)); } #[rstest] @@ -387,17 +387,17 @@ mod tests { agent: Agent, commodity_id: CommodityID, region_id: RegionID, - process1: Rc, - process2: Rc, + process1: Arc, + process2: Arc, ) { let producers = hash_map! { - (commodity_id.clone(), region_id.clone(), 2020) => Rc::new(vec![process1.clone(), process2.clone()]) + (commodity_id.clone(), region_id.clone(), 2020) => Arc::new(vec![process1.clone(), process2.clone()]) }; let processes: ProcessMap = indexmap! { process1.id.clone() => process1.clone(), process2.id.clone() => process2.clone(), }; - let mut calls: Vec<(u32, Rc>>)> = Vec::new(); + let mut calls: Vec<(u32, Arc>>)> = Vec::new(); for_each_year_in_search_space( "process1;process2", &agent, diff --git a/src/input/asset.rs b/src/input/asset.rs index f38deb98d..da8b27798 100644 --- a/src/input/asset.rs +++ b/src/input/asset.rs @@ -13,7 +13,7 @@ use itertools::Itertools; use log::warn; use serde::Deserialize; use std::path::Path; -use std::rc::Rc; +use std::sync::Arc; const ASSETS_FILE_NAME: &str = "assets.csv"; @@ -124,7 +124,7 @@ where UserAsset::new( agent_id.clone(), - Rc::clone(process), + Arc::clone(process), region_id.clone(), asset.capacity, asset.commission_year, @@ -167,7 +167,7 @@ mod tests { }; let asset_out = UserAsset::new( "agent1".into(), - Rc::clone(processes.values().next().unwrap()), + Arc::clone(processes.values().next().unwrap()), "GBR".into(), Capacity(1.0), 2010, diff --git a/src/input/process.rs b/src/input/process.rs index 895f5f3a8..d86063854 100644 --- a/src/input/process.rs +++ b/src/input/process.rs @@ -14,7 +14,7 @@ use indexmap::IndexSet; use log::warn; use serde::Deserialize; use std::path::Path; -use std::rc::Rc; +use std::sync::Arc; mod availability; use availability::read_process_availabilities; @@ -71,7 +71,7 @@ pub fn read_processes( // Add data to Process objects for (id, process) in &mut processes { // This will always succeed as we know there will only be one reference to the process here - let process = Rc::get_mut(process).unwrap(); + let process = Arc::get_mut(process).unwrap(); // We have already checked that there are maps for every process so this will succeed process.activity_limits = activity_limits.remove(id).unwrap(); diff --git a/src/input/process/availability.rs b/src/input/process/availability.rs index cf10ceb2b..dadf88eb1 100644 --- a/src/input/process/availability.rs +++ b/src/input/process/availability.rs @@ -12,7 +12,7 @@ use serde::Deserialize; use std::collections::HashMap; use std::ops::RangeInclusive; use std::path::Path; -use std::rc::Rc; +use std::sync::Arc; const PROCESS_AVAILABILITIES_FILE_NAME: &str = "process_availabilities.csv"; @@ -150,7 +150,7 @@ where .with_context(|| { format!("Error creating activity limits for process {process_id}") })?; - inner_map.insert((region_id.clone(), year), Rc::new(availabilities)); + inner_map.insert((region_id.clone(), year), Arc::new(availabilities)); } map.insert(process_id.clone(), inner_map); } diff --git a/src/input/process/flow.rs b/src/input/process/flow.rs index 66c96a494..69a323308 100644 --- a/src/input/process/flow.rs +++ b/src/input/process/flow.rs @@ -14,7 +14,7 @@ use itertools::iproduct; use serde::Deserialize; use std::collections::HashMap; use std::path::Path; -use std::rc::Rc; +use std::sync::Arc; const PROCESS_FLOWS_FILE_NAME: &str = "process_flows.csv"; @@ -183,7 +183,7 @@ where // Create ProcessFlow object let process_flow = ProcessFlow { - commodity: Rc::clone(commodity), + commodity: Arc::clone(commodity), coeff: record.coeff, kind: FlowType::Fixed, cost: record.cost.unwrap_or(MoneyPerFlow(0.0)), @@ -195,7 +195,7 @@ where let flows_map = region_year_map .entry((region_id.clone(), year)) .or_default(); - let existing = Rc::get_mut(flows_map) + let existing = Arc::get_mut(flows_map) .unwrap() // safe: there will only be one copy .insert(commodity.id.clone(), process_flow.clone()) .is_some(); @@ -264,7 +264,7 @@ fn validate_flows_and_update_primary_output( // Update primary output if needed if process.primary_output != primary_output { // Safe: There should only be one ref to process - Rc::get_mut(process).unwrap().primary_output = primary_output; + Arc::get_mut(process).unwrap().primary_output = primary_output; } } @@ -404,9 +404,9 @@ mod tests { use map_macro::hash_map; use rstest::{fixture, rstest}; use std::iter; - use std::rc::Rc; + use std::sync::Arc; - fn flow(commodity: Rc, coeff: f64) -> ProcessFlow { + fn flow(commodity: Arc, coeff: f64) -> ProcessFlow { ProcessFlow { commodity, coeff: FlowPerActivity(coeff), @@ -424,7 +424,7 @@ mod tests { I: Clone + Iterator, { let years = years.unwrap_or(process.years.clone().collect()); - let map: Rc> = Rc::new(flows.collect()); + let map: Arc> = Arc::new(flows.collect()); let flows_inner = iproduct!(&process.regions, years) .map(|(region_id, year)| ((region_id.clone(), year), map.clone())) .collect(); @@ -473,8 +473,8 @@ mod tests { // Both commodities have the same units assert_eq!(svd_commodity.units, sed_commodity.units); - let commodity1 = Rc::new(svd_commodity); - let commodity2 = Rc::new(sed_commodity); + let commodity1 = Arc::new(svd_commodity); + let commodity2 = Arc::new(sed_commodity); let (_, flows_map) = build_maps( process, [ @@ -498,8 +498,8 @@ mod tests { // Ensure the two commodities have different units assert_ne!(sed_commodity_pj.units, sed_commodity_tonnes.units); - let commodity1 = Rc::new(sed_commodity_pj); - let commodity2 = Rc::new(sed_commodity_tonnes); + let commodity1 = Arc::new(sed_commodity_pj); + let commodity2 = Arc::new(sed_commodity_tonnes); let (_, flows_map) = build_maps( process, [ @@ -531,8 +531,8 @@ mod tests { other_commodity.units = "tonnes".into(); assert_ne!(sed_commodity_pj.units, other_commodity.units); - let sed_commodity = Rc::new(sed_commodity_pj); - let oth_commodity = Rc::new(other_commodity); + let sed_commodity = Arc::new(sed_commodity_pj); + let oth_commodity = Arc::new(other_commodity); let (_, flows_map) = build_maps( process, @@ -550,7 +550,7 @@ mod tests { #[rstest] fn single_sed_svd_output(svd_commodity: Commodity, process: Process) { - let commodity = Rc::new(svd_commodity); + let commodity = Arc::new(svd_commodity); let (_, flows_map) = build_maps( process, std::iter::once((commodity.id.clone(), flow(commodity.clone(), 1.0))), @@ -563,8 +563,8 @@ mod tests { #[rstest] fn no_sed_svd_outputs(other_commodity: Commodity, process: Process) { - let oth_commodity_1 = Rc::new(other_commodity.clone()); - let oth_commodity_2 = Rc::new(other_commodity.clone()); + let oth_commodity_1 = Arc::new(other_commodity.clone()); + let oth_commodity_2 = Arc::new(other_commodity.clone()); let (_, flows_map) = build_maps( process, [ @@ -592,9 +592,9 @@ mod tests { // Output commodity shares units with one input assert_eq!(svd_commodity.units, sed_commodity_pj.units); - let input1 = Rc::new(sed_commodity_pj); - let input2 = Rc::new(sed_commodity_tonnes); - let output = Rc::new(svd_commodity); + let input1 = Arc::new(sed_commodity_pj); + let input2 = Arc::new(sed_commodity_tonnes); + let output = Arc::new(svd_commodity); let (_, flows_map) = build_maps( process, @@ -616,7 +616,7 @@ mod tests { #[rstest] fn single_output_infer_primary(#[from(svd_commodity)] commodity: Commodity, process: Process) { let milestone_years = vec![2010, 2020]; - let commodity = Rc::new(commodity); + let commodity = Arc::new(commodity); let (mut processes, flows_map) = build_maps( process, std::iter::once((commodity.id.clone(), flow(commodity.clone(), 1.0))), @@ -637,8 +637,8 @@ mod tests { process: Process, ) { let milestone_years: Vec = vec![2010, 2020]; - let commodity1 = Rc::new(commodity1); - let commodity2 = Rc::new(commodity2); + let commodity1 = Arc::new(commodity1); + let commodity2 = Arc::new(commodity2); let (mut processes, flows_map) = build_maps( process, [ @@ -660,8 +660,8 @@ mod tests { process: Process, ) { let milestone_years = vec![2010, 2020]; - let commodity1 = Rc::new(commodity1); - let commodity2 = Rc::new(commodity2); + let commodity1 = Arc::new(commodity1); + let commodity2 = Arc::new(commodity2); let mut process = process; process.primary_output = Some(commodity2.id.clone()); let (mut processes, flows_map) = build_maps( @@ -688,8 +688,8 @@ mod tests { process: Process, ) { let milestone_years = vec![2010, 2020]; - let commodity1 = Rc::new(commodity1); - let commodity2 = Rc::new(commodity2); + let commodity1 = Arc::new(commodity1); + let commodity2 = Arc::new(commodity2); let (mut processes, flows_map) = build_maps( process, [ @@ -715,8 +715,8 @@ mod tests { ) { let milestone_years = vec![2010, 2015, 2020]; let flow_years = vec![2010, 2020]; - let commodity1 = Rc::new(commodity1); - let commodity2 = Rc::new(commodity2); + let commodity1 = Arc::new(commodity1); + let commodity2 = Arc::new(commodity2); let (mut processes, flows_map) = build_maps( process, [ @@ -741,8 +741,8 @@ mod tests { process: Process, ) { let milestone_years = vec![2010, 2015, 2020]; - let commodity1 = Rc::new(commodity1); - let commodity2 = Rc::new(commodity2); + let commodity1 = Arc::new(commodity1); + let commodity2 = Arc::new(commodity2); let (mut processes, flows_map) = build_maps( process, [ diff --git a/src/input/process/investment_constraints.rs b/src/input/process/investment_constraints.rs index 14788016c..c2c342fd4 100644 --- a/src/input/process/investment_constraints.rs +++ b/src/input/process/investment_constraints.rs @@ -14,7 +14,7 @@ use log::warn; use serde::Deserialize; use std::collections::HashMap; use std::path::Path; -use std::rc::Rc; +use std::sync::Arc; const PROCESS_INVESTMENT_CONSTRAINTS_FILE_NAME: &str = "process_investment_constraints.csv"; @@ -199,7 +199,7 @@ where .addition_limit .map(|limit| limit * Year(years_since_prev as f64)); - let constraint = Rc::new(ProcessInvestmentConstraint { addition_limit }); + let constraint = Arc::new(ProcessInvestmentConstraint { addition_limit }); try_insert(process_map, &(region.clone(), year), constraint.clone())?; } diff --git a/src/input/process/parameter.rs b/src/input/process/parameter.rs index 080e9d2b7..8b860ec91 100644 --- a/src/input/process/parameter.rs +++ b/src/input/process/parameter.rs @@ -10,7 +10,7 @@ use anyhow::{Context, Result, ensure}; use serde::Deserialize; use std::collections::HashMap; use std::path::Path; -use std::rc::Rc; +use std::sync::Arc; const PROCESS_PARAMETERS_FILE_NAME: &str = "process_parameters.csv"; @@ -123,7 +123,7 @@ where })?; // Insert parameter into the map - let param = Rc::new(param_raw.into_parameter()?); + let param = Arc::new(param_raw.into_parameter()?); let entry = map.entry(id.clone()).or_default(); for year in parameter_years { for region in parameter_regions.clone() { diff --git a/src/process.rs b/src/process.rs index 84b741f62..09847f301 100644 --- a/src/process.rs +++ b/src/process.rs @@ -14,27 +14,27 @@ use itertools::Itertools; use serde::Deserialize; use std::collections::HashMap; use std::ops::RangeInclusive; -use std::rc::Rc; +use std::sync::Arc; define_id_type! {ProcessID, "process ID"} /// A map of [`Process`]es, keyed by process ID -pub type ProcessMap = IndexMap>; +pub type ProcessMap = IndexMap>; /// A map indicating activity limits for a [`Process`] throughout the year. -pub type ProcessActivityLimitsMap = HashMap<(RegionID, u32), Rc>; +pub type ProcessActivityLimitsMap = HashMap<(RegionID, u32), Arc>; /// A map of [`ProcessParameter`]s, keyed by region and year -pub type ProcessParameterMap = HashMap<(RegionID, u32), Rc>; +pub type ProcessParameterMap = HashMap<(RegionID, u32), Arc>; /// A map of process flows, keyed by region and year. /// /// The value is actually a map itself, keyed by commodity ID. -pub type ProcessFlowsMap = HashMap<(RegionID, u32), Rc>>; +pub type ProcessFlowsMap = HashMap<(RegionID, u32), Arc>>; /// Map of process investment constraints, keyed by region and year pub type ProcessInvestmentConstraintsMap = - HashMap<(RegionID, u32), Rc>; + HashMap<(RegionID, u32), Arc>; /// Represents a process within the simulation #[derive(PartialEq, Debug, Clone)] @@ -390,7 +390,7 @@ impl ActivityLimits { #[derive(PartialEq, Debug, Clone)] pub struct ProcessFlow { /// The commodity produced or consumed by this flow - pub commodity: Rc, + pub commodity: Arc, /// Maximum annual commodity flow quantity relative to other commodity flows. /// /// Positive value indicates flow out and negative value indicates flow in. @@ -528,10 +528,10 @@ mod tests { use float_cmp::assert_approx_eq; use rstest::{fixture, rstest}; use std::collections::HashMap; - use std::rc::Rc; + use std::sync::Arc; #[fixture] - fn commodity_with_levy(region_id: RegionID, time_slice: TimeSliceID) -> Rc { + fn commodity_with_levy(region_id: RegionID, time_slice: TimeSliceID) -> Arc { let mut levies_prod = CommodityLevyMap::new(); let mut levies_cons = CommodityLevyMap::new(); @@ -580,7 +580,7 @@ mod tests { MoneyPerFlow(-3.0), ); - Rc::new(Commodity { + Arc::new(Commodity { id: "test_commodity".into(), description: "Test commodity".into(), kind: CommodityType::ServiceDemand, @@ -597,11 +597,11 @@ mod tests { fn commodity_with_consumption_levy( region_id: RegionID, time_slice: TimeSliceID, - ) -> Rc { + ) -> Arc { let mut levies = CommodityLevyMap::new(); levies.insert((region_id, 2020, time_slice), MoneyPerFlow(10.0)); - Rc::new(Commodity { + Arc::new(Commodity { id: "test_commodity".into(), description: "Test commodity".into(), kind: CommodityType::ServiceDemand, @@ -618,11 +618,11 @@ mod tests { fn commodity_with_production_levy( region_id: RegionID, time_slice: TimeSliceID, - ) -> Rc { + ) -> Arc { let mut levies = CommodityLevyMap::new(); levies.insert((region_id, 2020, time_slice), MoneyPerFlow(10.0)); - Rc::new(Commodity { + Arc::new(Commodity { id: "test_commodity".into(), description: "Test commodity".into(), kind: CommodityType::ServiceDemand, @@ -636,7 +636,7 @@ mod tests { } #[fixture] - fn commodity_with_incentive(region_id: RegionID, time_slice: TimeSliceID) -> Rc { + fn commodity_with_incentive(region_id: RegionID, time_slice: TimeSliceID) -> Arc { let mut levies_prod = CommodityLevyMap::new(); levies_prod.insert( (region_id.clone(), 2020, time_slice.clone()), @@ -645,7 +645,7 @@ mod tests { let mut levies_cons = CommodityLevyMap::new(); levies_cons.insert((region_id, 2020, time_slice), MoneyPerFlow(5.0)); - Rc::new(Commodity { + Arc::new(Commodity { id: "test_commodity".into(), description: "Test commodity".into(), kind: CommodityType::ServiceDemand, @@ -659,8 +659,8 @@ mod tests { } #[fixture] - fn commodity_no_levies() -> Rc { - Rc::new(Commodity { + fn commodity_no_levies() -> Arc { + Arc::new(Commodity { id: "test_commodity".into(), description: "Test commodity".into(), kind: CommodityType::ServiceDemand, @@ -676,7 +676,7 @@ mod tests { #[fixture] fn flow_with_cost() -> ProcessFlow { ProcessFlow { - commodity: Rc::new(Commodity { + commodity: Arc::new(Commodity { id: "test_commodity".into(), description: "Test commodity".into(), kind: CommodityType::ServiceDemand, @@ -699,7 +699,7 @@ mod tests { levies.insert((region_id, 2020, time_slice), MoneyPerFlow(10.0)); ProcessFlow { - commodity: Rc::new(Commodity { + commodity: Arc::new(Commodity { id: "test_commodity".into(), description: "Test commodity".into(), kind: CommodityType::ServiceDemand, @@ -722,7 +722,7 @@ mod tests { levies.insert((region_id, 2020, time_slice), MoneyPerFlow(-3.0)); ProcessFlow { - commodity: Rc::new(Commodity { + commodity: Arc::new(Commodity { id: "test_commodity".into(), description: "Test commodity".into(), kind: CommodityType::ServiceDemand, @@ -741,7 +741,7 @@ mod tests { #[rstest] fn get_levy_no_levies( - commodity_no_levies: Rc, + commodity_no_levies: Arc, region_id: RegionID, time_slice: TimeSliceID, ) { @@ -760,7 +760,7 @@ mod tests { #[rstest] fn get_levy_with_levy( - commodity_with_levy: Rc, + commodity_with_levy: Arc, region_id: RegionID, time_slice: TimeSliceID, ) { @@ -779,7 +779,7 @@ mod tests { #[rstest] fn get_levy_with_incentive( - commodity_with_incentive: Rc, + commodity_with_incentive: Arc, region_id: RegionID, time_slice: TimeSliceID, ) { @@ -797,7 +797,7 @@ mod tests { } #[rstest] - fn get_levy_different_region(commodity_with_levy: Rc, time_slice: TimeSliceID) { + fn get_levy_different_region(commodity_with_levy: Arc, time_slice: TimeSliceID) { let flow = ProcessFlow { commodity: commodity_with_levy, coeff: FlowPerActivity(1.0), @@ -813,7 +813,7 @@ mod tests { #[rstest] fn get_levy_different_year( - commodity_with_levy: Rc, + commodity_with_levy: Arc, region_id: RegionID, time_slice: TimeSliceID, ) { @@ -831,7 +831,7 @@ mod tests { } #[rstest] - fn get_levy_different_time_slice(commodity_with_levy: Rc, region_id: RegionID) { + fn get_levy_different_time_slice(commodity_with_levy: Arc, region_id: RegionID) { let flow = ProcessFlow { commodity: commodity_with_levy, coeff: FlowPerActivity(1.0), @@ -852,7 +852,7 @@ mod tests { #[rstest] fn get_levy_consumption_positive_coeff( - commodity_with_consumption_levy: Rc, + commodity_with_consumption_levy: Arc, region_id: RegionID, time_slice: TimeSliceID, ) { @@ -871,7 +871,7 @@ mod tests { #[rstest] fn get_levy_consumption_negative_coeff( - commodity_with_consumption_levy: Rc, + commodity_with_consumption_levy: Arc, region_id: RegionID, time_slice: TimeSliceID, ) { @@ -890,7 +890,7 @@ mod tests { #[rstest] fn get_levy_production_positive_coeff( - commodity_with_production_levy: Rc, + commodity_with_production_levy: Arc, region_id: RegionID, time_slice: TimeSliceID, ) { @@ -909,7 +909,7 @@ mod tests { #[rstest] fn get_levy_production_negative_coeff( - commodity_with_production_levy: Rc, + commodity_with_production_levy: Arc, region_id: RegionID, time_slice: TimeSliceID, ) { @@ -990,7 +990,7 @@ mod tests { #[test] fn is_input_and_is_output() { - let commodity = Rc::new(Commodity { + let commodity = Arc::new(Commodity { id: "test_commodity".into(), description: "Test commodity".into(), kind: CommodityType::ServiceDemand, @@ -1003,19 +1003,19 @@ mod tests { }); let flow_in = ProcessFlow { - commodity: Rc::clone(&commodity), + commodity: Arc::clone(&commodity), coeff: FlowPerActivity(-1.0), kind: FlowType::Fixed, cost: MoneyPerFlow(0.0), }; let flow_out = ProcessFlow { - commodity: Rc::clone(&commodity), + commodity: Arc::clone(&commodity), coeff: FlowPerActivity(1.0), kind: FlowType::Fixed, cost: MoneyPerFlow(0.0), }; let flow_zero = ProcessFlow { - commodity: Rc::clone(&commodity), + commodity: Arc::clone(&commodity), coeff: FlowPerActivity(0.0), kind: FlowType::Fixed, cost: MoneyPerFlow(0.0), diff --git a/src/simulation.rs b/src/simulation.rs index 99df07279..9400c3091 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -10,7 +10,7 @@ use anyhow::{Context, Result}; use context_manager; use log::info; use std::path::Path; -use std::rc::Rc; +use std::sync::Arc; pub mod optimisation; use optimisation::{DispatchRun, FlowMap}; @@ -229,7 +229,7 @@ pub fn candidate_assets_for_next_year( for region_id in &process.regions { candidates.push( Asset::new_candidate_for_dispatch( - Rc::clone(process), + Arc::clone(process), region_id.clone(), candidate_asset_capacity, next_year, diff --git a/src/simulation/investment.rs b/src/simulation/investment.rs index 741561ef1..8385b4df2 100644 --- a/src/simulation/investment.rs +++ b/src/simulation/investment.rs @@ -15,6 +15,7 @@ use context_manager; use indexmap::IndexMap; use itertools::Itertools; use log::{debug, warn}; +use rayon::prelude::*; use std::collections::HashMap; use strum::IntoEnumIterator; @@ -387,45 +388,54 @@ pub fn select_best_assets( region_id ); - // Appraise all options - let mut outputs = Vec::new(); - for asset in &opt_assets { - // For candidates, cap the asset's capacity by the current demand-limiting capacity - // and, where an addition constraint exists, the remaining installable capacity. - let mut asset = asset.clone(); - if !asset.is_commissioned() { - let dlc = AssetCapacity::from_capacity( - get_demand_limiting_capacity( - &model.time_slice_info, - &asset, - commodity, - &demand, - ), - asset.unit_size(), - ); - let cap = asset.capacity().min(dlc); - let max_capacity = remaining_capacities - .get(&asset) - .copied() - .map_or(cap, |remaining| cap.min(remaining)); - asset.make_mut().set_capacity(max_capacity); - } + // Appraise all options in parallel: each asset's appraisal is independent (all shared + // state is read-only within this block), so we can safely use Rayon here. + // Each HiGHS solve inside `appraise_investment` is configured to use only one thread + // (via `parallel="off"`) to avoid over-subscription. + let mut outputs: Vec = opt_assets + .par_iter() + .map(|asset| -> Result> { + // For candidates, cap the asset's capacity by the current demand-limiting + // capacity and, where an addition constraint exists, the remaining installable + // capacity. `make_mut` creates a new Arc allocation for the modified clone so + // there is no shared mutable state between iterations. + let mut asset = asset.clone(); + if !asset.is_commissioned() { + let dlc = AssetCapacity::from_capacity( + get_demand_limiting_capacity( + &model.time_slice_info, + &asset, + commodity, + &demand, + ), + asset.unit_size(), + ); + let cap = asset.capacity().min(dlc); + let max_capacity = remaining_capacities + .get(&asset) + .copied() + .map_or(cap, |remaining| cap.min(remaining)); + asset.make_mut().set_capacity(max_capacity); + } - // Skip assets with zero capacity - if asset.capacity().total_capacity() <= Capacity(0.0) { - continue; - } + // Skip assets with zero capacity + if asset.capacity().total_capacity() <= Capacity(0.0) { + return Ok(None); + } - let output = appraise_investment( - model, - &asset, - commodity, - objective_type, - &coefficients[&asset], - &demand, - )?; - outputs.push(output); - } + Ok(Some(appraise_investment( + model, + &asset, + commodity, + objective_type, + &coefficients[&asset], + &demand, + )?)) + }) + .collect::>>()? // propagate any solver error + .into_iter() + .flatten() + .collect(); // Save appraisal results writer.write_appraisal_debug_info( @@ -630,7 +640,7 @@ mod tests { use crate::units::{Flow, FlowPerActivity, MoneyPerFlow}; use indexmap::indexmap; use rstest::rstest; - use std::rc::Rc; + use std::sync::Arc; #[rstest] fn get_demand_limiting_capacity_works( @@ -640,15 +650,15 @@ mod tests { mut process: Process, ) { // Add flows for the process using the existing commodity fixture - let commodity_rc = Rc::new(svd_commodity); + let commodity_rc = Arc::new(svd_commodity); let process_flow = ProcessFlow { - commodity: Rc::clone(&commodity_rc), + commodity: Arc::clone(&commodity_rc), coeff: FlowPerActivity(2.0), // 2 units of flow per unit of activity kind: FlowType::Fixed, cost: MoneyPerFlow(0.0), }; let process_flows = indexmap! { commodity_rc.id.clone() => process_flow.clone() }; - let process_flows_map = process_flows_map(process.regions.clone(), Rc::new(process_flows)); + let process_flows_map = process_flows_map(process.regions.clone(), Arc::new(process_flows)); process.flows = process_flows_map; // Create asset with the configured process @@ -676,15 +686,15 @@ mod tests { time_slice_info2.time_slices.keys().collect_tuple().unwrap(); // Add flows for the process using the existing commodity fixture - let commodity_rc = Rc::new(svd_commodity); + let commodity_rc = Arc::new(svd_commodity); let process_flow = ProcessFlow { - commodity: Rc::clone(&commodity_rc), + commodity: Arc::clone(&commodity_rc), coeff: FlowPerActivity(1.0), // 1 unit of flow per unit of activity kind: FlowType::Fixed, cost: MoneyPerFlow(0.0), }; let process_flows = indexmap! { commodity_rc.id.clone() => process_flow.clone() }; - let process_flows_map = process_flows_map(process.regions.clone(), Rc::new(process_flows)); + let process_flows_map = process_flows_map(process.regions.clone(), Arc::new(process_flows)); process.flows = process_flows_map; // Add activity limits for the process @@ -724,16 +734,16 @@ mod tests { time_slice_info2.time_slices.keys().collect_tuple().unwrap(); // Configure a 1:1 activity-to-flow relationship. - let commodity_rc = Rc::new(svd_commodity); + let commodity_rc = Arc::new(svd_commodity); let process_flow = ProcessFlow { - commodity: Rc::clone(&commodity_rc), + commodity: Arc::clone(&commodity_rc), coeff: FlowPerActivity(1.0), kind: FlowType::Fixed, cost: MoneyPerFlow(0.0), }; let process_flows = indexmap! { commodity_rc.id.clone() => process_flow.clone() }; - process.flows = process_flows_map(process.regions.clone(), Rc::new(process_flows)); + process.flows = process_flows_map(process.regions.clone(), Arc::new(process_flows)); // Fine-grained limits imply a capacity requirement of 5: // TS1: 5 / 1 = 5 @@ -790,16 +800,16 @@ mod tests { #[case] activity_limit: Dimensionless, #[case] expected: Capacity, ) { - let commodity_rc = Rc::new(svd_commodity); + let commodity_rc = Arc::new(svd_commodity); let process_flow = ProcessFlow { - commodity: Rc::clone(&commodity_rc), + commodity: Arc::clone(&commodity_rc), coeff: FlowPerActivity(1.0), kind: FlowType::Fixed, cost: MoneyPerFlow(0.0), }; process.flows = process_flows_map( process.regions.clone(), - Rc::new(indexmap! { commodity_rc.id.clone() => process_flow }), + Arc::new(indexmap! { commodity_rc.id.clone() => process_flow }), ); let mut limits = ActivityLimits::new_with_full_availability(&time_slice_info); diff --git a/src/simulation/investment/appraisal.rs b/src/simulation/investment/appraisal.rs index 593a03228..56dae5e7d 100644 --- a/src/simulation/investment/appraisal.rs +++ b/src/simulation/investment/appraisal.rs @@ -15,7 +15,7 @@ use optimisation::ResultsMap; use serde::Serialize; use std::any::Any; use std::cmp::Ordering; -use std::rc::Rc; +use std::sync::Arc; pub mod coefficients; mod constraints; @@ -59,7 +59,7 @@ pub struct AppraisalOutput { /// The comparison metric to compare investment decisions pub metric: Option>, /// Activity coefficients and market costs used in the appraisal - pub coefficients: Rc, + pub coefficients: Arc, } impl AppraisalOutput { @@ -68,7 +68,7 @@ impl AppraisalOutput { asset: AssetRef, results: ResultsMap, metric: Option, - coefficients: Rc, + coefficients: Arc, ) -> Self { Self { asset, @@ -216,7 +216,7 @@ fn calculate_lcox( model: &Model, asset: &AssetRef, commodity: &Commodity, - coefficients: &Rc, + coefficients: &Arc, demand: &DemandMap, ) -> Result { let results = perform_optimisation(model, asset, commodity, coefficients, demand)?; @@ -245,7 +245,7 @@ fn calculate_npv( model: &Model, asset: &AssetRef, commodity: &Commodity, - coefficients: &Rc, + coefficients: &Arc, demand: &DemandMap, ) -> Result { let results = perform_optimisation(model, asset, commodity, coefficients, demand)?; @@ -282,7 +282,7 @@ pub fn appraise_investment( asset: &AssetRef, commodity: &Commodity, objective_type: &ObjectiveType, - coefficients: &Rc, + coefficients: &Arc, demand: &DemandMap, ) -> Result { let appraisal_method = match objective_type { @@ -354,7 +354,7 @@ mod tests { use crate::units::{Capacity, MoneyPerActivity}; use float_cmp::assert_approx_eq; use rstest::rstest; - use std::rc::Rc; + use std::sync::Arc; /// Parametrised tests for LCOX metric comparison. #[rstest] @@ -400,7 +400,7 @@ mod tests { #[rstest] fn compare_assets_fallback(process: Process, region_id: RegionID, agent_id: AgentID) { - let process = Rc::new(process); + let process = Arc::new(process); let capacity = Capacity(2.0); let asset1 = Asset::new_commissioned( agent_id.clone(), @@ -426,8 +426,8 @@ mod tests { assert!(compare_asset_fallback(&asset2, &asset3).is_gt()); } - fn objective_coeffs() -> Rc { - Rc::new(ObjectiveCoefficients { + fn objective_coeffs() -> Arc { + Arc::new(ObjectiveCoefficients { activity_coefficients: IndexMap::new(), market_costs: IndexMap::new(), }) @@ -530,7 +530,7 @@ mod tests { region_id: RegionID, agent_id: AgentID, ) { - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); let capacity = Capacity(10.0); let commission_years = [2015, 2020, 2010]; @@ -567,7 +567,7 @@ mod tests { /// Test that when metrics and commission years are equal, the original order is preserved #[rstest] fn appraisal_sort_maintains_order_when_all_equal(process: Process, region_id: RegionID) { - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); let capacity = Capacity(10.0); let commission_year = 2015; let agent_ids = ["agent1", "agent2", "agent3"]; @@ -608,7 +608,7 @@ mod tests { region_id: RegionID, agent_id: AgentID, ) { - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); let capacity = Capacity(10.0); // Create a mix of commissioned and candidate (non-commissioned) assets @@ -669,7 +669,7 @@ mod tests { region_id: RegionID, agent_id: AgentID, ) { - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); let capacity = Capacity(10.0); // Create a mix of commissioned and candidate (non-commissioned) assets @@ -784,7 +784,7 @@ mod tests { region_id: RegionID, agent_id: AgentID, ) { - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); let capacity = Capacity(10.0); let commissioned = Asset::new_commissioned( @@ -818,7 +818,7 @@ mod tests { region_id: RegionID, agent_id: AgentID, ) { - let process_rc = Rc::new(process); + let process_rc = Arc::new(process); let capacity = Capacity(10.0); let year = 2020; diff --git a/src/simulation/investment/appraisal/coefficients.rs b/src/simulation/investment/appraisal/coefficients.rs index 59c3c4a45..7911a9ec8 100644 --- a/src/simulation/investment/appraisal/coefficients.rs +++ b/src/simulation/investment/appraisal/coefficients.rs @@ -8,7 +8,7 @@ use crate::time_slice::{TimeSliceID, TimeSliceInfo}; use crate::units::{MoneyPerActivity, MoneyPerFlow}; use indexmap::IndexMap; use std::collections::HashMap; -use std::rc::Rc; +use std::sync::Arc; /// Per-time-slice cost coefficients for an asset. /// @@ -43,7 +43,7 @@ pub fn calculate_coefficients_for_assets( assets: &[AssetRef], prices: &Prices, year: u32, -) -> HashMap> { +) -> HashMap> { assets .iter() .map(|asset| { @@ -54,7 +54,7 @@ pub fn calculate_coefficients_for_assets( prices, year, ); - (asset.clone(), Rc::new(coefficient)) + (asset.clone(), Arc::new(coefficient)) }) .collect() } diff --git a/src/simulation/investment/appraisal/optimisation.rs b/src/simulation/investment/appraisal/optimisation.rs index a57827459..448abb093 100644 --- a/src/simulation/investment/appraisal/optimisation.rs +++ b/src/simulation/investment/appraisal/optimisation.rs @@ -127,6 +127,12 @@ pub fn perform_optimisation( let mut highs_model = problem.optimise(Sense::Maximise); apply_highs_options_from_toml(&mut highs_model, &model.parameters.highs.appraisal_options) .context("Failed to apply custom HiGHS options to appraisal optimisation")?; + // Enforce single-threaded solving: when appraisals run in parallel via Rayon each HiGHS + // instance must not spawn additional worker threads. Setting `parallel="off"` disables + // HiGHS's concurrent simplex strategy without touching the global thread-pool scheduler + // (setting `threads=N` would fail if the scheduler was already initialised on this thread + // by a previous solve with a different count). + highs_model.set_option("parallel", "off"); let solution = solve_optimal(highs_model) .map_err(ModelError::into_anyhow)? .get_solution(); diff --git a/src/simulation/market.rs b/src/simulation/market.rs index b1364d8b5..b42ccbb5a 100644 --- a/src/simulation/market.rs +++ b/src/simulation/market.rs @@ -497,8 +497,8 @@ mod tests { use crate::units::{ActivityPerCapacity, Capacity}; use indexmap::IndexSet; use rstest::{fixture, rstest}; - use std::rc::Rc; use std::slice::from_ref; + use std::sync::Arc; #[rstest] fn collect_investment_limits_for_candidates_empty_list() { @@ -513,7 +513,7 @@ mod tests { #[fixture] fn uncommissioned_asset_without_limit(process: Process, region_id: RegionID) -> AssetRef { - Asset::new_candidate(Rc::new(process), region_id, Capacity(10.0), 2015) + Asset::new_candidate(Arc::new(process), region_id, Capacity(10.0), 2015) .unwrap() .into() } @@ -531,7 +531,7 @@ mod tests { constraints.insert( (region_id.clone(), 2015), - Rc::new(ProcessInvestmentConstraint { + Arc::new(ProcessInvestmentConstraint { addition_limit: Some(Capacity(10.0)), }), ); @@ -550,7 +550,7 @@ mod tests { unit_size: None, }; - Asset::new_candidate(Rc::new(process), region_id, Capacity(15.0), 2015) + Asset::new_candidate(Arc::new(process), region_id, Capacity(15.0), 2015) .unwrap() .into() } diff --git a/src/simulation/optimisation/constraints.rs b/src/simulation/optimisation/constraints.rs index a5c56166c..ca6bb2940 100644 --- a/src/simulation/optimisation/constraints.rs +++ b/src/simulation/optimisation/constraints.rs @@ -341,7 +341,7 @@ mod tests { use crate::units::{FlowPerActivity, MoneyPerFlow}; use indexmap::indexmap; use rstest::rstest; - use std::rc::Rc; + use std::sync::Arc; #[rstest] // Max candidate output (2.0) < epsilon (10.0) → zero (guard prevents infeasibility) @@ -354,19 +354,19 @@ mod tests { svd_commodity: Commodity, mut process: Process, ) { - let commodity_rc = Rc::new(svd_commodity); + let commodity_rc = Arc::new(svd_commodity); // Add an output flow for the commodity to the process. With capacity 2.0, cap2act 1.0, // and full availability over a single annual time slice, max_candidate_output = 2.0. let flow = ProcessFlow { - commodity: Rc::clone(&commodity_rc), + commodity: Arc::clone(&commodity_rc), coeff: FlowPerActivity(1.0), kind: FlowType::Fixed, cost: MoneyPerFlow(0.0), }; process.flows = process_flows_map( process.regions.clone(), - Rc::new(indexmap! { commodity_rc.id.clone() => flow }), + Arc::new(indexmap! { commodity_rc.id.clone() => flow }), ); let result = candidate_balance_epsilon( diff --git a/src/simulation/prices.rs b/src/simulation/prices.rs index 249720a0c..7447973c2 100644 --- a/src/simulation/prices.rs +++ b/src/simulation/prices.rs @@ -1355,11 +1355,11 @@ mod tests { use indexmap::{IndexMap, IndexSet}; use rstest::rstest; use std::collections::{HashMap, HashSet}; - use std::rc::Rc; + use std::sync::Arc; fn build_process_flow(commodity: &Commodity, coeff: f64, cost: MoneyPerFlow) -> ProcessFlow { ProcessFlow { - commodity: Rc::new(commodity.clone()), + commodity: Arc::new(commodity.clone()), coeff: FlowPerActivity(coeff), kind: FlowType::Fixed, cost, @@ -1379,7 +1379,7 @@ mod tests { discount_rate: Dimensionless, ) -> Process { let mut process_flows_map = HashMap::new(); - process_flows_map.insert((region_id.clone(), year), Rc::new(flows)); + process_flows_map.insert((region_id.clone(), year), Arc::new(flows)); let mut process_parameter_map = HashMap::new(); let proc_param = ProcessParameter { @@ -1389,12 +1389,12 @@ mod tests { lifetime, discount_rate, }; - process_parameter_map.insert((region_id.clone(), year), Rc::new(proc_param)); + process_parameter_map.insert((region_id.clone(), year), Arc::new(proc_param)); let mut activity_limits_map = HashMap::new(); activity_limits_map.insert( (region_id.clone(), year), - Rc::new(ActivityLimits::new_with_full_availability(time_slice_info)), + Arc::new(ActivityLimits::new_with_full_availability(time_slice_info)), ); let regions: IndexSet = IndexSet::from([region_id.clone()]); @@ -1517,7 +1517,7 @@ mod tests { ); let asset = - Asset::new_candidate(Rc::new(process), region_id.clone(), Capacity(1.0), 2015u32) + Asset::new_candidate(Arc::new(process), region_id.clone(), Capacity(1.0), 2015u32) .unwrap(); let asset_ref = AssetRef::from(asset); let mut prices = @@ -1527,8 +1527,8 @@ mod tests { markets.insert((c.id.clone(), region_id.clone())); let mut commodities = CommodityMap::new(); - commodities.insert(b.id.clone(), Rc::new(b.clone())); - commodities.insert(c.id.clone(), Rc::new(c.clone())); + commodities.insert(b.id.clone(), Arc::new(b.clone())); + commodities.insert(c.id.clone(), Arc::new(c.clone())); let existing = vec![(&asset_ref, &time_slice, Activity(1.0))]; let candidates = Vec::new(); @@ -1599,7 +1599,7 @@ mod tests { ); let asset = - Asset::new_candidate(Rc::new(process), region_id.clone(), Capacity(4.0), 2015u32) + Asset::new_candidate(Arc::new(process), region_id.clone(), Capacity(4.0), 2015u32) .unwrap(); let asset_ref = AssetRef::from(asset); let mut prices = @@ -1609,8 +1609,8 @@ mod tests { markets.insert((c.id.clone(), region_id.clone())); let mut commodities = CommodityMap::new(); - commodities.insert(b.id.clone(), Rc::new(b.clone())); - commodities.insert(c.id.clone(), Rc::new(c.clone())); + commodities.insert(b.id.clone(), Arc::new(b.clone())); + commodities.insert(c.id.clone(), Arc::new(c.clone())); let existing = vec![(&asset_ref, &time_slice, Activity(2.0))]; let candidates = Vec::new();