Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/agent.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Agents drive the economy of the MUSE 2.0 simulation, through relative investment in different
//! assets.
use crate::commodity::Commodity;
use crate::id::define_id_getter;
use crate::region::RegionSelection;
use indexmap::IndexMap;
use serde::Deserialize;
Expand Down Expand Up @@ -33,6 +34,7 @@ pub struct Agent {
/// The agent's objectives.
pub objectives: Vec<AgentObjective>,
}
define_id_getter! {Agent}

/// Which processes apply to this agent
#[derive(Debug, Clone, PartialEq)]
Expand All @@ -46,8 +48,6 @@ pub enum SearchSpace {
/// Search space for an agent
#[derive(Debug, Clone, PartialEq)]
pub struct AgentSearchSpace {
/// Unique agent id identifying the agent this search space belongs to
pub agent_id: String,
/// The year the objective is relevant for
pub year: u32,
/// The commodity to apply the search space to
Expand Down
2 changes: 0 additions & 2 deletions src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@ mod tests {
time_of_day: "day".into(),
};
let process_param = ProcessParameter {
process_id: "process1".into(),
years: 2010..=2020,
capital_cost: 5.0,
fixed_operating_cost: 2.0,
Expand Down Expand Up @@ -246,7 +245,6 @@ mod tests {

fn create_asset_pool() -> AssetPool {
let process_param = ProcessParameter {
process_id: "process1".into(),
years: 2010..=2020,
capital_cost: 5.0,
fixed_operating_cost: 2.0,
Expand Down
2 changes: 1 addition & 1 deletion src/commodity.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![allow(missing_docs)]
use crate::input::{define_id_getter, HasID};
use crate::id::define_id_getter;
use crate::time_slice::{TimeSliceID, TimeSliceLevel};
use indexmap::IndexMap;
use serde::Deserialize;
Expand Down
63 changes: 63 additions & 0 deletions src/id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//! Code for handing IDs
use anyhow::{Context, Result};
use std::collections::HashSet;
use std::rc::Rc;

/// Indicates that the struct has an ID field
pub trait HasID {
/// Get a string representation of the struct's ID
fn get_id(&self) -> &str;
}

/// An object which is associated with a single region
pub trait HasRegionID {
/// Get the associated region ID
fn get_region_id(&self) -> &str;
}

/// Implement the `HasID` trait for the given type, assuming it has a field called `id`
macro_rules! define_id_getter {
($t:ty) => {
impl crate::id::HasID for $t {
fn get_id(&self) -> &str {
&self.id
}
}
};
}
pub(crate) use define_id_getter;

/// Implement the `HasRegionID` trait for the given type, assuming it has a field called `region_id`
macro_rules! define_region_id_getter {
($t:ty) => {
impl crate::id::HasRegionID for $t {
fn get_region_id(&self) -> &str {
&self.region_id
}
}
};
}
pub(crate) use define_region_id_getter;

/// A data structure containing a set of IDs
pub trait IDCollection {
/// Get the ID after checking that it exists this collection.
///
/// # Arguments
///
/// * `id` - The ID to look up
///
/// # Returns
///
/// A copy of the `Rc<str>` in `self` or an error if not found.
fn get_id(&self, id: &str) -> Result<Rc<str>>;
}

impl IDCollection for HashSet<Rc<str>> {
fn get_id(&self, id: &str) -> Result<Rc<str>> {
let id = self
.get(id)
.with_context(|| format!("Unknown ID {id} found"))?;
Ok(Rc::clone(id))
}
}
75 changes: 2 additions & 73 deletions src/input.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
//! Common routines for handling input data.
use crate::asset::AssetPool;
use crate::id::HasID;
use crate::model::{Model, ModelFile};
use anyhow::{bail, 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};
use std::collections::HashSet;
use std::fs;
use std::path::Path;
use std::rc::Rc;
Expand Down Expand Up @@ -96,48 +97,6 @@ pub fn input_err_msg<P: AsRef<Path>>(file_path: P) -> String {
format!("Error reading {}", file_path.as_ref().display())
}

/// Indicates that the struct has an ID field
pub trait HasID {
/// Get a string representation of the struct's ID
fn get_id(&self) -> &str;
}

/// Implement the `HasID` trait for the given type, assuming it has a field called `id`
macro_rules! define_id_getter {
($t:ty) => {
impl HasID for $t {
fn get_id(&self) -> &str {
&self.id
}
}
};
}

pub(crate) use define_id_getter;

/// A data structure containing a set of IDs
pub trait IDCollection {
/// Get the ID after checking that it exists this collection.
///
/// # Arguments
///
/// * `id` - The ID to look up
///
/// # Returns
///
/// A copy of the `Rc<str>` in `self` or an error if not found.
fn get_id(&self, id: &str) -> Result<Rc<str>>;
}

impl IDCollection for HashSet<Rc<str>> {
fn get_id(&self, id: &str) -> Result<Rc<str>> {
let id = self
.get(id)
.with_context(|| format!("Unknown ID {id} found"))?;
Ok(Rc::clone(id))
}
}

/// 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
Expand Down Expand Up @@ -166,36 +125,6 @@ where
fill_and_validate_map(file_path).with_context(|| input_err_msg(file_path))
}

/// Trait for converting an iterator into a [`HashMap`] grouped by IDs.
pub trait IntoIDMap<T> {
/// Convert into a [`HashMap`] grouped by IDs.
fn into_id_map(self, ids: &HashSet<Rc<str>>) -> Result<HashMap<Rc<str>, Vec<T>>>;
}

impl<T, I> IntoIDMap<T> for I
where
T: HasID,
I: Iterator<Item = T>,
{
/// Convert the specified iterator into a `HashMap` of the items grouped by ID.
///
/// # Arguments
///
/// `ids` - The set of valid IDs to check against.
fn into_id_map(self, ids: &HashSet<Rc<str>>) -> Result<HashMap<Rc<str>, Vec<T>>> {
let map = self
.map(|item| -> Result<_> {
let id = ids.get_id(item.get_id())?;
Ok((id, item))
})
.process_results(|iter| iter.into_group_map())?;

ensure!(!map.is_empty(), "CSV file is empty");

Ok(map)
}
}

/// Check that fractions sum to (approximately) one
fn check_fractions_sum_to_one<I>(fractions: I) -> Result<()>
where
Expand Down
5 changes: 2 additions & 3 deletions src/input/agent/objective.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
//! Code for reading the agent objectives CSV file.
use super::super::*;
use crate::agent::{Agent, AgentMap, AgentObjective, DecisionRule};
use crate::agent::{AgentMap, AgentObjective, DecisionRule};
use anyhow::{ensure, Context, Result};
use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;

const AGENT_OBJECTIVES_FILE_NAME: &str = "agent_objectives.csv";

define_id_getter! {Agent}

/// Read agent objective info from the agent_objectives.csv file.
///
/// # Arguments
Expand Down Expand Up @@ -172,6 +170,7 @@ fn check_agent_objectives(
#[cfg(test)]
mod tests {
use super::*;
use crate::agent::Agent;
use crate::agent::ObjectiveType;
use crate::region::RegionSelection;

Expand Down
4 changes: 2 additions & 2 deletions src/input/agent/region.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Code for loading the agent regions CSV file.
use super::super::region::{define_region_id_getter, read_regions_for_entity};
use super::super::HasID;
use super::super::region::read_regions_for_entity;
use crate::id::{define_region_id_getter, HasID};
use crate::region::RegionSelection;
use anyhow::Result;
use serde::Deserialize;
Expand Down
8 changes: 4 additions & 4 deletions src/input/agent/search_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use super::super::*;
use crate::agent::{AgentMap, AgentSearchSpace, SearchSpace};
use crate::commodity::CommodityMap;
use crate::id::IDCollection;
use anyhow::{Context, Result};
use serde::Deserialize;
use std::collections::HashMap;
Expand Down Expand Up @@ -56,7 +57,6 @@ impl AgentSearchSpaceRaw {

// Create AgentSearchSpace
Ok(AgentSearchSpace {
agent_id: self.agent_id.clone(),
year: self.year,
commodity: Rc::clone(commodity),
search_space,
Expand Down Expand Up @@ -97,12 +97,12 @@ where
I: Iterator<Item = AgentSearchSpaceRaw>,
{
let mut search_spaces = HashMap::new();
for search_space in iter {
for search_space_raw in iter {
let search_space =
search_space.to_agent_search_space(process_ids, commodities, milestone_years)?;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually quite common to reuse variable names like this in Rust (it's less dangerous than in e.g. Python) but this is ok too

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, yeah probably not clear why I did this but was just because later on I use agent_id from search_space_raw because search_space no longer has agent_id. So I just needed to keep seach_space_raw in the namespace. (Equally could have extracted agent_id before converting search_space_raw to search_space)

search_space_raw.to_agent_search_space(process_ids, commodities, milestone_years)?;

let (id, _agent) = agents
.get_key_value(search_space.agent_id.as_str())
.get_key_value(search_space_raw.agent_id.as_str())
.context("Invalid agent ID")?;

// Append to Vec with the corresponding key or create
Expand Down
2 changes: 1 addition & 1 deletion src/input/asset.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Code for reading [Asset]s from a CSV file.
use super::*;
use crate::asset::Asset;
use crate::id::IDCollection;
use crate::process::ProcessMap;
use anyhow::{ensure, Context, Result};
use itertools::Itertools;
Expand Down Expand Up @@ -100,7 +101,6 @@ mod tests {
#[test]
fn test_read_assets_from_iter() {
let process_param = ProcessParameter {
process_id: "process1".into(),
years: 2010..=2020,
capital_cost: 5.0,
fixed_operating_cost: 2.0,
Expand Down
1 change: 1 addition & 0 deletions src/input/commodity/cost.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Code for reading in the commodity cost CSV file.
use super::super::*;
use crate::commodity::{BalanceType, CommodityCost, CommodityCostMap};
use crate::id::IDCollection;
use crate::time_slice::TimeSliceInfo;
use anyhow::{ensure, Context, Result};
use serde::Deserialize;
Expand Down
1 change: 1 addition & 0 deletions src/input/commodity/demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use super::super::*;
use super::demand_slicing::{read_demand_slices, DemandSliceMap, DemandSliceMapKey};
use crate::commodity::DemandMap;
use crate::id::IDCollection;
use crate::time_slice::TimeSliceInfo;
use anyhow::{ensure, Result};
use serde::Deserialize;
Expand Down
1 change: 1 addition & 0 deletions src/input/commodity/demand_slicing.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Demand slicing determines how annual demand is distributed across the year.
use super::super::*;
use super::demand::*;
use crate::id::IDCollection;
use crate::time_slice::{TimeSliceID, TimeSliceInfo};
use anyhow::{ensure, Context, Result};
use itertools::Itertools;
Expand Down
13 changes: 1 addition & 12 deletions src/input/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,11 @@ use flow::read_process_flows;
mod parameter;
use parameter::read_process_parameters;
mod region;
use crate::id::define_id_getter;
use region::read_process_regions;

const PROCESSES_FILE_NAME: &str = "processes.csv";

macro_rules! define_process_id_getter {
($t:ty) => {
impl HasID for $t {
fn get_id(&self) -> &str {
&self.process_id
}
}
};
}
use define_process_id_getter;

#[derive(PartialEq, Debug, Deserialize)]
struct ProcessDescription {
id: Rc<str>,
Expand Down Expand Up @@ -307,7 +297,6 @@ mod tests {
.into_iter()
.map(|id| {
let parameter = ProcessParameter {
process_id: id.to_string(),
years: 2010..=2020,
capital_cost: 0.0,
fixed_operating_cost: 0.0,
Expand Down
1 change: 1 addition & 0 deletions src/input/process/availability.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Code for reading process availabilities CSV file
use super::super::*;
use crate::id::IDCollection;
use crate::process::ActivityLimitsMap;
use crate::time_slice::TimeSliceInfo;
use anyhow::{Context, Result};
Expand Down
Loading