muse2/input/agent/
search_space.rs1use super::super::*;
3use crate::agent::{AgentID, AgentMap, AgentSearchSpace};
4use crate::commodity::CommodityMap;
5use crate::id::IDCollection;
6use crate::process::ProcessID;
7use anyhow::{Context, Result};
8use indexmap::IndexSet;
9use serde::Deserialize;
10use std::collections::HashMap;
11use std::path::Path;
12use std::rc::Rc;
13
14const AGENT_SEARCH_SPACE_FILE_NAME: &str = "agent_search_space.csv";
15
16#[derive(PartialEq, Debug, Deserialize)]
17struct AgentSearchSpaceRaw {
18 agent_id: String,
20 commodity_id: String,
22 year: u32,
24 search_space: String,
27}
28
29impl AgentSearchSpaceRaw {
30 fn to_agent_search_space(
31 &self,
32 process_ids: &IndexSet<ProcessID>,
33 commodities: &CommodityMap,
34 milestone_years: &[u32],
35 ) -> Result<AgentSearchSpace> {
36 let search_space = parse_search_space_str(&self.search_space, process_ids)?;
38
39 let commodity = commodities
41 .get(self.commodity_id.as_str())
42 .context("Invalid commodity ID")?;
43
44 ensure!(
46 milestone_years.binary_search(&self.year).is_ok(),
47 "Invalid milestone year {}",
48 self.year
49 );
50
51 Ok(AgentSearchSpace {
53 year: self.year,
54 commodity: Rc::clone(commodity),
55 search_space,
56 })
57 }
58}
59
60fn parse_search_space_str(
67 search_space: &str,
68 process_ids: &IndexSet<ProcessID>,
69) -> Result<Vec<ProcessID>> {
70 let search_space = search_space.trim();
71 if search_space.is_empty() || search_space.eq_ignore_ascii_case("all") {
72 Ok(process_ids.iter().cloned().collect())
73 } else {
74 search_space
75 .split(';')
76 .map(|id| process_ids.get_id_by_str(id.trim()))
77 .try_collect()
78 }
79}
80
81pub fn read_agent_search_space(
91 model_dir: &Path,
92 agents: &AgentMap,
93 process_ids: &IndexSet<ProcessID>,
94 commodities: &CommodityMap,
95 milestone_years: &[u32],
96) -> Result<HashMap<AgentID, Vec<AgentSearchSpace>>> {
97 let file_path = model_dir.join(AGENT_SEARCH_SPACE_FILE_NAME);
98 let iter = read_csv_optional::<AgentSearchSpaceRaw>(&file_path)?;
99 read_agent_search_space_from_iter(iter, agents, process_ids, commodities, milestone_years)
100 .with_context(|| input_err_msg(&file_path))
101}
102
103fn read_agent_search_space_from_iter<I>(
104 iter: I,
105 agents: &AgentMap,
106 process_ids: &IndexSet<ProcessID>,
107 commodities: &CommodityMap,
108 milestone_years: &[u32],
109) -> Result<HashMap<AgentID, Vec<AgentSearchSpace>>>
110where
111 I: Iterator<Item = AgentSearchSpaceRaw>,
112{
113 let mut search_spaces = HashMap::new();
114 for search_space_raw in iter {
115 let search_space =
116 search_space_raw.to_agent_search_space(process_ids, commodities, milestone_years)?;
117
118 let (id, _agent) = agents
119 .get_key_value(search_space_raw.agent_id.as_str())
120 .context("Invalid agent ID")?;
121
122 search_spaces
124 .entry(id.clone())
125 .or_insert_with(|| Vec::with_capacity(1))
126 .push(search_space);
127 }
128
129 Ok(search_spaces)
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::commodity::{Commodity, CommodityCostMap, CommodityType, DemandMap};
136 use crate::time_slice::TimeSliceLevel;
137 use std::iter;
138
139 #[test]
140 fn test_search_space_raw_into_search_space() {
141 let process_ids = ["A".into(), "B".into(), "C".into()].into_iter().collect();
142 let commodity = Rc::new(Commodity {
143 id: "commodity1".into(),
144 description: "A commodity".into(),
145 kind: CommodityType::SupplyEqualsDemand,
146 time_slice_level: TimeSliceLevel::Annual,
147 costs: CommodityCostMap::new(),
148 demand: DemandMap::new(),
149 });
150 let commodities = iter::once(("commodity1".into(), Rc::clone(&commodity))).collect();
151
152 let raw = AgentSearchSpaceRaw {
154 agent_id: "agent1".into(),
155 commodity_id: "commodity1".into(),
156 year: 2020,
157 search_space: "A;B".into(),
158 };
159 assert!(raw
160 .to_agent_search_space(&process_ids, &commodities, &[2020])
161 .is_ok());
162
163 let raw = AgentSearchSpaceRaw {
165 agent_id: "agent1".into(),
166 commodity_id: "invalid_commodity".into(),
167 year: 2020,
168 search_space: "A;B".into(),
169 };
170 assert!(raw
171 .to_agent_search_space(&process_ids, &commodities, &[2020])
172 .is_err());
173
174 let raw = AgentSearchSpaceRaw {
176 agent_id: "agent1".into(),
177 commodity_id: "commodity1".into(),
178 year: 2020,
179 search_space: "A;D".into(),
180 };
181 assert!(raw
182 .to_agent_search_space(&process_ids, &commodities, &[2020])
183 .is_err());
184 }
185}