muse2/agent.rs
1//! Agents drive the economy of the MUSE2 simulation, through relative investment in different
2//! assets.
3use crate::commodity::CommodityID;
4use crate::id::define_id_type;
5use crate::process::{FlowDirection, Process};
6use crate::region::RegionID;
7use crate::units::Dimensionless;
8use indexmap::{IndexMap, IndexSet};
9use serde_string_enum::DeserializeLabeledStringEnum;
10use std::collections::HashMap;
11use std::rc::Rc;
12
13define_id_type! {AgentID}
14
15/// A map of [`Agent`]s, keyed by agent ID
16pub type AgentMap = IndexMap<AgentID, Agent>;
17
18/// A map of commodity portions for an agent, keyed by commodity and year
19pub type AgentCommodityPortionsMap = HashMap<(CommodityID, u32), Dimensionless>;
20
21/// A map for the agent's search space, keyed by commodity and year
22pub type AgentSearchSpaceMap = HashMap<(CommodityID, u32), Rc<Vec<Rc<Process>>>>;
23
24/// A map of objectives for an agent, keyed by year.
25///
26/// NB: As we currently only support the "single" decision rule, the only parameter we need for
27/// objectives is the type.
28pub type AgentObjectiveMap = HashMap<u32, ObjectiveType>;
29
30/// An agent in the simulation
31#[derive(Debug, Clone, PartialEq)]
32pub struct Agent {
33 /// A unique identifier for the agent.
34 pub id: AgentID,
35 /// A text description of the agent.
36 pub description: String,
37 /// The proportion of the commodity production that the agent is responsible for.
38 pub commodity_portions: AgentCommodityPortionsMap,
39 /// The processes that the agent will consider investing in.
40 pub search_space: AgentSearchSpaceMap,
41 /// The decision rule that the agent uses to decide investment.
42 pub decision_rule: DecisionRule,
43 /// The regions in which this agent operates.
44 pub regions: IndexSet<RegionID>,
45 /// The agent's objectives.
46 pub objectives: AgentObjectiveMap,
47}
48
49impl Agent {
50 /// Get all the processes in this agent's search space which produce the commodity in the given
51 /// year
52 pub fn iter_possible_producers_of<'a>(
53 &'a self,
54 region_id: &RegionID,
55 commodity_id: &'a CommodityID,
56 year: u32,
57 ) -> impl Iterator<Item = &'a Rc<Process>> + use<'a> {
58 let flows_key = (region_id.clone(), year);
59 self.search_space[&(commodity_id.clone(), year)]
60 .iter()
61 .filter(move |process| {
62 process.flows[&flows_key][commodity_id].direction() == FlowDirection::Output
63 })
64 }
65}
66
67/// The decision rule for a particular objective
68#[derive(Debug, Clone, PartialEq)]
69pub enum DecisionRule {
70 /// Used when there is only a single objective
71 Single,
72 /// A simple weighting of objectives
73 Weighted,
74 /// Objectives are considered in a specific order
75 Lexicographical {
76 /// 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.
77 tolerance: f64,
78 },
79}
80
81/// The type of objective for the agent
82#[derive(Debug, Clone, Copy, PartialEq, DeserializeLabeledStringEnum)]
83pub enum ObjectiveType {
84 /// Average cost of one unit of output commodity over its lifetime
85 #[string = "lcox"]
86 LevelisedCostOfX,
87 /// Net present value
88 #[string = "npv"]
89 NetPresentValue,
90}