1use crate::asset::{AssetPool, AssetRef};
3use crate::commodity::CommodityID;
4use crate::model::{Model, PricingStrategy};
5use crate::process::ProcessFlow;
6use crate::region::RegionID;
7use crate::simulation::optimisation::Solution;
8use crate::time_slice::{TimeSliceID, TimeSliceInfo};
9use crate::units::{MoneyPerActivity, MoneyPerFlow};
10use itertools::iproduct;
11use std::collections::{BTreeMap, HashMap};
12
13pub type ReducedCosts = HashMap<(AssetRef, TimeSliceID), MoneyPerActivity>;
15
16pub fn get_prices_and_reduced_costs(
21 model: &Model,
22 solution: &Solution,
23 assets: &AssetPool,
24 year: u32,
25) -> (CommodityPrices, ReducedCosts) {
26 let shadow_prices = CommodityPrices::from_iter(solution.iter_commodity_balance_duals());
27 let reduced_costs_for_candidates: HashMap<_, _> = solution
28 .iter_reduced_costs_for_candidates()
29 .map(|(asset, time_slice, cost)| ((asset.clone(), time_slice.clone()), cost))
30 .collect();
31
32 let (prices, reduced_costs_for_candidates) = match model.parameters.pricing_strategy {
33 PricingStrategy::ShadowPrices => (
35 shadow_prices.with_levies(model, year),
36 reduced_costs_for_candidates,
37 ),
38 PricingStrategy::ScarcityAdjusted => {
40 let adjusted_prices = shadow_prices
41 .clone()
42 .with_scarcity_adjustment(solution.iter_activity_duals())
43 .with_levies(model, year);
44 let unadjusted_prices = shadow_prices.with_levies(model, year);
45 let mut reduced_costs_for_candidates = reduced_costs_for_candidates;
46
47 remove_scarcity_influence_from_candidate_reduced_costs(
49 &mut reduced_costs_for_candidates,
50 &adjusted_prices,
51 &unadjusted_prices,
52 );
53
54 (adjusted_prices, reduced_costs_for_candidates)
55 }
56 };
57
58 let mut reduced_costs = reduced_costs_for_candidates;
60 reduced_costs.extend(reduced_costs_for_existing(
61 &model.time_slice_info,
62 assets,
63 &prices,
64 year,
65 ));
66
67 (prices, reduced_costs)
68}
69
70#[derive(Default, Clone)]
72pub struct CommodityPrices(BTreeMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow>);
73
74impl CommodityPrices {
75 fn with_levies(mut self, model: &Model, year: u32) -> Self {
85 for (region_id, time_slice) in
86 iproduct!(model.iter_regions(), model.time_slice_info.iter_ids())
87 {
88 let levy_key = (region_id.clone(), year, time_slice.clone());
89 for commodity in model.commodities.values() {
90 if let Some(levy) = commodity.levies.get(&levy_key) {
91 let key = (commodity.id.clone(), region_id.clone(), time_slice.clone());
92 self.0
93 .entry(key)
94 .and_modify(|price| *price = price.max(levy.value))
95 .or_insert(levy.value);
96 }
97 }
98 }
99
100 self
101 }
102
103 fn with_scarcity_adjustment<'a, I>(mut self, activity_duals: I) -> Self
109 where
110 I: Iterator<Item = (&'a AssetRef, &'a TimeSliceID, MoneyPerActivity)>,
111 {
112 let highest_duals = get_highest_activity_duals(activity_duals);
113
114 for (key, highest) in highest_duals.iter() {
117 if let Some(price) = self.0.get_mut(key) {
118 *price += MoneyPerFlow(highest.value());
120 }
121 }
122
123 self
124 }
125
126 pub fn insert(
128 &mut self,
129 commodity_id: &CommodityID,
130 region_id: &RegionID,
131 time_slice: &TimeSliceID,
132 price: MoneyPerFlow,
133 ) {
134 let key = (commodity_id.clone(), region_id.clone(), time_slice.clone());
135 self.0.insert(key, price);
136 }
137
138 pub fn iter(
144 &self,
145 ) -> impl Iterator<Item = (&CommodityID, &RegionID, &TimeSliceID, MoneyPerFlow)> {
146 self.0
147 .iter()
148 .map(|((commodity_id, region_id, ts), price)| (commodity_id, region_id, ts, *price))
149 }
150
151 pub fn get(
153 &self,
154 commodity_id: &CommodityID,
155 region_id: &RegionID,
156 time_slice: &TimeSliceID,
157 ) -> Option<MoneyPerFlow> {
158 self.0
159 .get(&(commodity_id.clone(), region_id.clone(), time_slice.clone()))
160 .copied()
161 }
162}
163
164impl<'a> FromIterator<(&'a CommodityID, &'a RegionID, &'a TimeSliceID, MoneyPerFlow)>
165 for CommodityPrices
166{
167 fn from_iter<I>(iter: I) -> Self
168 where
169 I: IntoIterator<Item = (&'a CommodityID, &'a RegionID, &'a TimeSliceID, MoneyPerFlow)>,
170 {
171 let map = iter
172 .into_iter()
173 .map(|(commodity_id, region_id, time_slice, price)| {
174 (
175 (commodity_id.clone(), region_id.clone(), time_slice.clone()),
176 price,
177 )
178 })
179 .collect();
180 CommodityPrices(map)
181 }
182}
183
184fn get_highest_activity_duals<'a, I>(
185 activity_duals: I,
186) -> HashMap<(CommodityID, RegionID, TimeSliceID), MoneyPerActivity>
187where
188 I: Iterator<Item = (&'a AssetRef, &'a TimeSliceID, MoneyPerActivity)>,
189{
190 let mut highest_duals = HashMap::new();
192 for (asset, time_slice, dual) in activity_duals {
193 for flow in asset.iter_flows().filter(|flow| flow.is_output()) {
195 highest_duals
197 .entry((
198 flow.commodity.id.clone(),
199 asset.region_id.clone(),
200 time_slice.clone(),
201 ))
202 .and_modify(|current_dual| {
203 if dual > *current_dual {
204 *current_dual = dual;
205 }
206 })
207 .or_insert(dual);
208 }
209 }
210
211 highest_duals
212}
213
214fn remove_scarcity_influence_from_candidate_reduced_costs(
216 reduced_costs: &mut ReducedCosts,
217 adjusted_prices: &CommodityPrices,
218 unadjusted_prices: &CommodityPrices,
219) {
220 for ((asset, time_slice), cost) in reduced_costs.iter_mut() {
221 *cost += asset
222 .iter_flows()
223 .map(|flow| {
224 get_scarcity_adjustment(
225 flow,
226 &asset.region_id,
227 time_slice,
228 adjusted_prices,
229 unadjusted_prices,
230 )
231 })
232 .sum();
233 }
234}
235
236fn get_scarcity_adjustment(
240 flow: &ProcessFlow,
241 region_id: &RegionID,
242 time_slice: &TimeSliceID,
243 adjusted_prices: &CommodityPrices,
244 unadjusted_prices: &CommodityPrices,
245) -> MoneyPerActivity {
246 let adjusted = adjusted_prices
247 .get(&flow.commodity.id, region_id, time_slice)
248 .expect("No adjusted price found");
249 let unadjusted = unadjusted_prices
250 .get(&flow.commodity.id, region_id, time_slice)
251 .expect("No unadjusted price found");
252 flow.coeff * (unadjusted - adjusted)
253}
254
255fn reduced_costs_for_existing<'a>(
257 time_slice_info: &'a TimeSliceInfo,
258 assets: &'a AssetPool,
259 prices: &'a CommodityPrices,
260 year: u32,
261) -> impl Iterator<Item = ((AssetRef, TimeSliceID), MoneyPerActivity)> + 'a {
262 iproduct!(assets.iter(), time_slice_info.iter_ids()).map(move |(asset, time_slice)| {
263 let operating_cost = asset.get_operating_cost(year, time_slice);
264 let revenue_from_flows = asset
265 .iter_flows()
266 .map(|flow| {
267 flow.coeff
268 * prices
269 .get(&flow.commodity.id, &asset.region_id, time_slice)
270 .unwrap()
271 })
272 .sum();
273 let reduced_cost = operating_cost - revenue_from_flows;
274
275 ((asset.clone(), time_slice.clone()), reduced_cost)
276 })
277}