Skip to content

Commit 78916ad

Browse files
authored
Merge pull request #1031 from EnergySystemsModellingLab/patch_model_v2
Functionality to patch models with changes
2 parents b55c6c1 + 589fbcd commit 78916ad

File tree

4 files changed

+484
-4
lines changed

4 files changed

+484
-4
lines changed

src/fixture.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::asset::{Asset, AssetPool, AssetRef};
88
use crate::commodity::{
99
Commodity, CommodityID, CommodityLevyMap, CommodityType, DemandMap, PricingStrategy,
1010
};
11+
use crate::patch::{FilePatch, ModelPatch};
1112
use crate::process::{
1213
ActivityLimits, Process, ProcessActivityLimitsMap, ProcessFlow, ProcessFlowsMap,
1314
ProcessInvestmentConstraintsMap, ProcessMap, ProcessParameter, ProcessParameterMap,
@@ -21,6 +22,7 @@ use crate::units::{
2122
Activity, ActivityPerCapacity, Capacity, Dimensionless, Flow, MoneyPerActivity,
2223
MoneyPerCapacity, MoneyPerCapacityPerYear, MoneyPerFlow, Year,
2324
};
25+
use anyhow::Result;
2426
use indexmap::indexmap;
2527
use indexmap::{IndexMap, IndexSet};
2628
use itertools::Itertools;
@@ -40,6 +42,44 @@ macro_rules! assert_error {
4042
}
4143
pub(crate) use assert_error;
4244

45+
/// Build a patched copy of `examples/simple` to a temporary directory and return the `TempDir`.
46+
///
47+
/// If the patched model cannot be built, for whatever reason, this function will panic.
48+
pub(crate) fn build_patched_simple_tempdir(file_patches: Vec<FilePatch>) -> tempfile::TempDir {
49+
ModelPatch::from_example("simple")
50+
.with_file_patches(file_patches)
51+
.build_to_tempdir()
52+
.unwrap()
53+
}
54+
55+
/// Check whether the simple example passes or fails validation after applying file patches
56+
macro_rules! patch_and_validate_simple {
57+
($file_patches:expr) => {{
58+
(|| -> Result<()> {
59+
let tmp = crate::fixture::build_patched_simple_tempdir($file_patches);
60+
crate::input::load_model(tmp.path())?;
61+
Ok(())
62+
})()
63+
}};
64+
}
65+
pub(crate) use patch_and_validate_simple;
66+
67+
/// Check whether the simple example runs successfully after applying file patches
68+
macro_rules! patch_and_run_simple {
69+
($file_patches:expr) => {{
70+
(|| -> Result<()> {
71+
let tmp = crate::fixture::build_patched_simple_tempdir($file_patches);
72+
let (model, assets) = crate::input::load_model(tmp.path())?;
73+
let output_path = tmp.path().join("output");
74+
std::fs::create_dir_all(&output_path)?;
75+
76+
crate::simulation::run(&model, assets, &output_path, false)?;
77+
Ok(())
78+
})()
79+
}};
80+
}
81+
pub(crate) use patch_and_run_simple;
82+
4383
#[fixture]
4484
pub fn region_id() -> RegionID {
4585
"GBR".into()
@@ -320,3 +360,34 @@ pub fn appraisal_output(asset: Asset, time_slice: TimeSliceID) -> AppraisalOutpu
320360
metric: 4.14,
321361
}
322362
}
363+
364+
#[cfg(test)]
365+
mod tests {
366+
use super::*;
367+
368+
#[test]
369+
fn patch_and_validate_simple_smoke() {
370+
let patches = Vec::new();
371+
assert!(patch_and_validate_simple!(patches).is_ok());
372+
}
373+
374+
#[test]
375+
fn patch_and_run_simple_smoke() {
376+
let patches = Vec::new();
377+
assert!(patch_and_run_simple!(patches).is_ok());
378+
}
379+
380+
#[test]
381+
fn test_patch_and_validate_simple_fail() {
382+
let patch = FilePatch::new("commodities.csv")
383+
.with_deletion("RSHEAT,Residential heating,svd,daynight");
384+
assert!(patch_and_validate_simple!(vec![patch]).is_err());
385+
}
386+
387+
#[test]
388+
fn test_patch_and_run_simple_fail() {
389+
let patch = FilePatch::new("commodities.csv")
390+
.with_deletion("RSHEAT,Residential heating,svd,daynight");
391+
assert!(patch_and_run_simple!(vec![patch]).is_err());
392+
}
393+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub mod input;
1818
pub mod log;
1919
pub mod model;
2020
pub mod output;
21+
pub mod patch;
2122
pub mod process;
2223
pub mod region;
2324
pub mod settings;

src/model/parameters.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ pub fn broken_model_options_allowed() -> bool {
2525
.expect("Broken options flag not set")
2626
}
2727

28+
/// Set global flag signalling whether broken model options are allowed
29+
///
30+
/// Can only be called once; subsequent calls will panic (except in tests, where it can be called
31+
/// multiple times so long as the value is the same).
32+
fn set_broken_model_options_flag(allowed: bool) {
33+
let result = BROKEN_OPTIONS_ALLOWED.set(allowed);
34+
if result.is_err() {
35+
if cfg!(test) {
36+
// Sanity check
37+
assert_eq!(allowed, broken_model_options_allowed());
38+
} else {
39+
panic!("Attempted to set BROKEN_OPTIONS_ALLOWED twice");
40+
}
41+
}
42+
}
43+
2844
macro_rules! define_unit_param_default {
2945
($name:ident, $type: ty, $value: expr) => {
3046
fn $name() -> $type {
@@ -157,10 +173,7 @@ impl ModelParameters {
157173
let file_path = model_dir.as_ref().join(MODEL_PARAMETERS_FILE_NAME);
158174
let model_params: ModelParameters = read_toml(&file_path)?;
159175

160-
// Set flag signalling whether broken model options are allowed or not
161-
BROKEN_OPTIONS_ALLOWED
162-
.set(model_params.allow_broken_options)
163-
.unwrap(); // Will only fail if there is a race condition, which shouldn't happen
176+
set_broken_model_options_flag(model_params.allow_broken_options);
164177

165178
model_params
166179
.validate()

0 commit comments

Comments
 (0)