Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Risk Measure Testing and Conformance

Purpose

This spec defines the conformance test suite for the RiskMeasure enum and its two methods (aggregate_cut, evaluate_risk), as specified in Risk Measure Trait. The suite verifies that both variants (Expectation, CVaR) produce correct risk-adjusted weights and aggregation results against hand-computable reference values. All test cases use small input sets (3-5 openings) so that expected outputs can be verified by manual calculation using the sorting-based greedy allocation procedure from Risk Measures SS7.

Test cases reference the method contracts from Risk Measure Trait SS2, the validation rules R1-R3 from Extension Points SS2.3, and the mathematical definitions from Risk Measures.

SS1. Conformance Test Suite

Test naming convention: test_risk_{variant}_{method}_{scenario} where {variant} is expectation or cvar, {method} is aggregate_cut or evaluate_risk, and {scenario} describes the test case.

Shared test fixture: Unless otherwise noted, tests use the following 3-opening fixture with uniform probabilities and 2 state variables:

OpeningProbabilityObjective ValueInterceptCoefficients
01/310010.0[1.0, 2.0]
11/320020.0[3.0, 4.0]
21/330030.0[5.0, 6.0]

SS1.1 aggregate_cut Conformance

Test NameInput ScenarioExpected Observable BehaviorVariant
test_risk_expectation_aggregate_cut_uniformShared fixture (3 openings, p=[1/3, 1/3, 1/3])Aggregated intercept = 20.0 (= (10+20+30)/3). Aggregated coefficients = [3.0, 4.0] (= ([1+3+5]/3, [2+4+6]/3)). Weights are the raw probabilities: [1/3, 1/3, 1/3].Expectation
test_risk_cvar_aggregate_cut_convex_combinationShared fixture with lambda=0.5, alpha=0.5. Per-scenario weight upper bounds: mu_bar = (1-0.5)(1/3) + 0.5(1/3)/0.5 = 1/2 for each opening. Total capacity = 3/2. Greedy allocation (descending cost order: 300, 200, 100): opening 2 gets 1/2, opening 1 gets 1/2, opening 0 gets 0.Risk-adjusted weights mu* = [0, 1/2, 1/2] (indexed by opening). Aggregated intercept = 0(10) + (1/2)(20) + (1/2)(30) = 25.0. Aggregated coefficients = [0(1) + (1/2)(3) + (1/2)(5), 0(2) + (1/2)(4) + (1/2)(6)] = [4.0, 5.0].CVaR
test_risk_cvar_aggregate_cut_pure_cvarShared fixture with lambda=1.0, alpha=0.5. Per-scenario upper bounds: mu_bar = 0 + (1/3)/0.5 = 2/3 each. Total capacity = 2. Greedy allocation: opening 2 gets 2/3, opening 1 gets 1/3, opening 0 gets 0.Risk-adjusted weights mu* = [0, 1/3, 2/3]. Aggregated intercept = 0(10) + (1/3)(20) + (2/3)(30) = 80/3 (approx 26.667). Aggregated coefficients = [(1/3)(3) + (2/3)(5), (1/3)(4) + (2/3)(6)] = [13/3, 16/3] (approx [4.333, 5.333]).CVaR
test_risk_cvar_aggregate_cut_alpha_oneShared fixture with lambda=0.5, alpha=1.0. Per-scenario upper bounds: mu_bar = (1-0.5)(1/3) + 0.5(1/3)/1.0 = 1/6 + 1/6 = 1/3 each. Total capacity = 1, so no excess. Greedy allocation assigns exactly mu_bar = 1/3 to each opening.Weights mu* = [1/3, 1/3, 1/3], identical to Expectation. Aggregated intercept = 20.0. Aggregated coefficients = [3.0, 4.0]. Result must be identical to the Expectation variant.CVaR
test_risk_cvar_aggregate_cut_extreme_alphaShared fixture with lambda=0.5, alpha=0.05. Per-scenario upper bounds: mu_bar = (0.5)(1/3) + (0.5)(1/3)/0.05 = 1/6 + 10/3 = 21/6 = 7/2 = 3.5 each. Total capacity = 3 × 7/2 = 21/2 = 10.5. Greedy allocation: opening 2 gets min(61/18, 1.0) = 1.0. Remaining = 0. Openings 1 and 0 get 0.Weights mu* = [0, 0, 1.0]. Aggregated intercept = 30.0. Aggregated coefficients = [5.0, 6.0]. Only the worst-cost opening contributes.CVaR
test_risk_expectation_aggregate_cut_single_opening1 opening: p=[1.0], objective=500, intercept=42.0, coefficients=[7.0, 8.0, 9.0].Aggregated intercept = 42.0. Aggregated coefficients = [7.0, 8.0, 9.0]. Trivial: single opening always returns the input unchanged.Expectation
test_risk_cvar_aggregate_cut_single_opening1 opening: p=[1.0], objective=500, intercept=42.0, coefficients=[7.0, 8.0, 9.0]. lambda=0.5, alpha=0.5. mu_bar = (0.5)(1.0) + (0.5)(1.0)/0.5 = 0.5 + 1.0 = 1.5. Greedy allocation: the sole opening gets min(1.5, 1.0) = 1.0.Weight mu* = [1.0]. Aggregated intercept = 42.0. Aggregated coefficients = [7.0, 8.0, 9.0]. Single-opening CVaR is identical to Expectation.CVaR
test_risk_cvar_aggregate_cut_nonuniform_probabilities3 openings with p=[0.5, 0.3, 0.2], objectives=[100, 300, 200], intercepts=[10, 30, 20], coefficients=[[1.0], [3.0], [2.0]]. lambda=0.5, alpha=0.5. Per-scenario upper bounds: mu_bar_0 = 0.5(0.5)+0.5(0.5)/0.5 = 0.25+0.5 = 0.75, mu_bar_1 = 0.5(0.3)+0.5(0.3)/0.5 = 0.15+0.3 = 0.45, mu_bar_2 = 0.5(0.2)+0.5(0.2)/0.5 = 0.1+0.2 = 0.3. Total capacity = 1.5. Sort by cost desc: opening 1 (cost=300), opening 2 (cost=200), opening 0 (cost=100). Allocation: opening 1 gets 0.45, opening 2 gets 0.3, opening 0 gets 1-0.45-0.3 = 0.25.Weights mu* = [0.25, 0.45, 0.3]. Aggregated intercept = 0.25(10) + 0.45(30) + 0.3(20) = 2.5 + 13.5 + 6.0 = 22.0. Aggregated coefficients = [0.25(1.0) + 0.45(3.0) + 0.3(2.0)] = [0.25 + 1.35 + 0.6] = [2.2].CVaR
test_risk_cvar_aggregate_cut_tied_costs3 openings with p=[1/3, 1/3, 1/3], objectives=[200, 200, 100], intercepts=[10, 20, 30], coefficients=[[1.0], [3.0], [5.0]]. lambda=1.0, alpha=0.5. mu_bar = (1/3)/0.5 = 2/3 each. Total capacity = 2. Greedy desc: openings 0 and 1 (cost=200, tied), then opening 2 (cost=100). Allocation: first tied opening gets 2/3, second tied opening gets 1/3 (1 - 2/3), opening 2 gets 0.Weights for the two cost-200 openings sum to 1.0. The allocation among tied openings is implementation-defined (any split that totals 1.0 is correct). The cost-100 opening receives weight 0. Aggregated intercept is between 10.0 and 20.0 depending on tie-breaking.CVaR

