-
Notifications
You must be signed in to change notification settings - Fork 3
Enable total capacity constraints #1417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
334b8d2
a239960
0eebbb2
b2e0ab2
43a8753
f577577
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| process_id,regions,commission_years,addition_limit | ||
| gassupply1,R1,all,10 | ||
| gasCCGT,R1,all,10 | ||
| windturbine,R1,all,10 | ||
| gasboiler,R1,all,10 | ||
| heatpump,R1,all,10 | ||
| process_id,regions,commission_years,addition_limit,total_capacity_limit | ||
| gassupply1,R1,all,10,100 | ||
| gasCCGT,R1,all,10,100 | ||
| windturbine,R1,all,10,100 | ||
| gasboiler,R1,all,10,100 | ||
| heatpump,R1,all,10,100 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ use crate::process::{ | |
| ProcessID, ProcessInvestmentConstraint, ProcessInvestmentConstraintsMap, ProcessMap, | ||
| }; | ||
| use crate::region::parse_region_str; | ||
| use crate::units::{CapacityPerYear, Year}; | ||
| use crate::units::{Capacity, CapacityPerYear, Year}; | ||
| use anyhow::{Context, Result, ensure}; | ||
| use itertools::iproduct; | ||
| use serde::Deserialize; | ||
|
|
@@ -24,18 +24,27 @@ struct ProcessInvestmentConstraintRaw { | |
| regions: String, | ||
| commission_years: String, | ||
| addition_limit: CapacityPerYear, | ||
| total_capacity_limit: Option<Capacity>, | ||
| } | ||
|
|
||
| impl ProcessInvestmentConstraintRaw { | ||
| /// Validate the constraint record for logical consistency and required fields | ||
| fn validate(&self) -> Result<()> { | ||
| // Validate that value is finite | ||
| // Validate that addition_limit is finite | ||
| ensure!( | ||
| self.addition_limit.is_finite() && self.addition_limit >= CapacityPerYear(0.0), | ||
| "Invalid value for addition constraint: '{}'; must be non-negative and finite.", | ||
| self.addition_limit | ||
| ); | ||
|
|
||
| // Validate total_capacity_limit: must be in range [0, inf) | ||
| if let Some(limit) = self.total_capacity_limit { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks good. Long run, I think we'd want to make
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue created: #1418 |
||
| ensure!( | ||
| limit.is_finite() && limit >= Capacity(0.0), | ||
| "Invalid value for total capacity constraint: '{limit}'; must be non-negative and finite.", | ||
| ); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
|
|
@@ -155,12 +164,16 @@ mod tests { | |
| use crate::units::Capacity; | ||
| use rstest::rstest; | ||
|
|
||
| fn validate_raw_constraint(addition_limit: CapacityPerYear) -> Result<()> { | ||
| fn validate_raw_constraint( | ||
| addition_limit: CapacityPerYear, | ||
| total_capacity_limit: Option<Capacity>, | ||
| ) -> Result<()> { | ||
| let constraint = ProcessInvestmentConstraintRaw { | ||
| process_id: "test_process".into(), | ||
| regions: "ALL".into(), | ||
| commission_years: "2030".into(), | ||
| addition_limit, | ||
| total_capacity_limit, | ||
| }; | ||
| constraint.validate() | ||
| } | ||
|
|
@@ -175,6 +188,7 @@ mod tests { | |
| regions: "GBR".into(), | ||
| commission_years: "ALL".into(), // Should apply to milestone years [2012, 2016] | ||
| addition_limit: CapacityPerYear(100.0), | ||
| total_capacity_limit: Some(Capacity(100.0)), | ||
| }]; | ||
|
|
||
| let result = read_process_investment_constraints_from_iter( | ||
|
|
@@ -221,18 +235,21 @@ mod tests { | |
| regions: "GBR".into(), | ||
| commission_years: "2010".into(), | ||
| addition_limit: CapacityPerYear(100.0), | ||
| total_capacity_limit: Some(Capacity(100.0)), | ||
| }, | ||
| ProcessInvestmentConstraintRaw { | ||
| process_id: "process1".into(), | ||
| regions: "ALL".into(), | ||
| commission_years: "2015".into(), | ||
| addition_limit: CapacityPerYear(200.0), | ||
| total_capacity_limit: Some(Capacity(100.0)), | ||
| }, | ||
| ProcessInvestmentConstraintRaw { | ||
| process_id: "process1".into(), | ||
| regions: "USA".into(), | ||
| commission_years: "2020".into(), | ||
| addition_limit: CapacityPerYear(50.0), | ||
| total_capacity_limit: Some(Capacity(100.0)), | ||
| }, | ||
| ]; | ||
|
|
||
|
|
@@ -292,6 +309,7 @@ mod tests { | |
| regions: "ALL".into(), | ||
| commission_years: "ALL".into(), | ||
| addition_limit: CapacityPerYear(75.0), | ||
| total_capacity_limit: Some(Capacity(100.0)), | ||
| }]; | ||
|
|
||
| // Read constraints into the map | ||
|
|
@@ -340,6 +358,7 @@ mod tests { | |
| regions: "GBR".into(), | ||
| commission_years: "2025".into(), // Outside milestone years (2010-2020) | ||
| addition_limit: CapacityPerYear(100.0), | ||
| total_capacity_limit: Some(Capacity(100.0)), | ||
| }]; | ||
|
|
||
| // Should fail with milestone year validation error | ||
|
|
@@ -354,38 +373,72 @@ mod tests { | |
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn validate_addition_with_finite_value() { | ||
| // Valid: addition constraint with positive value | ||
| let valid = validate_raw_constraint(CapacityPerYear(10.0)); | ||
| valid.unwrap(); | ||
|
|
||
| // Valid: addition constraint with zero value | ||
| let valid = validate_raw_constraint(CapacityPerYear(0.0)); | ||
| #[rstest] | ||
| #[case(CapacityPerYear(10.0), None)] | ||
| #[case(CapacityPerYear(0.0), None)] | ||
| #[case(CapacityPerYear(10.0), Some(Capacity(100.0)))] | ||
| #[case(CapacityPerYear(10.0), Some(Capacity(0.0)))] | ||
| fn validate_constraints_valid( | ||
| #[case] addition_limit: CapacityPerYear, | ||
| #[case] total_capacity_limit: Option<Capacity>, | ||
| ) { | ||
| // Valid: capacity constraints with values >= 0, and total_capacity_limit as None | ||
| let valid = validate_raw_constraint(addition_limit, total_capacity_limit); | ||
| valid.unwrap(); | ||
| } | ||
|
|
||
| // Not valid: addition constraint with negative value | ||
| let invalid = validate_raw_constraint(CapacityPerYear(-10.0)); | ||
| assert_error!( | ||
| invalid, | ||
| "Invalid value for addition constraint: '-10'; must be non-negative and finite." | ||
| ); | ||
| #[rstest] | ||
| #[case(CapacityPerYear(-10.0), None, "Invalid value for addition constraint: '-10'; must be non-negative and finite.")] | ||
| #[case(CapacityPerYear(10.0), Some(Capacity(-100.0)), "Invalid value for total capacity constraint: '-100'; must be non-negative and finite.")] | ||
| fn validate_constraints_rejects_negative( | ||
| #[case] addition_limit: CapacityPerYear, | ||
| #[case] total_capacity_limit: Option<Capacity>, | ||
| #[case] error_msg: &str, | ||
| ) { | ||
| // Not valid: capacity constraints with negative value | ||
| let invalid = validate_raw_constraint(addition_limit, total_capacity_limit); | ||
| assert_error!(invalid, error_msg); | ||
| } | ||
|
|
||
| #[test] | ||
| fn validate_addition_rejects_infinite() { | ||
| // Invalid: infinite value | ||
| let invalid = validate_raw_constraint(CapacityPerYear(f64::INFINITY)); | ||
| assert_error!( | ||
| invalid, | ||
| "Invalid value for addition constraint: 'inf'; must be non-negative and finite." | ||
| ); | ||
| #[rstest] | ||
| #[case( | ||
| CapacityPerYear(f64::INFINITY), | ||
| None, | ||
| "Invalid value for addition constraint: 'inf'; must be non-negative and finite." | ||
| )] | ||
| #[case( | ||
| CapacityPerYear(10.0), | ||
| Some(Capacity(f64::INFINITY)), | ||
| "Invalid value for total capacity constraint: 'inf'; must be non-negative and finite." | ||
| )] | ||
| fn validate_constraints_rejects_infinite( | ||
| #[case] addition_limit: CapacityPerYear, | ||
| #[case] total_capacity_limit: Option<Capacity>, | ||
| #[case] error_msg: &str, | ||
| ) { | ||
| // Not valid: capacity constraints with infinite value | ||
| let invalid = validate_raw_constraint(addition_limit, total_capacity_limit); | ||
| assert_error!(invalid, error_msg); | ||
| } | ||
|
|
||
| // Invalid: NaN value | ||
| let invalid = validate_raw_constraint(CapacityPerYear(f64::NAN)); | ||
| assert_error!( | ||
| invalid, | ||
| "Invalid value for addition constraint: 'NaN'; must be non-negative and finite." | ||
| ); | ||
| #[rstest] | ||
| #[case( | ||
| CapacityPerYear(f64::NAN), | ||
| None, | ||
| "Invalid value for addition constraint: 'NaN'; must be non-negative and finite." | ||
| )] | ||
| #[case( | ||
| CapacityPerYear(10.0), | ||
| Some(Capacity(f64::NAN)), | ||
| "Invalid value for total capacity constraint: 'NaN'; must be non-negative and finite." | ||
| )] | ||
| fn validate_constraints_rejects_nan( | ||
| #[case] addition_limit: CapacityPerYear, | ||
| #[case] total_capacity_limit: Option<Capacity>, | ||
| #[case] error_msg: &str, | ||
| ) { | ||
| // Not valid: capacity constraints with NaN value | ||
| let invalid = validate_raw_constraint(addition_limit, total_capacity_limit); | ||
| assert_error!(invalid, error_msg); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.