diff --git a/examples/simple/process_parameters.csv b/examples/simple/process_parameters.csv index b9cb5d37a..c72742599 100644 --- a/examples/simple/process_parameters.csv +++ b/examples/simple/process_parameters.csv @@ -1,7 +1,7 @@ -process_id,year,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate,capacity_to_activity -GASDRV,all,10.0,0.3,2.0,25,0.1,1.0 -GASPRC,all,7.0,0.21,0.5,25,0.1,1.0 -WNDFRM,all,1000.0,30.0,0.4,25,0.1,31.54 -GASCGT,all,700.0,21.0,0.55,30,0.1,31.54 -RGASBR,all,55.56,1.6668,0.16,15,0.1,1.0 -RELCHP,all,138.9,4.167,0.17,15,0.1,1.0 +process_id,regions,year,capital_cost,fixed_operating_cost,variable_operating_cost,lifetime,discount_rate,capacity_to_activity +GASDRV,GBR,all,10.0,0.3,2.0,25,0.1,1.0 +GASPRC,GBR,all,7.0,0.21,0.5,25,0.1,1.0 +WNDFRM,GBR,all,1000.0,30.0,0.4,25,0.1,31.54 +GASCGT,GBR,all,700.0,21.0,0.55,30,0.1,31.54 +RGASBR,GBR,all,55.56,1.6668,0.16,15,0.1,1.0 +RELCHP,GBR,all,138.9,4.167,0.17,15,0.1,1.0 diff --git a/src/asset.rs b/src/asset.rs index 28376017b..bff05328c 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -62,7 +62,7 @@ impl Asset { + self .process .parameters - .get(&self.commission_year) + .get(&(self.region_id.clone(), self.commission_year)) .unwrap() .lifetime } @@ -84,7 +84,7 @@ impl Asset { * self .process .parameters - .get(&self.commission_year) + .get(&(self.region_id.clone(), self.commission_year)) .unwrap() .capacity_to_activity } @@ -225,7 +225,7 @@ mod tests { let years = RangeInclusive::new(2010, 2020).collect_vec(); let process_parameter_map: ProcessParameterMap = years .iter() - .map(|&year| (year, process_param.clone())) + .map(|&year| (("GBR".into(), year), process_param.clone())) .collect(); let commodity = Rc::new(Commodity { id: "commodity1".into(), @@ -278,7 +278,7 @@ mod tests { let years = RangeInclusive::new(2010, 2020).collect_vec(); let process_parameter_map: ProcessParameterMap = years .iter() - .map(|&year| (year, process_param.clone())) + .map(|&year| (("GBR".into(), year), process_param.clone())) .collect(); let process = Rc::new(Process { id: "process1".into(), diff --git a/src/input.rs b/src/input.rs index 758373484..a642d216d 100644 --- a/src/input.rs +++ b/src/input.rs @@ -143,11 +143,11 @@ where /// If the key already exists, it returns an error with a message indicating the key's existence. pub fn try_insert(map: &mut HashMap, key: K, value: V) -> Result<()> where - K: Eq + Hash + Clone + std::fmt::Display, + K: Eq + Hash + Clone + std::fmt::Debug, { let existing = map.insert(key.clone(), value); match existing { - Some(_) => bail!("Key {} already exists in the map", key), + Some(_) => bail!("Key {:?} already exists in the map", key), None => Ok(()), } } diff --git a/src/input/process.rs b/src/input/process.rs index 301723468..a957f27cb 100644 --- a/src/input/process.rs +++ b/src/input/process.rs @@ -243,7 +243,7 @@ fn validate_svd_commodity( .get(&*flow.process_id) .unwrap() .keys() - .contains(&year) + .contains(&(region_id.clone(), year)) && params .availabilities .get(&*flow.process_id) @@ -319,7 +319,7 @@ mod tests { capacity_to_activity: 0.0, }; for year in [2010, 2020] { - parameter_map.insert(year, parameter.clone()); + parameter_map.insert(("GBR".into(), year), parameter.clone()); } (id.into(), parameter_map) }) diff --git a/src/input/process/parameter.rs b/src/input/process/parameter.rs index 2b760903c..a4ee3c0a3 100644 --- a/src/input/process/parameter.rs +++ b/src/input/process/parameter.rs @@ -2,6 +2,7 @@ use super::super::*; use crate::id::IDCollection; use crate::process::{Process, ProcessID, ProcessParameter, ProcessParameterMap}; +use crate::region::parse_region_str; use crate::year::parse_year_str; use ::log::warn; use anyhow::{ensure, Context, Result}; @@ -14,6 +15,7 @@ const PROCESS_PARAMETERS_FILE_NAME: &str = "process_parameters.csv"; #[derive(PartialEq, Debug, Deserialize)] struct ProcessParameterRaw { process_id: String, + regions: String, year: String, capital_cost: f64, fixed_operating_cost: f64, @@ -112,48 +114,71 @@ fn read_process_parameters_from_iter( where I: Iterator, { - let mut params: HashMap = HashMap::new(); + let mut map: HashMap = HashMap::new(); for param_raw in iter { + // Get process let id = process_ids.get_id_by_str(¶m_raw.process_id)?; - - let entry = params.entry(id.clone()).or_default(); let process = processes .get(&id) .with_context(|| format!("Process {id} not found"))?; + + // Get years let process_year_range = &process.years; let process_years: Vec = milestone_years .iter() .copied() .filter(|year| process_year_range.contains(year)) .collect(); - let parameter_years = parse_year_str(¶m_raw.year, &process_years).with_context(|| { format!("Invalid year for process {id}. Valid years are {process_years:?}") })?; + + // Get regions + let process_regions = process.regions.clone(); + let parameter_regions = parse_region_str(¶m_raw.regions, &process_regions) + .with_context(|| { + format!("Invalid region for process {id}. Valid regions are {process_regions:?}") + })?; + + // Insert parameter into the map let param = param_raw.into_parameter()?; + let entry = map.entry(id.clone()).or_default(); for year in parameter_years { - try_insert(entry, year, param.clone())?; + for region in parameter_regions.clone() { + try_insert(entry, (region, year), param.clone())?; + } } } - // Check parameters cover all years of the process - for (id, parameter) in params.iter() { - let year_range = &processes.get(id).unwrap().years; + // Check parameters cover all years and regions of the process + for (id, parameters) in map.iter() { + let process = processes.get(id).unwrap(); + let year_range = &process.years; let reference_years: HashSet = milestone_years .iter() .copied() .filter(|year| year_range.contains(year)) .collect(); - let parameter_years: HashSet = parameter.keys().copied().collect(); + let reference_regions = process.regions.clone(); + + let mut missing_keys = Vec::new(); + for year in &reference_years { + for region in &reference_regions { + let key = (region.clone(), *year); + if !parameters.contains_key(&key) { + missing_keys.push(key); + } + } + } ensure!( - parameter_years == reference_years, - "Error in parameters for process {}: years do not match the process years", - id + !missing_keys.is_empty(), + "Process {} is missing parameters for the following regions and years: {:?}", + id, + missing_keys ); } - - Ok(params) + Ok(map) } #[cfg(test)] @@ -174,6 +199,7 @@ mod tests { discount_rate, capacity_to_activity, year: "all".to_string(), + regions: "all".to_string(), } } diff --git a/src/process.rs b/src/process.rs index b066b5e77..ae1fb9a50 100644 --- a/src/process.rs +++ b/src/process.rs @@ -61,7 +61,7 @@ impl Process { pub type EnergyLimitsMap = HashMap>; /// A map of [`ProcessParameter`]s, keyed by year -pub type ProcessParameterMap = HashMap; +pub type ProcessParameterMap = HashMap<(RegionID, u32), ProcessParameter>; /// Represents a maximum annual commodity flow for a given process #[derive(PartialEq, Debug, Deserialize, Clone)] diff --git a/src/simulation/optimisation.rs b/src/simulation/optimisation.rs index 85a404e3b..bd7f48705 100644 --- a/src/simulation/optimisation.rs +++ b/src/simulation/optimisation.rs @@ -232,7 +232,7 @@ fn calculate_cost_coefficient( coeff += asset .process .parameters - .get(&asset.commission_year) + .get(&(asset.region_id.clone(), asset.commission_year)) .unwrap() .variable_operating_cost } @@ -290,8 +290,8 @@ mod tests { capacity_to_activity: 1.0, }; let mut process_parameter_map = ProcessParameterMap::new(); - process_parameter_map.insert(2010, process_param.clone()); - process_parameter_map.insert(2020, process_param.clone()); + process_parameter_map.insert(("GBR".into(), 2010), process_param.clone()); + process_parameter_map.insert(("GBR".into(), 2020), process_param.clone()); let commodity = Rc::new(Commodity { id: "commodity1".into(), description: "Some description".into(),