Skip to content

Commit 5e025fc

Browse files
committed
Merge remote-tracking branch 'origin/main' into prohibit-dubious-ids
2 parents 4be5375 + d1dfee1 commit 5e025fc

21 files changed

Lines changed: 607 additions & 426 deletions

examples/simple/agents.csv

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
id,description,regions,decision_rule,decision_lexico_tolerance
2-
A0_GEX,Gas extractors,GBR,single,
3-
A0_GPR,Gas processors,GBR,single,
4-
A0_ELC,Electricity generators,GBR,single,
5-
A0_RES,Residential consumer,GBR,single,
2+
A0_GEX,Gas extractors,all,single,
3+
A0_GPR,Gas processors,all,single,
4+
A0_ELC,Electricity generators,all,single,
5+
A0_RES,Residential consumer,all,single,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
commodity_id,regions,years,time_slice,balance_type,value
2-
CO2EMT,GBR,all,annual,net,0.04
2+
CO2EMT,all,all,annual,net,0.04
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
process_id,regions,years,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate,capacity_to_activity
2-
GASDRV,GBR,all,10.0,0.3,2.0,25,0.1,1.0
3-
GASPRC,GBR,all,7.0,0.21,0.5,25,0.1,1.0
4-
WNDFRM,GBR,all,1000.0,30.0,0.4,25,0.1,31.54
5-
GASCGT,GBR,all,700.0,21.0,0.55,30,0.1,31.54
6-
RGASBR,GBR,all,55.56,1.6668,0.16,15,0.1,1.0
7-
RELCHP,GBR,all,138.9,4.167,0.17,15,0.1,1.0
2+
GASDRV,all,all,10.0,0.3,2.0,25,0.1,1.0
3+
GASPRC,all,all,7.0,0.21,0.5,25,0.1,1.0
4+
WNDFRM,all,all,1000.0,30.0,0.4,25,0.1,31.54
5+
GASCGT,all,all,700.0,21.0,0.55,30,0.1,31.54
6+
RGASBR,all,all,55.56,1.6668,0.16,15,0.1,1.0
7+
RELCHP,all,all,138.9,4.167,0.17,15,0.1,1.0

examples/simple/processes.csv

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
id,description,regions,start_year,end_year
2-
GASDRV,Dry gas extraction,GBR,2020,2030
3-
GASPRC,Gas processing,GBR,2020,2030
4-
WNDFRM,Wind farm,GBR,2020,2030
5-
GASCGT,Gas combined cycle turbine,GBR,2020,2030
6-
RGASBR,Gas boiler,GBR,2020,2030
7-
RELCHP,Heat pump,GBR,2020,2030
2+
GASDRV,Dry gas extraction,all,2020,2030
3+
GASPRC,Gas processing,all,2020,2030
4+
WNDFRM,Wind farm,all,2020,2030
5+
GASCGT,Gas combined cycle turbine,all,2020,2030
6+
RGASBR,Gas boiler,all,2020,2030
7+
RELCHP,Heat pump,all,2020,2030

src/fixture.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ use crate::agent::{
44
Agent, AgentCommodityPortionsMap, AgentCostLimitsMap, AgentMap, AgentObjectiveMap,
55
AgentSearchSpaceMap, DecisionRule,
66
};
7+
use crate::commodity::CommodityID;
78
use crate::process::{
89
Process, ProcessEnergyLimitsMap, ProcessFlowsMap, ProcessMap, ProcessParameter,
910
ProcessParameterMap,
1011
};
1112
use crate::region::RegionID;
12-
use crate::time_slice::TimeSliceID;
13+
use crate::time_slice::{TimeSliceID, TimeSliceInfo};
1314
use indexmap::indexmap;
1415
use itertools::Itertools;
1516
use rstest::fixture;
@@ -28,6 +29,11 @@ macro_rules! assert_error {
2829
}
2930
pub(crate) use assert_error;
3031

32+
#[fixture]
33+
pub fn commodity_ids() -> HashSet<CommodityID> {
34+
iter::once("commodity1".into()).collect()
35+
}
36+
3137
#[fixture]
3238
pub fn region_ids() -> HashSet<RegionID> {
3339
["GBR".into(), "USA".into()].into_iter().collect()
@@ -97,3 +103,20 @@ pub fn time_slice() -> TimeSliceID {
97103
time_of_day: "day".into(),
98104
}
99105
}
106+
107+
#[fixture]
108+
pub fn time_slice_info() -> TimeSliceInfo {
109+
TimeSliceInfo {
110+
seasons: iter::once("winter".into()).collect(),
111+
times_of_day: iter::once("day".into()).collect(),
112+
fractions: [(
113+
TimeSliceID {
114+
season: "winter".into(),
115+
time_of_day: "day".into(),
116+
},
117+
1.0,
118+
)]
119+
.into_iter()
120+
.collect(),
121+
}
122+
}

