Skip to content

Commit 2207c82

Browse files
committed
add tests for equal metric count
1 parent 514ddc1 commit 2207c82

2 files changed

Lines changed: 139 additions & 3 deletions

File tree

src/simulation/investment.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ fn get_candidate_assets<'a>(
652652
}
653653

654654
/// Print debug message if there are multiple equally good outputs
655-
fn warn_on_equal_appraisal_outputs(
655+
fn log_on_equal_appraisal_outputs(
656656
outputs: &[AppraisalOutput],
657657
agent_id: &AgentID,
658658
commodity_id: &CommodityID,
@@ -826,7 +826,7 @@ fn select_best_assets(
826826
}
827827

828828
// Warn if there are multiple equally good assets
829-
warn_on_equal_appraisal_outputs(&outputs_for_opts, &agent.id, &commodity.id, region_id);
829+
log_on_equal_appraisal_outputs(&outputs_for_opts, &agent.id, &commodity.id, region_id);
830830

831831
let best_output = outputs_for_opts.into_iter().next().unwrap();
832832

src/simulation/investment/appraisal.rs

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,11 @@ pub fn sort_appraisal_outputs_by_investment_priority(outputs_for_opts: &mut Vec<
379379
}
380380

381381
/// Counts the number of top appraisal outputs in a sorted slice that are indistinguishable
382-
/// by both metric and fallback ordering.
382+
/// by both metric and fallback ordering. Excludes the first element from the count.
383383
pub fn count_equal_and_best_appraisal_outputs(outputs: &[AppraisalOutput]) -> usize {
384+
if outputs.is_empty() {
385+
return 0;
386+
}
384387
outputs[1..]
385388
.iter()
386389
.take_while(|output| {
@@ -955,4 +958,137 @@ mod tests {
955958
// The invalid output should have been filtered out
956959
assert_eq!(outputs.len(), 0);
957960
}
961+
962+
/// Tests for counting number of equal metrics using identical assets so only metric values
963+
/// affect the count.
964+
#[rstest]
965+
#[case(vec![5.0], 0, "single_element")]
966+
#[case(vec![5.0, 5.0, 5.0], 2, "all_equal_returns_len_minus_one")]
967+
#[case(vec![1.0, 2.0, 3.0], 0, "none_equal_to_best")]
968+
#[case(vec![5.0, 5.0, 9.0], 1, "partial_equality_stops_at_first_difference")]
969+
#[case(vec![5.0, 5.0, 9.0, 5.0], 1, "equality_does_not_resume_after_gap")]
970+
fn count_equal_best_lcox_metric(
971+
asset: Asset,
972+
#[case] metric_values: Vec<f64>,
973+
#[case] expected_count: usize,
974+
#[case] description: &str,
975+
) {
976+
let metrics: Vec<Box<dyn MetricTrait>> = metric_values
977+
.into_iter()
978+
.map(|v| Box::new(LCOXMetric::new(MoneyPerActivity(v))) as Box<dyn MetricTrait>)
979+
.collect();
980+
981+
let outputs =
982+
appraisal_outputs_with_investment_priority_invariant_to_assets(metrics, &asset);
983+
984+
assert_eq!(
985+
count_equal_and_best_appraisal_outputs(&outputs),
986+
expected_count,
987+
"Failed for case: {description}"
988+
);
989+
}
990+
991+
/// Empty slice count should return 0.
992+
#[test]
993+
fn count_equal_best_empty_slice_returns_zero() {
994+
let outputs: Vec<AppraisalOutput> = vec![];
995+
assert_eq!(count_equal_and_best_appraisal_outputs(&outputs), 0);
996+
}
997+
998+
/// Equal metrics but differing asset fallback (commissioned vs. candidate) →
999+
/// outputs are distinguishable, so count should be 0.
1000+
#[rstest]
1001+
fn count_equal_best_equal_metric_different_fallback_returns_zero(
1002+
process: Process,
1003+
region_id: RegionID,
1004+
agent_id: AgentID,
1005+
) {
1006+
let process_rc = Rc::new(process);
1007+
let capacity = Capacity(10.0);
1008+
1009+
let commissioned = Asset::new_commissioned(
1010+
agent_id.clone(),
1011+
process_rc.clone(),
1012+
region_id.clone(),
1013+
capacity,
1014+
2020,
1015+
)
1016+
.unwrap();
1017+
let candidate =
1018+
Asset::new_candidate(process_rc.clone(), region_id.clone(), capacity, 2020).unwrap();
1019+
1020+
let metric_value = MoneyPerActivity(5.0);
1021+
let outputs = appraisal_outputs(
1022+
vec![commissioned, candidate],
1023+
vec![
1024+
Box::new(LCOXMetric::new(metric_value)),
1025+
Box::new(LCOXMetric::new(metric_value)),
1026+
],
1027+
);
1028+
1029+
assert_eq!(count_equal_and_best_appraisal_outputs(&outputs), 0);
1030+
}
1031+
1032+
/// Equal metrics and equal asset fallback (same commissioned status and commission year) →
1033+
/// the second element is indistinguishable, so count should be 1.
1034+
#[rstest]
1035+
fn count_equal_best_equal_metric_and_equal_fallback_returns_one(
1036+
process: Process,
1037+
region_id: RegionID,
1038+
agent_id: AgentID,
1039+
) {
1040+
let process_rc = Rc::new(process);
1041+
let capacity = Capacity(10.0);
1042+
let year = 2020;
1043+
1044+
let asset1 = Asset::new_commissioned(
1045+
agent_id.clone(),
1046+
process_rc.clone(),
1047+
region_id.clone(),
1048+
capacity,
1049+
year,
1050+
)
1051+
.unwrap();
1052+
let asset2 = Asset::new_commissioned(
1053+
agent_id.clone(),
1054+
process_rc.clone(),
1055+
region_id.clone(),
1056+
capacity,
1057+
year,
1058+
)
1059+
.unwrap();
1060+
1061+
let metric_value = MoneyPerActivity(5.0);
1062+
let outputs = appraisal_outputs(
1063+
vec![asset1, asset2],
1064+
vec![
1065+
Box::new(LCOXMetric::new(metric_value)),
1066+
Box::new(LCOXMetric::new(metric_value)),
1067+
],
1068+
);
1069+
1070+
assert_eq!(count_equal_and_best_appraisal_outputs(&outputs), 1);
1071+
}
1072+
1073+
/// Equal NPV metrics and identical assets → second element should be counted.
1074+
#[rstest]
1075+
fn count_equal_best_equal_npv_metrics(asset: Asset) {
1076+
let make_npv = |surplus: f64, fixed_cost: f64| {
1077+
Box::new(NPVMetric::new(ProfitabilityIndex {
1078+
total_annualised_surplus: Money(surplus),
1079+
annualised_fixed_cost: Money(fixed_cost),
1080+
})) as Box<dyn MetricTrait>
1081+
};
1082+
1083+
let metrics = vec![
1084+
make_npv(200.0, 100.0),
1085+
make_npv(200.0, 100.0), // Equal to best
1086+
make_npv(100.0, 100.0), // Worse
1087+
];
1088+
1089+
let outputs =
1090+
appraisal_outputs_with_investment_priority_invariant_to_assets(metrics, &asset);
1091+
1092+
assert_eq!(count_equal_and_best_appraisal_outputs(&outputs), 1);
1093+
}
9581094
}

0 commit comments

Comments
 (0)