Skip to content

update appraisal documentation#1125

Open
Aurashk wants to merge 12 commits intomainfrom
update-npv-docs
Open

update appraisal documentation#1125
Aurashk wants to merge 12 commits intomainfrom
update-npv-docs

Conversation

@Aurashk
Copy link
Collaborator

@Aurashk Aurashk commented Feb 12, 2026

Description

I went through the documentation for npv and tried to ensure that it accurately reflects what is done in the code. I think there was a couple of erroneous - signs in the docs but please double check. When we choose the best asset using profitability index we used to negate them so that the smallest is best, but this was just to allow us to deal with NPV and LCOX metrics in the same way. When it comes to calculating activity coefficients, I don't see a negation like this in the code, and it makes sense that we would want to maximise annualised revenue rather than minimise it.

I modified/added a couple of bits to try and make the documentation clearer but let me know what you think:

  • Split up the NPV and LCOX activity coefficients into separate expressions, they are very similar but for me the subtle differences make it harder to register what the activity coefficients actually represent.
  • Added an example subsection at the end to help the reader understand the different interpretations (maximise profit vs minimise cost). Maybe this should be in it's own file though.

Fixes #1103

Type of change

  • Bug fix (non-breaking change to fix an issue)
  • New feature (non-breaking change to add functionality)
  • Refactoring (non-breaking, non-functional change to improve maintainability)
  • Optimization (non-breaking change to speed up the code)
  • Breaking change (whatever its nature)
  • Documentation (improve or add documentation)

Key checklist

  • All tests pass: $ cargo test
  • The documentation builds and looks OK: $ cargo doc
  • Update release notes for the latest release if this PR adds a new feature or fixes a bug
    present in the previous release

Further checks

  • Code is commented, particularly in hard-to-understand areas
  • Tests added that prove fix is effective or that feature works

Copilot AI review requested due to automatic review settings February 12, 2026 16:44
@Aurashk Aurashk requested a review from alexdewar February 12, 2026 16:45
@codecov
Copy link

codecov bot commented Feb 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.52%. Comparing base (5595bd8) to head (1656187).
⚠️ Report is 56 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1125      +/-   ##
==========================================
- Coverage   87.54%   87.52%   -0.02%     
==========================================
  Files          55       55              
  Lines        7460     7626     +166     
  Branches     7460     7626     +166     
==========================================
+ Hits         6531     6675     +144     
- Misses        631      649      +18     
- Partials      298      302       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the documentation for NPV (Net Present Value) and LCOX (Levelised Cost of X) investment appraisal methods to accurately reflect their implementation in the codebase. The changes fix erroneous negative signs in the formulas, separate NPV and LCOX activity coefficient expressions for clarity, and add a comprehensive worked example using a gas power plant.

Changes:

  • Corrected mathematical formulas for NPV and LCOX activity coefficients, removing incorrect negative signs and adding proper superscript notation (AC_t^NPV and AC_t^LCOX)
  • Separated NPV and LCOX formulas into distinct expressions to highlight their differences
  • Added detailed gas power plant example demonstrating NPV and LCOX calculations across two time periods with complete numerical workthrough

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +224 to +225
\text{Cost Index} = \frac{\text{AFC} \times \text{cap}_r + \sum_t \text{act}_t
\times \text{AC}_t^{\text{LCOX}}}{\sum_t \text{act}_t}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent LaTeX formatting for variable names. The cost index formula at lines 224-225 uses '\text{act}_t', '\text{AC}_t', etc. with the \text{} wrapper, but the equivalent cost index formula in the example section at line 386 uses 'act_t', 'AC_t', etc. without the \text{} wrapper. Similarly, line 182 uses '\text{act}_t' for the profitability index, but line 317 uses 'act_t' in the example. For consistency, either use \text{} for all variable names in standalone formulas, or don't use it for any. The most readable option would be to consistently use \text{} for all variable names in metric formulas (lines 224-225, 182) and update the example formulas (lines 317, 325, 386) to match.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... It seems like we weren't consistently using \text before this PR, but we probably don't also want to be changing act to \text{act} haphazardly, without updating it everywhere (although the latter is arguably more correct).

Up to you whether you want to revert or whatever. Not esp important.

Copy link
Collaborator Author

@Aurashk Aurashk Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should I update the rest of the file to consistently use \text as copilot says? then we can make an issue about making sure it's consistent throughout the docs? (Maybe we can assign copilot to do that too :) )

Copy link
Collaborator

@alexdewar alexdewar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've suggested some tweaks and I've asked some questions, but this looks really good! The worked example is especially helpful.

