1use super::super::*;
3use crate::agent::{AgentID, AgentMap, AgentObjective, DecisionRule};
4use anyhow::{ensure, Context, Result};
5use std::collections::HashMap;
6use std::path::Path;
7
8const AGENT_OBJECTIVES_FILE_NAME: &str = "agent_objectives.csv";
9
10pub fn read_agent_objectives(
20 model_dir: &Path,
21 agents: &AgentMap,
22 milestone_years: &[u32],
23) -> Result<HashMap<AgentID, Vec<AgentObjective>>> {
24 let file_path = model_dir.join(AGENT_OBJECTIVES_FILE_NAME);
25 let agent_objectives_csv = read_csv(&file_path)?;
26 read_agent_objectives_from_iter(agent_objectives_csv, agents, milestone_years)
27 .with_context(|| input_err_msg(&file_path))
28}
29
30fn read_agent_objectives_from_iter<I>(
31 iter: I,
32 agents: &AgentMap,
33 milestone_years: &[u32],
34) -> Result<HashMap<AgentID, Vec<AgentObjective>>>
35where
36 I: Iterator<Item = AgentObjective>,
37{
38 let mut objectives = HashMap::new();
39 for objective in iter {
40 let (id, agent) = agents
41 .get_key_value(&objective.agent_id)
42 .context("Invalid agent ID")?;
43
44 check_objective_parameter(&objective, &agent.decision_rule)?;
46
47 ensure!(
49 milestone_years.binary_search(&objective.year).is_ok(),
50 "Invalid milestone year {}",
51 objective.year
52 );
53
54 objectives
56 .entry(id.clone())
57 .or_insert_with(|| Vec::with_capacity(1))
58 .push(objective);
59 }
60
61 for (agent_id, agent) in agents {
63 let agent_objectives = objectives
64 .get(agent_id)
65 .with_context(|| format!("Agent {} has no objectives", agent_id))?;
66 for &year in milestone_years {
67 let objectives_for_year: Vec<_> = agent_objectives
68 .iter()
69 .filter(|obj| obj.year == year)
70 .collect();
71 check_agent_objectives(&objectives_for_year, &agent.decision_rule, agent_id, year)?;
72 }
73 }
74
75 Ok(objectives)
76}
77
78fn check_objective_parameter(
80 objective: &AgentObjective,
81 decision_rule: &DecisionRule,
82) -> Result<()> {
83 macro_rules! check_field_none {
85 ($field:ident) => {
86 ensure!(
87 objective.$field.is_none(),
88 "Field {} should be empty for this decision rule",
89 stringify!($field)
90 )
91 };
92 }
93
94 macro_rules! check_field_some {
96 ($field:ident) => {
97 ensure!(
98 objective.$field.is_some(),
99 "Required field {} is empty",
100 stringify!($field)
101 )
102 };
103 }
104
105 match decision_rule {
106 DecisionRule::Single => {
107 check_field_none!(decision_weight);
108 check_field_none!(decision_lexico_order);
109 }
110 DecisionRule::Weighted => {
111 check_field_some!(decision_weight);
112 check_field_none!(decision_lexico_order);
113 }
114 DecisionRule::Lexicographical { tolerance: _ } => {
115 check_field_none!(decision_weight);
116 check_field_some!(decision_lexico_order);
117 }
118 };
119
120 Ok(())
121}
122
123fn check_agent_objectives(
125 objectives: &[&AgentObjective],
126 decision_rule: &DecisionRule,
127 agent_id: &AgentID,
128 year: u32,
129) -> Result<()> {
130 let count = objectives.len();
131 match decision_rule {
132 DecisionRule::Single => {
133 ensure!(
134 count == 1,
135 "Agent {} has {} objectives for milestone year {} but should have exactly 1",
136 agent_id,
137 count,
138 year
139 );
140 }
141 DecisionRule::Weighted => {
142 ensure!(
143 count > 1,
144 "Agent {} has {} objectives for milestone year {} but should have more than 1",
145 agent_id,
146 count,
147 year
148 );
149 }
150 DecisionRule::Lexicographical { tolerance: _ } => {
151 let mut lexico_orders: Vec<u32> = objectives
152 .iter()
153 .filter_map(|obj| obj.decision_lexico_order)
154 .collect();
155 lexico_orders.sort_unstable();
156 ensure!(
157 lexico_orders == [1, 2],
158 "Agent {} must have objectives with decision_lexico_order values of 1 and 2 for milestone year {}, but found {:?}",
159 agent_id,
160 year,
161 lexico_orders
162 );
163 }
164 }
165
166 Ok(())
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::agent::{Agent, AgentCommodityPortionsMap, AgentCostLimitsMap, ObjectiveType};
173
174 #[test]
175 fn test_check_objective_parameter() {
176 macro_rules! objective {
177 ($decision_weight:expr, $decision_lexico_order:expr) => {
178 AgentObjective {
179 agent_id: "agent".into(),
180 year: 2020,
181 objective_type: ObjectiveType::LevelisedCostOfX,
182 decision_weight: $decision_weight,
183 decision_lexico_order: $decision_lexico_order,
184 }
185 };
186 }
187
188 let decision_rule = DecisionRule::Single;
190 let objective = objective!(None, None);
191 assert!(check_objective_parameter(&objective, &decision_rule).is_ok());
192 let objective = objective!(Some(1.0), None);
193 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
194 let objective = objective!(None, Some(1));
195 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
196
197 let decision_rule = DecisionRule::Weighted;
199 let objective = objective!(Some(1.0), None);
200 assert!(check_objective_parameter(&objective, &decision_rule).is_ok());
201 let objective = objective!(None, None);
202 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
203 let objective = objective!(None, Some(1));
204 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
205
206 let decision_rule = DecisionRule::Lexicographical { tolerance: 1.0 };
208 let objective = objective!(None, Some(1));
209 assert!(check_objective_parameter(&objective, &decision_rule).is_ok());
210 let objective = objective!(None, None);
211 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
212 let objective = objective!(Some(1.0), None);
213 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
214 }
215
216 #[test]
217 fn test_read_agent_objectives_from_iter() {
218 let agents = [(
219 "agent".into(),
220 Agent {
221 id: "agent".into(),
222 description: "".into(),
223 commodity_portions: AgentCommodityPortionsMap::new(),
224 search_space: Vec::new(),
225 decision_rule: DecisionRule::Single,
226 cost_limits: AgentCostLimitsMap::new(),
227 regions: HashSet::new(),
228 objectives: Vec::new(),
229 },
230 )]
231 .into_iter()
232 .collect();
233 let milestone_years = [2020];
234
235 let objective = AgentObjective {
237 agent_id: "agent".into(),
238 year: 2020,
239 objective_type: ObjectiveType::LevelisedCostOfX,
240 decision_weight: None,
241 decision_lexico_order: None,
242 };
243 let expected = [("agent".into(), vec![objective.clone()])]
244 .into_iter()
245 .collect();
246 let actual = read_agent_objectives_from_iter(
247 [objective.clone()].into_iter(),
248 &agents,
249 &milestone_years,
250 )
251 .unwrap();
252 assert_eq!(actual, expected);
253
254 assert!(
256 read_agent_objectives_from_iter([].into_iter(), &agents, &milestone_years).is_err()
257 );
258
259 assert!(
261 read_agent_objectives_from_iter([objective].into_iter(), &agents, &[2020, 2030])
262 .is_err()
263 );
264
265 let bad_objective = AgentObjective {
267 agent_id: "agent".into(),
268 year: 2020,
269 objective_type: ObjectiveType::LevelisedCostOfX,
270 decision_weight: Some(1.0), decision_lexico_order: None,
272 };
273 assert!(read_agent_objectives_from_iter(
274 [bad_objective].into_iter(),
275 &agents,
276 &milestone_years
277 )
278 .is_err());
279 }
280
281 #[test]
282 fn test_check_agent_objectives() {
283 let agent_id = AgentID::new("agent");
284 let objective1 = AgentObjective {
285 agent_id: agent_id.clone(),
286 year: 2020,
287 objective_type: ObjectiveType::LevelisedCostOfX,
288 decision_weight: None,
289 decision_lexico_order: Some(1),
290 };
291 let objective2 = AgentObjective {
292 agent_id: agent_id.clone(),
293 year: 2020,
294 objective_type: ObjectiveType::LevelisedCostOfX,
295 decision_weight: None,
296 decision_lexico_order: Some(2),
297 };
298
299 let decision_rule = DecisionRule::Single;
301 let objectives = [&objective1];
302
303 assert!(check_agent_objectives(&objectives, &decision_rule, &agent_id, 2020).is_ok());
304 let objectives = [&objective1, &objective2];
305 assert!(check_agent_objectives(&objectives, &decision_rule, &agent_id, 2020).is_err());
306
307 let decision_rule = DecisionRule::Weighted;
309 let objectives = [&objective1, &objective2];
310 assert!(check_agent_objectives(&objectives, &decision_rule, &agent_id, 2020).is_ok());
311 let objectives = [&objective1];
312 assert!(check_agent_objectives(&objectives, &decision_rule, &agent_id, 2020).is_err());
313
314 let decision_rule = DecisionRule::Lexicographical { tolerance: 1.0 };
316 let objectives = [&objective1, &objective2];
317 assert!(check_agent_objectives(&objectives, &decision_rule, &agent_id, 2020).is_ok());
318 let objectives = [&objective1, &objective1];
319 assert!(check_agent_objectives(&objectives, &decision_rule, &agent_id, 2020).is_err());
320 }
321}