Skip to content

Commit 10cc711

Browse files
authored
Merge branch 'main' into rc-assets
2 parents fa3f5d9 + c814596 commit 10cc711

18 files changed

Lines changed: 671 additions & 395 deletions

examples/simple/process_availabilities.csv

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
process_id,regions,years,time_slice,limit_type,value
2-
GASDRV,all,all,,up,0.9
3-
GASPRC,all,all,,up,0.9
4-
GASCGT,all,all,,up,0.9
5-
RGASBR,all,all,,up,1.0
6-
RELCHP,all,all,,up,1.0
2+
GASDRV,all,all,annual,up,0.9
3+
GASPRC,all,all,annual,up,0.9
4+
GASCGT,all,all,annual,up,0.9
5+
RGASBR,all,all,annual,up,1.0
6+
RELCHP,all,all,annual,up,1.0
77
WNDFRM,all,all,winter.night,up,0.486418015
88
WNDFRM,all,all,winter.day,up,0.543166784
99
WNDFRM,all,all,winter.peak,up,0.504433498

src/commands.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::input::load_model;
33
use crate::log;
44
use crate::output::{create_output_directory, get_output_dir};
55
use crate::settings::Settings;
6-
use ::log::{error, info};
6+
use ::log::info;
77
use anyhow::{ensure, Context, Result};
88
use clap::{Parser, Subcommand};
99
use include_dir::{include_dir, Dir, DirEntry};
@@ -98,20 +98,13 @@ pub fn handle_run_command(
9898
log::init(settings.log_level.as_deref(), &output_path)
9999
.context("Failed to initialise logging.")?;
100100

101-
let load_and_run_model = || {
102-
// Load the model to run
103-
let (model, assets) = load_model(model_path).context("Failed to load model.")?;
104-
info!("Loaded model from {}", model_path.display());
105-
info!("Output data will be written to {}", output_path.display());
101+
// Load the model to run
102+
let (model, assets) = load_model(model_path).context("Failed to load model.")?;
103+
info!("Loaded model from {}", model_path.display());
104+
info!("Output data will be written to {}", output_path.display());
106105

107-
// Run the simulation
108-
crate::simulation::run(model, assets, &output_path, settings.debug_model)
109-
};
110-
111-
// Once the logger is initialised, we can write fatal errors to log
112-
if let Err(err) = load_and_run_model() {
113-
error!("{err:?}");
114-
}
106+
// Run the simulation
107+
crate::simulation::run(model, assets, &output_path, settings.debug_model)?;
115108

116109
Ok(())
117110
}

src/commodity.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Commodities are substances or forms of energy that can be produced and consumed by processes.
22
use crate::id::{define_id_getter, define_id_type};
33
use crate::region::RegionID;
4-
use crate::time_slice::{TimeSliceID, TimeSliceLevel};
4+
use crate::time_slice::{TimeSliceID, TimeSliceLevel, TimeSliceSelection};
55
use indexmap::IndexMap;
66
use serde::Deserialize;
77
use serde_string_enum::DeserializeLabeledStringEnum;
@@ -16,8 +16,8 @@ pub type CommodityMap = IndexMap<CommodityID, Rc<Commodity>>;
1616
/// A map of [`CommodityCost`]s, keyed by region ID, year and time slice ID
1717
pub type CommodityCostMap = HashMap<(RegionID, u32, TimeSliceID), CommodityCost>;
1818

19-
/// A map of demand values, keyed by region ID, year and time slice ID
20-
pub type DemandMap = HashMap<(RegionID, u32, TimeSliceID), f64>;
19+
/// A map of demand values, keyed by region ID, year and time slice selection
20+
pub type DemandMap = HashMap<(RegionID, u32, TimeSliceSelection), f64>;
2121

