Skip to content
Merged
15 changes: 9 additions & 6 deletions src/agent.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
//! 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::id::{define_id_getter, define_id_type};
use crate::process::ProcessID;
use crate::region::RegionSelection;
use indexmap::IndexMap;
use serde::Deserialize;
use serde_string_enum::DeserializeLabeledStringEnum;
use std::collections::HashSet;
use std::rc::Rc;

define_id_type! {AgentID}

/// A map of [`Agent`]s, keyed by agent ID
pub type AgentMap = IndexMap<Rc<str>, Agent>;
pub type AgentMap = IndexMap<AgentID, Agent>;

/// An agent in the simulation
#[derive(Debug, Clone, PartialEq)]
pub struct Agent {
/// A unique identifier for the agent.
pub id: Rc<str>,
pub id: AgentID,
/// A text description of the agent.
pub description: String,
/// The commodities that the agent is responsible for servicing.
Expand All @@ -34,15 +37,15 @@ pub struct Agent {
/// The agent's objectives.
pub objectives: Vec<AgentObjective>,
}
define_id_getter! {Agent}
define_id_getter! {Agent, AgentID}

/// Which processes apply to this agent
#[derive(Debug, Clone, PartialEq)]
pub enum SearchSpace {
/// All processes are considered
AllProcesses,
/// Only these specific processes are considered
Some(HashSet<Rc<str>>),
Some(HashSet<ProcessID>),
}

/// Search space for an agent
Expand Down Expand Up @@ -74,7 +77,7 @@ pub enum DecisionRule {
#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct AgentObjective {
/// Unique agent id identifying the agent this objective belongs to
pub agent_id: String,
pub agent_id: AgentID,
/// The year the objective is relevant for
pub year: u32,
/// Acronym identifying the objective (e.g. LCOX)
Expand Down
14 changes: 8 additions & 6 deletions src/asset.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Assets are instances of a process which are owned and invested in by agents.
use crate::agent::AgentID;
use crate::commodity::Commodity;
use crate::process::Process;
use crate::region::RegionID;
use crate::time_slice::TimeSliceID;
use std::collections::HashSet;
use std::ops::RangeInclusive;
Expand All @@ -21,11 +23,11 @@ pub struct Asset {
/// A unique identifier for the asset
pub id: AssetID,
/// A unique identifier for the agent
pub agent_id: Rc<str>,
pub agent_id: AgentID,
/// The [`Process`] that this asset corresponds to
pub process: Rc<Process>,
/// The region in which the asset is located
pub region_id: Rc<str>,
pub region_id: RegionID,
/// Capacity of asset
pub capacity: f64,
/// The year the asset comes online
Expand All @@ -38,9 +40,9 @@ impl Asset {
/// The `id` field is initially set to [`AssetID::INVALID`], but is changed to a unique value
/// when the asset is stored in an [`AssetPool`].
pub fn new(
agent_id: Rc<str>,
agent_id: AgentID,
process: Rc<Process>,
region_id: Rc<str>,
region_id: RegionID,
capacity: f64,
commission_year: u32,
) -> Self {
Expand Down Expand Up @@ -146,7 +148,7 @@ impl AssetPool {
/// Iterate over active assets for a particular region
pub fn iter_for_region<'a>(
&'a self,
region_id: &'a Rc<str>,
region_id: &'a RegionID,
) -> impl Iterator<Item = &'a Asset> {
self.iter().filter(|asset| asset.region_id == *region_id)
}
Expand All @@ -155,7 +157,7 @@ impl AssetPool {
/// commodity
pub fn iter_for_region_and_commodity<'a>(
&'a self,
region_id: &'a Rc<str>,
region_id: &'a RegionID,
commodity: &'a Rc<Commodity>,
) -> impl Iterator<Item = &'a Asset> {
self.iter_for_region(region_id)
Expand Down
25 changes: 14 additions & 11 deletions src/commodity.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
#![allow(missing_docs)]
use crate::id::define_id_getter;
use crate::id::{define_id_getter, define_id_type};
use crate::region::RegionID;
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;

define_id_type! {CommodityID}

/// A map of [`Commodity`]s, keyed by commodity ID
pub type CommodityMap = IndexMap<Rc<str>, Rc<Commodity>>;
pub type CommodityMap = IndexMap<CommodityID, Rc<Commodity>>;

/// 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)]
pub struct Commodity {
/// Unique identifier for the commodity (e.g. "ELC")
pub id: Rc<str>,
pub id: CommodityID,
/// Text description of commodity (e.g. "electricity")
pub description: String,
#[serde(rename = "type")] // NB: we can't name a field type as it's a reserved keyword
Expand All @@ -29,7 +32,7 @@ pub struct Commodity {
#[serde(skip)]
pub demand: DemandMap,
}
define_id_getter! {Commodity}
define_id_getter! {Commodity, CommodityID}

/// Type of balance for application of cost
#[derive(PartialEq, Clone, Debug, DeserializeLabeledStringEnum)]
Expand All @@ -54,7 +57,7 @@ pub struct CommodityCost {
/// Used for looking up [`CommodityCost`]s in a [`CommodityCostMap`]
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
struct CommodityCostKey {
region_id: Rc<str>,
region_id: RegionID,
year: u32,
time_slice: TimeSliceID,
}
Expand All @@ -72,7 +75,7 @@ impl CommodityCostMap {
/// Insert a [`CommodityCost`] into the map
pub fn insert(
&mut self,
region_id: Rc<str>,
region_id: RegionID,
year: u32,
time_slice: TimeSliceID,
value: CommodityCost,
Expand All @@ -88,12 +91,12 @@ impl CommodityCostMap {
/// Retrieve a [`CommodityCost`] from the map
pub fn get(
&self,
region_id: &Rc<str>,
region_id: &RegionID,
year: u32,
time_slice: &TimeSliceID,
) -> Option<&CommodityCost> {
let key = CommodityCostKey {
region_id: Rc::clone(region_id),
region_id: region_id.clone(),
year,
time_slice: time_slice.clone(),
};
Expand Down Expand Up @@ -124,7 +127,7 @@ pub struct DemandMap(HashMap<DemandMapKey, f64>);
/// The key for a [`DemandMap`]
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
struct DemandMapKey {
region_id: Rc<str>,
region_id: RegionID,
year: u32,
time_slice: TimeSliceID,
}
Expand All @@ -136,7 +139,7 @@ impl DemandMap {
}

/// Retrieve the demand for the specified region, year and time slice
pub fn get(&self, region_id: &Rc<str>, year: u32, time_slice: &TimeSliceID) -> f64 {
pub fn get(&self, region_id: &RegionID, year: u32, time_slice: &TimeSliceID) -> f64 {
self.0
.get(&DemandMapKey {
region_id: region_id.clone(),
Expand All @@ -148,7 +151,7 @@ impl DemandMap {
}

/// Insert a new demand entry for the specified region, year and time slice
pub fn insert(&mut self, region_id: Rc<str>, year: u32, time_slice: TimeSliceID, demand: f64) {
pub fn insert(&mut self, region_id: RegionID, year: u32, time_slice: TimeSliceID, demand: f64) {
self.0.insert(
DemandMapKey {
region_id,
Expand Down
109 changes: 91 additions & 18 deletions src/id.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,80 @@
//! Code for handing IDs
use crate::region::RegionID;
use anyhow::{Context, Result};
use std::collections::HashSet;
use std::rc::Rc;

/// A trait alias for ID types
pub trait IDLike:
Eq + std::hash::Hash + std::borrow::Borrow<str> + Clone + std::fmt::Display
{
}
impl<T> IDLike for T where
T: Eq + std::hash::Hash + std::borrow::Borrow<str> + Clone + std::fmt::Display
{
}
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.

I'm not a fan of what the formatter's done here. Yuck


macro_rules! define_id_type {
($name:ident) => {
#[derive(
Clone, std::hash::Hash, PartialEq, Eq, serde::Deserialize, Debug, serde::Serialize,
)]
/// An ID type (e.g. `AgentID`, `CommodityID`, etc.)
pub struct $name(pub std::rc::Rc<str>);

impl std::borrow::Borrow<str> for $name {
fn borrow(&self) -> &str {
&self.0
}
}

impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl From<&str> for $name {
fn from(s: &str) -> Self {
$name(std::rc::Rc::from(s))
}
}

impl From<String> for $name {
fn from(s: String) -> Self {
$name(std::rc::Rc::from(s))
}
}

impl $name {
/// Create a new ID from a string slice
pub fn new(id: &str) -> Self {
$name(std::rc::Rc::from(id))
}
}
};
}
pub(crate) use define_id_type;

#[cfg(test)]
define_id_type!(GenericID);
Comment thread
tsmbland marked this conversation as resolved.

/// 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;
pub trait HasID<ID: IDLike> {
/// Get the struct's ID
fn get_id(&self) -> &ID;
}

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

/// 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 {
($t:ty, $id_ty:ty) => {
impl crate::id::HasID<$id_ty> for $t {
fn get_id(&self) -> &$id_ty {
&self.id
}
}
Expand All @@ -31,7 +86,7 @@ pub(crate) use define_id_getter;
macro_rules! define_region_id_getter {
($t:ty) => {
impl crate::id::HasRegionID for $t {
fn get_region_id(&self) -> &str {
fn get_region_id(&self) -> &RegionID {
&self.region_id
}
}
Expand All @@ -40,24 +95,42 @@ macro_rules! define_region_id_getter {
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.
pub trait IDCollection<ID: IDLike> {
/// Get the ID from the collection by its string representation.
///
/// # Arguments
///
/// * `id` - The string representation of the ID
///
/// # Returns
///
/// A copy of the ID in `self`, or an error if not found.
fn get_id_by_str(&self, id: &str) -> Result<ID>;

/// Check if the ID is in the collection, returning a copy of it if found.
///
/// # Arguments
///
/// * `id` - The ID to look up
/// * `id` - The ID to check
///
/// # Returns
///
/// A copy of the `Rc<str>` in `self` or an error if not found.
fn get_id(&self, id: &str) -> Result<Rc<str>>;
/// A copy of the ID in `self`, or an error if not found.
fn get_id(&self, id: &ID) -> Result<ID>;
}

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

fn get_id(&self, id: &ID) -> Result<ID> {
let found = self
.get(id.borrow())
.with_context(|| format!("Unknown ID {id} found"))?;
Ok(found.clone())
}
}
Loading