Repository navigation
Discount myopic unit commitment start costs to the run's first period - #399
SutubraResearch wants to merge 1 commit into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Important Review skippedReview was skipped as selected files did not have any reviewable changes. ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughStartup and startup-emission costs now use the myopic run's discounting year when it is set. A regression test checks these costs against variable-cost discount weights across two myopic unit-commitment windows. ChangesMyopic startup-cost discounting
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix · Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to
|
jia-hui-chen0
left a comment
There was a problem hiding this comment.
Correct, and it follows the existing idiom: the core cost rule (costs.py:309-315) and the three economies-of-scale cost rules already use myopic_discounting_year when it is set. The output side already did too: TableWriter.write_costs passes myopic_discounting_year to poll_cost_results in myopic mode. So before this PR, a later window's objective weighted start costs differently from what output_cost reported; after it, the two agree.
This matches the fix I used in CANOE's Ontario runs. With a 3% discount rate, the 2040–2050 window weighted start costs 1.03^15 ≈ 1.56 times higher than every other cost.
The test is a good one: it compares each start variable's objective coefficient with the weight the core rule gives a variable cost in the same period, and it covers the startup emission cost as well.
Non-blocking: the same four-line base-year selection now appears in five places. A small helper in costs.py would keep future extensions from repeating the original bug. That could be a follow-up.
Co-authored-by: Jiahui Chen <59991575+jia-hui-chen0@users.noreply.github.com>
62dcae7 to
3967b83
Compare
Also found independently by @jia-hui-chen0.
Fixes #398.
What is wrong
In a myopic run the core cost rule and the economies-of-scale cost terms discount every cost to the run's first period (
myopic_discounting_year).append_startup_costsdiscounts start costs and start emission costs to the first period of the window being solved. So in every window after the first, starts weigh more against all other costs than they should: at a 2% discount rate, 1.219 times as much in a window ten years after the first and 1.486 times at twenty years. The model's choices are affected; the written costs are not.A small example
With periods 2030, 2040 and 2050 solved one window at a time at 2%, a variable cost of 1 a year in the 2040 window enters the objective as 8.9826 × 1/1.02^10 = 7.3688, discounted to 2030. A start cost of 1 a year enters as 8.9826, discounted only to 2040. The ratio is 1.02^10 = 1.219.
What changed
append_startup_costsintemoa/extensions/unit_commitment/components/startup.pyreplacesp_0withmyopic_discounting_yearwhen it is set, asperiod_cost_ruleand the three economies-of-scale cost terms do. Both the start cost and the start emission cost use it.p_0is first set, not after the discount rate is read as incosts.py. That keeps it off the lines feat: add an investment hurdle rate that changes choices but not reported costs #392 rewrites, so the two merge without conflict.append_startup_costs, which the unit commitment documentation renders, now says which year start costs are discounted to.Nothing else in the extension discounts from the window's first period. The written start costs (
poll_startup_cost_results) already receivemyopic_discounting_yearfromTableWriter.write_costs, and the commitment constraints involve no discounting.The test
tests/test_unit_commitment_discount.pyruns a small myopic case: 2030 and 2040 solved as one window each, at 2%. One generator built from 1 MW units serves a day-only demand, so both units start every day. Starts carry a start cost and start emissions with an emission cost. In each window the test compares every start variable's objective coefficient with its undiscounted yearly start and start emission cost, multiplied by the discount weight the core rule gives the generator's variable cost in the same period. It reads the undiscounted cost fromuc_startup_cost_rptvanduc_startup_emissions_rpe, so it does not depend on how starts are counted. Values are compared withpytest.approxat1e-5; there are no saved outputs. The test does not compare a later window's objective with its written costs, since those also differ for reasons unrelated to starts.Without the fix the 2040 window fails: each start variable has coefficient 116.774 against an expected 95.795, a ratio of 1.219. With the fix the test passes.
The dataset is new (
tests/testing_data/uc_myopic_discount.sql, withtests/testing_configs/config_uc_myopic_discount.toml).test_week.sqlhas only one optimization period, and its periods are numbered from 0, which Temoa reads asmyopic_discounting_yearnot being set.The full suite passes locally (290 tests, Python 3.12, Gurobi's size-limited licence), and ruff and mypy are clean. On our fork's CI the type check passed, and all 290 tests passed on Ubuntu and Windows with Python 3.12, 3.13 and 3.14 and on macOS with Python 3.14. The macOS jobs for Python 3.12 and 3.13 never started: in three attempts GitHub found no hosted macOS runner for them.
What doesn't change
output_costalready discounts starts to the run's first period. A myopic run's written costs can still change, because its later windows may choose differently.myopic_discounting_year; without itp_0stays the first optimization period, as before.Overlaps
#387 (start weighting by days per season) and #392 (investment hurdle rate) also edit
append_startup_costs. This change merges cleanly onto both:git merge-treeagainst their heads8adf18c5and294f1277reports no conflicts. On each merged result the new test passes, along with that pull request's own tests.#388 also changes myopic cost code. It adds a separate horizon-end parameter, leaves
myopic_discounting_yearas it is and shares no files with this change.Summary by CodeRabbit
Bug Fixes
Tests