muse2/input/agent/
objective.rs

1//! Code for reading the agent objectives CSV file.
2use 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
10/// Read agent objective info from the agent_objectives.csv file.
11///
12/// # Arguments
13///
14/// * `model_dir` - Folder containing model configuration files
15///
16/// # Returns
17///
18/// A map of Agents, with the agent ID as the key
19pub 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 that required parameters are present and others are absent
45        check_objective_parameter(&objective, &agent.decision_rule)?;
46
47        // Check that the year is a valid milestone year
48        ensure!(
49            milestone_years.binary_search(&objective.year).is_ok(),
50            "Invalid milestone year {}",
51            objective.year
52        );
53
54        // Append to Vec with the corresponding key or create
55        objectives
56            .entry(id.clone())
57            .or_insert_with(|| Vec::with_capacity(1))
58            .push(objective);
59    }
60
61    // Check that agents have appropriate objectives for their decision rule every year
62    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
78/// Check that required parameters are present and others are absent
79fn check_objective_parameter(
80    objective: &AgentObjective,
81    decision_rule: &DecisionRule,
82) -> Result<()> {
83    // Check that the user hasn't supplied a value for a field we're not using
84    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    // Check that required fields are present
95    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
123/// Check that a set of objectives meets the requirements of a decision rule
124fn 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        // DecisionRule::Single
189        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        // DecisionRule::Weighted
198        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        // DecisionRule::Lexicographical
207        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        // Valid
236        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        // Missing objective for agent
255        assert!(
256            read_agent_objectives_from_iter([].into_iter(), &agents, &milestone_years).is_err()
257        );
258
259        // Missing objective for milestone year
260        assert!(
261            read_agent_objectives_from_iter([objective].into_iter(), &agents, &[2020, 2030])
262                .is_err()
263        );
264
265        // Bad parameter
266        let bad_objective = AgentObjective {
267            agent_id: "agent".into(),
268            year: 2020,
269            objective_type: ObjectiveType::LevelisedCostOfX,
270            decision_weight: Some(1.0), // Should only accept None for DecisionRule::Single
271            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        // DecisionRule::Single
300        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        // DecisionRule::Weighted
308        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        // DecisionRule::Lexicographical
315        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}