muse2/simulation/investment/
appraisal.rs1use 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::units::Capacity;
9use anyhow::Result;
10use std::cmp::Ordering;
11
12pub mod coefficients;
13mod constraints;
14mod costs;
15mod optimisation;
16use coefficients::ObjectiveCoefficients;
17use float_cmp::approx_eq;
18use optimisation::perform_optimisation;
19
20pub struct AppraisalOutput {
22 pub asset: AssetRef,
24 pub capacity: Capacity,
26 pub unmet_demand: DemandMap,
28 pub metric: f64,
30}
31
32impl AppraisalOutput {
33 pub fn compare_metric(&self, other: &Self) -> Ordering {
42 assert!(
43 !(self.metric.is_nan() || other.metric.is_nan()),
44 "Appraisal metric cannot be NaN"
45 );
46
47 if approx_eq!(f64, self.metric, other.metric) {
48 Ordering::Equal
49 } else {
50 self.metric.partial_cmp(&other.metric).unwrap()
51 }
52 }
53}
54
55fn calculate_lcox(
60 model: &Model,
61 asset: &AssetRef,
62 max_capacity: Option<Capacity>,
63 commodity: &Commodity,
64 coefficients: &ObjectiveCoefficients,
65 demand: &DemandMap,
66) -> Result<AppraisalOutput> {
67 let results = perform_optimisation(
69 asset,
70 max_capacity,
71 commodity,
72 coefficients,
73 demand,
74 &model.time_slice_info,
75 highs::Sense::Minimise,
76 )?;
77
78 let annual_fixed_cost = coefficients.capacity_coefficient;
80 let activity_costs = &coefficients.activity_coefficients;
81 let cost_index = lcox(
82 results.capacity,
83 annual_fixed_cost,
84 &results.activity,
85 activity_costs,
86 );
87
88 Ok(AppraisalOutput {
90 asset: asset.clone(),
91 capacity: results.capacity,
92 unmet_demand: results.unmet_demand,
93 metric: cost_index.value(),
94 })
95}
96
97fn calculate_npv(
99 model: &Model,
100 asset: &AssetRef,
101 max_capacity: Option<Capacity>,
102 commodity: &Commodity,
103 coefficients: &ObjectiveCoefficients,
104 demand: &DemandMap,
105) -> Result<AppraisalOutput> {
106 let results = perform_optimisation(
108 asset,
109 max_capacity,
110 commodity,
111 coefficients,
112 demand,
113 &model.time_slice_info,
114 highs::Sense::Maximise,
115 )?;
116
117 let annual_fixed_cost = -coefficients.capacity_coefficient;
119 let activity_surpluses = &coefficients.activity_coefficients;
120 let profitability_index = profitability_index(
121 results.capacity,
122 annual_fixed_cost,
123 &results.activity,
124 activity_surpluses,
125 );
126
127 Ok(AppraisalOutput {
130 asset: asset.clone(),
131 capacity: results.capacity,
132 unmet_demand: results.unmet_demand,
133 metric: -profitability_index.value(),
134 })
135}
136
137pub fn appraise_investment(
139 model: &Model,
140 asset: &AssetRef,
141 max_capacity: Option<Capacity>,
142 commodity: &Commodity,
143 objective_type: &ObjectiveType,
144 coefficients: &ObjectiveCoefficients,
145 demand: &DemandMap,
146) -> Result<AppraisalOutput> {
147 let appraisal_method = match objective_type {
148 ObjectiveType::LevelisedCostOfX => calculate_lcox,
149 ObjectiveType::NetPresentValue => calculate_npv,
150 };
151 appraisal_method(model, asset, max_capacity, commodity, coefficients, demand)
152}