Skip to content

Commit 2a58ffe

Browse files
authored
Merge pull request #461 from EnergySystemsModellingLab/lexico_order
Add `decision_lexico_order` parameter to `agent_objectives` table
2 parents 78b707c + 3200df0 commit 2a58ffe

3 files changed

Lines changed: 123 additions & 25 deletions

File tree

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
agent_id,year,objective_type,decision_weight
2-
A0_GEX,2020,lcox,
3-
A0_GEX,2030,lcox,
4-
A0_GPR,2020,lcox,
5-
A0_GPR,2030,lcox,
6-
A0_ELC,2020,lcox,
7-
A0_ELC,2030,lcox,
8-
A0_RES,2020,eac,
9-
A0_RES,2030,eac,
1+
agent_id,year,objective_type,decision_weight,decision_lexico_order
2+
A0_GEX,2020,lcox,,
3+
A0_GEX,2030,lcox,,
4+
A0_GPR,2020,lcox,,
5+
A0_GPR,2030,lcox,,
6+
A0_ELC,2020,lcox,,
7+
A0_ELC,2030,lcox,,
8+
A0_RES,2020,eac,,
9+
A0_RES,2030,eac,,

src/agent.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,10 @@ pub struct AgentObjective {
7171
pub year: u32,
7272
/// Acronym identifying the objective (e.g. LCOX)
7373
pub objective_type: ObjectiveType,
74-
/// For the weighted sum objective, the set of weights to apply to each objective.
74+
/// For the weighted sum decision rule, the set of weights to apply to each objective.
7575
pub decision_weight: Option<f64>,
76+
/// For the lexico decision rule, the order in which to consider objectives.
77+
pub decision_lexico_order: Option<u32>,
7678
}
7779

7880
/// The type of objective for the agent

src/input/agent/objective.rs

Lines changed: 111 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,17 @@ where
6161
.push(objective);
6262
}
6363