SS1.2 evaluate_risk Conformance

Test NameInput ScenarioExpected Observable BehaviorVariant
test_risk_expectation_evaluate_risk_uniformCosts=[100, 200, 300], p=[1/3, 1/3, 1/3].Result = (1/3)(100) + (1/3)(200) + (1/3)(300) = 200.0. Probability-weighted mean.Expectation
test_risk_cvar_evaluate_risk_convex_combinationCosts=[100, 200, 300], p=[1/3, 1/3, 1/3], lambda=0.5, alpha=0.5. E[Z] = 200. CVaR_0.5: dual weights with upper bounds p/alpha = 2/3 each, greedy: cost=300 gets 2/3, cost=200 gets 1/3, cost=100 gets 0. CVaR_0.5 = (2/3)(300) + (1/3)(200) = 800/3 (approx 266.667).Result = (1-0.5)(200) + 0.5(800/3) = 100 + 400/3 = 700/3 (approx 233.333).CVaR
test_risk_cvar_evaluate_risk_pure_cvarCosts=[100, 200, 300], p=[1/3, 1/3, 1/3], lambda=1.0, alpha=0.5. CVaR_0.5 = 800/3 (same computation as above).Result = (1-1.0)(200) + 1.0(800/3) = 800/3 (approx 266.667). Pure CVaR, no expectation component.CVaR
test_risk_cvar_evaluate_risk_alpha_oneCosts=[100, 200, 300], p=[1/3, 1/3, 1/3], lambda=0.5, alpha=1.0. CVaR_1.0 = E[Z] = 200 (alpha=1 means CVaR equals expectation).Result = (1-0.5)(200) + 0.5(200) = 200.0. Identical to Expectation.CVaR
test_risk_cvar_evaluate_risk_extreme_alphaCosts=[100, 200, 300], p=[1/3, 1/3, 1/3], lambda=0.5, alpha=0.05. CVaR_0.05: upper bounds = (1/3)/0.05 = 20/3 each. Greedy: cost=300 gets min(20/3, 1.0) = 1.0, remaining = 0. CVaR_0.05 = 300.Result = 0.5(200) + 0.5(300) = 250.0. The CVaR component equals the single worst-cost scenario.CVaR
test_risk_cvar_evaluate_risk_nonuniform_probabilitiesCosts=[100, 300, 200], p=[0.5, 0.3, 0.2], lambda=0.5, alpha=0.5. E[Z] = 0.5(100) + 0.3(300) + 0.2(200) = 50 + 90 + 40 = 180. CVaR_0.5 dual: upper bounds p/alpha = [1.0, 0.6, 0.4], total = 2.0. Greedy desc (cost=300 opening 1, cost=200 opening 2, cost=100 opening 0): opening 1 gets 0.6, opening 2 gets 0.4, remaining = 0, opening 0 gets 0. CVaR_0.5 = 0.6(300) + 0.4(200) = 260.Result = 0.5(180) + 0.5(260) = 90 + 130 = 220.0.CVaR
test_risk_expectation_evaluate_risk_single_openingCosts=[500], p=[1.0].Result = 500.0. Trivially the single cost value.Expectation
test_risk_cvar_evaluate_risk_single_openingCosts=[500], p=[1.0], lambda=0.5, alpha=0.5.Result = 0.5(500) + 0.5(500) = 500.0. Single opening: CVaR equals the single cost.CVaR

