1use super::super::*;
3use crate::commodity::{BalanceType, CommodityCost, CommodityCostMap, CommodityID};
4use crate::id::IDCollection;
5use crate::region::{parse_region_str, RegionID};
6use crate::time_slice::TimeSliceInfo;
7use crate::year::parse_year_str;
8use anyhow::{ensure, Context, Result};
9use serde::Deserialize;
10use std::collections::{HashMap, HashSet};
11use std::path::Path;
12
13const COMMODITY_COSTS_FILE_NAME: &str = "commodity_costs.csv";
14
15#[derive(PartialEq, Debug, Deserialize, Clone)]
17struct CommodityCostRaw {
18 commodity_id: String,
20 regions: String,
22 balance_type: BalanceType,
24 year: String,
26 time_slice: String,
28 value: f64,
30}
31
32pub fn read_commodity_costs(
46 model_dir: &Path,
47 commodity_ids: &HashSet<CommodityID>,
48 region_ids: &HashSet<RegionID>,
49 time_slice_info: &TimeSliceInfo,
50 milestone_years: &[u32],
51) -> Result<HashMap<CommodityID, CommodityCostMap>> {
52 let file_path = model_dir.join(COMMODITY_COSTS_FILE_NAME);
53 let commodity_costs_csv = read_csv::<CommodityCostRaw>(&file_path)?;
54 read_commodity_costs_iter(
55 commodity_costs_csv,
56 commodity_ids,
57 region_ids,
58 time_slice_info,
59 milestone_years,
60 )
61 .with_context(|| input_err_msg(&file_path))
62}
63
64fn read_commodity_costs_iter<I>(
65 iter: I,
66 commodity_ids: &HashSet<CommodityID>,
67 region_ids: &HashSet<RegionID>,
68 time_slice_info: &TimeSliceInfo,
69 milestone_years: &[u32],
70) -> Result<HashMap<CommodityID, CommodityCostMap>>
71where
72 I: Iterator<Item = CommodityCostRaw>,
73{
74 let mut map = HashMap::new();
75
76 let mut commodity_regions: HashMap<CommodityID, HashSet<RegionID>> = HashMap::new();
79
80 for cost in iter {
81 let commodity_id = commodity_ids.get_id_by_str(&cost.commodity_id)?;
82 let regions = parse_region_str(&cost.regions, region_ids)?;
83 let years = parse_year_str(&cost.year, milestone_years)?;
84 let ts_selection = time_slice_info.get_selection(&cost.time_slice)?;
85
86 let map = map
88 .entry(commodity_id.clone())
89 .or_insert_with(CommodityCostMap::new);
90
91 let cost = CommodityCost {
93 balance_type: cost.balance_type,
94 value: cost.value,
95 };
96
97 for region in regions.iter() {
99 commodity_regions
100 .entry(commodity_id.clone())
101 .or_default()
102 .insert(region.clone());
103 for year in years.iter() {
104 for (time_slice, _) in time_slice_info.iter_selection(&ts_selection) {
105 try_insert(
106 map,
107 (region.clone(), *year, time_slice.clone()),
108 cost.clone(),
109 )?;
110 }
111 }
112 }
113 }
114
115 for (commodity_id, regions) in commodity_regions.iter() {
117 let map = map.get(commodity_id).unwrap();
118 validate_commodity_cost_map(map, regions, milestone_years, time_slice_info)
119 .with_context(|| format!("Missing costs for commodity {}", commodity_id))?;
120 }
121 Ok(map)
122}
123
124fn validate_commodity_cost_map(
125 map: &CommodityCostMap,
126 regions: &HashSet<RegionID>,
127 milestone_years: &[u32],
128 time_slice_info: &TimeSliceInfo,
129) -> Result<()> {
130 for region_id in regions.iter() {
132 for year in milestone_years.iter() {
133 for time_slice in time_slice_info.iter_ids() {
134 ensure!(
135 map.contains_key(&(region_id.clone(), *year, time_slice.clone())),
136 "Missing cost for region {}, year {}, time slice {}",
137 region_id,
138 year,
139 time_slice
140 );
141 }
142 }
143 }
144 Ok(())
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::time_slice::TimeSliceID;
151
152 #[test]
153 fn test_validate_commodity_costs_map() {
154 let slice = TimeSliceID {
156 season: "winter".into(),
157 time_of_day: "day".into(),
158 };
159 let ts_info = TimeSliceInfo {
160 seasons: ["winter".into()].into(),
161 times_of_day: ["day".into()].into(),
162 fractions: [(slice.clone(), 1.0)].into(),
163 };
164
165 let regions = HashSet::from(["UK".into()]);
166 let milestone_years = [2020];
167 let cost = CommodityCost {
168 balance_type: BalanceType::Net,
169 value: 1.0,
170 };
171
172 let mut map = CommodityCostMap::new();
174 map.insert(("UK".into(), 2020, slice.clone()), cost.clone());
175 assert!(validate_commodity_cost_map(&map, ®ions, &milestone_years, &ts_info).is_ok());
176
177 let regions2 = HashSet::from(["UK".into(), "FR".into()]);
179 assert!(validate_commodity_cost_map(&map, ®ions2, &milestone_years, &ts_info).is_err());
180
181 assert!(validate_commodity_cost_map(&map, ®ions, &[2020, 2030], &ts_info).is_err());
183
184 let slice2 = TimeSliceID {
186 season: "winter".into(),
187 time_of_day: "night".into(),
188 };
189 let ts_info2 = TimeSliceInfo {
190 seasons: ["winter".into()].into(),
191 times_of_day: ["day".into(), "night".into()].into(),
192 fractions: [(slice.clone(), 0.5), (slice2.clone(), 0.5)].into(),
193 };
194 assert!(validate_commodity_cost_map(&map, ®ions, &milestone_years, &ts_info2).is_err());
195 }
196}