1use super::super::{input_err_msg, read_csv, try_insert};
3use crate::agent::{AgentID, AgentMap, AgentObjectiveMap, DecisionRule, ObjectiveType};
4use crate::id::GetIDValue;
5use crate::input::parse_year_str;
6use crate::units::Dimensionless;
7use anyhow::{Context, Result, ensure};
8use itertools::Itertools;
9use serde::Deserialize;
10use std::collections::HashMap;
11use std::path::Path;
12
13const AGENT_OBJECTIVES_FILE_NAME: &str = "agent_objectives.csv";
14
15#[derive(Debug, Clone, Deserialize, PartialEq)]
17struct AgentObjectiveRaw {
18 agent_id: AgentID,
20 years: String,
22 objective_type: ObjectiveType,
24 decision_weight: Option<Dimensionless>,
26 decision_lexico_order: Option<u32>,
28}
29
30pub fn read_agent_objectives(
42 model_dir: &Path,
43 agents: &AgentMap,
44 milestone_years: &[u32],
45) -> Result<HashMap<AgentID, AgentObjectiveMap>> {
46 let file_path = model_dir.join(AGENT_OBJECTIVES_FILE_NAME);
47 let agent_objectives_csv = read_csv(&file_path)?;
48 read_agent_objectives_from_iter(agent_objectives_csv, agents, milestone_years)
49 .with_context(|| input_err_msg(&file_path))
50}
51
52fn read_agent_objectives_from_iter<I>(
53 iter: I,
54 agents: &AgentMap,
55 milestone_years: &[u32],
56) -> Result<HashMap<AgentID, AgentObjectiveMap>>
57where
58 I: Iterator<Item = AgentObjectiveRaw>,
59{
60 let mut all_objectives = HashMap::new();
61 for objective in iter {
62 let (id, agent) = agents.get_id_value(&objective.agent_id)?;
63
64 check_objective_parameter(&objective, &agent.decision_rule)?;
66
67 let agent_objectives = all_objectives
68 .entry(id.clone())
69 .or_insert_with(AgentObjectiveMap::new);
70 for year in parse_year_str(&objective.years, milestone_years)? {
71 try_insert(agent_objectives, &year, objective.objective_type).with_context(|| {
72 format!("Duplicate agent objective entry for agent {id} and year {year}")
73 })?;
74 }
75 }
76
77 for agent_id in agents.keys() {
79 let agent_objectives = all_objectives
80 .get(agent_id)
81 .with_context(|| format!("Agent {agent_id} has no objectives"))?;
82
83 let missing_years = milestone_years
84 .iter()
85 .copied()
86 .filter(|year| !agent_objectives.contains_key(year))
87 .collect_vec();
88 ensure!(
89 missing_years.is_empty(),
90 "Agent {agent_id} is missing objectives for the following milestone years: {missing_years:?}"
91 );
92 }
93
94 Ok(all_objectives)
95}
96
97fn check_objective_parameter(
99 objective: &AgentObjectiveRaw,
100 decision_rule: &DecisionRule,
101) -> Result<()> {
102 macro_rules! check_field_none {
104 ($field:ident) => {
105 ensure!(
106 objective.$field.is_none(),
107 "Field {} should be empty for this decision rule",
108 stringify!($field)
109 )
110 };
111 }
112
113 macro_rules! check_field_some {
115 ($field:ident) => {
116 ensure!(
117 objective.$field.is_some(),
118 "Required field {} is empty",
119 stringify!($field)
120 )
121 };
122 }
123
124 match decision_rule {
125 DecisionRule::Single => {
126 check_field_none!(decision_weight);
127 check_field_none!(decision_lexico_order);
128 }
129 DecisionRule::Weighted => {
130 check_field_some!(decision_weight);
131 check_field_none!(decision_lexico_order);
132 }
133 DecisionRule::Lexicographical { tolerance: _ } => {
134 check_field_none!(decision_weight);
135 check_field_some!(decision_lexico_order);
136 }
137 }
138
139 Ok(())
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use crate::agent::ObjectiveType;
146 use crate::fixture::{agents, assert_error};
147 use rstest::{fixture, rstest};
148 use std::iter;
149
150 macro_rules! objective {
151 ($decision_weight:expr, $decision_lexico_order:expr) => {
152 AgentObjectiveRaw {
153 agent_id: "agent".into(),
154 years: "2020".into(),
155 objective_type: ObjectiveType::LevelisedCostOfX,
156 decision_weight: $decision_weight,
157 decision_lexico_order: $decision_lexico_order,
158 }
159 };
160 }
161
162 #[test]
163 fn check_objective_parameter_single() {
164 let decision_rule = DecisionRule::Single;
166 let objective = objective!(None, None);
167 check_objective_parameter(&objective, &decision_rule).unwrap();
168 let objective = objective!(Some(Dimensionless(1.0)), None);
169 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
170 let objective = objective!(None, Some(1));
171 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
172 }
173
174 #[test]
175 fn check_objective_parameter_weighted() {
176 let decision_rule = DecisionRule::Weighted;
178 let objective = objective!(Some(Dimensionless(1.0)), None);
179 check_objective_parameter(&objective, &decision_rule).unwrap();
180 let objective = objective!(None, None);
181 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
182 let objective = objective!(None, Some(1));
183 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
184 }
185
186 #[test]
187 fn check_objective_parameter_lexico() {
188 let decision_rule = DecisionRule::Lexicographical { tolerance: 1.0 };
190 let objective = objective!(None, Some(1));
191 check_objective_parameter(&objective, &decision_rule).unwrap();
192 let objective = objective!(None, None);
193 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
194 let objective = objective!(Some(Dimensionless(1.0)), None);
195 assert!(check_objective_parameter(&objective, &decision_rule).is_err());
196 }
197
198 #[fixture]
199 fn objective_raw() -> AgentObjectiveRaw {
200 AgentObjectiveRaw {
201 agent_id: "agent1".into(),
202 years: "2020".into(),
203 objective_type: ObjectiveType::LevelisedCostOfX,
204 decision_weight: None,
205 decision_lexico_order: None,
206 }
207 }
208
209 #[rstest]
210 fn read_agent_objectives_from_iter_valid(agents: AgentMap, objective_raw: AgentObjectiveRaw) {
211 let milestone_years = [2020];
212 let expected = iter::once((
213 "agent1".into(),
214 iter::once((2020, objective_raw.objective_type)).collect(),
215 ))
216 .collect();
217 let actual = read_agent_objectives_from_iter(
218 iter::once(objective_raw.clone()),
219 &agents,
220 &milestone_years,
221 )
222 .unwrap();
223 assert_eq!(actual, expected);
224 }
225
226 #[rstest]
227 fn read_agent_objectives_from_iter_invalid_no_objective_for_agent(agents: AgentMap) {
228 assert_error!(
230 read_agent_objectives_from_iter(iter::empty(), &agents, &[2020]),
231 "Agent agent1 has no objectives"
232 );
233 }
234
235 #[rstest]
236 fn read_agent_objectives_from_iter_invalid_no_objective_for_year(
237 agents: AgentMap,
238 objective_raw: AgentObjectiveRaw,
239 ) {
240 assert_error!(
242 read_agent_objectives_from_iter(iter::once(objective_raw), &agents, &[2020, 2030]),
243 "Agent agent1 is missing objectives for the following milestone years: [2030]"
244 );
245 }
246
247 #[rstest]
248 fn read_agent_objectives_from_iter_invalid_bad_param(agents: AgentMap) {
249 let bad_objective = AgentObjectiveRaw {
251 agent_id: "agent1".into(),
252 years: "2020".into(),
253 objective_type: ObjectiveType::LevelisedCostOfX,
254 decision_weight: Some(Dimensionless(1.0)), decision_lexico_order: None,
256 };
257 assert_error!(
258 read_agent_objectives_from_iter([bad_objective].into_iter(), &agents, &[2020]),
259 "Field decision_weight should be empty for this decision rule"
260 );
261 }
262}