From 59b0489f68767ba1d037a79555a39150f1e819cd Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 12 Feb 2025 11:14:17 +0000 Subject: [PATCH 1/6] Make read_csv_id_file return ordered map + keep regions ordered --- src/input.rs | 12 ++++++++---- src/input/region.rs | 6 +++--- src/model.rs | 4 ++-- src/region.rs | 4 ++++ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/input.rs b/src/input.rs index 86e36c07..0b67cc94 100644 --- a/src/input.rs +++ b/src/input.rs @@ -3,6 +3,7 @@ use crate::agent::AssetPool; use crate::model::{Model, ModelFile}; use anyhow::{ensure, Context, Result}; use float_cmp::approx_eq; +use indexmap::IndexMap; use itertools::Itertools; use serde::de::{Deserialize, DeserializeOwned, Deserializer}; use std::collections::{HashMap, HashSet}; @@ -115,16 +116,19 @@ impl IDCollection for HashSet> { } } -/// Read a CSV file of items with IDs -pub fn read_csv_id_file(file_path: &Path) -> Result, T>> +/// Read a CSV file of items with IDs. +/// +/// As this function is only ever used for top-level CSV files (i.e. the ones which actually define +/// the IDs for a given type), we use an ordered map to maintain the order in the input files. +pub fn read_csv_id_file(file_path: &Path) -> Result, T>> where T: HasID + DeserializeOwned, { - fn fill_and_validate_map(file_path: &Path) -> Result, T>> + fn fill_and_validate_map(file_path: &Path) -> Result, T>> where T: HasID + DeserializeOwned, { - let mut map = HashMap::new(); + let mut map = IndexMap::new(); for record in read_csv::(file_path)? { let id = record.get_id(); diff --git a/src/input/region.rs b/src/input/region.rs index 3693ceb9..0944a1cb 100644 --- a/src/input/region.rs +++ b/src/input/region.rs @@ -1,6 +1,6 @@ //! Code for reading region-related information from CSV files. use super::*; -use crate::region::{Region, RegionSelection}; +use crate::region::{Region, RegionMap, RegionSelection}; use anyhow::{anyhow, ensure, Context, Result}; use serde::de::DeserializeOwned; use std::collections::{HashMap, HashSet}; @@ -37,7 +37,7 @@ pub(crate) use define_region_id_getter; /// # Returns /// /// A `HashMap, Region>` with the parsed regions data or an error. The keys are region IDs. -pub fn read_regions(model_dir: &Path) -> Result, Region>> { +pub fn read_regions(model_dir: &Path) -> Result { read_csv_id_file(&model_dir.join(REGIONS_FILE_NAME)) } @@ -164,7 +164,7 @@ AP,Asia Pacific" let regions = read_regions(dir.path()).unwrap(); assert_eq!( regions, - HashMap::from([ + RegionMap::from([ ( "NA".into(), Region { diff --git a/src/model.rs b/src/model.rs index 9718a307..7c9070f3 100644 --- a/src/model.rs +++ b/src/model.rs @@ -4,7 +4,7 @@ use crate::agent::Agent; use crate::commodity::Commodity; use crate::input::*; use crate::process::Process; -use crate::region::Region; +use crate::region::RegionMap; use crate::time_slice::TimeSliceInfo; use anyhow::{ensure, Context, Result}; use serde::Deserialize; @@ -21,7 +21,7 @@ pub struct Model { pub commodities: HashMap, Rc>, pub processes: HashMap, Rc>, pub time_slice_info: TimeSliceInfo, - pub regions: HashMap, Region>, + pub regions: RegionMap, } /// Represents the contents of the entire model file. diff --git a/src/region.rs b/src/region.rs index 4cd4ede8..609c5369 100644 --- a/src/region.rs +++ b/src/region.rs @@ -1,10 +1,14 @@ //! Regions represent different geographical areas in which agents, processes, etc. are active. +use indexmap::IndexMap; use itertools::Itertools; use serde::Deserialize; use std::collections::HashSet; use std::fmt::Display; use std::rc::Rc; +/// A map of [`Region`]s, keyed by region ID +pub type RegionMap = IndexMap, Region>; + /// Represents a region with an ID and a longer description. #[derive(Debug, Deserialize, PartialEq)] pub struct Region { From aca08431ac7ae0ee43d450c8f6d4e07cbce46876 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 12 Feb 2025 10:58:41 +0000 Subject: [PATCH 2/6] Keep agents ordered --- src/agent.rs | 4 ++++ src/input/agent.rs | 12 ++++++------ src/input/agent/objective.rs | 8 ++++---- src/model.rs | 4 ++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 65f7b0ca..920ce669 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -4,12 +4,16 @@ use crate::commodity::Commodity; use crate::process::Process; use crate::region::RegionSelection; use crate::time_slice::TimeSliceID; +use indexmap::IndexMap; use serde::Deserialize; use serde_string_enum::DeserializeLabeledStringEnum; use std::collections::HashSet; use std::ops::RangeInclusive; use std::rc::Rc; +/// A map of [`Agent`]s, keyed by agent ID +pub type AgentMap = IndexMap, Agent>; + /// An agent in the simulation #[derive(Debug, Clone, PartialEq)] pub struct Agent { diff --git a/src/input/agent.rs b/src/input/agent.rs index 90d91f27..67efdf7a 100644 --- a/src/input/agent.rs +++ b/src/input/agent.rs @@ -1,6 +1,6 @@ //! Code for reading in agent-related data from CSV files. use super::*; -use crate::agent::{Agent, DecisionRule, SearchSpace}; +use crate::agent::{Agent, AgentMap, DecisionRule, SearchSpace}; use crate::commodity::Commodity; use crate::process::Process; use crate::region::RegionSelection; @@ -57,7 +57,7 @@ pub fn read_agents( commodities: &HashMap, Rc>, processes: &HashMap, Rc>, region_ids: &HashSet>, -) -> Result, Agent>> { +) -> Result { let process_ids = processes.keys().cloned().collect(); let mut agents = read_agents_file(model_dir, commodities, &process_ids)?; let agent_ids = agents.keys().cloned().collect(); @@ -88,7 +88,7 @@ pub fn read_agents_file( model_dir: &Path, commodities: &HashMap, Rc>, process_ids: &HashSet>, -) -> Result, Agent>> { +) -> Result { let file_path = model_dir.join(AGENT_FILE_NAME); let agents_csv = read_csv(&file_path)?; read_agents_file_from_iter(agents_csv, commodities, process_ids) @@ -100,11 +100,11 @@ fn read_agents_file_from_iter( iter: I, commodities: &HashMap, Rc>, process_ids: &HashSet>, -) -> Result, Agent>> +) -> Result where I: Iterator, { - let mut agents = HashMap::new(); + let mut agents = AgentMap::new(); for agent_raw in iter { let commodity = commodities .get(agent_raw.commodity_id.as_str()) @@ -191,7 +191,7 @@ mod tests { regions: RegionSelection::default(), objectives: Vec::new(), }; - let expected = HashMap::from_iter([("agent".into(), agent_out)]); + let expected = AgentMap::from_iter(iter::once(("agent".into(), agent_out))); let actual = read_agents_file_from_iter(iter::once(agent), &commodities, &process_ids).unwrap(); assert_eq!(actual, expected); diff --git a/src/input/agent/objective.rs b/src/input/agent/objective.rs index 0f5f0253..73b7ea43 100644 --- a/src/input/agent/objective.rs +++ b/src/input/agent/objective.rs @@ -1,6 +1,6 @@ //! Code for reading the agent objectives CSV file. use super::super::*; -use crate::agent::{Agent, AgentObjective, DecisionRule}; +use crate::agent::{Agent, AgentMap, AgentObjective, DecisionRule}; use anyhow::{ensure, Context, Result}; use std::collections::HashMap; use std::path::Path; @@ -21,7 +21,7 @@ define_id_getter! {Agent} /// A map of Agents, with the agent ID as the key pub fn read_agent_objectives( model_dir: &Path, - agents: &HashMap, Agent>, + agents: &AgentMap, ) -> Result, Vec>> { let file_path = model_dir.join(AGENT_OBJECTIVES_FILE_NAME); let agent_objectives_csv = read_csv(&file_path)?; @@ -31,7 +31,7 @@ pub fn read_agent_objectives( fn read_agent_objectives_from_iter( iter: I, - agents: &HashMap, Agent>, + agents: &AgentMap, ) -> Result, Vec>> where I: Iterator, @@ -164,7 +164,7 @@ mod tests { costs: CommodityCostMap::new(), demand: DemandMap::new(), }); - let agents: HashMap<_, _> = [( + let agents = [( "agent".into(), Agent { id: "agent".into(), diff --git a/src/model.rs b/src/model.rs index 7c9070f3..d31ff982 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,6 +1,6 @@ //! Code for simulation models. #![allow(missing_docs)] -use crate::agent::Agent; +use crate::agent::AgentMap; use crate::commodity::Commodity; use crate::input::*; use crate::process::Process; @@ -17,7 +17,7 @@ const MODEL_FILE_NAME: &str = "model.toml"; /// Model definition pub struct Model { pub milestone_years: Vec, - pub agents: HashMap, Agent>, + pub agents: AgentMap, pub commodities: HashMap, Rc>, pub processes: HashMap, Rc>, pub time_slice_info: TimeSliceInfo, From cd03f9030f9292140a72f558a4e92663b96c6d3b Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 12 Feb 2025 11:03:17 +0000 Subject: [PATCH 3/6] Keep commodities ordered --- src/commodity.rs | 4 ++++ src/input/agent.rs | 10 +++++----- src/input/commodity.rs | 6 +++--- src/input/process.rs | 8 ++++---- src/input/process/flow.rs | 10 +++++----- src/model.rs | 4 ++-- src/simulation/update.rs | 6 +++--- 7 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/commodity.rs b/src/commodity.rs index f9af8e7e..ecd265a5 100644 --- a/src/commodity.rs +++ b/src/commodity.rs @@ -1,11 +1,15 @@ #![allow(missing_docs)] use crate::input::*; use crate::time_slice::{TimeSliceID, TimeSliceLevel}; +use indexmap::IndexMap; use serde::Deserialize; use serde_string_enum::DeserializeLabeledStringEnum; use std::collections::HashMap; use std::rc::Rc; +/// A map of [`Commodity`]s, keyed by commodity ID +pub type CommodityMap = IndexMap, Rc>; + /// A commodity within the simulation. Represents a substance (e.g. CO2) or form of energy (e.g. /// electricity) that can be produced and/or consumed by technologies in the model. #[derive(PartialEq, Debug, Deserialize)] diff --git a/src/input/agent.rs b/src/input/agent.rs index 67efdf7a..1c6ac02f 100644 --- a/src/input/agent.rs +++ b/src/input/agent.rs @@ -1,7 +1,7 @@ //! Code for reading in agent-related data from CSV files. use super::*; use crate::agent::{Agent, AgentMap, DecisionRule, SearchSpace}; -use crate::commodity::Commodity; +use crate::commodity::CommodityMap; use crate::process::Process; use crate::region::RegionSelection; use anyhow::{ensure, Context, Result}; @@ -54,7 +54,7 @@ struct AgentRaw { /// A map of Agents, with the agent ID as the key pub fn read_agents( model_dir: &Path, - commodities: &HashMap, Rc>, + commodities: &CommodityMap, processes: &HashMap, Rc>, region_ids: &HashSet>, ) -> Result { @@ -86,7 +86,7 @@ pub fn read_agents( /// A map of Agents, with the agent ID as the key pub fn read_agents_file( model_dir: &Path, - commodities: &HashMap, Rc>, + commodities: &CommodityMap, process_ids: &HashSet>, ) -> Result { let file_path = model_dir.join(AGENT_FILE_NAME); @@ -98,7 +98,7 @@ pub fn read_agents_file( /// Read agents info from an iterator. fn read_agents_file_from_iter( iter: I, - commodities: &HashMap, Rc>, + commodities: &CommodityMap, process_ids: &HashSet>, ) -> Result where @@ -149,7 +149,7 @@ where mod tests { use super::*; use crate::agent::DecisionRule; - use crate::commodity::{CommodityCostMap, CommodityType, DemandMap}; + use crate::commodity::{Commodity, CommodityCostMap, CommodityType, DemandMap}; use crate::region::RegionSelection; use crate::time_slice::TimeSliceLevel; use std::iter; diff --git a/src/input/commodity.rs b/src/input/commodity.rs index 9a91896f..8608b9e0 100644 --- a/src/input/commodity.rs +++ b/src/input/commodity.rs @@ -1,9 +1,9 @@ //! Code for reading in commodity-related data from CSV files. -use crate::commodity::Commodity; +use crate::commodity::{Commodity, CommodityMap}; use crate::input::*; use crate::time_slice::TimeSliceInfo; use anyhow::Result; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::path::Path; use std::rc::Rc; @@ -32,7 +32,7 @@ pub fn read_commodities( region_ids: &HashSet>, time_slice_info: &TimeSliceInfo, milestone_years: &[u32], -) -> Result, Rc>> { +) -> Result { let commodities = read_csv_id_file::(&model_dir.join(COMMODITY_FILE_NAME))?; let commodity_ids = commodities.keys().cloned().collect(); let mut costs = read_commodity_costs( diff --git a/src/input/process.rs b/src/input/process.rs index ecb591e4..e57174d5 100644 --- a/src/input/process.rs +++ b/src/input/process.rs @@ -1,5 +1,5 @@ //! Code for reading process-related information from CSV files. -use crate::commodity::{Commodity, CommodityType}; +use crate::commodity::{Commodity, CommodityMap, CommodityType}; use crate::input::*; use crate::process::{Process, ProcessCapacityMap, ProcessFlow, ProcessParameter}; use crate::region::RegionSelection; @@ -55,7 +55,7 @@ define_id_getter! {ProcessDescription} /// This function returns a map of processes, with the IDs as keys. pub fn read_processes( model_dir: &Path, - commodities: &HashMap, Rc>, + commodities: &CommodityMap, region_ids: &HashSet>, time_slice_info: &TimeSliceInfo, year_range: &RangeInclusive, @@ -83,7 +83,7 @@ pub fn read_processes( /// Perform consistency checks for commodity flows. fn validate_commodities( - commodities: &HashMap, Rc>, + commodities: &CommodityMap, flows: &HashMap, Vec>, ) -> anyhow::Result<()> { for (commodity_id, commodity) in commodities { @@ -303,7 +303,7 @@ mod tests { demand: DemandMap::new(), }); - let commodities: HashMap, Rc> = [ + let commodities: CommodityMap = [ (Rc::clone(&commodity_sed.id), Rc::clone(&commodity_sed)), ( Rc::clone(&commodity_non_sed.id), diff --git a/src/input/process/flow.rs b/src/input/process/flow.rs index e9f0b812..14d5bf20 100644 --- a/src/input/process/flow.rs +++ b/src/input/process/flow.rs @@ -1,6 +1,6 @@ //! Code for reading process flows file use super::define_process_id_getter; -use crate::commodity::Commodity; +use crate::commodity::CommodityMap; use crate::input::*; use crate::process::{FlowType, ProcessFlow}; use anyhow::{ensure, Context, Result}; @@ -30,7 +30,7 @@ define_process_id_getter! {ProcessFlowRaw} pub fn read_process_flows( model_dir: &Path, process_ids: &HashSet>, - commodities: &HashMap, Rc>, + commodities: &CommodityMap, ) -> Result, Vec>> { let file_path = model_dir.join(PROCESS_FLOWS_FILE_NAME); let process_flow_csv = read_csv(&file_path)?; @@ -42,7 +42,7 @@ pub fn read_process_flows( fn read_process_flows_from_iter( iter: I, process_ids: &HashSet>, - commodities: &HashMap, Rc>, + commodities: &CommodityMap, ) -> Result, Vec>> where I: Iterator, @@ -151,14 +151,14 @@ fn validate_pac_flows(flows: &HashMap, Vec>) -> Result<()> #[cfg(test)] mod tests { use super::*; - use crate::commodity::{CommodityCostMap, CommodityType, DemandMap}; + use crate::commodity::{Commodity, CommodityCostMap, CommodityType, DemandMap}; use crate::time_slice::TimeSliceLevel; use std::iter; #[test] fn test_read_process_flows_from_iter_good() { let process_ids = ["id1".into(), "id2".into()].into_iter().collect(); - let commodities: HashMap, Rc> = ["commodity1", "commodity2"] + let commodities: CommodityMap = ["commodity1", "commodity2"] .into_iter() .map(|id| { let commodity = Commodity { diff --git a/src/model.rs b/src/model.rs index d31ff982..38db2f61 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,7 +1,7 @@ //! Code for simulation models. #![allow(missing_docs)] use crate::agent::AgentMap; -use crate::commodity::Commodity; +use crate::commodity::CommodityMap; use crate::input::*; use crate::process::Process; use crate::region::RegionMap; @@ -18,7 +18,7 @@ const MODEL_FILE_NAME: &str = "model.toml"; pub struct Model { pub milestone_years: Vec, pub agents: AgentMap, - pub commodities: HashMap, Rc>, + pub commodities: CommodityMap, pub processes: HashMap, Rc>, pub time_slice_info: TimeSliceInfo, pub regions: RegionMap, diff --git a/src/simulation/update.rs b/src/simulation/update.rs index 041a8c6b..52b0299a 100644 --- a/src/simulation/update.rs +++ b/src/simulation/update.rs @@ -2,9 +2,9 @@ use super::optimisation::Solution; use super::CommodityPrices; use crate::agent::AssetPool; -use crate::commodity::Commodity; +use crate::commodity::CommodityMap; use log::info; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::rc::Rc; /// Update commodity flows for assets based on the result of the dispatch optimisation. @@ -14,7 +14,7 @@ pub fn update_commodity_flows(_solution: &Solution, _assets: &mut AssetPool) { /// Update commodity prices for assets based on the result of the dispatch optimisation. pub fn update_commodity_prices( - commodities: &HashMap, Rc>, + commodities: &CommodityMap, solution: &Solution, prices: &mut CommodityPrices, ) { From a8c3a2e236054509d8ff8ce34e7e220249c70302 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 12 Feb 2025 11:06:25 +0000 Subject: [PATCH 4/6] Keep processes ordered --- src/input/agent.rs | 6 +++--- src/input/asset.rs | 10 +++++----- src/input/process.rs | 8 ++++---- src/model.rs | 5 ++--- src/process.rs | 4 ++++ 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/input/agent.rs b/src/input/agent.rs index 1c6ac02f..540b628b 100644 --- a/src/input/agent.rs +++ b/src/input/agent.rs @@ -2,11 +2,11 @@ use super::*; use crate::agent::{Agent, AgentMap, DecisionRule, SearchSpace}; use crate::commodity::CommodityMap; -use crate::process::Process; +use crate::process::ProcessMap; use crate::region::RegionSelection; use anyhow::{ensure, Context, Result}; use serde::Deserialize; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::path::Path; use std::rc::Rc; @@ -55,7 +55,7 @@ struct AgentRaw { pub fn read_agents( model_dir: &Path, commodities: &CommodityMap, - processes: &HashMap, Rc>, + processes: &ProcessMap, region_ids: &HashSet>, ) -> Result { let process_ids = processes.keys().cloned().collect(); diff --git a/src/input/asset.rs b/src/input/asset.rs index 774f8782..994b12c3 100644 --- a/src/input/asset.rs +++ b/src/input/asset.rs @@ -1,11 +1,11 @@ //! Code for reading [Asset]s from a CSV file. use crate::agent::Asset; use crate::input::*; -use crate::process::Process; +use crate::process::ProcessMap; use anyhow::{ensure, Context, Result}; use itertools::Itertools; use serde::Deserialize; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::path::Path; use std::rc::Rc; @@ -35,7 +35,7 @@ struct AssetRaw { pub fn read_assets( model_dir: &Path, agent_ids: &HashSet>, - processes: &HashMap, Rc>, + processes: &ProcessMap, region_ids: &HashSet>, ) -> Result> { let file_path = model_dir.join(ASSETS_FILE_NAME); @@ -59,7 +59,7 @@ pub fn read_assets( fn read_assets_from_iter( iter: I, agent_ids: &HashSet>, - processes: &HashMap, Rc>, + processes: &ProcessMap, region_ids: &HashSet>, ) -> Result> where @@ -92,7 +92,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::process::{ProcessCapacityMap, ProcessParameter}; + use crate::process::{Process, ProcessCapacityMap, ProcessParameter}; use crate::region::RegionSelection; use itertools::assert_equal; use std::iter; diff --git a/src/input/process.rs b/src/input/process.rs index e57174d5..3aeff696 100644 --- a/src/input/process.rs +++ b/src/input/process.rs @@ -1,7 +1,7 @@ //! Code for reading process-related information from CSV files. use crate::commodity::{Commodity, CommodityMap, CommodityType}; use crate::input::*; -use crate::process::{Process, ProcessCapacityMap, ProcessFlow, ProcessParameter}; +use crate::process::{Process, ProcessCapacityMap, ProcessFlow, ProcessMap, ProcessParameter}; use crate::region::RegionSelection; use crate::time_slice::TimeSliceInfo; use anyhow::Result; @@ -59,7 +59,7 @@ pub fn read_processes( region_ids: &HashSet>, time_slice_info: &TimeSliceInfo, year_range: &RangeInclusive, -) -> Result, Rc>> { +) -> Result { let file_path = model_dir.join(PROCESSES_FILE_NAME); let descriptions = read_csv_id_file::(&file_path)?; let process_ids = HashSet::from_iter(descriptions.keys().cloned()); @@ -128,7 +128,7 @@ fn create_process_map( mut flows: HashMap, Vec>, mut parameters: HashMap, ProcessParameter>, mut regions: HashMap, RegionSelection>, -) -> Result, Rc>> +) -> Result where I: Iterator, { @@ -159,7 +159,7 @@ where Ok((description.id, process.into())) }) - .process_results(|iter| iter.collect()) + .try_collect() } #[cfg(test)] diff --git a/src/model.rs b/src/model.rs index 38db2f61..02b3c7c5 100644 --- a/src/model.rs +++ b/src/model.rs @@ -3,12 +3,11 @@ use crate::agent::AgentMap; use crate::commodity::CommodityMap; use crate::input::*; -use crate::process::Process; +use crate::process::ProcessMap; use crate::region::RegionMap; use crate::time_slice::TimeSliceInfo; use anyhow::{ensure, Context, Result}; use serde::Deserialize; -use std::collections::HashMap; use std::path::Path; use std::rc::Rc; @@ -19,7 +18,7 @@ pub struct Model { pub milestone_years: Vec, pub agents: AgentMap, pub commodities: CommodityMap, - pub processes: HashMap, Rc>, + pub processes: ProcessMap, pub time_slice_info: TimeSliceInfo, pub regions: RegionMap, } diff --git a/src/process.rs b/src/process.rs index c35c6f02..dd820d3b 100644 --- a/src/process.rs +++ b/src/process.rs @@ -2,12 +2,16 @@ use crate::commodity::Commodity; use crate::region::RegionSelection; use crate::time_slice::TimeSliceID; +use indexmap::IndexMap; use serde::Deserialize; use serde_string_enum::DeserializeLabeledStringEnum; use std::collections::HashMap; use std::ops::RangeInclusive; use std::rc::Rc; +/// A map of [`Process`]es, keyed by process ID +pub type ProcessMap = IndexMap, Rc>; + #[derive(PartialEq, Debug)] pub struct Process { pub id: Rc, From d38f6d4d19c9503a21a60bdd49e01a2a17da2223 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 12 Feb 2025 11:28:43 +0000 Subject: [PATCH 5/6] Keep time slice info ordered --- src/input/time_slice.rs | 13 ++++++------- src/time_slice.rs | 41 ++++++++++++++++++++++------------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/input/time_slice.rs b/src/input/time_slice.rs index b0868eb4..f9ea2ae5 100644 --- a/src/input/time_slice.rs +++ b/src/input/time_slice.rs @@ -1,15 +1,14 @@ //! Code for reading in time slice info from a CSV file. #![allow(missing_docs)] use crate::input::*; +use crate::time_slice::{TimeSliceID, TimeSliceInfo}; use anyhow::{ensure, Context, Result}; +use indexmap::IndexSet; use serde::Deserialize; use serde_string_enum::DeserializeLabeledStringEnum; -use std::collections::{HashMap, HashSet}; use std::path::Path; use std::rc::Rc; -use crate::time_slice::{TimeSliceID, TimeSliceInfo}; - const TIME_SLICES_FILE_NAME: &str = "time_slices.csv"; /// A time slice record retrieved from a CSV file @@ -22,7 +21,7 @@ struct TimeSliceRaw { } /// Get the specified `String` from `set` or insert if it doesn't exist -fn get_or_insert(value: String, set: &mut HashSet>) -> Rc { +fn get_or_insert(value: String, set: &mut IndexSet>) -> Rc { // Sadly there's no entry API for HashSets: https://github.com/rust-lang/rfcs/issues/1490 match set.get(value.as_str()) { Some(value) => Rc::clone(value), @@ -39,9 +38,9 @@ fn read_time_slice_info_from_iter(iter: I) -> Result where I: Iterator, { - let mut seasons: HashSet> = HashSet::new(); - let mut times_of_day = HashSet::new(); - let mut fractions = HashMap::new(); + let mut seasons = IndexSet::new(); + let mut times_of_day = IndexSet::new(); + let mut fractions = IndexMap::new(); for time_slice in iter { let season = get_or_insert(time_slice.season, &mut seasons); let time_of_day = get_or_insert(time_slice.time_of_day, &mut times_of_day); diff --git a/src/time_slice.rs b/src/time_slice.rs index c5528856..d2059669 100644 --- a/src/time_slice.rs +++ b/src/time_slice.rs @@ -3,11 +3,10 @@ //! Time slices provide a mechanism for users to indicate production etc. varies with the time of //! day and time of year. #![allow(missing_docs)] -use crate::input::*; use anyhow::{Context, Result}; +use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use serde_string_enum::DeserializeLabeledStringEnum; -use std::collections::{HashMap, HashSet}; use std::fmt::Display; use std::iter; use std::rc::Rc; @@ -42,11 +41,11 @@ pub enum TimeSliceSelection { #[derive(PartialEq, Debug)] pub struct TimeSliceInfo { /// Names of seasons - pub seasons: HashSet>, + pub seasons: IndexSet>, /// Names of times of day (e.g. "evening") - pub times_of_day: HashSet>, + pub times_of_day: IndexSet>, /// The fraction of the year that this combination of season and time of day occupies - pub fractions: HashMap, + pub fractions: IndexMap, } impl Default for TimeSliceInfo { @@ -102,7 +101,11 @@ impl TimeSliceInfo { let time_slice = self.get_time_slice_id_from_str(time_slice)?; Ok(TimeSliceSelection::Single(time_slice)) } else { - let season = self.seasons.get_id(time_slice)?; + let season = self + .seasons + .get(time_slice) + .with_context(|| format!("'{time_slice}' is not a valid season"))? + .clone(); Ok(TimeSliceSelection::Season(season)) } } @@ -208,6 +211,7 @@ pub enum TimeSliceLevel { mod tests { use super::*; use float_cmp::assert_approx_eq; + use itertools::assert_equal; #[test] fn test_iter_selection() { @@ -229,22 +233,20 @@ mod tests { .collect(), }; - assert_eq!( - HashSet::<&TimeSliceID>::from_iter( - ts_info - .iter_selection(&TimeSliceSelection::Annual) - .map(|(ts, _)| ts) - ), - HashSet::from_iter(slices.iter()) + assert_equal( + ts_info + .iter_selection(&TimeSliceSelection::Annual) + .map(|(ts, _)| ts), + slices.iter(), ); - itertools::assert_equal( + assert_equal( ts_info .iter_selection(&TimeSliceSelection::Season("winter".into())) .map(|(ts, _)| ts), iter::once(&slices[0]), ); let ts = ts_info.get_time_slice_id_from_str("summer.night").unwrap(); - itertools::assert_equal( + assert_equal( ts_info .iter_selection(&TimeSliceSelection::Single(ts)) .map(|(ts, _)| ts), @@ -281,7 +283,7 @@ mod tests { macro_rules! check_share { ($selection:expr, $expected:expr) => { let expected = $expected; - let actual: HashMap<_, _> = HashMap::from_iter( + let actual: IndexMap<_, _> = IndexMap::from_iter( ts_info .calculate_share(&$selection, 8.0) .map(|(ts, share)| (ts.clone(), share)), @@ -294,12 +296,13 @@ mod tests { } // Whole year - let expected: HashMap<_, _> = HashMap::from_iter(slices.iter().map(|ts| (ts.clone(), 2.0))); + let expected: IndexMap<_, _> = + IndexMap::from_iter(slices.iter().map(|ts| (ts.clone(), 2.0))); check_share!(TimeSliceSelection::Annual, expected); // One season let selection = TimeSliceSelection::Season("winter".into()); - let expected: HashMap<_, _> = HashMap::from_iter( + let expected: IndexMap<_, _> = IndexMap::from_iter( ts_info .iter_selection(&selection) .map(|(ts, _)| (ts.clone(), 4.0)), @@ -309,7 +312,7 @@ mod tests { // Single time slice let time_slice = ts_info.get_time_slice_id_from_str("winter.day").unwrap(); let selection = TimeSliceSelection::Single(time_slice.clone()); - let expected: HashMap<_, _> = HashMap::from_iter(iter::once((time_slice, 8.0))); + let expected: IndexMap<_, _> = IndexMap::from_iter(iter::once((time_slice, 8.0))); check_share!(selection, expected); } } From 4119ab3c377ee9930b99962e9cc9e926061e4f19 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 12 Feb 2025 11:29:42 +0000 Subject: [PATCH 6/6] Keep commodity prices ordered --- src/simulation.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simulation.rs b/src/simulation.rs index 93b58722..a4596914 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -2,8 +2,8 @@ use crate::agent::AssetPool; use crate::model::Model; use crate::time_slice::TimeSliceID; +use indexmap::IndexMap; use log::info; -use std::collections::HashMap; use std::rc::Rc; pub mod optimisation; @@ -18,7 +18,7 @@ type CommodityPriceKey = (Rc, TimeSliceID); /// A map relating commodity ID + time slice to current price (endogenous) #[derive(Default)] -pub struct CommodityPrices(HashMap); +pub struct CommodityPrices(IndexMap); impl CommodityPrices { /// Get the price for the given commodity and time slice