muse2/simulation/investment/appraisal/
coefficients.rs

1//! Calculation of cost coefficients for investment tools.
2use super::costs::annual_fixed_cost;
3use crate::agent::ObjectiveType;
4use crate::asset::AssetRef;
5use crate::model::Model;
6use crate::simulation::CommodityPrices;
7use crate::time_slice::{TimeSliceID, TimeSliceInfo};
8use crate::units::{MoneyPerActivity, MoneyPerCapacity, MoneyPerFlow};
9use indexmap::IndexMap;
10use std::collections::HashMap;
11
12/// Map storing cost coefficients for an asset.
13///
14/// These are calculated according to the objective type of the agent owning the asset.
15/// Map storing coefficients for each variable
16pub struct ObjectiveCoefficients {
17    /// Cost per unit of capacity
18    pub capacity_coefficient: MoneyPerCapacity,
19    /// Cost per unit of activity in each time slice
20    pub activity_coefficients: IndexMap<TimeSliceID, MoneyPerActivity>,
21    /// Unmet demand coefficient
22    pub unmet_demand_coefficient: MoneyPerFlow,
23}
24
25/// Calculates cost coefficients for a set of assets for a given objective type.
26pub fn calculate_coefficients_for_assets(
27    model: &Model,
28    objective_type: &ObjectiveType,
29    assets: &[AssetRef],
30    prices: &CommodityPrices,
31    year: u32,
32) -> HashMap<AssetRef, ObjectiveCoefficients> {
33    assets
34        .iter()
35        .map(|asset| {
36            let coefficient = match objective_type {
37                ObjectiveType::LevelisedCostOfX => calculate_coefficients_for_lcox(
38                    asset,
39                    &model.time_slice_info,
40                    prices,
41                    model.parameters.value_of_lost_load,
42                    year,
43                ),
44                ObjectiveType::NetPresentValue => {
45                    calculate_coefficients_for_npv(asset, &model.time_slice_info, prices, year)
46                }
47            };
48            (asset.clone(), coefficient)
49        })
50        .collect()
51}
52
53/// Calculates the cost coefficients for LCOX.
54pub fn calculate_coefficients_for_lcox(
55    asset: &AssetRef,
56    time_slice_info: &TimeSliceInfo,
57    prices: &CommodityPrices,
58    value_of_lost_load: MoneyPerFlow,
59    year: u32,
60) -> ObjectiveCoefficients {
61    // Capacity coefficient
62    let capacity_coefficient = annual_fixed_cost(asset);
63
64    // Activity coefficients
65    let mut activity_coefficients = IndexMap::new();
66    for time_slice in time_slice_info.iter_ids() {
67        let coefficient = calculate_activity_coefficient_for_lcox(asset, time_slice, prices, year);
68        activity_coefficients.insert(time_slice.clone(), coefficient);
69    }
70
71    // Unmet demand coefficient
72    let unmet_demand_coefficient = value_of_lost_load;
73
74    ObjectiveCoefficients {
75        capacity_coefficient,
76        activity_coefficients,
77        unmet_demand_coefficient,
78    }
79}
80
81/// Calculates the cost coefficients for NPV.
82pub fn calculate_coefficients_for_npv(
83    asset: &AssetRef,
84    time_slice_info: &TimeSliceInfo,
85    prices: &CommodityPrices,
86    year: u32,
87) -> ObjectiveCoefficients {
88    // Capacity coefficient
89    let capacity_coefficient = -annual_fixed_cost(asset);
90
91    // Activity coefficients
92    let mut activity_coefficients = IndexMap::new();
93    for time_slice in time_slice_info.iter_ids() {
94        let coefficient = calculate_activity_coefficient_for_npv(asset, time_slice, prices, year);
95        activity_coefficients.insert(time_slice.clone(), coefficient);
96    }
97
98    // Unmet demand coefficient (we don't apply a cost to unmet demand, so we set this to zero)
99    let unmet_demand_coefficient = MoneyPerFlow(0.0);
100
101    ObjectiveCoefficients {
102        capacity_coefficient,
103        activity_coefficients,
104        unmet_demand_coefficient,
105    }
106}
107
108/// Calculate a single activity coefficient for the LCOX objective for a given time slice.
109fn calculate_activity_coefficient_for_lcox(
110    asset: &AssetRef,
111    time_slice: &TimeSliceID,
112    prices: &CommodityPrices,
113    year: u32,
114) -> MoneyPerActivity {
115    // Get the operating cost of the asset. This includes the variable operating cost, levies and
116    // flow costs, but excludes costs/revenues from commodity consumption/production.
117    let operating_cost = asset.get_operating_cost(year, time_slice);
118
119    // Revenue from flows excluding the primary output
120    let revenue_from_flows = asset.get_revenue_from_flows_excluding_primary(prices, time_slice);
121
122    // The activity coefficient is the operating cost minus the revenue from flows
123    operating_cost - revenue_from_flows
124}
125
126/// Calculate a single activity coefficient for the NPV objective for a given time slice.
127fn calculate_activity_coefficient_for_npv(
128    asset: &AssetRef,
129    time_slice: &TimeSliceID,
130    prices: &CommodityPrices,
131    year: u32,
132) -> MoneyPerActivity {
133    // Get the operating cost of the asset. This includes the variable operating cost, levies and
134    // flow costs, but excludes costs/revenues from commodity consumption/production.
135    let operating_cost = asset.get_operating_cost(year, time_slice);
136
137    // Revenue from flows including the primary output
138    let revenue_from_flows = asset.get_revenue_from_flows(prices, time_slice);
139
140    // The activity coefficient is the revenue from flows minus the operating cost
141    revenue_from_flows - operating_cost
142}