diff --git a/src/input/process/flow.rs b/src/input/process/flow.rs index 25650a20b..b4e6e013f 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}; @@ -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." ); } @@ -132,6 +132,7 @@ where } validate_flows_and_update_primary_output(processes, &flows_map)?; + validate_secondary_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_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_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 { + 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::*;