2222
/// A commodity within the simulation.
2323
///
@@ -42,6 +42,10 @@ pub struct Commodity {
4242
#[serde(skip)]
4343
pub costs: CommodityCostMap,
4444
/// Demand as defined in input files. Will be empty for non-service-demand commodities.
45+
///
46+
/// The [`TimeSliceSelection`] part of the key is always at the same [`TimeSliceLevel`] as the
47+
/// `time_slice_level` field. E.g. if the `time_slice_level` is seasonal, then there will be
48+
/// keys representing each season (and not e.g. individual time slices).
4549
#[serde(skip)]
4650
pub demand: DemandMap,
4751
}
@@ -92,19 +96,20 @@ pub enum CommodityType {
9296
#[cfg(test)]
9397
mod tests {
9498
use super::*;
99+
use crate::time_slice::TimeSliceSelection;
95100

96101
#[test]
97102
fn test_demand_map() {
98-
let time_slice = TimeSliceID {
103+
let ts_selection = TimeSliceSelection::Single(TimeSliceID {
99104
season: "all-year".into(),
100105
time_of_day: "all-day".into(),
101-
};
106+
});
102107
let value = 0.25;
103108
let mut map = DemandMap::new();
104-
map.insert(("North".into(), 2020, time_slice.clone()), value);
109+
map.insert(("North".into(), 2020, ts_selection.clone()), value);
105110

106111
assert_eq!(
107-
map.get(&("North".into(), 2020, time_slice)).unwrap(),
112+
map.get(&("North".into(), 2020, ts_selection)).unwrap(),
108113
&value
109114
)
110115
}

src/fixture.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ use crate::agent::{
55
AgentSearchSpaceMap, DecisionRule,
66
};
77
use crate::asset::{Asset, AssetPool};
8-
use crate::commodity::CommodityID;
8+
use crate::commodity::{Commodity, CommodityCostMap, CommodityID, CommodityType, DemandMap};
99
use crate::process::{
1010
Process, ProcessEnergyLimitsMap, ProcessFlowsMap, ProcessMap, ProcessParameter,
1111
ProcessParameterMap,
1212
};
1313
use crate::region::RegionID;
14-
use crate::time_slice::{TimeSliceID, TimeSliceInfo};
14+
use crate::time_slice::{TimeSliceID, TimeSliceInfo, TimeSliceLevel};
1515
use indexmap::indexmap;
1616
use itertools::Itertools;
1717
use rstest::fixture;
18-
use std::collections::HashSet;
18+
use std::collections::{HashMap, HashSet};
1919
use std::iter;
2020
use std::rc::Rc;
2121

@@ -55,6 +55,22 @@ pub fn commodity_id() -> CommodityID {
5555
"commodity1".into()
5656
}
5757

58+
#[fixture]
59+
pub fn svd_commodity() -> Commodity {
60+
Commodity {
61+
id: "commodity1".into(),
62+
description: "".into(),
63+
kind: CommodityType::ServiceDemand,
64+
time_slice_level: TimeSliceLevel::DayNight,
65+
costs: CommodityCostMap::new(),
66+
demand: DemandMap::new(),
67+
}
68+
}
69+
70+
pub fn get_svd_map(commodity: &Commodity) -> HashMap<CommodityID, &Commodity> {
71+
iter::once((commodity.id.clone(), commodity)).collect()
72+
}
73+
5874
#[fixture]
5975
pub fn asset(process: Process) -> Asset {
6076
let region_id: RegionID = "GBR".into();
@@ -139,9 +155,9 @@ pub fn time_slice() -> TimeSliceID {
139155
#[fixture]
140156
pub fn time_slice_info() -> TimeSliceInfo {
141157
TimeSliceInfo {
142-
seasons: iter::once("winter".into()).collect(),
143158
times_of_day: iter::once("day".into()).collect(),
144-
fractions: [(
159+
seasons: iter::once(("winter".into(), 1.0)).collect(),
160+
time_slices: [(
145161
TimeSliceID {
146162
season: "winter".into(),
147163
time_of_day: "day".into(),

src/id.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ impl<T> IDLike for T where T: Eq + Hash + Borrow<str> + Clone + Display + From<S
1212

1313
macro_rules! define_id_type {
1414
($name:ident) => {
15-
#[derive(Clone, std::hash::Hash, PartialEq, Eq, Debug, serde::Serialize)]
15+
#[derive(
16+
Clone, std::hash::Hash, PartialOrd, Ord, PartialEq, Eq, Debug, serde::Serialize,
17+
)]
1618
/// An ID type (e.g. `AgentID`, `CommodityID`, etc.)
1719
pub struct $name(pub std::rc::Rc<str>);
1820

src/input/commodity/cost.rs

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ where
101101
.or_default()
102102
.insert(region.clone());
103103
for year in years.iter() {
104-
for (time_slice, _) in time_slice_info.iter_selection(&ts_selection) {
104+
for (time_slice, _) in ts_selection.iter(time_slice_info) {
105105
try_insert(
106106
map,
107107
(region.clone(), *year, time_slice.clone()),
@@ -146,51 +146,86 @@ fn validate_commodity_cost_map(
146146

147147
#[cfg(test)]
148148
mod tests {
149+
use std::iter;
150+
149151
use super::*;
152+
use crate::fixture::{assert_error, region_id, time_slice, time_slice_info};
150153
use crate::time_slice::TimeSliceID;
154+
use rstest::{fixture, rstest};
151155

152-
#[test]
153-
fn test_validate_commodity_costs_map() {
154-
// Set up time slices
155-
let slice = TimeSliceID {
156-
season: "winter".into(),
157-
time_of_day: "day".into(),
158-
};
159-
let ts_info = TimeSliceInfo {
160-
seasons: ["winter".into()].into(),
161-
times_of_day: ["day".into()].into(),
162-
fractions: [(slice.clone(), 1.0)].into(),
163-
};
156+
#[fixture]
157+
fn region_ids(region_id: RegionID) -> HashSet<RegionID> {
158+
iter::once(region_id).collect()
159+
}
164160

165-
let regions = HashSet::from(["UK".into()]);
166-
let milestone_years = [2020];
161+
#[fixture]
162+
fn cost_map(time_slice: TimeSliceID) -> CommodityCostMap {
167163
let cost = CommodityCost {
168164
balance_type: BalanceType::Net,
169165
value: 1.0,
170166
};
171167

172-
// Valid map
173168
let mut map = CommodityCostMap::new();
174-
map.insert(("UK".into(), 2020, slice.clone()), cost.clone());
175-
assert!(validate_commodity_cost_map(&map, &regions, &milestone_years, &ts_info).is_ok());
169+
map.insert(("GBR".into(), 2020, time_slice.clone()), cost.clone());
170+
map
171+
}
172+
173+
#[rstest]
174+
fn test_validate_commodity_costs_map_valid(
175+
cost_map: CommodityCostMap,
176+
time_slice_info: TimeSliceInfo,
177+
region_ids: HashSet<RegionID>,
178+
) {
179+
// Valid map
180+
assert!(
181+
validate_commodity_cost_map(&cost_map, &region_ids, &[2020], &time_slice_info).is_ok()
182+
);
183+
}
176184

185+
#[rstest]
186+
fn test_validate_commodity_costs_map_invalid_missing_region(
187+
cost_map: CommodityCostMap,
188+
time_slice_info: TimeSliceInfo,
189+
) {
177190
// Missing region
178-
let regions2 = HashSet::from(["UK".into(), "FR".into()]);
179-
assert!(validate_commodity_cost_map(&map, &regions2, &milestone_years, &ts_info).is_err());
191+
let region_ids = HashSet::from(["GBR".into(), "FRA".into()]);
192+
assert_error!(
193+
validate_commodity_cost_map(&cost_map, &region_ids, &[2020], &time_slice_info),
194+
"Missing cost for region FRA, year 2020, time slice winter.day"
195+
);
196+
}
180197

198+
#[rstest]
199+
fn test_validate_commodity_costs_map_invalid_missing_year(
200+
cost_map: CommodityCostMap,
201+
time_slice_info: TimeSliceInfo,
202+
region_ids: HashSet<RegionID>,
203+
) {
181204
// Missing year
182-
assert!(validate_commodity_cost_map(&map, &regions, &[2020, 2030], &ts_info).is_err());
205+
assert_error!(
206+
validate_commodity_cost_map(&cost_map, &region_ids, &[2020, 2030], &time_slice_info),
207+
"Missing cost for region GBR, year 2030, time slice winter.day"
208+
);
209+
}
183210

211+
#[rstest]
212+
fn test_validate_commodity_costs_map_invalid(
213+
cost_map: CommodityCostMap,
214+
region_ids: HashSet<RegionID>,
215+
) {
184216
// Missing time slice
185-
let slice2 = TimeSliceID {
217+
let time_slice = TimeSliceID {
186218
season: "winter".into(),
187219
time_of_day: "night".into(),
188220
};
189-
let ts_info2 = TimeSliceInfo {
190-
seasons: ["winter".into()].into(),
221+
let time_slice_info = TimeSliceInfo {
222+
seasons: [("winter".into(), 1.0)].into(),
191223
times_of_day: ["day".into(), "night".into()].into(),
192-
fractions: [(slice.clone(), 0.5), (slice2.clone(), 0.5)].into(),
224+
time_slices: [(time_slice.clone(), 0.5), (time_slice.clone(), 0.5)].into(),
193225
};
194-
assert!(validate_commodity_cost_map(&map, &regions, &milestone_years, &ts_info2).is_err());
226+
assert_error!(
227+
validate_commodity_cost_map(&cost_map, &region_ids, &[2020], &time_slice_info),
228+
"Missing cost for region GBR, year 2020, time slice winter.night"
229+
);
195230
}
196231
}

0 commit comments

Comments
 (0)