Skip to content

Discount myopic unit commitment start costs to the run's first period - #399

Open
SutubraResearch wants to merge 1 commit into
TemoaProject:unstablefrom
SutubraResearch:fix/uc-startup-myopic-discount
Open

SutubraResearch wants to merge 1 commit into
TemoaProject:unstablefrom
SutubraResearch:fix/uc-startup-myopic-discount

Conversation

@SutubraResearch

@SutubraResearch SutubraResearch commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

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_costs discounts 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_costs in temoa/extensions/unit_commitment/components/startup.py replaces p_0 with myopic_discounting_year when it is set, as period_cost_rule and the three economies-of-scale cost terms do. Both the start cost and the start emission cost use it.
  • The override comes right after p_0 is first set, not after the discount rate is read as in costs.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.
  • The docstring of 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 receive myopic_discounting_year from TableWriter.write_costs, and the commitment constraints involve no discounting.

The test

tests/test_unit_commitment_discount.py runs 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 from uc_startup_cost_rptv and uc_startup_emissions_rpe, so it does not depend on how starts are counted. Values are compared with pytest.approx at 1e-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, with tests/testing_configs/config_uc_myopic_discount.toml). test_week.sql has only one optimization period, and its periods are numbered from 0, which Temoa reads as myopic_discounting_year not 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

  • How written costs are computed. output_cost already discounts starts to the run's first period. A myopic run's written costs can still change, because its later windows may choose differently.
  • Perfect-foresight runs and every mode other than myopic. Only the myopic sequencer sets myopic_discounting_year; without it p_0 stays the first optimization period, as before.
  • The first window of a myopic run, whose first period is the run's first period.

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-tree against their heads 8adf18c5 and 294f1277 reports 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_year as it is and shares no files with this change.

Summary by CodeRabbit

  • Bug Fixes

    • Corrected discounting for startup and startup-emission costs in myopic unit-commitment runs.
    • These costs now use the same discount period as other variable costs, ensuring consistent objective calculations across optimization windows.
  • Tests

    • Added coverage for discounting behavior across multiple myopic optimization periods.

@codecov

codecov Bot commented Oct 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 62dcae7 and 3967b83.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: TemoaProject/temoa/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 97be9db3-f0c2-441a-bcfe-331278f55525
📥 Commits

Reviewing files that changed from the base of the PR and between d5148db and 62dcae7.

📒 Files selected for processing: 5
  • temoa/extensions/unit_commitment/components/startup.py
  • tests/conftest.py
  • tests/test_unit_commitment_discount.py
  • tests/testing_configs/config_uc_myopic_discount.toml
  • tests/testing_data/uc_myopic_discount.sql

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.

Walkthrough

Startup 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.

Changes

Myopic startup-cost discounting

Layer / File(s) Summary
Discount base selection
temoa/extensions/unit_commitment/components/startup.py
When myopic_discounting_year is nonzero, startup costs and startup-emission costs use it as the discount base. Otherwise, the first optimized period remains the base.
Myopic-window regression coverage
tests/testing_configs/config_uc_myopic_discount.toml, tests/testing_data/uc_myopic_discount.sql, tests/conftest.py, tests/test_unit_commitment_discount.py
The new fixture and configuration define a two-window myopic unit-commitment scenario. The test checks startup and startup-emission objective coefficients against the variable-cost discount weight in each window.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 62dca

No actionable issue is identified with the myopic startup-cost discounting change. It is ready for normal merge checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 3 files. (2 skipped: 2… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: applying the run's first-period discount to myopic unit-commitment startup costs.
Linked Issues check Passed Issue #398 requires myopic unit-commitment startup cost and startup emission cost coefficients to use myopic_discounting_year. append_startup_costs now replaces p_0 with that value when it is no…
Out of Scope Changes check Passed The changes stay within issue #398. The production change updates startup-cost discounting and its documentation. The test, test configuration, SQL fixture, and database refresh entry support the requ…
Full details: Linked Issues check

Explanation

Issue #398 requires myopic unit-commitment startup cost and startup emission cost coefficients to use myopic_discounting_year. append_startup_costs now replaces p_0 with that value when it is nonzero. The same p_0 is passed to both startup cost terms. The added regression test solves separate 2030 and 2040 windows, verifies the discount year is 2030, and compares startup coefficients with the core variable-cost discount weight. The docstring records the behavior.

Full details: Out of Scope Changes check

Explanation

The changes stay within issue #398. The production change updates startup-cost discounting and its documentation. The test, test configuration, SQL fixture, and database refresh entry support the required myopic regression. No unrelated production behavior is shown in the supplied change summary or inspected files.

Full details: Docstring Coverage

Explanation

Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 3 files. (2 skipped: 2 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jia-hui-chen0 jia-hui-chen0 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@SutubraResearch
SutubraResearch force-pushed the fix/uc-startup-myopic-discount branch from 62dcae7 to 3967b83 Compare October 23, 2026 18:24

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants