Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6a8b391
refactor and remove capacity term from npv objective
Aurashk Nov 11, 2025
b470397
move annual fixed cost calculation
Aurashk Nov 13, 2025
6560d41
Check flows in milestone years only
dalonsoa Nov 10, 2025
9e7281f
Remove unnecessary check
dalonsoa Nov 10, 2025
6e9c0fa
Check only milestone y within process range of activity
dalonsoa Nov 11, 2025
3c74d56
white_check_mark: Update tests to cover new functionality
dalonsoa Nov 11, 2025
d3f4c75
Add output file for unmet demand
tsmbland Nov 3, 2025
4116dbd
Add (empty) unmet demand file
tsmbland Nov 3, 2025
4a354bb
Give timeslice level output data for appraisal
tsmbland Nov 5, 2025
878b1b5
Delete unused import
tsmbland Nov 5, 2025
415972a
Check flows in milestone years only
dalonsoa Nov 10, 2025
51fb6fb
Remove unnecessary check
dalonsoa Nov 10, 2025
f114085
Check only milestone y within process range of activity
dalonsoa Nov 11, 2025
9ca4348
white_check_mark: Update tests to cover new functionality
dalonsoa Nov 11, 2025
9482fcb
Give timeslice level output data for appraisal
tsmbland Nov 5, 2025
727f4bd
Delete unused import
tsmbland Nov 5, 2025
8f28ded
Check flows in milestone years only
dalonsoa Nov 10, 2025
b5e6aff
Remove unnecessary check
dalonsoa Nov 10, 2025
bc37c16
Check only milestone y within process range of activity
dalonsoa Nov 11, 2025
0fa5b92
white_check_mark: Update tests to cover new functionality
dalonsoa Nov 11, 2025
aa5e1be
Give timeslice level output data for appraisal
tsmbland Nov 5, 2025
c49bd58
Delete unused import
tsmbland Nov 5, 2025
0f2a801
Merge branch 'main' into remove-capacity-spend-term-from-npv-objective
Aurashk Nov 13, 2025
819ce80
simplify
Aurashk Nov 13, 2025
ed29667
remove too many arguments supression
Aurashk Nov 13, 2025
aab4fd9
make variable map members private
Aurashk Nov 13, 2025
bad4729
update docs
Aurashk Nov 17, 2025
8f6931b
Merge branch 'main' into remove-capacity-spend-term-from-npv-objective
Aurashk Nov 17, 2025
01fbfde
update docs npv epsilon
Aurashk Nov 17, 2025
f22be2a
remove unused variable
Aurashk Nov 17, 2025
3c96831
Merge branch 'main' into remove-capacity-spend-term-from-npv-objective
Aurashk Nov 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/model/investment.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,15 @@ operational constraints (e.g., minimum load levels) and the balance level of the

- **Optimise capacity and dispatch to maximise annualised profit:** Solve a small optimisation
sub-problem to maximise the asset’s surplus, subject to its operational rules and the specific
demand tranche it is being asked to serve.
demand tranche it is being asked to serve. \\(\varepsilon \approx 1×10^{-14}\\) is added to each
\\(AC_t \\) to allow assets which are breakeven (or very close to breakeven) to be dispatched.

