diff --git a/docs/model/investment.md b/docs/model/investment.md index 1b9c0c793..134d90710 100644 --- a/docs/model/investment.md +++ b/docs/model/investment.md @@ -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 \\). diff --git a/src/simulation/investment/appraisal.rs b/src/simulation/investment/appraisal.rs index e768ab2ed..6a250f1ac 100644 --- a/src/simulation/investment/appraisal.rs +++ b/src/simulation/investment/appraisal.rs @@ -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; @@ -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, diff --git a/src/simulation/investment/appraisal/coefficients.rs b/src/simulation/investment/appraisal/coefficients.rs index 71a268fc5..3cf01c50b 100644 --- a/src/simulation/investment/appraisal/coefficients.rs +++ b/src/simulation/investment/appraisal/coefficients.rs @@ -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() { @@ -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, } diff --git a/src/simulation/investment/appraisal/optimisation.rs b/src/simulation/investment/appraisal/optimisation.rs index 6216a9808..eb4677e4c 100644 --- a/src/simulation/investment/appraisal/optimisation.rs +++ b/src/simulation/investment/appraisal/optimisation.rs @@ -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, }; @@ -22,10 +22,46 @@ struct VariableMap { capacity_var: Variable, /// Activity variables in each time slice activity_vars: IndexMap, - // Unmet demand variables + /// Unmet demand variables unmet_demand_vars: IndexMap, } +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 @@ -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, @@ -103,11 +112,9 @@ pub fn perform_optimisation( time_slice_info: &TimeSliceInfo, sense: Sense, ) -> Result { - // 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(