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