Skip to main content

muse2/
commodity.rs

1//! Commodities are substances or forms of energy that can be produced and consumed by processes.
2use crate::id::{define_id_getter, define_id_type};
3use crate::region::RegionID;
4use crate::time_slice::{TimeSliceID, TimeSliceLevel, TimeSliceSelection};
5use crate::units::{Flow, MoneyPerFlow};
6use indexmap::IndexMap;
7use serde::Deserialize;
8use std::collections::HashMap;
9use std::rc::Rc;
10
11define_id_type! {CommodityID, "commodity ID"}
12
13/// A map of [`Commodity`]s, keyed by commodity ID
14pub type CommodityMap = IndexMap<CommodityID, Rc<Commodity>>;
15
16/// A map of [`MoneyPerFlow`]s, keyed by region ID, year and time slice ID for a specific levy
17pub type CommodityLevyMap = HashMap<(RegionID, u32, TimeSliceID), MoneyPerFlow>;
18
19/// A map of demand values, keyed by region ID, year and time slice selection
20pub type DemandMap = HashMap<(RegionID, u32, TimeSliceSelection), Flow>;
21
22/// A commodity within the simulation.
23///
24/// Represents a substance (e.g. CO2) or form of energy (e.g. electricity) that can be produced or
25/// consumed by processes.
26#[derive(PartialEq, Debug, Clone)]
27pub struct Commodity {
28    /// Unique identifier for the commodity (e.g. "ELC")
29    pub id: CommodityID,
30    /// Text description of commodity (e.g. "electricity")
31    pub description: String,
32    /// Commodity balance type
33    pub kind: CommodityType,
34    /// The time slice level for commodity balance
35    pub time_slice_level: TimeSliceLevel,
36    /// Defines the strategy used for calculating commodity prices
37    pub pricing_strategy: PricingStrategy,
38    /// Production levies for this commodity for different combinations of region, year and time slice.
39    ///
40    /// May be empty if there are no production levies for this commodity, otherwise there must be
41    /// entries for every combination of parameters. Note that these values can be negative,
42    /// indicating an incentive.
43    pub levies_prod: CommodityLevyMap,
44    /// Consumption levies for this commodity for different combinations of region, year and time slice.
45    ///
46    /// May be empty if there are no consumption levies for this commodity, otherwise there must be
47    /// entries for every combination of parameters. Note that these values can be negative,
48    /// indicating an incentive.
49    pub levies_cons: CommodityLevyMap,
50    /// Demand as defined in input files. Will be empty for non-service-demand commodities.
51    ///
52    /// The [`TimeSliceSelection`] part of the key is always at the same [`TimeSliceLevel`] as the
53    /// `time_slice_level` field. E.g. if the `time_slice_level` is seasonal, then there will be
54    /// keys representing each season (and not e.g. individual time slices).
55    pub demand: DemandMap,
56    /// Units for this commodity represented as a string e.g Petajoules, Tonnes
57    /// This is only used for validation purposes.
58    pub units: String,
59}
60define_id_getter! {Commodity, CommodityID}
61
62/// Type of balance for application of cost
63#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Hash)]
64pub enum BalanceType {
65    /// Applies to production, with an equal and opposite levy/incentive on consumption
66    #[serde(rename = "net")]
67    Net,
68    /// Applies to consumption only
69    #[serde(rename = "cons")]
70    Consumption,
71    /// Applies to production only
72    #[serde(rename = "prod")]
73    Production,
74}
75
76/// Commodity balance type
77#[derive(PartialEq, Debug, Deserialize, Clone)]
78pub enum CommodityType {
79    /// Supply and demand of this commodity must be balanced
80    #[serde(rename = "sed")]
81    SupplyEqualsDemand,
82    /// Specifies a demand (specified in input files) which must be met by the simulation
83    #[serde(rename = "svd")]
84    ServiceDemand,
85    /// Either an input or an output to the simulation.
86    ///
87    /// This represents a commodity which can either be produced or consumed, but not both.
88    #[serde(rename = "oth")]
89    Other,
90}
91
92/// The strategy used for calculating commodity prices
93#[derive(Debug, PartialEq, Clone, Copy, Deserialize, Hash, Eq)]
94pub enum PricingStrategy {
95    /// Take commodity prices directly from the shadow prices
96    #[serde(rename = "shadow")]
97    Shadow,
98    /// Adjust shadow prices for scarcity
99    #[serde(rename = "scarcity")]
100    ScarcityAdjusted,
101    /// Use marginal cost of highest-cost active asset producing the commodity
102    #[serde(rename = "marginal")]
103    MarginalCost,
104    /// Use load-weighted average marginal cost across active assets producing the commodity
105    #[serde(rename = "marginal_average")]
106    MarginalCostAverage,
107    /// Use full cost of highest-cost active asset producing the commodity
108    #[serde(rename = "full")]
109    FullCost,
110    /// Use load-weighted average full cost across active assets producing the commodity
111    #[serde(rename = "full_average")]
112    FullCostAverage,
113    /// Commodities that should not have prices calculated
114    #[serde(rename = "unpriced")]
115    Unpriced,
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::time_slice::TimeSliceSelection;
122
123    #[test]
124    fn demand_map_works() {
125        let ts_selection = TimeSliceSelection::Single(TimeSliceID {
126            season: "all-year".into(),
127            time_of_day: "all-day".into(),
128        });
129        let value = Flow(0.25);
130        let mut map = DemandMap::new();
131        map.insert(("North".into(), 2020, ts_selection.clone()), value);
132
133        assert_eq!(map[&("North".into(), 2020, ts_selection)], value);
134    }
135
136    #[test]
137    fn commodity_levy_map_works() {
138        let ts = TimeSliceID {
139            season: "winter".into(),
140            time_of_day: "day".into(),
141        };
142        let value = MoneyPerFlow(0.5);
143        let mut map = CommodityLevyMap::new();
144        assert!(
145            map.insert(("GBR".into(), 2010, ts.clone()), value)
146                .is_none()
147        );
148        assert_eq!(map[&("GBR".into(), 2010, ts)], value);
149    }
150}