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