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