use super::*;
use crate::agent::{Agent, DecisionRule, SearchSpace};
use crate::commodity::Commodity;
use crate::process::Process;
use crate::region::RegionSelection;
use anyhow::{ensure, Context, Result};
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::rc::Rc;
pub mod objective;
use objective::read_agent_objectives;
pub mod region;
use region::read_agent_regions;
const AGENT_FILE_NAME: &str = "agents.csv";
#[derive(Debug, Deserialize, PartialEq, Clone)]
struct AgentRaw {
id: Rc<str>,
description: String,
commodity_id: String,
#[serde(deserialize_with = "deserialise_proportion_nonzero")]
commodity_portion: f64,
search_space: Option<String>,
decision_rule: DecisionRule,
capex_limit: Option<f64>,
annual_cost_limit: Option<f64>,
}
pub fn read_agents(
model_dir: &Path,
commodities: &HashMap<Rc<str>, Rc<Commodity>>,
processes: &HashMap<Rc<str>, Rc<Process>>,
region_ids: &HashSet<Rc<str>>,
) -> Result<HashMap<Rc<str>, Agent>> {
let process_ids = processes.keys().cloned().collect();
let mut agents = read_agents_file(model_dir, commodities, &process_ids)?;
let agent_ids = agents.keys().cloned().collect();
let mut agent_regions = read_agent_regions(model_dir, &agent_ids, region_ids)?;
let mut objectives = read_agent_objectives(model_dir, &agents)?;
for (id, agent) in agents.iter_mut() {
agent.regions = agent_regions.remove(id).unwrap();
agent.objectives = objectives.remove(id).unwrap();
}
Ok(agents)
}
pub fn read_agents_file(
model_dir: &Path,
commodities: &HashMap<Rc<str>, Rc<Commodity>>,
process_ids: &HashSet<Rc<str>>,
) -> Result<HashMap<Rc<str>, Agent>> {
let file_path = model_dir.join(AGENT_FILE_NAME);
let agents_csv = read_csv(&file_path)?;
read_agents_file_from_iter(agents_csv, commodities, process_ids)
.with_context(|| input_err_msg(&file_path))
}
fn read_agents_file_from_iter<I>(
iter: I,
commodities: &HashMap<Rc<str>, Rc<Commodity>>,
process_ids: &HashSet<Rc<str>>,
) -> Result<HashMap<Rc<str>, Agent>>
where
I: Iterator<Item = AgentRaw>,
{
let mut agents = HashMap::new();
for agent_raw in iter {
let commodity = commodities
.get(agent_raw.commodity_id.as_str())
.context("Invalid commodity ID")?;
let search_space = match agent_raw.search_space {
None => SearchSpace::AllProcesses,
Some(processes) => {
let mut set = HashSet::new();
for id in processes.split(';') {
set.insert(process_ids.get_id(id)?);
}
SearchSpace::Some(set)
}
};
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,
capex_limit: agent_raw.capex_limit,
annual_cost_limit: agent_raw.annual_cost_limit,
regions: RegionSelection::default(),
objectives: Vec::new(),
};
ensure!(
agents.insert(agent_raw.id, agent).is_none(),
"Duplicate agent ID"
);
}
Ok(agents)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agent::DecisionRule;
use crate::commodity::{CommodityCostMap, CommodityType, DemandMap};
use crate::region::RegionSelection;
use crate::time_slice::TimeSliceLevel;
use std::iter;
#[test]
fn test_read_agents_file_from_iter() {
let process_ids = ["A".into(), "B".into(), "C".into()].into_iter().collect();
let commodity = Rc::new(Commodity {
id: "commodity1".into(),
description: "A commodity".into(),
kind: CommodityType::SupplyEqualsDemand,
time_slice_level: TimeSliceLevel::Annual,
costs: CommodityCostMap::new(),
demand: DemandMap::new(),
});
let commodities = iter::once(("commodity1".into(), Rc::clone(&commodity))).collect();
let search_space = HashSet::from_iter(["A".into(), "B".into()]);
let agent = AgentRaw {
id: "agent".into(),
description: "".into(),
commodity_id: "commodity1".into(),
commodity_portion: 1.0,
search_space: Some("A;B".into()),
decision_rule: DecisionRule::Single,
capex_limit: None,
annual_cost_limit: None,
};
let agent_out = Agent {
id: "agent".into(),
description: "".into(),
commodity,
commodity_portion: 1.0,
search_space: SearchSpace::Some(search_space),
decision_rule: DecisionRule::Single,
capex_limit: None,
annual_cost_limit: None,
regions: RegionSelection::default(),
objectives: Vec::new(),
};
let expected = HashMap::from_iter([("agent".into(), agent_out)]);
let actual =
read_agents_file_from_iter(iter::once(agent), &commodities, &process_ids).unwrap();
assert_eq!(actual, expected);
let agent = AgentRaw {
id: "agent".into(),
description: "".into(),
commodity_id: "made_up_commodity".into(),
commodity_portion: 1.0,
search_space: None,
decision_rule: DecisionRule::Single,
capex_limit: None,
annual_cost_limit: None,
};
assert!(read_agents_file_from_iter(iter::once(agent), &commodities, &process_ids).is_err());
let agent = AgentRaw {
id: "agent".into(),
description: "".into(),
commodity_id: "commodity1".into(),
commodity_portion: 1.0,
search_space: Some("A;D".into()),
decision_rule: DecisionRule::Single,
capex_limit: None,
annual_cost_limit: None,
};
assert!(read_agents_file_from_iter(iter::once(agent), &commodities, &process_ids).is_err());
let agents = [
AgentRaw {
id: "agent".into(),
description: "".into(),
commodity_id: "commodity1".into(),
commodity_portion: 1.0,
search_space: None,
decision_rule: DecisionRule::Single,
capex_limit: None,
annual_cost_limit: None,
},
AgentRaw {
id: "agent".into(),
description: "".into(),
commodity_id: "commodity1".into(),
commodity_portion: 1.0,
search_space: None,
decision_rule: DecisionRule::Single,
capex_limit: None,
annual_cost_limit: None,
},
];
assert!(
read_agents_file_from_iter(agents.into_iter(), &commodities, &process_ids).is_err()
);
}
}