1use super::{input_err_msg, read_csv};
3use crate::commodity::CommodityMap;
4use crate::id::IDCollection;
5use crate::process::{
6 Process, ProcessActivityLimitsMap, ProcessFlowsMap, ProcessID, ProcessInvestmentConstraintsMap,
7 ProcessMap, ProcessParameterMap,
8};
9use crate::region::{RegionID, parse_region_str};
10use crate::time_slice::TimeSliceInfo;
11use crate::units::ActivityPerCapacity;
12use anyhow::{Context, Ok, Result, ensure};
13use indexmap::IndexSet;
14use log::warn;
15use serde::Deserialize;
16use std::path::Path;
17use std::rc::Rc;
18
19mod availability;
20use availability::read_process_availabilities;
21mod flow;
22use flow::read_process_flows;
23mod parameter;
24use crate::id::define_id_getter;
25use parameter::read_process_parameters;
26mod investment_constraints;
27use investment_constraints::read_process_investment_constraints;
28
29const PROCESSES_FILE_NAME: &str = "processes.csv";
30
31#[derive(PartialEq, Debug, Deserialize)]
32struct ProcessRaw {
33 id: ProcessID,
34 description: String,
35 regions: String,
36 primary_output: Option<String>,
37 start_year: Option<u32>,
38 end_year: Option<u32>,
39 capacity_to_activity: Option<ActivityPerCapacity>,
40}
41define_id_getter! {ProcessRaw, ProcessID}
42
43pub fn read_processes(
57 model_dir: &Path,
58 commodities: &CommodityMap,
59 region_ids: &IndexSet<RegionID>,
60 time_slice_info: &TimeSliceInfo,
61 milestone_years: &[u32],
62) -> Result<ProcessMap> {
63 let mut processes = read_processes_file(model_dir, milestone_years, region_ids, commodities)?;
64 let mut activity_limits = read_process_availabilities(model_dir, &processes, time_slice_info)?;
65 let mut flows = read_process_flows(model_dir, &mut processes, commodities, milestone_years)?;
66 let mut parameters = read_process_parameters(model_dir, &processes, milestone_years)?;
67 let mut investment_constraints =
68 read_process_investment_constraints(model_dir, &processes, milestone_years)?;
69
70 for (id, process) in &mut processes {
72 let process = Rc::get_mut(process).unwrap();
74
75 process.activity_limits = activity_limits.remove(id).unwrap();
77 process.flows = flows.remove(id).unwrap();
78 process.parameters = parameters.remove(id).unwrap();
79 process.investment_constraints = investment_constraints.remove(id).unwrap_or_default();
80 }
81
82 Ok(processes)
83}
84
85fn read_processes_file(
86 model_dir: &Path,
87 milestone_years: &[u32],
88 region_ids: &IndexSet<RegionID>,
89 commodities: &CommodityMap,
90) -> Result<ProcessMap> {
91 let file_path = model_dir.join(PROCESSES_FILE_NAME);
92 let processes_csv = read_csv(&file_path)?;
93 read_processes_file_from_iter(processes_csv, milestone_years, region_ids, commodities)
94 .with_context(|| input_err_msg(&file_path))
95}
96
97fn read_processes_file_from_iter<I>(
98 iter: I,
99 milestone_years: &[u32],
100 region_ids: &IndexSet<RegionID>,
101 commodities: &CommodityMap,
102) -> Result<ProcessMap>
103where
104 I: Iterator<Item = ProcessRaw>,
105{
106 let mut processes = ProcessMap::new();
107 for process_raw in iter {
108 let start_year = process_raw.start_year.unwrap_or_else(|| {
109 warn!(
110 "Using default start year {} for process {}.",
111 milestone_years[0], process_raw.id
112 );
113 milestone_years[0]
114 });
115 let end_year = process_raw.end_year.unwrap_or_else(|| {
116 warn!(
117 "Using default end year {} for process {}.",
118 milestone_years.last().unwrap(),
119 process_raw.id
120 );
121 *milestone_years.last().unwrap()
122 });
123
124 ensure!(
126 start_year <= end_year,
127 "Error in parameter for process {}: start_year > end_year",
128 process_raw.id
129 );
130 let years = start_year..=end_year;
131
132 let regions = parse_region_str(&process_raw.regions, region_ids)?;
134
135 let primary_output = process_raw
137 .primary_output
138 .map(|id| {
139 let id = commodities.get_id(id.trim())?;
140 Ok(id.clone())
141 })
142 .transpose()?;
143
144 let capacity_to_activity = process_raw
145 .capacity_to_activity
146 .unwrap_or(ActivityPerCapacity(1.0));
147
148 ensure!(
150 capacity_to_activity >= ActivityPerCapacity(0.0),
151 "Error in process {}: capacity_to_activity must be >= 0",
152 process_raw.id
153 );
154
155 let process = Process {
156 id: process_raw.id.clone(),
157 description: process_raw.description,
158 years,
159 activity_limits: ProcessActivityLimitsMap::new(),
160 flows: ProcessFlowsMap::new(),
161 parameters: ProcessParameterMap::new(),
162 regions,
163 primary_output,
164 capacity_to_activity,
165 investment_constraints: ProcessInvestmentConstraintsMap::new(),
166 };
167
168 ensure!(
169 processes.insert(process_raw.id, process.into()).is_none(),
170 "Duplicate process ID"
171 );
172 }
173
174 Ok(processes)
175}