SS2. Variant Equivalence Tests

These tests verify that risk-neutral parameter settings produce results identical to the Expectation variant. This ensures that the CVaR code path degenerates correctly to the baseline when risk aversion is absent.

Test NameInput ScenarioExpected Observable BehaviorRationale
test_risk_cvar_aggregate_cut_lambda_zero_equivalenceShared fixture with lambda=0, alpha=0.5. Per validation rule R3 (Extension Points SS2.3), lambda=0 is normalized to the Expectation variant at config load time. If the CVaR code path is reached with lambda=0, upper bounds reduce to mu_bar = p, so weights = p.Aggregated intercept = 20.0. Aggregated coefficients = [3.0, 4.0]. Identical to test_risk_expectation_aggregate_cut_uniform.R3 normalization: lambda=0 CVaR produces identical output to Expectation.
test_risk_cvar_evaluate_risk_lambda_zero_equivalenceCosts=[100, 200, 300], p=[1/3, 1/3, 1/3], lambda=0, alpha=0.5. The convex combination becomes (1-0)E[Z] + 0(CVaR) = E[Z].Result = 200.0. Identical to test_risk_expectation_evaluate_risk_uniform.R3 normalization: lambda=0 CVaR produces identical output to Expectation.
test_risk_cvar_aggregate_cut_alpha_one_equivalenceShared fixture with lambda=0.5, alpha=1.0. When alpha=1, CVaR_1 = E[Z], so the convex combination is (1-lambda)E + lambda E = E. Upper bounds mu_bar = (1-lambda)p + lambda p/1 = p.Aggregated intercept = 20.0. Aggregated coefficients = [3.0, 4.0]. Identical to Expectation despite lambda=0.5.alpha=1 makes CVaR equal to expectation, so any lambda produces the same result.
test_risk_cvar_evaluate_risk_alpha_one_equivalenceCosts=[100, 200, 300], p=[1/3, 1/3, 1/3], lambda=0.5, alpha=1.0.Result = 200.0. Identical to Expectation.alpha=1 makes CVaR component equal to E[Z].

SS3. Numerical Properties

These tests verify mathematical invariants that must hold for any valid input, independent of specific numeric values. Each test should be run across multiple input configurations (the shared fixture and the nonuniform-probability configurations from SS1).

SS3.1 Weight Invariants

Test NamePropertyExpected Observable BehaviorReference
test_risk_cvar_aggregate_cut_weights_sum_to_oneRisk-adjusted weights form a valid probability distribution: .For any valid input, the sum of risk-adjusted weights must equal 1.0 within floating-point tolerance (1e-12). Verified with: shared fixture (lambda=0.5, alpha=0.5, weights=[0, 1/2, 1/2], sum=1.0), pure CVaR (lambda=1.0, alpha=0.5, weights=[0, 1/3, 2/3], sum=1.0), alpha=1.0 (weights=[1/3, 1/3, 1/3], sum=1.0).Risk Measure Trait SS2.1, postcondition 4
test_risk_cvar_aggregate_cut_weights_nonnegativeAll risk-adjusted weights are non-negative: for all .For any valid input, every weight must satisfy mu* >= 0. In the shared fixture with lambda=0.5, alpha=0.5: weights are [0, 1/2, 1/2], all non-negative. The zero weight for the lowest-cost opening is valid (greedy allocation exhausted the budget before reaching it).Risk Measure Trait SS2.1, postcondition 5
test_risk_cvar_aggregate_cut_weights_bounded_by_mu_barEach weight is bounded by its per-scenario upper bound: .For the shared fixture with lambda=0.5, alpha=0.5: mu_bar = 1/2 for each opening. Weights [0, 1/2, 1/2] satisfy 0 <= 1/2, 1/2 <= 1/2, 1/2 <= 1/2. For nonuniform probabilities (p=[0.5, 0.3, 0.2], lambda=0.5, alpha=0.5): mu_bar = [0.75, 0.45, 0.3], weights = [0.25, 0.45, 0.3], all bounded: 0.25 <= 0.75, 0.45 <= 0.45, 0.3 <= 0.3.Risk Measures SS4.2, Risk Measure Trait SS2.1, postcondition 5
test_risk_cvar_aggregate_cut_weights_descending_cost_orderWhen costs are strictly ordered, the greedy allocation assigns weakly descending weights to the cost-descending sequence: if then .For the shared fixture (costs [100, 200, 300], lambda=0.5, alpha=0.5): weights in cost-descending order are [1/2, 1/2, 0], which is weakly descending (1/2 >= 1/2 >= 0). For pure CVaR (lambda=1.0, alpha=0.5): weights in cost-descending order are [2/3, 1/3, 0], which is strictly descending.Risk Measures SS7, greedy allocation places maximum weight on highest-cost scenarios first

SS3.2 Monotonicity Invariants

