diff --git a/src/agent.rs b/src/agent.rs index d7c0f2740..79b8d22e1 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -2,7 +2,7 @@ //! assets. use crate::commodity::CommodityID; use crate::id::define_id_type; -use crate::process::Process; +use crate::process::{FlowDirection, Process}; use crate::region::RegionID; use crate::units::{Dimensionless, Money}; use indexmap::{IndexMap, IndexSet}; @@ -63,7 +63,9 @@ impl Agent { let flows_key = (region_id.clone(), year); self.search_space[&(commodity_id.clone(), year)] .iter() - .filter(move |process| process.flows[&flows_key][commodity_id].is_output()) + .filter(move |process| { + process.flows[&flows_key][commodity_id].direction() == FlowDirection::Output + }) } } diff --git a/src/asset.rs b/src/asset.rs index 5e65da80f..e16516925 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -1,7 +1,7 @@ //! Assets are instances of a process which are owned and invested in by agents. use crate::agent::AgentID; use crate::commodity::CommodityID; -use crate::process::{Process, ProcessFlow, ProcessID, ProcessParameter}; +use crate::process::{FlowDirection, Process, ProcessFlow, ProcessID, ProcessParameter}; use crate::region::RegionID; use crate::simulation::CommodityPrices; use crate::time_slice::TimeSliceID; @@ -338,7 +338,9 @@ impl Asset { input_prices: &CommodityPrices, time_slice: &TimeSliceID, ) -> MoneyPerActivity { - -self.get_revenue_from_flows_with_filter(input_prices, time_slice, ProcessFlow::is_input) + -self.get_revenue_from_flows_with_filter(input_prices, time_slice, |x| { + x.direction() == FlowDirection::Input + }) } /// Get the total revenue from a subset of flows. diff --git a/src/graph.rs b/src/graph.rs index f3672e19b..9a6327789 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1,6 +1,6 @@ //! Module for creating and analysing commodity graphs use crate::commodity::{CommodityID, CommodityMap, CommodityType}; -use crate::process::{ProcessID, ProcessMap}; +use crate::process::{FlowDirection, ProcessID, ProcessMap}; use crate::region::RegionID; use crate::simulation::investment::InvestmentSet; use crate::time_slice::{TimeSliceInfo, TimeSliceLevel, TimeSliceSelection}; @@ -97,14 +97,14 @@ fn create_commodities_graph_for_region_year( // Get output nodes for the process let mut outputs: Vec<_> = flows .values() - .filter(|flow| flow.is_output()) + .filter(|flow| flow.direction() == FlowDirection::Output) .map(|flow| GraphNode::Commodity(flow.commodity.id.clone())) .collect(); // Get input nodes for the process let mut inputs: Vec<_> = flows .values() - .filter(|flow| flow.is_input()) + .filter(|flow| flow.direction() == FlowDirection::Input) .map(|flow| GraphNode::Commodity(flow.commodity.id.clone())) .collect(); diff --git a/src/input/process/flow.rs b/src/input/process/flow.rs index b2452799e..a80f2e6cb 100644 --- a/src/input/process/flow.rs +++ b/src/input/process/flow.rs @@ -1,7 +1,9 @@ //! Code for reading process flows file use super::super::{input_err_msg, read_csv}; use crate::commodity::{CommodityID, CommodityMap}; -use crate::process::{FlowType, ProcessFlow, ProcessFlowsMap, ProcessID, ProcessMap}; +use crate::process::{ + FlowDirection, FlowType, ProcessFlow, ProcessFlowsMap, ProcessID, ProcessMap, +}; use crate::region::{RegionID, parse_region_str}; use crate::units::{FlowPerActivity, MoneyPerFlow}; use crate::year::parse_year_str; @@ -30,9 +32,9 @@ struct ProcessFlowRaw { impl ProcessFlowRaw { fn validate(&self) -> Result<()> { - // Check that flow is not infinity, nan, 0 etc. + // Check that flow is not infinity or nan. ensure!( - self.coeff.is_normal(), + self.coeff.is_finite(), "Invalid value for coeff ({})", self.coeff ); @@ -189,9 +191,9 @@ fn validate_flows_and_update_primary_output( /// /// This is only possible if there is only one output flow for the process. fn infer_primary_output(map: &IndexMap) -> Result> { - let mut iter = map - .iter() - .filter_map(|(commodity_id, flow)| flow.is_output().then_some(commodity_id)); + let mut iter = map.iter().filter_map(|(commodity_id, flow)| { + (flow.direction() == FlowDirection::Output).then_some(commodity_id) + }); let Some(first_output) = iter.next() else { // If there are only input flows, then the primary output should be None @@ -217,12 +219,15 @@ fn check_flows_primary_output( })?; ensure!( - flow.is_output(), + flow.direction() == FlowDirection::Output, "Primary output commodity '{primary_output}' isn't an output flow", ); } else { ensure!( - flows_map.values().all(ProcessFlow::is_input), + flows_map + .values() + .all(|x| x.direction() == FlowDirection::Input + || x.direction() == FlowDirection::Zero), "First year is only inputs, but subsequent years have outputs, although no primary \ output is specified" ); @@ -245,13 +250,13 @@ fn validate_secondary_flows( // 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(); + let mut flows: HashMap<(CommodityID, RegionID), Vec<&ProcessFlow>> = 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())) + .then_some(((commodity_id.clone(), region_id.clone()), flow)) }); for (key, value) in flow { @@ -267,8 +272,14 @@ fn validate_secondary_flows( "Flow of commodity {commodity_id} in region {region_id} for process {process_id} \ does not cover all years" ); + let input_or_zero = value + .iter() + .all(|&x| [FlowDirection::Input, FlowDirection::Zero].contains(&x.direction())); + let output_or_zero = value + .iter() + .all(|&x| [FlowDirection::Output, FlowDirection::Zero].contains(&x.direction())); ensure!( - value.iter().all(|&x| x == value[0]), + input_or_zero || output_or_zero, "Flow of commodity {commodity_id} in region {region_id} for process {process_id} \ behaves as input or output in different years." ); diff --git a/src/process.rs b/src/process.rs index 95667a40f..97c183eeb 100644 --- a/src/process.rs +++ b/src/process.rs @@ -105,28 +105,28 @@ impl ProcessFlow { /// Get the levy/incentive for this process flow with the given parameters, if any fn get_levy(&self, region_id: &RegionID, year: u32, time_slice: &TimeSliceID) -> MoneyPerFlow { - if let Some(levy) = if self.is_input() { - self.commodity + match self.direction() { + FlowDirection::Input => *self + .commodity .levies_cons .get(&(region_id.clone(), year, time_slice.clone())) - } else { - self.commodity + .unwrap_or(&MoneyPerFlow(0.0)), + FlowDirection::Output => *self + .commodity .levies_prod .get(&(region_id.clone(), year, time_slice.clone())) - } { - return *levy; + .unwrap_or(&MoneyPerFlow(0.0)), + FlowDirection::Zero => MoneyPerFlow(0.0), } - MoneyPerFlow(0.0) } - /// Returns true if this flow is an input (i.e., coeff < 0) - pub fn is_input(&self) -> bool { - self.coeff < FlowPerActivity(0.0) - } - - /// Returns true if this flow is an output (i.e., coeff > 0) - pub fn is_output(&self) -> bool { - self.coeff > FlowPerActivity(0.0) + /// Direction of the flow + pub fn direction(&self) -> FlowDirection { + match self.coeff { + x if x < FlowPerActivity(0.0) => FlowDirection::Input, + x if x > FlowPerActivity(0.0) => FlowDirection::Output, + _ => FlowDirection::Zero, + } } } @@ -143,6 +143,17 @@ pub enum FlowType { Flexible, } +/// Direction of the flow (see [`ProcessFlow`]) +#[derive(PartialEq, Debug)] +pub enum FlowDirection { + /// The flow is an input (i.e., coeff < 0) + Input, + /// The flow is an output (i.e., coeff > 0) + Output, + /// The flow is zero, neither input nor output (i.e., coeff == 0) + Zero, +} + /// Additional parameters for a process #[derive(PartialEq, Clone, Debug)] pub struct ProcessParameter { @@ -640,11 +651,8 @@ mod tests { cost: MoneyPerFlow(0.0), }; - assert!(flow_in.is_input()); - assert!(!flow_in.is_output()); - assert!(flow_out.is_output()); - assert!(!flow_out.is_input()); - assert!(!flow_zero.is_input()); - assert!(!flow_zero.is_output()); + assert!(flow_in.direction() == FlowDirection::Input); + assert!(flow_out.direction() == FlowDirection::Output); + assert!(flow_zero.direction() == FlowDirection::Zero); } } diff --git a/src/simulation/prices.rs b/src/simulation/prices.rs index 6812e17f4..ebcb47bf9 100644 --- a/src/simulation/prices.rs +++ b/src/simulation/prices.rs @@ -2,6 +2,7 @@ use crate::asset::AssetRef; use crate::commodity::CommodityID; use crate::model::{Model, PricingStrategy}; +use crate::process::FlowDirection; use crate::region::RegionID; use crate::simulation::optimisation::Solution; use crate::time_slice::{TimeSliceID, TimeSliceInfo}; @@ -218,7 +219,10 @@ where let mut highest_duals = HashMap::new(); for (asset, time_slice, dual) in activity_duals { // Iterate over all output flows - for flow in asset.iter_flows().filter(|flow| flow.is_output()) { + for flow in asset + .iter_flows() + .filter(|flow| flow.direction() == FlowDirection::Output) + { // Update the highest dual for this commodity/time slice highest_duals .entry((