\\[
maximise \Big\\{ -AFC \* cap - \sum_t act_t \* AC_t
maximise \Big\\{ - \sum_t act_t \* (AC_t + \varepsilon)
\Big\\}
\\]

Where \\( cap \\) and \\( act_t \\) are decision variables, and subject to:
Where \\( act_t \\) is a decision variable, and subject to:

- The asset operational constraints (e.g., \\( avail_{LB}, avail_{EQ} \\), etc.), activity less
than capacity, applied to its activity profile \\( act_t \\).
Expand Down
3 changes: 2 additions & 1 deletion src/simulation/investment/appraisal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::model::Model;
use crate::time_slice::TimeSliceID;
use crate::units::{Activity, Capacity};
use anyhow::Result;
use costs::annual_fixed_cost;
use indexmap::IndexMap;
use std::cmp::Ordering;

Expand Down Expand Up @@ -126,7 +127,7 @@ fn calculate_npv(
)?;

// Calculate profitability index for the hypothetical investment
let annual_fixed_cost = -coefficients.capacity_coefficient;
let annual_fixed_cost = annual_fixed_cost(asset);
let activity_surpluses = &coefficients.activity_coefficients;
let profitability_index = profitability_index(
results.capacity,
Expand Down
5 changes: 1 addition & 4 deletions src/simulation/investment/appraisal/coefficients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,6 @@ pub fn calculate_coefficients_for_npv(
// assets are still dispatched
const EPSILON_ACTIVITY_COEFFICIENT: MoneyPerActivity = MoneyPerActivity(f64::EPSILON * 100.0);

// Capacity coefficient
let capacity_coefficient = -annual_fixed_cost(asset);

// Activity coefficients
let mut activity_coefficients = IndexMap::new();
for time_slice in time_slice_info.iter_ids() {
Expand All @@ -107,7 +104,7 @@ pub fn calculate_coefficients_for_npv(
let unmet_demand_coefficient = MoneyPerFlow(0.0);

ObjectiveCoefficients {
capacity_coefficient,
capacity_coefficient: MoneyPerCapacity(0.0),
activity_coefficients,
unmet_demand_coefficient,
}
Expand Down
73 changes: 40 additions & 33 deletions src/simulation/investment/appraisal/optimisation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Optimisation problem for investment tools.
use super::DemandMap;
use super::coefficients::ObjectiveCoefficients;
use super::ObjectiveCoefficients;
use super::constraints::{
add_activity_constraints, add_capacity_constraint, add_demand_constraints,
};
Expand All @@ -22,10 +22,46 @@ struct VariableMap {
capacity_var: Variable,
/// Activity variables in each time slice
activity_vars: IndexMap<TimeSliceID, Variable>,
// Unmet demand variables
/// Unmet demand variables
unmet_demand_vars: IndexMap<TimeSliceID, Variable>,
}

impl VariableMap {
/// Creates a new variable map by adding variables to the optimisation problem.
///
/// # Arguments
/// * `problem` - The optimisation problem to add variables to
/// * `cost_coefficients` - Objective function coefficients for each variable
///
/// # Returns
/// A new `VariableMap` containing all created decision variables
fn add_to_problem(problem: &mut Problem, cost_coefficients: &ObjectiveCoefficients) -> Self {
// Create capacity variable with its associated cost
let capacity_var =
problem.add_column(cost_coefficients.capacity_coefficient.value(), 0.0..);

// Create activity variables for each time slice
let mut activity_vars = IndexMap::new();
for (time_slice, cost) in &cost_coefficients.activity_coefficients {
let var = problem.add_column(cost.value(), 0.0..);
activity_vars.insert(time_slice.clone(), var);
}

// Create unmet demand variables for each time slice
let mut unmet_demand_vars = IndexMap::new();
for time_slice in cost_coefficients.activity_coefficients.keys() {
let var = problem.add_column(cost_coefficients.unmet_demand_coefficient.value(), 0.0..);
unmet_demand_vars.insert(time_slice.clone(), var);
}

Self {
capacity_var,
activity_vars,
unmet_demand_vars,
}
}
}

/// Map containing optimisation results and coefficients
pub struct ResultsMap {
/// Capacity variable
Expand All @@ -36,33 +72,6 @@ pub struct ResultsMap {
pub unmet_demand: DemandMap,
}

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

// Create activity variables
let mut activity_vars = IndexMap::new();
for (time_slice, cost) in &cost_coefficients.activity_coefficients {
let var = problem.add_column(cost.value(), 0.0..);
activity_vars.insert(time_slice.clone(), var);
}

// Create unmet demand variables
// One per time slice, all of which use the same coefficient
let mut unmet_demand_vars = IndexMap::new();
for time_slice in cost_coefficients.activity_coefficients.keys() {
let var = problem.add_column(cost_coefficients.unmet_demand_coefficient.value(), 0.0..);
unmet_demand_vars.insert(time_slice.clone(), var);
}

VariableMap {
capacity_var,
activity_vars,
unmet_demand_vars,
}
}

/// Adds constraints to the problem.
fn add_constraints(
problem: &mut Problem,
Expand Down Expand Up @@ -103,11 +112,9 @@ pub fn perform_optimisation(
time_slice_info: &TimeSliceInfo,
sense: Sense,
) -> Result<ResultsMap> {
// Set up problem
// Create problem and add variables
let mut problem = Problem::default();

// Add variables
let variables = add_variables(&mut problem, coefficients);
let variables = VariableMap::add_to_problem(&mut problem, coefficients);

// Add constraints
add_constraints(
Expand Down