Test NamePropertyExpected Observable BehaviorReference
test_risk_cvar_evaluate_risk_geq_expectationWhen alpha < 1, the CVaR risk-adjusted cost is at least as large as the expected value: .For the shared fixture costs=[100, 200, 300] with lambda=0.5, alpha=0.5: rho = 700/3 (approx 233.333) >= E[Z] = 200. For pure CVaR (lambda=1.0, alpha=0.5): rho = 800/3 (approx 266.667) >= 200. For lambda=0.5, alpha=0.05: rho = 250 >= 200.Risk Measure Trait SS2.2, postcondition 4
test_risk_cvar_evaluate_risk_monotone_in_lambdaIncreasing lambda (stronger risk aversion) weakly increases the risk-adjusted cost when alpha < 1: .For the shared fixture costs=[100, 200, 300] with alpha=0.5: lambda=0 gives 200.0, lambda=0.5 gives 700/3 (approx 233.333), lambda=1.0 gives 800/3 (approx 266.667). The sequence 200 <= 233.333 <= 266.667 is monotonically non-decreasing.Risk Measures SS3, convex combination definition
test_risk_cvar_evaluate_risk_monotone_in_alphaDecreasing alpha (more extreme tail focus) weakly increases the risk-adjusted cost: .For the shared fixture costs=[100, 200, 300] with lambda=0.5: alpha=1.0 gives 200.0, alpha=0.5 gives 700/3 (approx 233.333), alpha=0.05 gives 250.0. The sequence 200 <= 233.333 <= 250 is monotonically non-decreasing.Risk Measures SS2, CVaR definition

SS3.3 Validation Rejection Tests

These tests verify that invalid parameters are rejected during configuration loading, per Extension Points SS2.3 rules R1-R3 and Risk Measure Trait SS5.

Test NameInput ScenarioExpected Observable BehaviorRule
test_risk_cvar_config_reject_alpha_zeroCVaR config with alpha=0.0, lambda=0.5.Configuration loading rejects with an error. alpha=0 produces undefined CVaR (division by zero in mu_bar computation).R1
test_risk_cvar_config_reject_alpha_negativeCVaR config with alpha=-0.1, lambda=0.5.Configuration loading rejects with an error. alpha must be in (0, 1].R1
test_risk_cvar_config_reject_alpha_greater_than_oneCVaR config with alpha=1.5, lambda=0.5.Configuration loading rejects with an error. alpha must be in (0, 1].R1
test_risk_cvar_config_reject_lambda_negativeCVaR config with alpha=0.5, lambda=-0.1.Configuration loading rejects with an error. lambda must be in [0, 1].R2
test_risk_cvar_config_reject_lambda_greater_than_oneCVaR config with alpha=0.5, lambda=1.5.Configuration loading rejects with an error. lambda must be in [0, 1].R2
test_risk_cvar_config_normalize_lambda_zeroCVaR config with alpha=0.5, lambda=0.0. Shared fixture openings (costs [100, 200, 300], probabilities [1/3, 1/3, 1/3]).Configuration loading accepts without error. The CVaR config is normalized to the Expectation variant internally. aggregate_cut returns intercept = 20.0, coefficients = [3.0, 4.0] (identical to Expectation). evaluate_risk returns 200.0 (identical to Expectation).R3

Cross-References

  • Risk Measure Trait – Enum definition (SS1), method contracts (SS2), BackwardOutcome type (SS1), validation rules (SS5), special cases (SS6)
  • Risk Measures – CVaR definition (SS2), convex combination (SS3), EAVaR dual representation (SS4.2), risk-averse subgradient theorem (SS5), sorting-based greedy allocation (SS7)
  • Cut Management – Single-cut aggregation (SS3) that the Expectation variant reproduces
  • Extension Points – Risk measure variant table (SS2.1), validation rules R1-R3 (SS2.3), variant selection pipeline (SS6)
  • Backend Testing – Conformance test suite structure and requirements table format (reference pattern for this spec)