Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions examples/simple/agent_objectives.csv
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
agent_id,year,objective_type,decision_weight,decision_lexico_tolerance
A0_GEX,2020,lcox,,
A0_GEX,2030,lcox,,
A0_GPR,2020,lcox,,
A0_GPR,2030,lcox,,
A0_ELC,2020,lcox,,
A0_ELC,2030,lcox,,
A0_RES,2020,eac,,
A0_RES,2030,eac,,
agent_id,year,objective_type,decision_weight
A0_GEX,2020,lcox,
A0_GEX,2030,lcox,
A0_GPR,2020,lcox,
A0_GPR,2030,lcox,
A0_ELC,2020,lcox,
A0_ELC,2030,lcox,
A0_RES,2020,eac,
A0_RES,2030,eac,
10 changes: 5 additions & 5 deletions examples/simple/agents.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
id,description,commodity_id,commodity_portion,search_space,decision_rule,capex_limit,annual_cost_limit
A0_GEX,Gas extractors,GASPRD,1,,single,,
A0_GPR,Gas processors,GASNAT,1,,single,,
A0_ELC,Electricity generators,ELCTRI,1,,single,,
A0_RES,Residential consumer,RSHEAT,1,,single,,
id,description,commodity_id,commodity_portion,search_space,decision_rule,capex_limit,annual_cost_limit,decision_lexico_tolerance
A0_GEX,Gas extractors,GASPRD,1,,single,,,
A0_GPR,Gas processors,GASNAT,1,,single,,,
A0_ELC,Electricity generators,ELCTRI,1,,single,,,
A0_RES,Residential consumer,RSHEAT,1,,single,,,
12 changes: 5 additions & 7 deletions src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,17 @@ pub enum SearchSpace {
}

/// The decision rule for a particular objective
#[derive(Debug, Clone, PartialEq, DeserializeLabeledStringEnum)]
#[derive(Debug, Clone, PartialEq)]
pub enum DecisionRule {
/// Used when there is only a single objective
#[string = "single"]
Single,
/// A simple weighting of objectives
#[string = "weighted"]
Weighted,
/// Objectives are considered in a specific order
#[string = "lexico"]
Lexicographical,
Lexicographical {
/// The tolerance around the main objective to consider secondary objectives. This is an absolute value of maximum deviation in the units of the main objective.
tolerance: f64,
},
}

/// An objective for an agent with associated parameters
Expand All @@ -73,8 +73,6 @@ pub struct AgentObjective {
pub objective_type: ObjectiveType,
/// For the weighted sum objective, the set of weights to apply to each objective.
pub decision_weight: Option<f64>,
/// The tolerance around the main objective to consider secondary objectives. This is an absolute value of maximum deviation in the units of the main objective.
pub decision_lexico_tolerance: Option<f64>,
}

/// The type of objective for the agent
Expand Down
55 changes: 47 additions & 8 deletions src/input/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::agent::{Agent, AgentMap, DecisionRule, SearchSpace};
use crate::commodity::CommodityMap;
use crate::process::ProcessMap;
use crate::region::RegionSelection;
use anyhow::{ensure, Context, Result};
use anyhow::{bail, ensure, Context, Result};
use serde::Deserialize;
use std::collections::HashSet;
use std::path::Path;
Expand Down Expand Up @@ -33,11 +33,13 @@ struct AgentRaw {
/// by semicolons or `None`, meaning all processes.
search_space: Option<String>,
/// The decision rule that the agent uses to decide investment.
decision_rule: DecisionRule,
decision_rule: String,
/// The maximum capital cost the agent will pay.
capex_limit: Option<f64>,
/// The maximum annual operating cost (fuel plus var_opex etc) that the agent will pay.
annual_cost_limit: Option<f64>,
/// The tolerance around the main objective to consider secondary objectives.
decision_lexico_tolerance: Option<f64>,
}

/// Read agents info from various CSV files.
Expand Down Expand Up @@ -124,13 +126,31 @@ where
}
};

// Parse decision rule
let decision_rule = match agent_raw.decision_rule.to_ascii_lowercase().as_str() {
"single" => DecisionRule::Single,
"weighted" => DecisionRule::Weighted,
"lexico" => {
let tolerance = agent_raw
.decision_lexico_tolerance
.with_context(|| "Missing tolerance for lexico decision rule")?;
ensure!(
tolerance >= 0.0,
"Lexico tolerance must be non-negative, got {}",
tolerance
);
DecisionRule::Lexicographical { tolerance }
}
invalid_rule => bail!("Invalid decision rule: {}", invalid_rule),
};