64-
// Validate that each agent has at least one objective for each milestone year
65-
for (agent_id, _agent) in agents {
64+
// Check that agents have appropriate objectives for their decision rule every year
65+
for (agent_id, agent) in agents {
6666
let agent_objectives = objectives
6767
.get(agent_id)
6868
.with_context(|| format!("Agent {} has no objectives", agent_id))?;
6969
for &year in milestone_years {
70-
ensure!(
71-
agent_objectives.iter().any(|obj| obj.year == year),
72-
"Agent {} is missing objectives for milestone year {}",
73-
agent_id,
74-
year
75-
);
70+
let objectives_for_year: Vec<_> = agent_objectives
71+
.iter()
72+
.filter(|obj| obj.year == year)
73+
.collect();
74+
check_agent_objectives(&objectives_for_year, &agent.decision_rule, agent_id, year)?;
7675
}
7776
}
7877

@@ -109,18 +108,67 @@ fn check_objective_parameter(
109108
match decision_rule {
110109
DecisionRule::Single => {
111110
check_field_none!(decision_weight);
111+
check_field_none!(decision_lexico_order);
112112
}
113113
DecisionRule::Weighted => {
114114
check_field_some!(decision_weight);
115+
check_field_none!(decision_lexico_order);
115116
}
116117
DecisionRule::Lexicographical { tolerance: _ } => {
117118
check_field_none!(decision_weight);
119+
check_field_some!(decision_lexico_order);
118120
}
119121
};
120122

121123
Ok(())
122124
}
123125

126+
/// Check that a set of objectives meets the requirements of a decision rule
127+
fn check_agent_objectives(
128+
objectives: &[&AgentObjective],
129+
decision_rule: &DecisionRule,
130+
agent_id: &str,
131+
year: u32,
132+
) -> Result<()> {
133+
let count = objectives.len();
134+
match decision_rule {
135+
DecisionRule::Single => {
136+
ensure!(
137+
count == 1,
138+
"Agent {} has {} objectives for milestone year {} but should have exactly 1",
139+
agent_id,
140+
count,
141+
year
142+
);
143+
}
144+
DecisionRule::Weighted => {
145+
ensure!(
146+
count > 1,
147+
"Agent {} has {} objectives for milestone year {} but should have more than 1",
148+
agent_id,
149+
count,
150+
year
151+
);
152+
}
153+
DecisionRule::Lexicographical { tolerance: _ } => {
154+
let mut lexico_orders: Vec<u32> = objectives
155+
.iter()
156+
.filter_map(|obj| obj.decision_lexico_order)
157+
.collect();
158+
lexico_orders.sort_unstable();
159+
ensure!(
160+
lexico_orders == [1, 2],
161+
"Agent {} must have objectives with decision_lexico_order values of 1 and 2 for milestone year {}, but found {:?}",
162+
agent_id,
163+
year,
164+
lexico_orders
165+
);
166+
}
167+
}
168+
169+
Ok(())
170+
}
171+
124172
#[cfg(test)]
125173
mod tests {
126174
use super::*;
@@ -132,35 +180,42 @@ mod tests {
132180
#[test]
133181
fn test_check_objective_parameter() {
134182
macro_rules! objective {
135-
($decision_weight:expr) => {
183+
($decision_weight:expr, $decision_lexico_order:expr) => {
136184
AgentObjective {
137185
agent_id: "agent".into(),
138186
year: 2020,
139187
objective_type: ObjectiveType::EquivalentAnnualCost,
140188
decision_weight: $decision_weight,
189+
decision_lexico_order: $decision_lexico_order,
141190
}
142191
};
143192
}
144193

145194
// DecisionRule::Single
146195
let decision_rule = DecisionRule::Single;
147-
let objective = objective!(None);
196+
let objective = objective!(None, None);
148197
assert!(check_objective_parameter(&objective, &decision_rule).is_ok());
149-
let objective = objective!(Some(1.0));
198+
let objective = objective!(Some(1.0), None);
199+
assert!(check_objective_parameter(&objective, &decision_rule).is_err());
200+
let objective = objective!(None, Some(1));
150201
assert!(check_objective_parameter(&objective, &decision_rule).is_err());
151202

152203
// DecisionRule::Weighted
153204
let decision_rule = DecisionRule::Weighted;
154-
let objective = objective!(Some(1.0));
205+
let objective = objective!(Some(1.0), None);
155206
assert!(check_objective_parameter(&objective, &decision_rule).is_ok());
156-
let objective = objective!(None);
207+
let objective = objective!(None, None);
208+
assert!(check_objective_parameter(&objective, &decision_rule).is_err());
209+
let objective = objective!(None, Some(1));
157210
assert!(check_objective_parameter(&objective, &decision_rule).is_err());
158211

159212
// DecisionRule::Lexicographical
160213
let decision_rule = DecisionRule::Lexicographical { tolerance: 1.0 };
161-
let objective = objective!(None);
214+
let objective = objective!(None, Some(1));
162215
assert!(check_objective_parameter(&objective, &decision_rule).is_ok());
163-
let objective = objective!(Some(1.0));
216+
let objective = objective!(None, None);
217+
assert!(check_objective_parameter(&objective, &decision_rule).is_err());
218+
let objective = objective!(Some(1.0), None);
164219
assert!(check_objective_parameter(&objective, &decision_rule).is_err());
165220
}
166221

@@ -199,6 +254,7 @@ mod tests {
199254
year: 2020,
200255
objective_type: ObjectiveType::EquivalentAnnualCost,
201256
decision_weight: None,
257+
decision_lexico_order: None,
202258
};
203259
let expected = [("agent".into(), vec![objective.clone()])]
204260
.into_iter()
@@ -228,6 +284,7 @@ mod tests {
228284
year: 2020,
229285
objective_type: ObjectiveType::EquivalentAnnualCost,
230286
decision_weight: Some(1.0), // Should only accept None for DecisionRule::Single
287+
decision_lexico_order: None,
231288
};
232289
assert!(read_agent_objectives_from_iter(
233290
[bad_objective].into_iter(),
@@ -236,4 +293,43 @@ mod tests {
236293
)
237294
.is_err());
238295
}
296+
297+
#[test]
298+
fn test_check_agent_objectives() {
299+
let objective1 = AgentObjective {
300+
agent_id: "agent".into(),
301+
year: 2020,
302+
objective_type: ObjectiveType::EquivalentAnnualCost,
303+
decision_weight: None,
304+
decision_lexico_order: Some(1),
305+
};
306+
let objective2 = AgentObjective {
307+
agent_id: "agent".into(),
308+
year: 2020,
309+
objective_type: ObjectiveType::EquivalentAnnualCost,
310+
decision_weight: None,
311+
decision_lexico_order: Some(2),
312+
};
313+
314+
// DecisionRule::Single
315+
let decision_rule = DecisionRule::Single;
316+
let objectives = [&objective1];
317+
assert!(check_agent_objectives(&objectives, &decision_rule, "agent", 2020).is_ok());
318+
let objectives = [&objective1, &objective2];
319+
assert!(check_agent_objectives(&objectives, &decision_rule, "agent", 2020).is_err());
320+
321+
// DecisionRule::Weighted
322+
let decision_rule = DecisionRule::Weighted;
323+
let objectives = [&objective1, &objective2];
324+
assert!(check_agent_objectives(&objectives, &decision_rule, "agent", 2020).is_ok());
325+
let objectives = [&objective1];
326+
assert!(check_agent_objectives(&objectives, &decision_rule, "agent", 2020).is_err());
327+
328+
// DecisionRule::Lexicographical
329+
let decision_rule = DecisionRule::Lexicographical { tolerance: 1.0 };
330+
let objectives = [&objective1, &objective2];
331+
assert!(check_agent_objectives(&objectives, &decision_rule, "agent", 2020).is_ok());
332+
let objectives = [&objective1, &objective1];
333+
assert!(check_agent_objectives(&objectives, &decision_rule, "agent", 2020).is_err());
334+
}
239335
}

0 commit comments

Comments
 (0)