From 27702fe5d67fd8e123c162edfa4041ae7c60d9fa Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Tue, 21 Oct 2025 10:56:13 +0100 Subject: [PATCH 1/4] :recycle: Change comparison in costs --- src/input/process/flow.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input/process/flow.rs b/src/input/process/flow.rs index 25650a20b..2945ec6ff 100644 --- a/src/input/process/flow.rs +++ b/src/input/process/flow.rs @@ -46,7 +46,7 @@ impl ProcessFlowRaw { // Check that flow cost is non-negative if let Some(cost) = self.cost { ensure!( - (0.0..f64::INFINITY).contains(&cost.value()), + (cost.value() >= 0.0), "Invalid value for flow cost ({cost}). Must be >=0." ); } From 778d62695df24ebd3dd02ce2549a0ba47c77b241 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 22 Oct 2025 09:28:48 +0100 Subject: [PATCH 2/4] :sparkles: Add validation of secondary flows --- src/input/process/flow.rs | 50 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/input/process/flow.rs b/src/input/process/flow.rs index 2945ec6ff..2f9e9175d 100644 --- a/src/input/process/flow.rs +++ b/src/input/process/flow.rs @@ -2,7 +2,7 @@ use super::super::{input_err_msg, read_csv}; use crate::commodity::{CommodityID, CommodityMap}; use crate::process::{FlowType, ProcessFlow, ProcessFlowsMap, ProcessID, ProcessMap}; -use crate::region::parse_region_str; +use crate::region::{RegionID, parse_region_str}; use crate::units::{FlowPerActivity, MoneyPerFlow}; use crate::year::parse_year_str; use anyhow::{Context, Result, ensure}; @@ -132,6 +132,7 @@ where } validate_flows_and_update_primary_output(processes, &flows_map)?; + validate_secondary_io_flows(processes, &flows_map)?; Ok(flows_map) } @@ -229,6 +230,53 @@ fn check_flows_primary_output( Ok(()) } +/// 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( + processes: &mut ProcessMap, + flows_map: &HashMap, +) -> Result<()> { + for (process_id, process) in processes.iter() { + // 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}"))?; + + // 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> = HashMap::new(); + for (&year, region_id) in iter { + let flow = map[&(region_id.clone(), year)] + .iter() + .filter_map(|(commodity_id, flow)| { + (Some(commodity_id) != process.primary_output.as_ref()) + .then_some(((commodity_id.clone(), region_id.clone()), flow.is_input())) + }); + + for (key, value) in flow { + flows.entry(key).or_insert_with(Vec::new).push(value); + } + } + + // Finally we check that the flows for a given commodity and region are defined for all + // years and that they are all inputs or all outputs + for ((commodity_id, region_id), value) in flows.iter() { + ensure!( + value.len() == process.years.len(), + "Flow of commodity {commodity_id} in region {region_id} for process {process_id} \ + does not cover all years" + ); + ensure!( + value.iter().all(|&x| x == value[0]), + "Flow of commodity {commodity_id} in region {region_id} for process {process_id} \ + behaves as input or output in different years." + ); + } + } + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; From 36de83879badbe8229b96b6bb0ce5a703ade4763 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 22 Oct 2025 10:02:07 +0100 Subject: [PATCH 3/4] Fix clippy complains --- src/input/process/flow.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/input/process/flow.rs b/src/input/process/flow.rs index 2f9e9175d..1e182b1df 100644 --- a/src/input/process/flow.rs +++ b/src/input/process/flow.rs @@ -254,13 +254,13 @@ fn validate_secondary_io_flows( }); for (key, value) in flow { - flows.entry(key).or_insert_with(Vec::new).push(value); + flows.entry(key).or_default().push(value); } } // Finally we check that the flows for a given commodity and region are defined for all // years and that they are all inputs or all outputs - for ((commodity_id, region_id), value) in flows.iter() { + for ((commodity_id, region_id), value) in &flows { ensure!( value.len() == process.years.len(), "Flow of commodity {commodity_id} in region {region_id} for process {process_id} \ From 03f3cfd78bbdcddd4a6f66e8f37aae6ff4c59cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Alonso=20=C3=81lvarez?= <6095790+dalonsoa@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:37:27 +0100 Subject: [PATCH 4/4] Rename validate_secondary_io_flows to validate_secondary_flows --- src/input/process/flow.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/input/process/flow.rs b/src/input/process/flow.rs index 1e182b1df..b4e6e013f 100644 --- a/src/input/process/flow.rs +++ b/src/input/process/flow.rs @@ -132,7 +132,7 @@ where } validate_flows_and_update_primary_output(processes, &flows_map)?; - validate_secondary_io_flows(processes, &flows_map)?; + validate_secondary_flows(processes, &flows_map)?; Ok(flows_map) } @@ -232,7 +232,7 @@ fn check_flows_primary_output( /// 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( +fn validate_secondary_flows( processes: &mut ProcessMap, flows_map: &HashMap, ) -> Result<()> {