let agent = Agent {
id: Rc::clone(&agent_raw.id),
description: agent_raw.description,
commodity: Rc::clone(commodity),
commodity_portion: agent_raw.commodity_portion,
search_space,
decision_rule: agent_raw.decision_rule,
decision_rule,
capex_limit: agent_raw.capex_limit,
annual_cost_limit: agent_raw.annual_cost_limit,
regions: RegionSelection::default(),
Expand Down Expand Up @@ -176,9 +196,10 @@ mod tests {
commodity_id: "commodity1".into(),
commodity_portion: 1.0,
search_space: Some("A;B".into()),
decision_rule: DecisionRule::Single,
decision_rule: "single".into(),
capex_limit: None,
annual_cost_limit: None,
decision_lexico_tolerance: None,
};
let agent_out = Agent {
id: "agent".into(),
Expand All @@ -204,9 +225,10 @@ mod tests {
commodity_id: "made_up_commodity".into(),
commodity_portion: 1.0,
search_space: None,
decision_rule: DecisionRule::Single,
decision_rule: "single".into(),
capex_limit: None,
annual_cost_limit: None,
decision_lexico_tolerance: None,
};
assert!(read_agents_file_from_iter(iter::once(agent), &commodities, &process_ids).is_err());

Expand All @@ -217,9 +239,10 @@ mod tests {
commodity_id: "commodity1".into(),
commodity_portion: 1.0,
search_space: Some("A;D".into()),
decision_rule: DecisionRule::Single,
decision_rule: "single".into(),
capex_limit: None,
annual_cost_limit: None,
decision_lexico_tolerance: None,
};
assert!(read_agents_file_from_iter(iter::once(agent), &commodities, &process_ids).is_err());

Expand All @@ -231,23 +254,39 @@ mod tests {
commodity_id: "commodity1".into(),
commodity_portion: 1.0,
search_space: None,
decision_rule: DecisionRule::Single,
decision_rule: "single".into(),
capex_limit: None,
annual_cost_limit: None,
decision_lexico_tolerance: None,
},
AgentRaw {
id: "agent".into(),
description: "".into(),
commodity_id: "commodity1".into(),
commodity_portion: 1.0,
search_space: None,
decision_rule: DecisionRule::Single,
decision_rule: "single".into(),
capex_limit: None,
annual_cost_limit: None,
decision_lexico_tolerance: None,
},
];
assert!(
read_agents_file_from_iter(agents.into_iter(), &commodities, &process_ids).is_err()
);

// Lexico tolerance missing for lexico decision rule
let agent = AgentRaw {
id: "agent".into(),
description: "".into(),
commodity_id: "commodity1".into(),
commodity_portion: 1.0,
search_space: None,
decision_rule: "lexico".into(),
capex_limit: None,
annual_cost_limit: None,
decision_lexico_tolerance: None,
};
assert!(read_agents_file_from_iter(iter::once(agent), &commodities, &process_ids).is_err());
}
}
30 changes: 9 additions & 21 deletions src/input/agent/objective.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,12 @@ fn check_objective_parameter(
match decision_rule {
DecisionRule::Single => {
check_field_none!(decision_weight);
check_field_none!(decision_lexico_tolerance);
}
DecisionRule::Weighted => {
check_field_none!(decision_lexico_tolerance);
check_field_some!(decision_weight);
}
DecisionRule::Lexicographical => {
DecisionRule::Lexicographical { tolerance: _ } => {
check_field_none!(decision_weight);
check_field_some!(decision_lexico_tolerance);
}
};

Expand All @@ -135,42 +132,35 @@ mod tests {
#[test]
fn test_check_objective_parameter() {
macro_rules! objective {
($decision_weight:expr, $decision_lexico_tolerance:expr) => {
($decision_weight:expr) => {
AgentObjective {
agent_id: "agent".into(),
year: 2020,
objective_type: ObjectiveType::EquivalentAnnualCost,
decision_weight: $decision_weight,
decision_lexico_tolerance: $decision_lexico_tolerance,
}
};
}

// DecisionRule::Single
let decision_rule = DecisionRule::Single;
let objective = objective!(None, None);
let objective = objective!(None);
assert!(check_objective_parameter(&objective, &decision_rule).is_ok());
let objective = objective!(Some(1.0), None);
assert!(check_objective_parameter(&objective, &decision_rule).is_err());
let objective = objective!(None, Some(1.0));
let objective = objective!(Some(1.0));
assert!(check_objective_parameter(&objective, &decision_rule).is_err());

// DecisionRule::Weighted
let decision_rule = DecisionRule::Weighted;
let objective = objective!(Some(1.0), None);
let objective = objective!(Some(1.0));
assert!(check_objective_parameter(&objective, &decision_rule).is_ok());
let objective = objective!(None, None);
assert!(check_objective_parameter(&objective, &decision_rule).is_err());
let objective = objective!(None, Some(1.0));
let objective = objective!(None);
assert!(check_objective_parameter(&objective, &decision_rule).is_err());

// DecisionRule::Lexicographical
let decision_rule = DecisionRule::Lexicographical;
let objective = objective!(None, Some(1.0));
let decision_rule = DecisionRule::Lexicographical { tolerance: 1.0 };
let objective = objective!(None);
assert!(check_objective_parameter(&objective, &decision_rule).is_ok());
let objective = objective!(None, None);
assert!(check_objective_parameter(&objective, &decision_rule).is_err());
let objective = objective!(Some(1.0), None);
let objective = objective!(Some(1.0));
assert!(check_objective_parameter(&objective, &decision_rule).is_err());
}

Expand Down Expand Up @@ -209,7 +199,6 @@ mod tests {
year: 2020,
objective_type: ObjectiveType::EquivalentAnnualCost,
decision_weight: None,
decision_lexico_tolerance: None,
};
let expected = [("agent".into(), vec![objective.clone()])]
.into_iter()
Expand Down Expand Up @@ -239,7 +228,6 @@ mod tests {
year: 2020,
objective_type: ObjectiveType::EquivalentAnnualCost,
decision_weight: Some(1.0), // Should only accept None for DecisionRule::Single
decision_lexico_tolerance: None,
};
assert!(read_agent_objectives_from_iter(
[bad_objective].into_iter(),
Expand Down
Loading