Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
395d465
Rough approach to patch models with diff files
tsmbland Dec 9, 2025
b9764c2
Add tests
tsmbland Dec 9, 2025
1504078
More robustness and error handling
tsmbland Dec 9, 2025
e87fd9d
Use indexset to prevent duplicate rows, fix issues with newline chara…
tsmbland Dec 11, 2025
9f8fe17
Robustness to white space, correct reading of headers
tsmbland Dec 11, 2025
a6109ad
Simplify the code - don't need to preserve whitespace
tsmbland Dec 11, 2025
951b6b1
Store base_filename within Patch
tsmbland Dec 12, 2025
3cffe4b
Builder method for making patches in tests
tsmbland Dec 12, 2025
859c288
Make the header optional
tsmbland Dec 12, 2025
2cb08e0
Store fields rather than comma-separated strings
tsmbland Dec 12, 2025
7427b56
Allow patching to permanent paths
tsmbland Dec 12, 2025
28b2613
Introduce ModelPatch
tsmbland Dec 15, 2025
bae9f85
Path handling fixes
tsmbland Dec 15, 2025
670c610
Move patch code to standalone module
tsmbland Dec 15, 2025
23b4cd5
Add toml tests and integration tests
tsmbland Dec 16, 2025
5117c45
Pass toml patch as a string
tsmbland Dec 16, 2025
15ed820
Add missing_commodity_patch example
tsmbland Dec 16, 2025
13374a0
Fix error in missing_commodity_patch
tsmbland Dec 16, 2025
47d4496
Remove diff files interface
tsmbland Dec 16, 2025
bdc34a6
Fix test_toml_patch_and_validate
tsmbland Dec 16, 2025
4be1f4f
Simplify error handling a bit
tsmbland Dec 16, 2025
d3165a1
Fix tests
tsmbland Dec 16, 2025
84ccf2e
Small fixes
tsmbland Dec 16, 2025
f04599f
Remove checks we don't need
tsmbland Dec 16, 2025
a9b88bb
Fix issues with tests
tsmbland Dec 18, 2025
687617e
Method renames
tsmbland Dec 18, 2025
5d3f7c9
Add macros
tsmbland Dec 18, 2025
93fbad5
Merge branch 'main' into patch_model_v2
tsmbland Dec 18, 2025
2b445b0
Small fixes from self-review
tsmbland Dec 18, 2025
6dc1e29
build_patched_simple_tempdir should panic upon failure
tsmbland Dec 18, 2025
d6bbf9d
No longer set broken model options in build_patched_simple_tempdir
tsmbland Dec 18, 2025
a87a26e
Remove import
tsmbland Dec 18, 2025
589fbcd
Add from_example method
tsmbland Dec 19, 2025
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
71 changes: 71 additions & 0 deletions src/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::asset::{Asset, AssetPool, AssetRef};
use crate::commodity::{
Commodity, CommodityID, CommodityLevyMap, CommodityType, DemandMap, PricingStrategy,
};
use crate::patch::{FilePatch, ModelPatch};
use crate::process::{
ActivityLimits, Process, ProcessActivityLimitsMap, ProcessFlow, ProcessFlowsMap,
ProcessInvestmentConstraintsMap, ProcessMap, ProcessParameter, ProcessParameterMap,
Expand All @@ -21,6 +22,7 @@ use crate::units::{
Activity, ActivityPerCapacity, Capacity, Dimensionless, Flow, MoneyPerActivity,
MoneyPerCapacity, MoneyPerCapacityPerYear, MoneyPerFlow, Year,
};
use anyhow::Result;
use indexmap::indexmap;
use indexmap::{IndexMap, IndexSet};
use itertools::Itertools;
Expand All @@ -40,6 +42,44 @@ macro_rules! assert_error {
}
pub(crate) use assert_error;

/// Build a patched copy of `examples/simple` to a temporary directory and return the `TempDir`.
///
/// If the patched model cannot be built, for whatever reason, this function will panic.
pub(crate) fn build_patched_simple_tempdir(file_patches: Vec<FilePatch>) -> tempfile::TempDir {
ModelPatch::from_example("simple")
.with_file_patches(file_patches)
.build_to_tempdir()
.unwrap()
}

/// Check whether the simple example passes or fails validation after applying file patches
macro_rules! patch_and_validate_simple {
($file_patches:expr) => {{
(|| -> Result<()> {
let tmp = crate::fixture::build_patched_simple_tempdir($file_patches);
crate::input::load_model(tmp.path())?;
Ok(())
})()
}};
}
pub(crate) use patch_and_validate_simple;

/// Check whether the simple example runs successfully after applying file patches
macro_rules! patch_and_run_simple {
($file_patches:expr) => {{
(|| -> Result<()> {
let tmp = crate::fixture::build_patched_simple_tempdir($file_patches);
let (model, assets) = crate::input::load_model(tmp.path())?;
let output_path = tmp.path().join("output");
std::fs::create_dir_all(&output_path)?;

crate::simulation::run(&model, assets, &output_path, false)?;
Ok(())
})()
}};
}
pub(crate) use patch_and_run_simple;

#[fixture]
pub fn region_id() -> RegionID {
"GBR".into()
Expand Down Expand Up @@ -320,3 +360,34 @@ pub fn appraisal_output(asset: Asset, time_slice: TimeSliceID) -> AppraisalOutpu
metric: 4.14,
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn patch_and_validate_simple_smoke() {
let patches = Vec::new();
assert!(patch_and_validate_simple!(patches).is_ok());
}

#[test]
fn patch_and_run_simple_smoke() {
let patches = Vec::new();
assert!(patch_and_run_simple!(patches).is_ok());
}

#[test]
fn test_patch_and_validate_simple_fail() {
let patch = FilePatch::new("commodities.csv")
.with_deletion("RSHEAT,Residential heating,svd,daynight");
assert!(patch_and_validate_simple!(vec![patch]).is_err());
}

#[test]
fn test_patch_and_run_simple_fail() {
let patch = FilePatch::new("commodities.csv")
.with_deletion("RSHEAT,Residential heating,svd,daynight");
assert!(patch_and_run_simple!(vec![patch]).is_err());
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod input;
pub mod log;
pub mod model;
pub mod output;
pub mod patch;
pub mod process;
pub mod region;
pub mod settings;
Expand Down
21 changes: 17 additions & 4 deletions src/model/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ pub fn broken_model_options_allowed() -> bool {
.expect("Broken options flag not set")
}

/// Set global flag signalling whether broken model options are allowed
///
/// Can only be called once; subsequent calls will panic (except in tests, where it can be called
/// multiple times so long as the value is the same).
fn set_broken_model_options_flag(allowed: bool) {
let result = BROKEN_OPTIONS_ALLOWED.set(allowed);
if result.is_err() {
if cfg!(test) {
// Sanity check
assert_eq!(allowed, broken_model_options_allowed());
} else {
panic!("Attempted to set BROKEN_OPTIONS_ALLOWED twice");
}
}
}

macro_rules! define_unit_param_default {
($name:ident, $type: ty, $value: expr) => {
fn $name() -> $type {
Expand Down Expand Up @@ -157,10 +173,7 @@ impl ModelParameters {
let file_path = model_dir.as_ref().join(MODEL_PARAMETERS_FILE_NAME);
let model_params: ModelParameters = read_toml(&file_path)?;

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

model_params
.validate()
Expand Down
Loading