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