Skip to content

Commit 0a0b7db

Browse files
authored
Merge pull request #1036 from EnergySystemsModellingLab/activity_limits_bug_fix
Fix bug with activity limits
2 parents db2c40a + 8f9ba79 commit 0a0b7db

File tree

13 files changed

+1300
-1382
lines changed

13 files changed

+1300
-1382
lines changed

src/graph/validate.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ fn prepare_commodities_graph_for_validation(
2323
base_graph: &CommoditiesGraph,
2424
processes: &ProcessMap,
2525
commodities: &CommodityMap,
26-
time_slice_info: &TimeSliceInfo,
2726
region_id: &RegionID,
2827
year: u32,
2928
time_slice_selection: &TimeSliceSelection,
@@ -42,7 +41,7 @@ fn prepare_commodities_graph_for_validation(
4241
let process = &processes[process_id];
4342

4443
// Check if the process has availability > 0 in any time slice in the selection
45-
can_be_active(process, &key, time_slice_selection, time_slice_info)
44+
can_be_active(process, &key, time_slice_selection)
4645
});
4746

4847
// Add demand edges
@@ -80,7 +79,6 @@ fn can_be_active(
8079
process: &Process,
8180
target: &(RegionID, u32),
8281
time_slice_selection: &TimeSliceSelection,
83-
time_slice_info: &TimeSliceInfo,
8482
) -> bool {
8583
let (target_region, target_year) = target;
8684

@@ -92,11 +90,7 @@ fn can_be_active(
9290
let Some(limits_map) = process.activity_limits.get(target) else {
9391
continue;
9492
};
95-
if limits_map
96-
.get_limit(time_slice_selection, time_slice_info)
97-
.end()
98-
> &Dimensionless(0.0)
99-
{
93+
if limits_map.get_limit(time_slice_selection).end() > &Dimensionless(0.0) {
10094
return true;
10195
}
10296
}
@@ -220,7 +214,6 @@ pub fn validate_commodity_graphs_for_model(
220214
base_graph,
221215
processes,
222216
commodities,
223-
time_slice_info,
224217
region_id,
225218
*year,
226219
&ts_selection,

src/process.rs

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use crate::commodity::{Commodity, CommodityID};
44
use crate::id::define_id_type;
55
use crate::region::RegionID;
6-
use crate::time_slice::{Season, TimeSliceID, TimeSliceInfo, TimeSliceLevel, TimeSliceSelection};
6+
use crate::time_slice::{Season, TimeSliceID, TimeSliceInfo, TimeSliceSelection};
77
use crate::units::{
88
ActivityPerCapacity, Dimensionless, FlowPerActivity, MoneyPerActivity, MoneyPerCapacity,
99
MoneyPerCapacityPerYear, MoneyPerFlow,
@@ -243,7 +243,7 @@ impl ActivityLimits {
243243
/// Add an annual limit
244244
fn add_annual_limit(&mut self, limit: RangeInclusive<Dimensionless>) -> Result<()> {
245245
// Get current limit for the year
246-
let current_limit = self.get_limit_for_year(&TimeSliceInfo::default());
246+
let current_limit = self.get_limit_for_year();
247247

248248
// Ensure that the new limit overlaps with the current limit
249249
// If not, it's impossible to satisfy both limits, so we must exit with an error
@@ -266,12 +266,11 @@ impl ActivityLimits {
266266
pub fn get_limit(
267267
&self,
268268
time_slice_selection: &TimeSliceSelection,
269-
time_slice_info: &TimeSliceInfo,
270269
) -> RangeInclusive<Dimensionless> {
271270
match time_slice_selection {
272271
TimeSliceSelection::Single(ts_id) => self.get_limit_for_time_slice(ts_id),
273272
TimeSliceSelection::Season(season) => self.get_limit_for_season(season),
274-
TimeSliceSelection::Annual => self.get_limit_for_year(time_slice_info),
273+
TimeSliceSelection::Annual => self.get_limit_for_year(),
275274
}
276275
}
277276

@@ -325,14 +324,16 @@ impl ActivityLimits {
325324
}
326325

327326
/// Get the limit for the entire year
328-
fn get_limit_for_year(&self, time_slice_info: &TimeSliceInfo) -> RangeInclusive<Dimensionless> {
327+
fn get_limit_for_year(&self) -> RangeInclusive<Dimensionless> {
329328
// Get the sum of limits for all seasons
330329
let mut total_lower = Dimensionless(0.0);
331330
let mut total_upper = Dimensionless(0.0);
332-
for ts_selection in time_slice_info.iter_selections_at_level(TimeSliceLevel::Season) {
333-
let TimeSliceSelection::Season(season) = ts_selection else {
334-
panic!("Expected season selection")
335-
};
331+
let seasons = self
332+
.time_slice_limits
333+
.keys()
334+
.map(|ts_id| ts_id.season.clone())
335+
.unique();
336+
for season in seasons {
336337
let season_limit = self.get_limit_for_season(&season);
337338
total_lower += *season_limit.start();
338339
total_upper += *season_limit.end();
@@ -990,7 +991,7 @@ mod tests {
990991
}
991992

992993
// Annual limit should be 0..1
993-
let annual_limit = limits.get_limit(&TimeSliceSelection::Annual, &time_slice_info2);
994+
let annual_limit = limits.get_limit(&TimeSliceSelection::Annual);
994995
assert_approx_eq!(Dimensionless, *annual_limit.start(), Dimensionless(0.0));
995996
assert_approx_eq!(Dimensionless, *annual_limit.end(), Dimensionless(1.0));
996997
}
@@ -1014,11 +1015,35 @@ mod tests {
10141015
}
10151016

10161017
// The seasonal limit should reflect the given bound
1017-
let season_limit = result.get_limit(
1018-
&TimeSliceSelection::Season("winter".into()),
1019-
&time_slice_info2,
1018+
let season_limit = result.get_limit(&TimeSliceSelection::Season("winter".into()));
1019+
assert_eq!(*season_limit.end(), Dimensionless(0.01));
1020+
}
1021+
1022+
#[rstest]
1023+
fn test_new_from_limits_with_annual_limit_applied(time_slice_info2: TimeSliceInfo) {
1024+
let mut limits = HashMap::new();
1025+
1026+
// Set an annual upper limit that is stricter than the sum of timeslices
1027+
limits.insert(
1028+
TimeSliceSelection::Annual,
1029+
Dimensionless(0.0)..=Dimensionless(0.01),
10201030
);
1031+
1032+
let result = ActivityLimits::new_from_limits(&limits, &time_slice_info2).unwrap();
1033+
1034+
// Each timeslice upper bound should be capped by the annual upper bound (0.01)
1035+
for (ts_id, _ts_len) in time_slice_info2.iter() {
1036+
let ts_limit = result.get_limit_for_time_slice(&ts_id);
1037+
assert_eq!(*ts_limit.end(), Dimensionless(0.01));
1038+
}
1039+
1040+
// The seasonal limit should be capped by the annual upper bound (0.01)
1041+
let season_limit = result.get_limit(&TimeSliceSelection::Season("winter".into()));
10211042
assert_eq!(*season_limit.end(), Dimensionless(0.01));
1043+
1044+
// The annual limit should reflect the given bound
1045+
let annual_limit = result.get_limit(&TimeSliceSelection::Annual);
1046+
assert_eq!(*annual_limit.end(), Dimensionless(0.01));
10221047
}
10231048

10241049
#[rstest]

tests/data/missing_commodity/assets.csv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ asset_id,process_id,region_id,agent_id,commission_year,decommission_year,capacit
77
5,RELCHP,GBR,A0_RES,2020,2035,399.98
88
6,RBIOBL,GBR,A0_RES,2030,,355.83840587648046
99
7,BIOPLL,GBR,A0_BPL,2030,,427.00608705177655
10-
8,BIOPRO,GBR,A0_BPD,2030,,108.68349219309701
10+
8,BIOPRO,GBR,A0_BPD,2030,,448.35639140436535
1111
9,RBIOBL,GBR,A0_RES,2040,,3655.8189696
1212
10,BIOPLL,GBR,A0_BPL,2040,,2167.9571005431867
13-
11,BIOPRO,GBR,A0_BPD,2040,,2616.027854781616
13+
11,BIOPRO,GBR,A0_BPD,2040,,2276.354955570347

0 commit comments

Comments
 (0)