Skip to content

Commit 8a495f3

Browse files
authored
Merge pull request #888 from EnergySystemsModellingLab/precompute_coefficients
Precompute investment objective coefficients
2 parents 1cacfaf + 44038aa commit 8a495f3

4 files changed

Lines changed: 61 additions & 36 deletions

File tree

src/simulation/investment.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::collections::HashMap;
1818

1919
pub mod appraisal;
2020
use appraisal::appraise_investment;
21+
use appraisal::coefficients::calculate_coefficients_for_assets;
2122

2223
/// A map of demand across time slices for a specific commodity and region
2324
type DemandMap = IndexMap<TimeSliceID, Flow>;
@@ -364,7 +365,11 @@ fn select_best_assets(
364365
year: u32,
365366
writer: &mut DataWriter,
366367
) -> Result<Vec<AssetRef>> {
367-
let mut best_assets: Vec<AssetRef> = Vec::new();
368+
let objective_type = &agent.objectives[&year];
369+
370+
// Calculate coefficients for all asset options according to the agent's objective
371+
let coefficients =
372+
calculate_coefficients_for_assets(model, objective_type, &opt_assets, reduced_costs);
368373

369374
let mut remaining_candidate_capacity = HashMap::from_iter(
370375
opt_assets
@@ -374,7 +379,7 @@ fn select_best_assets(
374379
);
375380

376381
let mut round = 0;
377-
let objective_type = &agent.objectives[&year];
382+
let mut best_assets: Vec<AssetRef> = Vec::new();
378383
while is_any_remaining_demand(&demand) {
379384
ensure!(
380385
!opt_assets.is_empty(),
@@ -397,7 +402,7 @@ fn select_best_assets(
397402
max_capacity,
398403
commodity,
399404
objective_type,
400-
reduced_costs,
405+
&coefficients[asset],
401406
&demand,
402407
)?;
403408

src/simulation/investment/appraisal.rs

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ use crate::asset::AssetRef;
55
use crate::commodity::Commodity;
66
use crate::finance::{lcox, profitability_index};
77
use crate::model::Model;
8-
use crate::simulation::prices::ReducedCosts;
98
use crate::units::Capacity;
109
use anyhow::Result;
1110

12-
mod coefficients;
11+
pub mod coefficients;
1312
mod constraints;
1413
mod costs;
1514
mod optimisation;
16-
use coefficients::{calculate_coefficients_for_lcox, calculate_coefficients_for_npv};
15+
use coefficients::ObjectiveCoefficients;
1716
use optimisation::perform_optimisation;
1817

1918
/// The output of investment appraisal required to compare potential investment decisions
@@ -37,36 +36,28 @@ fn calculate_lcox(
3736
asset: &AssetRef,
3837
max_capacity: Option<Capacity>,
3938
commodity: &Commodity,
40-
reduced_costs: &ReducedCosts,
39+
coefficients: &ObjectiveCoefficients,
4140
demand: &DemandMap,
4241
) -> 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-
5142
// Perform optimisation to calculate capacity, activity and unmet demand
5243
let results = perform_optimisation(
5344
asset,
5445
max_capacity,
5546
commodity,
56-
&coefficients,
47+
coefficients,
5748
demand,
5849
&model.time_slice_info,
5950
highs::Sense::Minimise,
6051
)?;
6152

6253
// Calculate LCOX for the hypothetical investment
6354
let annual_fixed_cost = coefficients.capacity_coefficient;
64-
let activity_costs = coefficients.activity_coefficients;
55+
let activity_costs = &coefficients.activity_coefficients;
6556
let cost_index = lcox(
6657
results.capacity,
6758
annual_fixed_cost,
6859
&results.activity,
69-
&activity_costs,
60+
activity_costs,
7061
);
7162

7263
// Return appraisal output
@@ -84,31 +75,28 @@ fn calculate_npv(
8475
asset: &AssetRef,
8576
max_capacity: Option<Capacity>,
8677
commodity: &Commodity,
87-
reduced_costs: &ReducedCosts,
78+
coefficients: &ObjectiveCoefficients,
8879
demand: &DemandMap,
8980
) -> Result<AppraisalOutput> {
90-
// Calculate coefficients
91-
let coefficients = calculate_coefficients_for_npv(asset, &model.time_slice_info, reduced_costs);
92-
9381
// Perform optimisation to calculate capacity, activity and unmet demand
9482
let results = perform_optimisation(
9583
asset,
9684
max_capacity,
9785
commodity,
98-
&coefficients,
86+
coefficients,
9987
demand,
10088
&model.time_slice_info,
10189
highs::Sense::Maximise,
10290
)?;
10391

10492
// Calculate profitability index for the hypothetical investment
10593
let annual_fixed_cost = -coefficients.capacity_coefficient;
106-
let activity_surpluses = coefficients.activity_coefficients;
94+
let activity_surpluses = &coefficients.activity_coefficients;
10795
let profitability_index = profitability_index(
10896
results.capacity,
10997
annual_fixed_cost,
11098
&results.activity,
111-
&activity_surpluses,
99+
activity_surpluses,
112100
);
113101

114102
// Return appraisal output
@@ -128,12 +116,12 @@ pub fn appraise_investment(
128116
max_capacity: Option<Capacity>,
129117
commodity: &Commodity,
130118
objective_type: &ObjectiveType,
131-
reduced_costs: &ReducedCosts,
119+
coefficients: &ObjectiveCoefficients,
132120
demand: &DemandMap,
133121
) -> Result<AppraisalOutput> {
134122
let appraisal_method = match objective_type {
135123
ObjectiveType::LevelisedCostOfX => calculate_lcox,
136124
ObjectiveType::NetPresentValue => calculate_npv,
137125
};
138-
appraisal_method(model, asset, max_capacity, commodity, reduced_costs, demand)
126+
appraisal_method(model, asset, max_capacity, commodity, coefficients, demand)
139127
}

src/simulation/investment/appraisal/coefficients.rs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,60 @@
11
//! Calculation of cost coefficients for investment tools.
22
use super::costs::{activity_cost, activity_surplus, annual_fixed_cost};
3+
use crate::agent::ObjectiveType;
34
use crate::asset::AssetRef;
5+
use crate::model::Model;
46
use crate::simulation::prices::ReducedCosts;
57
use crate::time_slice::{TimeSliceID, TimeSliceInfo};
68
use crate::units::{MoneyPerActivity, MoneyPerCapacity, MoneyPerFlow};
79
use indexmap::IndexMap;
10+
use std::collections::HashMap;
811

12+
/// Map storing cost coefficients for an asset.
13+
///
14+
/// These are calculated according to the objective type of the agent owning the asset.
915
/// Map storing coefficients for each variable
10-
pub struct CoefficientsMap {
16+
pub struct ObjectiveCoefficients {
1117
/// Cost per unit of capacity
1218
pub capacity_coefficient: MoneyPerCapacity,
1319
/// Cost per unit of activity in each time slice
1420
pub activity_coefficients: IndexMap<TimeSliceID, MoneyPerActivity>,
15-
// Unmet demand coefficient
21+
/// Unmet demand coefficient
1622
pub unmet_demand_coefficient: MoneyPerFlow,
1723
}
1824

25+
/// Calculates cost coefficients for a set of assets for a given objective type.
26+
pub fn calculate_coefficients_for_assets(
27+
model: &Model,
28+
objective_type: &ObjectiveType,
29+
assets: &[AssetRef],
30+
reduced_costs: &ReducedCosts,
31+
) -> HashMap<AssetRef, ObjectiveCoefficients> {
32+
assets
33+
.iter()
34+
.map(|asset| {
35+
let coefficient = match objective_type {
36+
ObjectiveType::LevelisedCostOfX => calculate_coefficients_for_lcox(
37+
asset,
38+
&model.time_slice_info,
39+
reduced_costs,
40+
model.parameters.value_of_lost_load,
41+
),
42+
ObjectiveType::NetPresentValue => {
43+
calculate_coefficients_for_npv(asset, &model.time_slice_info, reduced_costs)
44+
}
45+
};
46+
(asset.clone(), coefficient)
47+
})
48+
.collect()
49+
}
50+
1951
/// Calculates the cost coefficients for LCOX.
2052
pub fn calculate_coefficients_for_lcox(
2153
asset: &AssetRef,
2254
time_slice_info: &TimeSliceInfo,
2355
reduced_costs: &ReducedCosts,
2456
value_of_lost_load: MoneyPerFlow,
25-
) -> CoefficientsMap {
57+
) -> ObjectiveCoefficients {
2658
// Capacity coefficient
2759
let capacity_coefficient = annual_fixed_cost(asset);
2860

@@ -36,7 +68,7 @@ pub fn calculate_coefficients_for_lcox(
3668
// Unmet demand coefficient
3769
let unmet_demand_coefficient = value_of_lost_load;
3870

39-
CoefficientsMap {
71+
ObjectiveCoefficients {
4072
capacity_coefficient,
4173
activity_coefficients,
4274
unmet_demand_coefficient,
@@ -48,7 +80,7 @@ pub fn calculate_coefficients_for_npv(
4880
asset: &AssetRef,
4981
time_slice_info: &TimeSliceInfo,
5082
reduced_costs: &ReducedCosts,
51-
) -> CoefficientsMap {
83+
) -> ObjectiveCoefficients {
5284
// Capacity coefficient
5385
let capacity_coefficient = -annual_fixed_cost(asset);
5486

@@ -62,7 +94,7 @@ pub fn calculate_coefficients_for_npv(
6294
// Unmet demand coefficient (we don't apply a cost to unmet demand, so we set this to zero)
6395
let unmet_demand_coefficient = MoneyPerFlow(0.0);
6496

65-
CoefficientsMap {
97+
ObjectiveCoefficients {
6698
capacity_coefficient,
6799
activity_coefficients,
68100
unmet_demand_coefficient,

src/simulation/investment/appraisal/optimisation.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Optimisation problem for investment tools.
22
use super::DemandMap;
3-
use super::coefficients::CoefficientsMap;
3+
use super::coefficients::ObjectiveCoefficients;
44
use super::constraints::{
55
add_activity_constraints, add_capacity_constraint, add_demand_constraints,
66
};
@@ -37,7 +37,7 @@ pub struct ResultsMap {
3737
}
3838

3939
/// Add variables to the problem based on cost coefficients
40-
fn add_variables(problem: &mut Problem, cost_coefficients: &CoefficientsMap) -> VariableMap {
40+
fn add_variables(problem: &mut Problem, cost_coefficients: &ObjectiveCoefficients) -> VariableMap {
4141
// Create capacity variable
4242
let capacity_var = problem.add_column(cost_coefficients.capacity_coefficient.value(), 0.0..);
4343

@@ -98,7 +98,7 @@ pub fn perform_optimisation(
9898
asset: &AssetRef,
9999
max_capacity: Option<Capacity>,
100100
commodity: &Commodity,
101-
coefficients: &CoefficientsMap,
101+
coefficients: &ObjectiveCoefficients,
102102
demand: &DemandMap,
103103
time_slice_info: &TimeSliceInfo,
104104
sense: Sense,

0 commit comments

Comments
 (0)