Skip to content

Commit 68cd34e

Browse files
authored
Merge pull request #1214 from EnergySystemsModellingLab/fix-schema-npv-not-broken
Fix: Docs wrongly states that NPV is broken
2 parents 6c9170c + b771079 commit 68cd34e

3 files changed

Lines changed: 28 additions & 35 deletions

File tree

schemas/input/agent_objectives.yaml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,16 @@ fields:
1717
type: string
1818
description: The year(s) to which this entry applies
1919
notes:
20-
One or more milestone years separated by semicolons, `all` to select all years or a year
21-
range in the form 'start..end' to select all valid years within range, inclusive. Either 'start'
20+
One or more milestone years separated by semicolons, `all` to select all years or a year range
21+
in the form 'start..end' to select all valid years within range, inclusive. Either 'start'
2222
'end' or both can be omitted, which will set the corresponding limit to the minimum or maximum
2323
valid year, respectively.
2424
- name: objective_type
2525
type: string
26-
enum: [lcox, npv]
26+
enum: [npv, lcox]
2727
description: The type of objective
2828
notes: |
29-
Must be `npv` (net present value) or `lcox` (levelised cost of X). Note that support for NPV
30-
is [currently broken](https://github.com/EnergySystemsModellingLab/MUSE2/issues/716), so don't
31-
enable this option unless you know what you're doing.
29+
Must be `npv` (net present value) or `lcox` (levelised cost of X).
3230
- name: decision_weight
3331
type: number
3432
description: Weight for weighted sum decision rule

src/simulation/investment.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub mod appraisal;
2020
use appraisal::coefficients::calculate_coefficients_for_assets;
2121
use appraisal::{
2222
AppraisalOutput, appraise_investment, count_equal_and_best_appraisal_outputs,
23-
sort_appraisal_outputs_by_investment_priority,
23+
sort_and_filter_appraisal_outputs,
2424
};
2525

2626
/// A map of demand across time slices for a specific market
@@ -796,16 +796,11 @@ fn select_best_assets(
796796
&demand,
797797
)?;
798798

799-
sort_appraisal_outputs_by_investment_priority(&mut outputs_for_opts);
799+
// Sort by investment priority and discord non-feasible options
800+
sort_and_filter_appraisal_outputs(&mut outputs_for_opts);
800801

801-
// Check if all options have zero capacity. If so, we cannot meet demand, so have to bail
802+
// Check if there are any remaining options. If not, we cannot meet demand, so have to bail
802803
// out.
803-
//
804-
// This may happen if:
805-
// - the asset has zero activity limits for all time slices with
806-
// demand.
807-
// - known issue with the NPV objective
808-
// (see https://github.com/EnergySystemsModellingLab/MUSE2/issues/716).
809804
if outputs_for_opts.is_empty() {
810805
let remaining_demands: Vec<_> = demand
811806
.iter()

src/simulation/investment/appraisal.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -358,18 +358,18 @@ fn compare_asset_fallback(asset1: &Asset, asset2: &Asset) -> Ordering {
358358
.cmp(&(asset1.is_commissioned(), asset1.commission_year()))
359359
}
360360

361-
/// Sort appraisal outputs by their investment priority.
361+
/// Sort appraisal outputs by their investment priority and exclude non-feasible options.
362362
///
363-
/// Primarily this is decided by their appraisal metric.
364-
/// When appraisal metrics are equal, a tie-breaker fallback is used. Commissioned assets
365-
/// are preferred over uncommissioned assets, and newer assets are preferred over older
366-
/// ones. The function does not guarantee that all ties will be resolved.
363+
/// Investment priority is primarily decided by appraisal metric. When appraisal metrics are equal,
364+
/// a tie-breaker fallback is used. Commissioned assets are preferred over uncommissioned assets,
365+
/// and newer assets are preferred over older ones. The function does not guarantee that all ties
366+
/// will be resolved.
367367
///
368-
/// Before sorting, outputs are filtered using [`AppraisalOutput::is_valid`], which
369-
/// excludes entries with invalid metrics (e.g. `None`) as well as zero capacity. This
370-
/// avoids meaningless or `NaN` appraisal metrics that could cause the program to panic,
371-
/// so the length of the returned vector may be less than the input.
372-
pub fn sort_appraisal_outputs_by_investment_priority(outputs_for_opts: &mut Vec<AppraisalOutput>) {
368+
/// Before sorting, outputs are filtered using [`AppraisalOutput::is_valid`], which excludes entries
369+
/// with invalid metrics (e.g. `None`) as well as zero capacity. This avoids meaningless or `NaN`
370+
/// appraisal metrics that could cause the program to panic, so the length of the returned vector
371+
/// may be less than the input.
372+
pub fn sort_and_filter_appraisal_outputs(outputs_for_opts: &mut Vec<AppraisalOutput>) {
373373
outputs_for_opts.retain(AppraisalOutput::is_valid);
374374
outputs_for_opts.sort_by(|output1, output2| match output1.compare_metric(output2) {
375375
// If equal, we fall back on comparing asset properties
@@ -626,7 +626,7 @@ mod tests {
626626

627627
let mut outputs =
628628
appraisal_outputs_with_investment_priority_invariant_to_assets(metrics, &asset);
629-
sort_appraisal_outputs_by_investment_priority(&mut outputs);
629+
sort_and_filter_appraisal_outputs(&mut outputs);
630630

631631
assert_approx_eq!(f64, outputs[0].metric.as_ref().unwrap().value(), 3.0); // Best (lowest)
632632
assert_approx_eq!(f64, outputs[1].metric.as_ref().unwrap().value(), 5.0);
@@ -653,7 +653,7 @@ mod tests {
653653

654654
let mut outputs =
655655
appraisal_outputs_with_investment_priority_invariant_to_assets(metrics, &asset);
656-
sort_appraisal_outputs_by_investment_priority(&mut outputs);
656+
sort_and_filter_appraisal_outputs(&mut outputs);
657657

658658
// Higher profitability index is better, so should be sorted: 3.0, 2.0, 1.5
659659
assert_approx_eq!(f64, outputs[0].metric.as_ref().unwrap().value(), 3.0); // Best (highest PI)
@@ -685,7 +685,7 @@ mod tests {
685685

686686
let mut outputs =
687687
appraisal_outputs_with_investment_priority_invariant_to_assets(metrics, &asset);
688-
sort_appraisal_outputs_by_investment_priority(&mut outputs);
688+
sort_and_filter_appraisal_outputs(&mut outputs);
689689

690690
// Zero AFC should be first despite lower absolute surplus value
691691
assert_approx_eq!(f64, outputs[0].metric.as_ref().unwrap().value(), 50.0); // Zero AFC (uses surplus)
@@ -709,7 +709,7 @@ mod tests {
709709
let mut outputs =
710710
appraisal_outputs_with_investment_priority_invariant_to_assets(metrics, &asset);
711711
// This should panic when trying to compare different metric types
712-
sort_appraisal_outputs_by_investment_priority(&mut outputs);
712+
sort_and_filter_appraisal_outputs(&mut outputs);
713713
}
714714

715715
/// Test that when metrics are equal, commissioned assets are sorted by commission year (newer first)
@@ -745,7 +745,7 @@ mod tests {
745745
];
746746

747747
let mut outputs = appraisal_outputs(assets, metrics);
748-
sort_appraisal_outputs_by_investment_priority(&mut outputs);
748+
sort_and_filter_appraisal_outputs(&mut outputs);
749749

750750
// Should be sorted by commission year, newest first: 2020, 2015, 2010
751751
assert_eq!(outputs[0].asset.commission_year(), 2020);
@@ -782,7 +782,7 @@ mod tests {
782782
];
783783

784784
let mut outputs = appraisal_outputs(assets.clone(), metrics);
785-
sort_appraisal_outputs_by_investment_priority(&mut outputs);
785+
sort_and_filter_appraisal_outputs(&mut outputs);
786786

787787
// Verify order is preserved - should match the original agent_ids array
788788
for (&expected_id, output) in agent_ids.iter().zip(outputs) {
@@ -838,7 +838,7 @@ mod tests {
838838
];
839839

840840
let mut outputs = appraisal_outputs(assets, metrics);
841-
sort_appraisal_outputs_by_investment_priority(&mut outputs);
841+
sort_and_filter_appraisal_outputs(&mut outputs);
842842

843843
// Commissioned assets should be prioritised first
844844
assert!(outputs[0].asset.is_commissioned());
@@ -901,7 +901,7 @@ mod tests {
901901
];
902902

903903
let mut outputs = appraisal_outputs(assets, metrics);
904-
sort_appraisal_outputs_by_investment_priority(&mut outputs);
904+
sort_and_filter_appraisal_outputs(&mut outputs);
905905

906906
// non-commissioned asset prioritised because it has a slightly better metric
907907
assert_approx_eq!(
@@ -934,7 +934,7 @@ mod tests {
934934
})
935935
.collect();
936936

937-
sort_appraisal_outputs_by_investment_priority(&mut outputs);
937+
sort_and_filter_appraisal_outputs(&mut outputs);
938938

939939
// All zero capacity outputs should be filtered out
940940
assert_eq!(outputs.len(), 0);
@@ -953,7 +953,7 @@ mod tests {
953953
};
954954
let mut outputs = vec![output];
955955

956-
sort_appraisal_outputs_by_investment_priority(&mut outputs);
956+
sort_and_filter_appraisal_outputs(&mut outputs);
957957

958958
// The invalid output should have been filtered out
959959
assert_eq!(outputs.len(), 0);

0 commit comments

Comments
 (0)