muse2/simulation/investment/
appraisal.rs

1//! Calculation for investment tools such as Levelised Cost of X (LCOX) and Net Present Value (NPV).
2use super::DemandMap;
3use crate::agent::ObjectiveType;
4use crate::asset::AssetRef;
5use crate::commodity::Commodity;
6use crate::finance::{lcox, profitability_index};
7use crate::model::Model;
8use crate::simulation::prices::ReducedCosts;
9use crate::units::Capacity;
10use anyhow::Result;
11
12mod coefficients;
13mod constraints;
14mod costs;
15mod optimisation;
16use coefficients::{calculate_coefficients_for_lcox, calculate_coefficients_for_npv};
17use optimisation::perform_optimisation;
18
19/// The output of investment appraisal required to compare potential investment decisions
20pub struct AppraisalOutput {
21    /// The asset being appraised
22    pub asset: AssetRef,
23    /// The hypothetical capacity to install
24    pub capacity: Capacity,
25    /// The hypothetical unmet demand following investment in this asset
26    pub unmet_demand: DemandMap,
27    /// The comparison metric to compare investment decisions (lower is better)
28    pub metric: f64,
29}
30
31/// Calculate LCOX for a hypothetical investment in the given asset.
32///
33/// This is more commonly referred to as Levelised Cost of *Electricity*, but as the model can
34/// include other flows, we use the term LCOX.
35fn calculate_lcox(
36    model: &Model,
37    asset: &AssetRef,
38    max_capacity: Option<Capacity>,
39    commodity: &Commodity,
40    reduced_costs: &ReducedCosts,
41    demand: &DemandMap,
42) -> Result<AppraisalOutput> {
43    // Calculate coefficients
44    let coefficients = calculate_coefficients_for_lcox(
45        asset,
46        &model.time_slice_info,
47        reduced_costs,
48        model.parameters.value_of_lost_load,
49    );
50
51    // Perform optimisation to calculate capacity, activity and unmet demand
52    let results = perform_optimisation(
53        asset,
54        max_capacity,
55        commodity,
56        &coefficients,
57        demand,
58        &model.time_slice_info,
59        highs::Sense::Minimise,
60    )?;
61
62    // Calculate LCOX for the hypothetical investment
63    let annual_fixed_cost = coefficients.capacity_coefficient;
64    let activity_costs = coefficients.activity_coefficients;
65    let cost_index = lcox(
66        results.capacity,
67        annual_fixed_cost,
68        &results.activity,
69        &activity_costs,
70    );
71
72    // Return appraisal output
73    Ok(AppraisalOutput {
74        asset: asset.clone(),
75        capacity: results.capacity,
76        unmet_demand: results.unmet_demand,
77        metric: cost_index.value(),
78    })
79}
80
81/// Calculate NPV for a hypothetical investment in the given asset.
82fn calculate_npv(
83    model: &Model,
84    asset: &AssetRef,
85    max_capacity: Option<Capacity>,
86    commodity: &Commodity,
87    reduced_costs: &ReducedCosts,
88    demand: &DemandMap,
89) -> Result<AppraisalOutput> {
90    // Calculate coefficients
91    let coefficients = calculate_coefficients_for_npv(asset, &model.time_slice_info, reduced_costs);
92
93    // Perform optimisation to calculate capacity, activity and unmet demand
94    let results = perform_optimisation(
95        asset,
96        max_capacity,
97        commodity,
98        &coefficients,
99        demand,
100        &model.time_slice_info,
101        highs::Sense::Maximise,
102    )?;
103
104    // Calculate profitability index for the hypothetical investment
105    let annual_fixed_cost = -coefficients.capacity_coefficient;
106    let activity_surpluses = coefficients.activity_coefficients;
107    let profitability_index = profitability_index(
108        results.capacity,
109        annual_fixed_cost,
110        &results.activity,
111        &activity_surpluses,
112    );
113
114    // Return appraisal output
115    // Higher profitability index is better, so we make it negative for comparison
116    Ok(AppraisalOutput {
117        asset: asset.clone(),
118        capacity: results.capacity,
119        unmet_demand: results.unmet_demand,
120        metric: -profitability_index.value(),
121    })
122}
123
124/// Appraise the given investment with the specified objective type
125pub fn appraise_investment(
126    model: &Model,
127    asset: &AssetRef,
128    max_capacity: Option<Capacity>,
129    commodity: &Commodity,
130    objective_type: &ObjectiveType,
131    reduced_costs: &ReducedCosts,
132    demand: &DemandMap,
133) -> Result<AppraisalOutput> {
134    let appraisal_method = match objective_type {
135        ObjectiveType::LevelisedCostOfX => calculate_lcox,
136        ObjectiveType::NetPresentValue => calculate_npv,
137    };
138    appraisal_method(model, asset, max_capacity, commodity, reduced_costs, demand)
139}