muse2/simulation/
prices.rs

1//! Code for updating the simulation state.
2use super::optimisation::Solution;
3use crate::commodity::CommodityID;
4use crate::model::Model;
5use crate::region::RegionID;
6use crate::time_slice::TimeSliceID;
7use crate::units::MoneyPerFlow;
8use itertools::iproduct;
9use std::collections::{BTreeMap, HashMap};
10
11/// A map relating commodity ID + region + time slice to current price (endogenous)
12#[derive(Default)]
13pub struct CommodityPrices(BTreeMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow>);
14
15impl CommodityPrices {
16    /// Calculate commodity prices based on the result of the dispatch optimisation and input data.
17    ///
18    /// Note that it is possible that there will still be commodities without specified prices, if
19    /// they were not included in the dispatch optimisation and no levies were specified by the
20    /// user.
21    pub fn calculate(model: &Model, solution: &Solution, year: u32) -> Self {
22        let mut prices = CommodityPrices::default();
23        prices.add_from_duals(solution);
24        prices.add_from_levies(model, year);
25
26        prices
27    }
28
29    /// Add commodity prices using activity and commodity balance duals.
30    ///
31    /// Commodity prices are calculated as the sum of the commodity balance duals and the highest
32    /// activity dual for each commodity/timeslice.
33    ///
34    /// # Arguments
35    ///
36    /// * `solution` - The solution to the dispatch optimisation
37    fn add_from_duals(&mut self, solution: &Solution) {
38        // Calculate highest activity dual for each commodity/region/timeslice
39        let mut highest_duals = HashMap::new();
40        for (asset, time_slice, dual) in solution.iter_activity_duals() {
41            // Iterate over all output flows
42            for flow in asset.iter_flows().filter(|flow| flow.is_output()) {
43                // Update the highest dual for this commodity/timeslice
44                highest_duals
45                    .entry((
46                        flow.commodity.id.clone(),
47                        asset.region_id.clone(),
48                        time_slice.clone(),
49                    ))
50                    .and_modify(|current_dual| {
51                        if dual > *current_dual {
52                            *current_dual = dual;
53                        }
54                    })
55                    .or_insert(dual);
56            }
57        }
58
59        // Add the highest activity dual for each commodity/region/timeslice to each commodity
60        // balance dual
61        for (commodity_id, region_id, time_slice, dual) in solution.iter_commodity_balance_duals() {
62            let key = (commodity_id.clone(), region_id.clone(), time_slice.clone());
63            let price = dual + highest_duals.get(&key).unwrap_or(&0.0);
64            self.0.insert(key, MoneyPerFlow(price));
65        }
66    }
67
68    /// Add prices based on levies/incentives.
69    ///
70    /// If a commodity already has a price based on the previous dual-based calculation, we choose
71    /// the higher of the two.
72    ///
73    /// # Arguments
74    ///
75    /// * `model` - The model
76    /// * `year` - The milestone year of interest
77    fn add_from_levies(&mut self, model: &Model, year: u32) {
78        for (region_id, time_slice) in
79            iproduct!(model.iter_regions(), model.time_slice_info.iter_ids())
80        {
81            let levy_key = (region_id.clone(), year, time_slice.clone());
82            for commodity in model.commodities.values() {
83                if let Some(levy) = commodity.levies.get(&levy_key) {
84                    let key = (commodity.id.clone(), region_id.clone(), time_slice.clone());
85                    self.0
86                        .entry(key)
87                        .and_modify(|price| *price = price.max(levy.value))
88                        .or_insert(levy.value);
89                }
90            }
91        }
92    }
93
94    /// Insert a price for the given commodity, region and time slice
95    pub fn insert(
96        &mut self,
97        commodity_id: &CommodityID,
98        region_id: &RegionID,
99        time_slice: &TimeSliceID,
100        price: MoneyPerFlow,
101    ) {
102        let key = (commodity_id.clone(), region_id.clone(), time_slice.clone());
103        self.0.insert(key, price);
104    }
105
106    /// Iterate over the map.
107    ///
108    /// # Returns
109    ///
110    /// An iterator of tuples containing commodity ID, region ID, time slice and price.
111    pub fn iter(
112        &self,
113    ) -> impl Iterator<Item = (&CommodityID, &RegionID, &TimeSliceID, MoneyPerFlow)> {
114        self.0
115            .iter()
116            .map(|((commodity_id, region_id, ts), price)| (commodity_id, region_id, ts, *price))
117    }
118}