Skip to content

Enable total capacity constraints#1417

Open
dc2917 wants to merge 3 commits into
mainfrom
add-total-capacity-constraint
Open

Enable total capacity constraints#1417
dc2917 wants to merge 3 commits into
mainfrom
add-total-capacity-constraint

Conversation

@dc2917

@dc2917 dc2917 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Description

This PR enables the option to provide total capacity constraints.

  • The csv reader looks for an optional total_capacity_limit column
  • If found, the entries are parsed with a custom deserialiser, which handles empty total_capacity_limit entries and treats them as None.
  • These are then stored in a new total_capacity_limit field of the ProcessInvestmentConstraintRaw struct.
  • Simple input validation takes place to check that the values, if not None are non-negative and finite.
    • Tests have been added for this validation
  • ProcessInvestmentConstraint now has a total_capacity_limit field, for use in get_addition_limit (not yet implemented)
  • get_addition_limit now also takes an Asset's current capacity for use in the calculation
  • Total capacity constraints for the MUSE1 default example have been added to examples/muse1_default/process_investment_constraints.csv

Fixes #1414

Type of change

  • Bug fix (non-breaking change to fix an issue)
  • New feature (non-breaking change to add functionality)
  • Refactoring (non-breaking, non-functional change to improve maintainability)
  • Optimization (non-breaking change to speed up the code)
  • Breaking change (whatever its nature)
  • Documentation (improve or add documentation)

Key checklist

  • All tests pass: $ cargo test
  • The documentation builds and looks OK: $ cargo doc
  • Update release notes for the latest release if this PR adds a new feature or fixes a bug
    present in the previous release

Further checks

  • Code is commented, particularly in hard-to-understand areas
  • Tests added that prove fix is effective or that feature works

@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.83333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.84%. Comparing base (18b9bc5) to head (0eebbb2).
⚠️ Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
src/input/process/investment_constraints.rs 95.34% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1417      +/-   ##
==========================================
- Coverage   89.44%   86.84%   -2.60%     
==========================================
  Files          59       59              
  Lines        8364     8424      +60     
  Branches     8364     8424      +60     
==========================================
- Hits         7481     7316     -165     
- Misses        562      791     +229     
+ Partials      321      317       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dc2917 dc2917 requested a review from tsmbland July 14, 2026 14:28
Comment thread examples/muse1_default/process_investment_constraints.csv
Comment thread src/process.rs
/// limits and total capacity limits, this will have more complex logic which will depend on the
/// current total capacity.
pub fn get_addition_limit(&self) -> Option<Capacity> {
#[allow(unused_variables)]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tsmbland Just wanted to get your thoughts on whether this is the right approach here. I was planning on implement the maths as part of #1415.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normal way would be to prefix the argument name with _. That said, contrary to what I originally said, I don't think we should pass a capacity here (see discussion above)

@tsmbland tsmbland left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start.

I've realised that my original plan wasn't as solid as I thought, so the actual implementation will be quite different. The input/validation code is nearly there though so I'd focus on tidying that up in this PR. If you put a note in the schema about the new parameter, mentioning that it's currently unused, then we could merge this and worry about the implementation later

Comment on lines +27 to +31
#[serde(
default,
deserialize_with = "ProcessInvestmentConstraintRaw::parse_empty_limit"
)]
total_capacity_limit: Option<Capacity>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[serde(
default,
deserialize_with = "ProcessInvestmentConstraintRaw::parse_empty_limit"
)]
total_capacity_limit: Option<Capacity>,
total_capacity_limit: Option<Capacity>,

The Option<Capacity> does everything you need

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤯

);

// Validate total_capacity_limit: must be in range [0, inf)
if let Some(limit) = self.total_capacity_limit {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. Long run, I think we'd want to make addition_limit optional as well. We should allow either total_capacity_limit or addition_limit to be empty, but not both (if both were empty, the user should just omit the row for that process/region/year)

}

#[test]
fn validate_total_capacity_limit() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realise you've copied the format of the above test so this is absolutely fine. If you wanted to improve this, look into using case from rstest (used throughout the code).

I might two functions testing both addition and total capacity limits: validate_constraint_valid and validate_constraint_invalid.

@dc2917 dc2917 Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you wanted to improve this, look into using case from rstest (used throughout the code).

Cool, I'd assumed (hoped) there would be a way to do this haha

Comment thread src/asset.rs
.get(&(self.region_id.clone(), self.commission_year))
.and_then(|c| c.get_addition_limit().map(|l| l * commodity_portion))
.and_then(|c| {
c.get_addition_limit(self.capacity())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The capacity here isn't what we need in this context. This is the capacity of an individual candidate asset, which is the amount of capacity that we consider installing at a time (we may install multiple of these).

What we'd need to know is the total amount of capacity already installed, so we know how much wiggle room we have above this to install in the current year.

That said, this is more complicated than I originally thought. Because we decide how much of the existing capacity to keep (and how much to get rid of) as part of the investment process, which hasn't happened yet, we have a bit of a chicken and egg situation as we don't know how much capacity is installed (or, rather, how much of this will actually be kept).

Hope is not all lost, but this will require a different approach.

If you look at what happens currently with the addition limits, we store these in a map keyed by asset, and gradually trim from this as we install new capacity (i.e. coming from candidate assets). One way to implement the total capacity limit would be to have another map, keyed by process instead of asset, then gradually trim from this with all selected assets (both new assets and those chosen to be retained).

If this doesn't make any sense, or you don't want to worry about this, you can just focus this PR on the inputs/validation, but I'd remove the self.capacity() arg in any case as it's not right.

Comment thread src/process.rs
/// limits and total capacity limits, this will have more complex logic which will depend on the
/// current total capacity.
pub fn get_addition_limit(&self) -> Option<Capacity> {
#[allow(unused_variables)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normal way would be to prefix the argument name with _. That said, contrary to what I originally said, I don't think we should pass a capacity here (see discussion above)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add total capacity constraint

2 participants