Besides that:

  • We should mention the fallback behaviour for NPV when AFC==0 somewhere; I don't think we do currently.
  • I think you're right about the minus signs. Your new version looks correct to me.
  • I feel like we should probably just drop the scopes stuff (the text and the terms in the equations). I'm not convinced the equations are right as they are and we're not planning on implementing this feature any time soon anyway.

@@ -140,10 +157,10 @@ 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. \\(\varepsilon \approx 1×10^{-14}\\) is added to each
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value of $\varepsilon$ in the code is actually f64::EPSILON * 100. Incidentally, do you know why we're not just using f64::EPSILON?

Copy link
Collaborator Author

@Aurashk Aurashk Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think f64::EPSILON is too small to meaningfully nullify any floating point precision errors which made the activity coefficient tiny and negative (don't think the constant is really intended for that purpose). So it's multiplied by 100 to get it to around 1e-14. I suppose you could argue it's cleaner just to use 1e-14 directly but that would be a bit more arbitrary too.


- Calculate cost per unit of activity \\( AC_{t}^{LCOX} \\) (Tool B). Note that the commodity
of interest (primary output \\( c_{primary} \\)) is excluded from the price term:
\\[
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this block been changed, other than the indentation?

Co-authored-by: Alex Dewar <alexdewar@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 13, 2026 12:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 289 to 292
AC_{t2}^{NPV} &= (1.0 \times 50) + (0.5 \times 15) + (-2.5 \times 25) - 5 \\\\
&= 50 + 7.5 - 62.5 - 5 \\\\
&= \text{£} -10 \text{/MWh}
\end{aligned}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LaTeX rendering for the negative value in this example is likely incorrect: \text{£} -10 \text{/MWh} will typically typeset awkwardly (and may drop spacing/formatting). Prefer a single \text{...} (e.g., \text{£-10/MWh}) or otherwise format the currency/unit consistently with the other example lines.

Copilot uses AI. Check for mistakes.

| Commodity | Notation | t1 (Peak) | t2 (Off-peak) |
|-----------|----------|-----------|---------------|
| Electricity | \\( \lambda\_{c_{elec},r,t} \\) | £90/MWh | £50/MWh |
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the example, electricity is introduced as the primary output using c_{primary}, but the market price table switches to c_{elec} in the price notation. Using a single symbol consistently (e.g., c_{primary} everywhere, or explicitly stating c_{primary}=c_{elec}) would prevent ambiguity.

Suggested change
| Electricity | \\( \lambda\_{c_{elec},r,t} \\) | £90/MWh | £50/MWh |
| Electricity | \\( \lambda\_{c_{primary},r,t} \\) | £90/MWh | £50/MWh |

Copilot uses AI. Check for mistakes.
\\[
AC_t^{NPV} = \sum_{c} \Big( output\_{coeff}[c] - input\_{coeff}[c] \Big) \cdot \lambda_{c,r,t} - cost\_{var}[t]
\\]

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example’s “general form” for AC_t^{NPV} only includes price terms and cost_{var}[t], while the earlier definition of AC_t^{NPV} also includes flow-specific costs/levies (and scope terms, even if currently unimplemented). Consider stating explicitly that the example assumes those additional terms are zero/omitted for simplicity, so readers don’t infer the implementation ignores them.

Suggested change
In this illustrative example, any additional flow-specific costs or levies (and any scope-related terms present in the full definition of \(AC_t^{NPV}\)) are assumed to be zero and are therefore omitted for simplicity.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 13, 2026 12:48
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 201 to 204
\\[
minimise \Big\\{
AF \* cap + \sum_t act_t \* AC_t + VoLL \* UnmetD_t
AF \times cap + \sum_t act_t \times AC_{t}^{LCOX} + VoLL \times UnmetD_t
\Big\\}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the LCOX optimisation objective, AF is used for fixed cost (AF \times cap), but the rest of the document defines annualised fixed cost as AFC_{opt,r} and the cost index formula below uses AFC. Please rename AF to AFC here (and keep notation consistent across Tool B).

Copilot uses AI. Check for mistakes.
Comment on lines 180 to 183
\\[
\text{Profitability Index} =
\frac{\sum_t \text{act}_t \cdot \text{AC}_t^{\text{NPV}}}{\text{AFC} \cdot \text{cap}}
\\]
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LaTeX variable formatting is inconsistent within this file: the profitability index uses \text{act}_t, \text{AC}_t, \text{AFC}, etc., while surrounding formulas and the worked example use act_t, AC_t, AFC without \text{}. Pick one style for variables in formulas (either remove \text{} here, or update the other formulas) to keep the notation consistent and easier to read.

Copilot uses AI. Check for mistakes.
Comment on lines +386 to +388
\\[
\text{cost index} = \frac{AFC \cdot cap + \sum_t act_t \cdot AC_t^{LCOX}}{\sum_t act_t}
\\]
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalisation of the metric name is inconsistent: earlier you define \text{Cost Index}, but in the example the formula uses \text{cost index}. Please use consistent capitalisation for the metric label across the document.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 13, 2026 13:01
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +223 to +225
\\[
\text{Cost Index} = \frac{\text{AFC} \times \text{cap}_r + \sum_t \text{act}_t
\times \text{AC}_t^{\text{LCOX}}}{\sum_t \text{act}_t}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cost index formula introduces cap_r, but that symbol isn’t defined elsewhere in the doc (the optimisation and parameter tables use cap). Consider using the same capacity symbol/indices consistently (e.g., cap or cap_{opt,r}) to avoid confusion.

Copilot uses AI. Check for mistakes.
Comment on lines 267 to 271
The net revenue calculation follows the general form:

\\[
AC_t^{NPV} = \sum_{c} \Big( output\_{coeff}[c] - input\_{coeff}[c] \Big) \cdot \lambda_{c,r,t} - cost\_{var}[t]
\\]
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the example, the simplified expression for AC_t^{NPV} drops several terms that appear in the full definition above (e.g., cost_input/cost_output and scope-related terms). It would help to explicitly state that these terms are assumed to be zero/omitted for simplicity in this example, so readers don’t think the implementation differs.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 13, 2026 13:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +223 to +225
\\[
\text{Cost Index} = \frac{\text{AFC} \times \text{cap}_r + \sum_t \text{act}_t
\times \text{AC}_t^{\text{LCOX}}}{\sum_t \text{act}_t}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Cost Index formula, capacity is written as \text{cap}_r, but elsewhere in Tool B (including the objective just above) the decision variable is cap without a regional subscript. Please make the notation consistent (either always subscript capacity by region or omit it throughout) so it’s clear these refer to the same variable.

Copilot uses AI. Check for mistakes.
cost\_{\text{output}}[c] \cdot output\_{\text{coeff}}[c] \Big) \\\\
&+ \sum\_{c} \Big( output\_{\text{coeff}}[c] - input\_{\text{coeff}}[c] \Big)
\cdot \lambda\_{c,r,t} \\\\
&+ \sum\_{s,c} in\\_scope[s] \cdot \Big\\{ \\\\
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the NPV activity-coefficient expression, in\\_scope[s] looks like an accidental extra backslash. In LaTeX, \\ is a linebreak and will likely break rendering here; it should be consistent with other escaped underscores (e.g., cost\_var) and use in\_scope instead.

Suggested change
&+ \sum\_{s,c} in\\_scope[s] \cdot \Big\\{ \\\\
&+ \sum\_{s,c} in\_scope[s] \cdot \Big\\{ \\\\

Copilot uses AI. Check for mistakes.
Comment on lines 101 to 105
&- \sum\_{c \neq c_{primary}} \Big( output\_{\text{coeff}}[c] - input\_{\text{coeff}}
[c] \Big)
\cdot \lambda\_{c,r,t} \\\\
&+ \sum\_{s,c} in\\_scope[s] \cdot \Big\\{ \\\\
&\quad \quad (cost\_{\text{prod}}[s,c] - \mu\_{s,c}^{\text{prod}})
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AC_t^{LCOX} formula has a line break inside input_{\text{coeff}}[c] (the [c] is on the next line). This is likely to render incorrectly in LaTeX; keep the term on one line (or break between factors) so the subscripted variable isn’t split.

Suggested change
&- \sum\_{c \neq c_{primary}} \Big( output\_{\text{coeff}}[c] - input\_{\text{coeff}}
[c] \Big)
\cdot \lambda\_{c,r,t} \\\\
&+ \sum\_{s,c} in\\_scope[s] \cdot \Big\\{ \\\\
&\quad \quad (cost\_{\text{prod}}[s,c] - \mu\_{s,c}^{\text{prod}})
&- \sum\_{c \neq c_{primary}} \Big( output\_{\text{coeff}}[c] - input\_{\text{coeff}}[c] \Big)
\cdot \lambda\_{c,r,t} \\\\
&+ \sum\_{s,c} in\\_scope[s] \cdot \Big\\{ \\\\
&\quad \quad (cost\_{\text{prod}}[s,c] - \mu\_{s,c}^{\text{prod}})
&\quad \quad (cost\_{\text{prod}}[s,c] - \mu\_{s,c}^{\text{prod}})

Copilot uses AI. Check for mistakes.
Comment on lines 200 to 204

\\[
minimise \Big\\{
AF \* cap + \sum_t act_t \* AC_t + VoLL \* UnmetD_t
AF \times cap + \sum_t act_t \times AC_{t}^{LCOX} + VoLL \times UnmetD_t
\Big\\}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tool B’s optimisation bullet says it ‘minimise[s] annualised cost’ but then describes solving the sub-problem ‘to maximise the asset's surplus’, which is contradictory. Also, the objective uses AF × cap while the rest of the document defines the annualised fixed cost as AFC; please align the wording and notation so the objective is unambiguous and consistent with the earlier AFC_{opt,r} definition and the cost-index formula.

Copilot uses AI. Check for mistakes.
Comment on lines +258 to +260
|-----------|----------|-----------|---------------|
| Electricity | \\( \lambda\_{c_{elec},r,t} \\) | £90/MWh | £50/MWh |
| Heat | \\( \lambda\_{c_{heat},r,t} \\) | £25/MWh | £15/MWh |
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the example, electricity is introduced as the primary output c_{primary}, but the price table uses \lambda_{c_{elec},r,t}. Either use c_{primary} consistently, or explicitly define c_{elec} = c_{primary} in the example to avoid ambiguity.

Copilot uses AI. Check for mistakes.
@Aurashk
Copy link
Collaborator Author

Aurashk commented Feb 13, 2026

I've suggested some tweaks and I've asked some questions, but this looks really good! The worked example is especially helpful.

Besides that:

  • We should mention the fallback behaviour for NPV when AFC==0 somewhere; I don't think we do currently.
  • I think you're right about the minus signs. Your new version looks correct to me.
  • I feel like we should probably just drop the scopes stuff (the text and the terms in the equations). I'm not convinced the equations are right as they are and we're not planning on implementing this feature any time soon anyway.

the equations look so much less intimidating without the scopes stuff !

Copilot AI review requested due to automatic review settings February 13, 2026 16:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

| Input (Natural gas) | \\( input\_{coeff}[c_{gas}] \\) | 2.5 MWh per unit activity | Fuel consumption |
| Variable operating cost | \\( cost\_{var}[t] \\) | £5/MWh of activity | Operating costs per unit activity |
<!-- markdownlint-enable MD013 -->

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example assumes all flow costs (the cost field on each ProcessFlow) are zero, but this should be stated explicitly. The general formulas at lines 78-82 and 90-97 include cost_input and cost_output terms representing these per-flow costs, but they are omitted from the example calculations without explanation. Consider adding a note in the "Asset Parameters" section stating that flow costs are assumed to be zero for simplicity in this example.

Suggested change
Note: In this illustrative example, all per-flow costs associated with individual ProcessFlows (the `cost` field, represented in the general formulas as \\( cost\_{input} \\) and \\( cost\_{output} \\)) are assumed to be zero; only the variable operating cost \\( cost\_{var}[t] \\) is included in the calculations.

Copilot uses AI. Check for mistakes.
Comment on lines +184 to +185
3. If there is still a tie, the first option in the data structure is selected,
which is non-deterministic. A warning is emitted when this occurs.
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states that "A warning is emitted when this occurs" referring to cases where two or more investment options have identical metrics and cannot be distinguished even after applying tie-breaking rules. However, the implementation in sort_appraisal_outputs_by_investment_priority (src/simulation/investment/appraisal.rs:349-356) does not emit any warning when ties remain unresolved. The function comment acknowledges "The function does not guarantee that all ties will be resolved" but no warning is logged. Either the documentation should be updated to remove the claim about warning emission, or the code should be updated to emit a warning when unresolved ties exist.

Copilot uses AI. Check for mistakes.
Comment on lines +226 to +227
\text{Cost Index} = \frac{\text{AFC} \times \text{cap}_r + \sum_t \text{act}_t
\times \text{AC}_t^{\text{LCOX}}}{\sum_t \text{act}_t}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent notation for capacity variable. Line 205 uses cap while line 226 uses \text{cap}_r. For consistency, both should use the same notation. Either remove the subscript from line 226 to match line 205, or add a subscript to line 205 to match line 226. The subscript _r for region is not essential here since the context makes it clear we're dealing with a specific asset and region.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update documentation to describe how NPV is implemented

2 participants