src/id.rs

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
//! Code for handling IDs
22
use anyhow::{Context, Result};
3-
use indexmap::IndexSet;
3+
use indexmap::{IndexMap, IndexSet};
4+
use std::borrow::Borrow;
45
use std::collections::HashSet;
6+
use std::fmt::Display;
7+
use std::hash::Hash;
58

69
/// A trait alias for ID types
7-
pub trait IDLike:
8-
Eq + std::hash::Hash + std::borrow::Borrow<str> + Clone + std::fmt::Display + From<String>
9-
{
10-
}
11-
impl<T> IDLike for T where
12-
T: Eq + std::hash::Hash + std::borrow::Borrow<str> + Clone + std::fmt::Display + From<String>
13-
{
14-
}
10+
pub trait IDLike: Eq + Hash + Borrow<str> + Clone + Display + From<String> {}
11+
impl<T> IDLike for T where T: Eq + Hash + Borrow<str> + Clone + Display + From<String> {}
1512

1613
macro_rules! define_id_type {
1714
($name:ident) => {
@@ -102,43 +99,25 @@ pub(crate) use define_id_getter;
10299

103100
/// A data structure containing a set of IDs
104101
pub trait IDCollection<ID: IDLike> {
105-
/// Get the ID from the collection by its string representation.
106-
///
107-
/// # Arguments
108-
///
109-
/// * `id` - The string representation of the ID
110-
///
111-
/// # Returns
112-
///
113-
/// A copy of the ID in `self`, or an error if not found.
114-
fn get_id_by_str(&self, id: &str) -> Result<ID>;
115-
116102
/// Check if the ID is in the collection, returning a copy of it if found.
117103
///
118104
/// # Arguments
119105
///
120-
/// * `id` - The ID to check
106+
/// * `id` - The ID to check (can be string or ID type)
121107
///
122108
/// # Returns
123109
///
124110
/// A copy of the ID in `self`, or an error if not found.
125-
fn get_id(&self, id: &ID) -> Result<ID>;
111+
fn get_id<T: Borrow<str> + Display + ?Sized>(&self, id: &T) -> Result<&ID>;
126112
}
127113

128114
macro_rules! define_id_methods {
129115
() => {
130-
fn get_id_by_str(&self, id: &str) -> Result<ID> {
131-
let found = self
132-
.get(id)
133-
.with_context(|| format!("Unknown ID {id} found"))?;
134-
Ok(found.clone())
135-
}
136-
137-
fn get_id(&self, id: &ID) -> Result<ID> {
116+
fn get_id<T: Borrow<str> + Display + ?Sized>(&self, id: &T) -> Result<&ID> {
138117
let found = self
139118
.get(id.borrow())
140119
.with_context(|| format!("Unknown ID {id} found"))?;
141-
Ok(found.clone())
120+
Ok(found)
142121
}
143122
};
144123
}
@@ -151,6 +130,15 @@ impl<ID: IDLike> IDCollection<ID> for IndexSet<ID> {
151130
define_id_methods!();
152131
}
153132

133+
impl<ID: IDLike, V> IDCollection<ID> for IndexMap<ID, V> {
134+
fn get_id<T: Borrow<str> + Display + ?Sized>(&self, id: &T) -> Result<&ID> {
135+
let (found, _) = self
136+
.get_key_value(id.borrow())
137+
.with_context(|| format!("Unknown ID {id} found"))?;
138+
Ok(found)
139+
}
140+
}
141+
154142
#[cfg(test)]
155143
mod tests {
156144
use super::*;

src/input.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ where
138138
Ok(())
139139
}
140140

141+
/// Check whether an iterator contains values that are sorted and unique
142+
pub fn is_sorted_and_unique<T, I>(iter: I) -> bool
143+
where
144+
T: PartialOrd + Clone,
145+
I: IntoIterator<Item = T>,
146+
{
147+
iter.into_iter().tuple_windows().all(|(a, b)| a < b)
148+
}
149+
141150
/// Inserts a key-value pair into a HashMap if the key does not already exist.
142151
///
143152
/// If the key already exists, it returns an error with a message indicating the key's existence.
@@ -200,9 +209,9 @@ pub fn load_model<P: AsRef<Path>>(model_dir: P) -> Result<(Model, AssetPool)> {
200209

201210
#[cfg(test)]
202211
mod tests {
203-
use crate::id::GenericID;
204-
205212
use super::*;
213+
use crate::id::GenericID;
214+
use rstest::rstest;
206215
use serde::de::value::{Error as ValueError, F64Deserializer};
207216
use serde::de::IntoDeserializer;
208217
use serde::Deserialize;
@@ -324,4 +333,16 @@ mod tests {
324333
assert!(check_fractions_sum_to_one([f64::INFINITY].into_iter()).is_err());
325334
assert!(check_fractions_sum_to_one([f64::NAN].into_iter()).is_err());
326335
}
336+
337+
#[rstest]
338+
#[case(&[], true)]
339+
#[case(&[1], true)]
340+
#[case(&[1,2], true)]
341+
#[case(&[1,2,3,4], true)]
342+
#[case(&[2,1],false)]
343+
#[case(&[1,1],false)]
344+
#[case(&[1,3,2,4], false)]
345+
fn test_is_sorted_and_unique(#[case] values: &[u32], #[case] expected: bool) {
346+
assert_eq!(is_sorted_and_unique(values), expected)
347+
}
327348
}

src/input/agent/commodity_portion.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
use super::super::*;
33
use crate::agent::{AgentCommodityPortionsMap, AgentID, AgentMap};
44
use crate::commodity::{CommodityID, CommodityMap, CommodityType};
5+
use crate::id::IDCollection;
56
use crate::region::RegionID;
67
use crate::year::parse_year_str;
78
use anyhow::{ensure, Context, Result};
@@ -66,9 +67,7 @@ where
6667
for agent_commodity_portion_raw in iter {
6768
// Get agent ID
6869
let agent_id_raw = agent_commodity_portion_raw.agent_id.as_str();
69-
let (id, _agent) = agents
70-
.get_key_value(agent_id_raw)
71-
.with_context(|| format!("Invalid agent ID {agent_id_raw}"))?;
70+
let id = agents.get_id(agent_id_raw)?;
7271

7372
// Get/create entry for agent
7473
let entry = agent_commodity_portions
@@ -77,9 +76,7 @@ where
7776

7877
// Insert portion for the commodity/year(s)
7978
let commodity_id_raw = agent_commodity_portion_raw.commodity_id.as_str();
80-
let (commodity_id, _commodity) = commodities
81-
.get_key_value(commodity_id_raw)
82-
.with_context(|| format!("Invalid commodity ID {commodity_id_raw}"))?;
79+
let commodity_id = commodities.get_id(commodity_id_raw)?;
8380
let years = parse_year_str(&agent_commodity_portion_raw.years, milestone_years)?;
8481
for year in years {
8582
try_insert(

src/input/agent/cost_limit.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ where
6161
let years = parse_year_str(&agent_cost_limits_raw.years, milestone_years)?;
6262

6363
// Get agent ID
64-
let agent_id = agent_ids.get_id_by_str(&agent_cost_limits_raw.agent_id)?;
64+
let agent_id = agent_ids.get_id(&agent_cost_limits_raw.agent_id)?;
6565

6666
// Get or create entry in the map
6767
let entry = map.entry(agent_id.clone()).or_default();

src/input/agent/search_space.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,16 @@ impl AgentSearchSpaceRaw {
5252
let search_space = Rc::new(parse_search_space_str(&self.search_space, process_ids)?);
5353

5454
// Get commodity
55-
let commodity_id = commodity_ids.get_id_by_str(&self.commodity_id)?;
55+
let commodity_id = commodity_ids.get_id(&self.commodity_id)?;
5656

5757
// Check that the year is a valid milestone year
5858
let year = parse_year_str(&self.years, milestone_years)?;
5959

60-
let (agent_id, _) = agents
61-
.get_key_value(self.agent_id.as_str())
62-
.context("Invalid agent ID")?;
60+
let agent_id = agents.get_id(&self.agent_id)?;
6361

6462
Ok(AgentSearchSpace {
6563
agent_id: agent_id.clone(),
66-
commodity_id,
64+
commodity_id: commodity_id.clone(),
6765
years: year,
6866
search_space,
6967
})
@@ -86,7 +84,7 @@ fn parse_search_space_str(
8684
} else {
8785
search_space
8886
.split(';')
89-
.map(|id| process_ids.get_id_by_str(id.trim()))
87+
.map(|id| Ok(process_ids.get_id(id.trim())?.clone()))
9088
.try_collect()
9189
}
9290
}

0 commit comments

Comments
 (0)