Skip to content

fix: amortize installation impacts over a unit's lifetime output - #61

Merged
TimoDiepers merged 2 commits into
mainfrom
fix/lifetime-installation-semantics
Aug 20, 2026
Merged

TimoDiepers merged 2 commits into
mainfrom
fix/lifetime-installation-semantics

Conversation

@TimoDiepers

Copy link
Copy Markdown
Collaborator

Problem

An installed unit's capacity was treated as available in every year of its operation window, so one unit could deliver its entire lifetime production each year. Installation-dependent flows (construction, end-of-life) were spread over too much output and under-counted by the number of operating years a vintage was dispatched — 16x for a 16-year process, and so on. Only single-year operation windows were correct.

Fix

The unit convention is now explicit: one installed unit delivers the production of its production exchange over its whole lifetime, so the per-process-time entry is the output of one running unit per year.

  • Production and operation-dependent flows are read at the vintage's lifecycle stage tau = t - v instead of summed over the operation window.
  • OperationCapacity compares unit counts: var_operation[p, v, t] <= var_installation[p, v].
  • Postprocessing capacity outputs (get_production_capacity, plot_capacity_balance, plot_utilization_heatmap) report annual capacity, comparable with production. get_installation returns unit counts, a lifetime quantity.
  • Convention documented in the optimizer module docstring, the postprocessing docstrings and plot labels, and in the foreground modeling, theory, constraints, postprocessing and quickstart docs.

Impact on results

Optimization results change for any process with a multi-year operation window; construction-heavy technologies are affected most. Models with a single-year window are unaffected.

Note for existing setups: existing_capacity and deployment limits are in process units, so values calibrated as an annual capacity now need multiplying by the number of operating years.

Tests

Test expectations restated in unit counts. test_single_route_lca_comparison.py gains a multi-period demand case that pins optimex to bc.LCA exactly when every unit is fully used, plus a case documenting that a single demand year legitimately strands capacity. Full suite passes.

An installed unit's capacity was treated as available in every year of its
operation window, so one unit could deliver its whole lifetime production each
year. Installation-dependent flows (construction, end-of-life) were therefore
spread over too much output, under-counting them by the number of operating
years a vintage was dispatched.

Production and operation-dependent flows are now taken at the vintage's current
lifecycle stage tau = t - v, and the capacity bound compares unit counts:
var_operation[p, v, t] <= var_installation[p, v].

Postprocessing capacity outputs report annual capacity, so they stay comparable
with production; get_installation returns unit counts, which are a lifetime
quantity. Docstrings, docs and changelog state the convention explicitly.
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 32.00000% with 68 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/optimex/postprocessing.py 17.50% 66 Missing ⚠️
src/optimex/optimizer.py 90.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

langeluca added a commit to langeluca/optimex that referenced this pull request Aug 1, 2026
…n-fix

Integrates RWTH-LTT#61, which fixes the lifetime installation-impact accounting for multi-year processes.

Previously, production and operation-dependent flows were summed over the complete operation window in every calendar year. As a result, one installed unit could incorrectly deliver its entire lifetime production each year. This understated the required number of installed units and therefore undercounted construction and other installation-dependent impacts by up to the number of operating years.

Resolves merge conflicts in CHANGES.md and lca_processor.py while retaining both the upstream background-activity lookup improvements and the fork's economic CAPEX/OPEX extensions.

Migrates the ethylene case study to the new capacity semantics: existing_capacity now represents process units. The assumed annual Steam Cracker capacity remains 0.5e9 kg ethylene per vintage and is converted to 25e9 process units for a 50-year lifetime.

Validation:
- 78 tests passed and 3 were skipped
- HiGHS was used locally because GLPK was unavailable
- Notebook JSON and the brownfield conversion identity were statically verified
Resolve conflicts against the v0.6.0 performance refactor (#64):

- optimizer.py: main moved the flow and production rates into cached helpers
  (`flow_rate`, `production_rate`) that summed the tensor over the whole
  operation window. Both now take the lifecycle stage `tau` and return the
  per-tau entry, i.e. the flow/output per operating unit and year; the cache
  key carries `tau`. Call sites pass `t - v`.
- optimizer.py: `OperationCapacity` keeps the PR's unit-count form and is
  indexed by ACTIVE_VINTAGE_TIME only, so `get_production_value` is now
  unused and removed.
- tests/test_single_route_lca_comparison.py: main rewrote the file with LF
  endings and added `test_background_inventory_handles_squeezed_solver_result`,
  so the whole file conflicted; rebuilt as main's version plus the PR's
  restated single-year test and new multi-period test.
- CHANGES.md: PR entries moved to [Unreleased] above the released 0.6.0.
@TimoDiepers
TimoDiepers force-pushed the fix/lifetime-installation-semantics branch from 8a9a505 to 48e8d51 Compare August 20, 2026 16:58
@TimoDiepers
TimoDiepers merged commit e04daf0 into main Aug 20, 2026
12 of 13 checks passed
@TimoDiepers
TimoDiepers deleted the fix/lifetime-installation-semantics branch August 20, 2026 17:07
@TimoDiepers TimoDiepers mentioned this pull request Aug 20, 2026
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.

1 participant