-
Notifications
You must be signed in to change notification settings - Fork 2
Fix availabilities validation for non-milestone years #935
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,6 @@ | ||||||
| //! Code for reading process availabilities CSV file | ||||||
| use super::super::{format_items_with_cap, input_err_msg, read_csv, try_insert}; | ||||||
| use crate::process::{ProcessActivityLimitsMap, ProcessID, ProcessMap}; | ||||||
| use crate::process::{Process, ProcessActivityLimitsMap, ProcessID, ProcessMap}; | ||||||
| use crate::region::parse_region_str; | ||||||
| use crate::time_slice::TimeSliceInfo; | ||||||
| use crate::units::{Dimensionless, Year}; | ||||||
|
|
@@ -74,6 +74,7 @@ enum LimitType { | |||||
| /// * `model_dir` - Folder containing model configuration files | ||||||
| /// * `processes` - Map of processes | ||||||
| /// * `time_slice_info` - Information about seasons and times of day | ||||||
| /// * `base_year` - First milestone year of simulation | ||||||
| /// | ||||||
| /// # Returns | ||||||
| /// | ||||||
|
|
@@ -83,18 +84,37 @@ pub fn read_process_availabilities( | |||||
| model_dir: &Path, | ||||||
| processes: &ProcessMap, | ||||||
| time_slice_info: &TimeSliceInfo, | ||||||
| base_year: u32, | ||||||
| ) -> Result<HashMap<ProcessID, ProcessActivityLimitsMap>> { | ||||||
| let file_path = model_dir.join(PROCESS_AVAILABILITIES_FILE_NAME); | ||||||
| let process_availabilities_csv = read_csv(&file_path)?; | ||||||
| read_process_availabilities_from_iter(process_availabilities_csv, processes, time_slice_info) | ||||||
| .with_context(|| input_err_msg(&file_path)) | ||||||
| read_process_availabilities_from_iter( | ||||||
| process_availabilities_csv, | ||||||
| processes, | ||||||
| time_slice_info, | ||||||
| base_year, | ||||||
| ) | ||||||
| .with_context(|| input_err_msg(&file_path)) | ||||||
| } | ||||||
|
|
||||||
| /// Process raw process availabilities input data into [`ProcessActivityLimitsMap`]s | ||||||
| /// Process raw process availabilities input data into [`ProcessActivityLimitsMap`]s. | ||||||
| /// | ||||||
| /// # Arguments | ||||||
| /// | ||||||
| /// * `iter` - Iterator of raw process availability records | ||||||
| /// * `processes` - Map of processes | ||||||
| /// * `time_slice_info` - Information about seasons and times of day | ||||||
| /// * `base_year` - First milestone year of simulation | ||||||
| /// | ||||||
| /// # Returns | ||||||
| /// | ||||||
| /// A [`HashMap`] with process IDs as the keys and [`ProcessActivityLimitsMap`]s as the values or an | ||||||
| /// error. | ||||||
| fn read_process_availabilities_from_iter<I>( | ||||||
| iter: I, | ||||||
| processes: &ProcessMap, | ||||||
| time_slice_info: &TimeSliceInfo, | ||||||
| base_year: u32, | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. This function is missing details in the docstring, anyway. |
||||||
| ) -> Result<HashMap<ProcessID, ProcessActivityLimitsMap>> | ||||||
| where | ||||||
| I: Iterator<Item = ProcessAvailabilityRaw>, | ||||||
|
|
@@ -140,7 +160,7 @@ where | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| validate_activity_limits_maps(&map, processes, time_slice_info)?; | ||||||
| validate_activity_limits_maps(&map, processes, time_slice_info, base_year)?; | ||||||
|
|
||||||
| Ok(map) | ||||||
| } | ||||||
|
|
@@ -150,38 +170,82 @@ fn validate_activity_limits_maps( | |||||
| all_availabilities: &HashMap<ProcessID, ProcessActivityLimitsMap>, | ||||||
| processes: &ProcessMap, | ||||||
| time_slice_info: &TimeSliceInfo, | ||||||
| base_year: u32, | ||||||
| ) -> Result<()> { | ||||||
| for (process_id, process) in processes { | ||||||
| // A map of maps: the outer map is keyed by region and year; the inner one by time slice | ||||||
| let map_for_process = all_availabilities | ||||||
| .get(process_id) | ||||||
| .with_context(|| format!("Missing availabilities for process {process_id}"))?; | ||||||
|
|
||||||
| let mut missing_keys = Vec::new(); | ||||||
| for (region_id, year) in iproduct!(&process.regions, &process.years) { | ||||||
| if let Some(map_for_region_year) = map_for_process.get(&(region_id.clone(), *year)) { | ||||||
| // There are at least some entries for this region/year combo; check if there are | ||||||
| // any time slices not covered | ||||||
| missing_keys.extend( | ||||||
| time_slice_info | ||||||
| .iter_ids() | ||||||
| .filter(|ts| !map_for_region_year.contains_key(ts)) | ||||||
| .map(|ts| (region_id, *year, ts)), | ||||||
| ); | ||||||
| } else { | ||||||
| // No entries for this region/year combo: by definition no time slices are covered | ||||||
| missing_keys.extend(time_slice_info.iter_ids().map(|ts| (region_id, *year, ts))); | ||||||
| } | ||||||
| check_missing_milestone_years(process, map_for_process, base_year)?; | ||||||
| check_missing_time_slices(process, map_for_process, time_slice_info)?; | ||||||
| } | ||||||
|
|
||||||
| Ok(()) | ||||||
| } | ||||||
|
|
||||||
| /// Check every milestone year in which the process can be commissioned has availabilities. | ||||||
| /// | ||||||
| /// Entries for non-milestone years in which the process can be commissioned (which are only | ||||||
| /// required for pre-defined assets, if at all) are not required and will be checked lazily when | ||||||
| /// assets requiring them are constructed. | ||||||
| fn check_missing_milestone_years( | ||||||
| process: &Process, | ||||||
| map_for_process: &ProcessActivityLimitsMap, | ||||||
| base_year: u32, | ||||||
| ) -> Result<()> { | ||||||
| let process_milestone_years = process | ||||||
| .years | ||||||
| .iter() | ||||||
| .copied() | ||||||
| .filter(|&year| year >= base_year); | ||||||
|
||||||
| let mut missing = Vec::new(); | ||||||
| for (region_id, year) in iproduct!(&process.regions, process_milestone_years) { | ||||||
| if !map_for_process.contains_key(&(region_id.clone(), year)) { | ||||||
| missing.push((region_id, year)); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| ensure!( | ||||||
| missing_keys.is_empty(), | ||||||
| "Process {process_id} is missing availabilities for the following regions, years and \ | ||||||
| time slices: {}", | ||||||
| format_items_with_cap(&missing_keys) | ||||||
| ); | ||||||
| ensure!( | ||||||
| missing.is_empty(), | ||||||
| "Process {} is missing availabilities for the following regions and milestone years: {}", | ||||||
| &process.id, | ||||||
| format_items_with_cap(&missing) | ||||||
| ); | ||||||
|
|
||||||
| Ok(()) | ||||||
| } | ||||||
|
|
||||||
| /// Check that entries for all time slices are provided for any process/region/year combo for which | ||||||
| /// we have any entries at all | ||||||
| fn check_missing_time_slices( | ||||||
| process: &Process, | ||||||
| map_for_process: &ProcessActivityLimitsMap, | ||||||
| time_slice_info: &TimeSliceInfo, | ||||||
| ) -> Result<()> { | ||||||
| let mut missing = Vec::new(); | ||||||
| for (region_id, &year) in iproduct!(&process.regions, &process.years) { | ||||||
| if let Some(map_for_region_year) = map_for_process.get(&(region_id.clone(), year)) { | ||||||
| // There are at least some entries for this region/year combo; check if there are | ||||||
| // any time slices not covered | ||||||
| missing.extend( | ||||||
| time_slice_info | ||||||
| .iter_ids() | ||||||
| .filter(|ts| !map_for_region_year.contains_key(ts)) | ||||||
| .map(|ts| (region_id, year, ts)), | ||||||
|
||||||
| .map(|ts| (region_id, year, ts)), | |
| .map(|ts| (region_id, *year, ts)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add to the docstring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops! It's a pity there doesn't seem to be any Rust tooling to check these doc comments...