Add combination tests for piecewise, status, investment & effects interactions#608
Add combination tests for piecewise, status, investment & effects interactions#608
Conversation
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughAdds a large new test module introducing comprehensive integration tests for piecewise conversions, investments, status/startup constraints, time-varying effects, multi-output conversions, and associated observables across many scenarios (≈1,236 added lines). Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@tests/test_math/test_combinations.py`:
- Around line 986-1014: The docstring text "Fuel emits 0.5 kg CO2/kWh" is
inconsistent with the test setup where the gas flow sets
effects_per_flow_hour={'costs': 1, 'CO2': 0.1}; update the docstring to state
"Fuel emits 0.1 kg CO2/kWh" (or change the Flow's 'CO2' value to 0.5 if you
prefer to keep the original docstring) so the comment matches the actual values
used in fx.Effect('CO2', ...) and the fx.Source('GasSrc') / fx.Flow(...)
effects_per_flow_hour configuration.
🧹 Nitpick comments (2)
tests/test_math/test_combinations.py (2)
264-277: Status validation formin_uptimeis correct but subtly incomplete at the trailing on-block.The loop at lines 266–277 checks that every "on" timestep has at least one "on" neighbor. However, if the entire trailing block is exactly 1 timestep long and is not the last index, the check catches it. If it is the last index, the
i == len(status) - 1guard auto-passes — which is the intended boundary relaxation.This works but is somewhat fragile: it doesn't directly verify "on-block length ≥ 2" (the actual min_uptime semantics). The later test
test_min_uptime_with_min_downtime(line 896) uses an explicit block-length counter, which is more robust. Consider using the same pattern here for consistency.
896-920: Block-length validation loops don't check the final block at end-of-horizon.The on-block loop (lines 896–905) and the off-block loop (lines 908–920) only assert block lengths when a transition occurs within the horizon. A trailing block that reaches the last timestep is never validated. This is intentionally correct for boundary-relaxed constraints, but adding a brief comment explaining why the trailing block is deliberately unchecked would improve readability.
📝 Suggested comment addition
for i, s in enumerate(status): if s > 0.5: on_block_len += 1 else: if on_block_len > 0: assert on_block_len >= 2, ( f'min_uptime violated: on-block of {on_block_len} at t<{i}: status={status}' ) on_block_len = 0 + # Trailing on-block at end of horizon is not checked: min_uptime constraint is relaxed at boundary
┌──────────────────────────────────────┬───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Class │ Tests │ What it covers │ ├──────────────────────────────────────┼───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ TestPiecewiseWithInvestment │ 2 │ Piecewise conversion + invest sizing; piecewise invest cost + optional skip │ ├──────────────────────────────────────┼───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ TestPiecewiseWithStatus │ 4 │ Non-1:1 piecewise + startup cost; gap-based min load + status; no zero point + status off; no zero point + startup cost │ ├──────────────────────────────────────┼───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ TestPiecewiseThreeSegments │ 3 │ 3-segment piecewise at high/low/mid load │ ├──────────────────────────────────────┼───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ TestStatusWithEffects │ 2 │ Startup cost on CO2 effect; active-hour cost on multiple effects │ ├──────────────────────────────────────┼───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ TestInvestWithRelativeMinimum │ 1 │ Invest sizing with relative_minimum forcing off-state (asserts status[0]<0.5) │ ├──────────────────────────────────────┼───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ TestConversionWithTimeVaryingEffects │ 2 │ Time-varying effects_per_flow_hour; dual-output CHP with time-varying costs │ ├──────────────────────────────────────┼───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ TestPiecewiseInvestWithStatus │ 1 │ Piecewise invest (economy of scale in seg2) + startup costs │ ├──────────────────────────────────────┼───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ TestStatusWithMultipleConstraints │ 2 │ startup_limit + max_downtime; min_uptime + min_downtime │ ├──────────────────────────────────────┼───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ TestEffectsWithConversion │ 2 │ Effect share_from_temporal + investment; effect maximum_total + status contribution │ ├──────────────────────────────────────┼───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ TestInvestWithEffects │ 1 │ invest_per_size on non-cost (CO2) effect with maximum_total constraint │ └──────────────────────────────────────┴───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Sensitivity highlights Every test has been validated to produce a different result if the feature it tests were removed: - Piecewise tests use non-1:1 ratios (e.g., fuel [30→60] / heat [30→50]) so removing piecewise changes the answer - Status tests verify that startup costs / off-states contribute uniquely to the objective - The test_invest_sizing_respects_relative_minimum now directly asserts status[0] < 0.5 (boiler OFF at t=0) and produces cost=125 vs 80 without relative_minimum - The "no zero point" tests cover the critical interaction where status must allow OFF despite no [0,0] piece in the piecewise definition
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@tests/test_math/test_combinations.py`:
- Around line 1032-1042: The min_uptime check currently only asserts inside the
loop when an off-state is encountered, so a trailing on-block isn't validated;
after the loop that iterates over status and updates on_block_len, add a final
check that if on_block_len > 0 then assert on_block_len >= 2 (or use the
configured min_uptime) and include the same failure message format referencing
status and the final index, ensuring the variables used (status, on_block_len,
min_uptime) match the existing names in the test.
🧹 Nitpick comments (3)
tests/test_math/test_combinations.py (3)
960-975: Startup counting logic assumesprevious_flow_rate > 0implies initial ON state.Line 970 counts any
status[0] > 0.5as a startup (sincei == 0satisfies theorcondition), then Line 973–974 subtracts it if the boiler was already on. This works but is fragile — ifprevious_flow_ratewere changed to 0 in a future refactor, the subtraction would incorrectly reduce the count. Consider making the initial-state logic explicit:Suggested simplification
- startups = sum(1 for i in range(len(status)) if status[i] > 0.5 and (i == 0 or status[i - 1] < 0.5)) - # Account for carry-over: was on before, so first on isn't a startup - # if status[0] > 0.5 then it was already on (previous_flow_rate=10) - if status[0] > 0.5: - startups -= 1 # Not a startup, was already on - assert startups <= 2, f'startup_limit violated: {startups} startups, status={status}' + # Count startups: transition from off→on. At t=0, previous was on (previous_flow_rate=10). + prev_on = True # previous_flow_rate=10 means was ON + startups = sum( + 1 for i in range(len(status)) + if status[i] > 0.5 and not (prev_on if i == 0 else status[i - 1] > 0.5) + ) + assert startups <= 2, f'startup_limit violated: {startups} startups, status={status}'
596-609:test_startup_cost_on_co2_effect: assertions are loose — consider tighter validation.The test only checks
CO2 ≤ 60andstartups ≤ 1, without asserting an exact cost. This means a regression that changes cost behavior (but still satisfies the constraints) would go undetected. Consider adding anassert_allcloseoncostsfor a specific expected value, similar to how other tests in this file assert exact objectives.
1059-1062: Loose cost bounds intest_min_uptime_with_min_downtime.The assertion
120 < costs < 240is a very wide range. Givenmin_uptime=2andmin_downtime=2with 6 timesteps, the optimal pattern is deterministic (e.g., ON for 4 hours, OFF for 2, or ON 2, OFF 2, ON 2). You could compute the exact expected cost for the optimal block pattern and useassert_allclose, which would catch more subtle regressions.
Summary
test_combinations.pywith 20 tests (60 runs with the 3-modeoptimizefixture) covering feature interactions that were previously untestedTest Coverage
TestPiecewiseWithInvestmentTestPiecewiseWithStatusTestPiecewiseThreeSegmentsTestStatusWithEffectsTestInvestWithRelativeMinimumrelative_minimumforcing off-stateTestConversionWithTimeVaryingEffectseffects_per_flow_hour; dual-output CHP with time-varying costsTestPiecewiseInvestWithStatusTestStatusWithMultipleConstraintsstartup_limit+max_downtime;min_uptime+min_downtimeTestEffectsWithConversionshare_from_temporal+ investment; effectmaximum_total+ statusTestInvestWithEffectsinvest_per_sizeon non-cost (CO2) effect withmaximum_totalKey scenarios tested
relative_minimumTest plan
main(pytest tests/test_math/test_combinations.py -v)pytest tests/test_math/ -v)🤖 Generated with Claude Code
Summary by CodeRabbit