Skip to content

Validate flows for non-primary outputs/inputs#962

Merged
dalonsoa merged 5 commits intomainfrom
927_flows
Oct 24, 2025
Merged

Validate flows for non-primary outputs/inputs#962
dalonsoa merged 5 commits intomainfrom
927_flows

Conversation

@dalonsoa
Copy link
Copy Markdown
Collaborator

Description

Adds a validation for the non-primary flows of a process, which must exist for all years and be of the same sign, i.e. always inputs or always outputs in all years. They can differ in different regions, though.

TODO

I've checked that this works as expected by playing around with the two_outputs example. It catches the situations described and report the right error. However, I am unable to write a test for it, fighting against borrowing and mutability rules of rust. Any advice on this front, @tsmbland , would be most welcomed.

So far, what I've tried is to copy one of the other tests on the topic and manually change the flow of one specific year for one specific region and commodity in the flow_map before passing it to the validation function, but that does not seem to be permitted...

Fixes #927
Fixes #929

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

Further checks

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

@dalonsoa dalonsoa requested review from Aurashk, alexdewar and tsmbland and removed request for tsmbland October 22, 2025 08:51
@codecov
Copy link
Copy Markdown

codecov bot commented Oct 22, 2025

Codecov Report

❌ Patch coverage is 78.78788% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.85%. Comparing base (07f1f53) to head (03f3cfd).
⚠️ Report is 18 commits behind head on main.

Files with missing lines Patch % Lines
src/input/process/flow.rs 78.78% 5 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #962      +/-   ##
==========================================
- Coverage   83.86%   83.85%   -0.02%     
==========================================
  Files          50       50              
  Lines        5541     5573      +32     
  Branches     5541     5573      +32     
==========================================
+ Hits         4647     4673      +26     
- Misses        658      662       +4     
- Partials      236      238       +2     

☔ View full report in Codecov by Sentry.
📢 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.

Copy link
Copy Markdown
Collaborator

@tsmbland tsmbland left a comment

Choose a reason for hiding this comment

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

Looks good and nicely kills two birds with one stone.

Worth noting that this will have to change slightly with #928. In this case we'll have inputs, outputs and zeros, so I don't think the Vec<bool> approach would work. But off the top of my head I can't think of an easy solution...

I wouldn't worry too much about tests for now. This sort of thing will be much easier if/when #655 is done

@dalonsoa
Copy link
Copy Markdown
Collaborator Author

We will adapt this when needed. I think it would not be too much trouble.

If tests are not something to worry about at the moment, then this is ready to go.

Copy link
Copy Markdown
Collaborator

@alexdewar alexdewar left a comment

Choose a reason for hiding this comment

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

Some minor suggestions, but totally up to you if you want to do them.

In terms of the bigger picture, this code has the same problem that the existing flows validation code has in that it assumes that users need to provide entries for every year in Process::years (see #934). (I'm not saying you should change this now, but I just wanted to make you aware of it!) We probably only want to require entries for the milestone years though (processes can have a start_year before the start of the simulation and in the longer run, they could be commissioned in any year: #859). The non-milestone years should be checked lazily (i.e. when we commission an asset). That said, it probably makes sense to check that all the process flows entries that are actually specified are of the correct type (input/output) in any case. Anyway, we can worry about this when we do #934.

ensure!(
(0.0..f64::INFINITY).contains(&cost.value()),
(cost.value() >= 0.0),
"Invalid value for flow cost ({cost}). Must be >=0."
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.

It was weird the way it was written before! I guess the previous check also verified that it was finite, but that's not a worry since we do that check when we deserialise now anyway


/// Checks that non-primary io are defined for all years (within a region) and that
/// they are only inputs or only outputs in all years.
fn validate_secondary_io_flows(
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.

I'd probably just call this validate_secondary_flows

// Get the flows for this process - there should be no error, as was checked already
let map = flows_map
.get(process_id)
.with_context(|| format!("Missing flows map for process {process_id}"))?;
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.

Given that this function and validate_flows_and_primary_output both iterate over the process maps, we could have that loop outside the functions instead and just pass in a Process and ProcessFlowsMap to each. Might be cleaner.

Not particularly important though.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I thought about it, but I did not want to intervene the existing functionality too much. Also, being used just during loading of data, didn't consider performance to be an issue.

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.

I was thinking more in terms of tidiness than performance, but I agree that it's ok as it is!


// Get the non-primary io flows for all years, if any, arranged by (commodity, region)
let iter = iproduct!(process.years.iter(), process.regions.iter());
let mut flows: HashMap<(CommodityID, RegionID), Vec<bool>> = HashMap::new();
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.

I think this code could be simpler if you just included the year in the key, i.e. made this a HashMap<(CommodityID, RegionID, u32), bool>.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We do not need the year for anything, so why storing that information at all? If I do that, it will also be harder to check if all the values for a specific commodity and region combination are of the same sign and complete.

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.

Actually, I'm not quite sure what I meant here... it is fine the way it is.

Copy link
Copy Markdown
Collaborator Author

@dalonsoa dalonsoa left a comment

Choose a reason for hiding this comment

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

The issue of the years needs to be address globaly in multiple places. This just follows the same format than the primary output validation for consistency. I feel it will be wrong to do something different here.


// Get the non-primary io flows for all years, if any, arranged by (commodity, region)
let iter = iproduct!(process.years.iter(), process.regions.iter());
let mut flows: HashMap<(CommodityID, RegionID), Vec<bool>> = HashMap::new();
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We do not need the year for anything, so why storing that information at all? If I do that, it will also be harder to check if all the values for a specific commodity and region combination are of the same sign and complete.

@alexdewar
Copy link
Copy Markdown
Collaborator

The issue of the years needs to be address globaly in multiple places. This just follows the same format than the primary output validation for consistency. I feel it will be wrong to do something different here.

Yep, it will need fixing globally. I wasn't suggesting you redo it now, just flagging that it will need revisiting.

@dalonsoa dalonsoa merged commit 4fcea11 into main Oct 24, 2025
8 checks passed
@dalonsoa dalonsoa deleted the 927_flows branch October 24, 2025 12:10
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.

Disallow omitting process flows for specific years Disallow having different process flow directions in different years

3 participants