Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## [4.18.17RC] - 2026-06-03 Unreleased in PyPI

- [CHANGED] split the ~2800-line `openTEPES_OutputResults.py` into per-concern modules at the package root — the output-side counterpart of the earlier `openTEPES_Input*` and `openTEPES_ProblemSolving*` splits. The result writers now live in `openTEPES_OutputResultsInvestment.py`, `...Generation.py`, `...Storage.py`, `...Hydrogen.py` (hydrogen network), `...Heat.py` (heat network), `...Network.py` (electricity network and map), `...Economic.py` (marginal, cost-summary, economic), `...Summary.py` (system summary, flexibility, reliability), and `...RawDump.py` (the raw parameter/variable/constraint DuckDB dump). Shared pieces sit in `openTEPES_OutputResultsCommon.py` (the output-directory helper and the three Altair plot builders) and `openTEPES_OutputResultsMapCommon.py` (the flow-series and snapshot-period helpers the three network maps had each copied). The old `openTEPES_OutputResults.py` is removed: `__init__.py` and `openTEPES.py` import the result functions from the concern modules directly, the same way the Input and ProblemSolving splits are wired (no re-export facade). Functions are grouped by topic; the dispatch order is still controlled by `OUTPUT_REGISTRY` in `openTEPES.py` and is unchanged. Every cross-module import uses the same `try`/`except ImportError` relative-import guard as the other split modules, so "Run Python File" works. Verified bit-identical with the output parity tool on case 9n (`PYTHONHASHSEED=0`, all 87 result tables; total cost 164.382043867 MEUR). Two small cleanups made while moving the code: removed 34 no-op `"".join([f"..."])` wrappers (each is just the f-string) and stopped `ESSOperationResults` from writing its inventory-utilization scaling back into `mTEPES.pMaxStorage` (the denominator is now computed locally, so the scaled value no longer leaks to later reads of the parameter).
- [ADDED] a `9n_H2` example case (the hydrogen counterpart of `9n_heat`): the minimal 9-node electricity system plus two electrolyzers, hydrogen demand at three nodes, and a hydrogen pipe network. Added to the single-stage CI solve suite in `tests/test_run.py` (expected cost 259.547 MEUR under the 7-day HiGHS fixture, verified deterministic across reruns), so CI exercises the hydrogen result writer (`NetworkH2OperationResults`) directly on a small fast case rather than only through `sSEP`. Output verified bit-identical before and after the sector-coupling module split, for both CSV and DuckDB inputs.
- [FIXED] two operating-reserve guards in the marginal and economic results read a leaked loop variable. The down-reserve-marginal guard and the up-reserve-revenue guard loop over storage units (`for ar,es in mTEPES.ar*mTEPES.es`) but checked `pIndOperReserveGen[nr]` / `pIndOperReserveCon[nr]` — `nr` only existed as a leftover from the earlier `for ar,nr ...` loop that builds the area-to-generator map. They now check the loop's own `es`, matching the two sibling guards (up-reserve-marginal, down-reserve-revenue) that were already correct. The down-reserve-revenue guard also read `pIndOperReserveGen[nr]` twice (`Gen ... or ... Gen`) where its siblings read `Gen ... or ... Con`; the second is now `Con`. On case 9n the result is unchanged; on a case where the leftover unit's reserve flags differ from the storage units' these guards now correctly decide which operating-reserve-revenue tables are written.
- [CHANGED] update the architecture diagram (`doc/img/openTEPES_architecture.svg`) so it matches the code. The old picture used the planned folder names (`io/`, `schema.py`, `solver/`, `solve.py`); it now shows the real flat module names (`openTEPES_InputSchema.py`, `openTEPES_ProblemSolving.py`, and so on), with the five real solver modules. Implemented modules are shaded green and planned ones are left white, with a small legend. Also commit a rendered `doc/img/openTEPES_architecture.png` and point the `README.md` at the PNG instead of the SVG, so the diagram shows on pages that do not display SVG (such as the PyPI project page). Added a short `doc/img/README.md` explaining the SVG is the hand-drawn source (there is no generator script), that the PNG is rendered from it, and how to regenerate the PNG on Windows, macOS and Linux.
- [FIXED] a binary investment problem (for example `IndBinNetInvest=1`) crashed under the HiGHS solver with `NoDualsError` on the first solve. `ProblemSolving` attaches the `dual` Suffix before the first solve only for a pure-LP model, because a mixed-integer problem has no duals; it then recovers the duals later by fixing the integer variables and re-solving as an LP. The check that decided "is this a pure LP" also required each variable to already have a value, but before the first solve no variable has a value yet, so a model with binary *investment* variables was wrongly treated as an LP, got the Suffix, and crashed when HiGHS was asked for duals it does not have. The check now looks only at whether a variable is integer/binary and unfixed, not at its value. Unit-commitment models did not hit this because their binary variables are given starting values. No change for any model that already worked.
- [ADDED] a test (`test_binary_investment` in `tests/test_run.py`) for a binary (integer) investment decision, which no other test covered — every other case solves the investment variables as a continuous relaxation. It switches on `IndBinNetInvest` for the `9n` case so the single candidate line becomes a {0,1} build-or-not decision; the cost (254.337 MEUR) differs from the continuous result (252.201 MEUR) because the binary decision forces a full line build. The test also guards the fix above. A companion fixture `case_7d_binary` runs the case from a private temporary copy (so its solver log files do not clash with the other 9n tests on Windows, where a log file can stay open after a solve), applies the 7-day truncation, and overrides one or more columns of the Option file.
Expand Down
Binary file modified doc/img/openTEPES_architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
142 changes: 50 additions & 92 deletions doc/img/openTEPES_architecture.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion openTEPES/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,13 @@
from .openTEPES_ModelFormulation import *
from .openTEPES_ProblemSolving import *
from .openTEPES_ProblemSolvingBenders import *
from .openTEPES_OutputResults import *
from .openTEPES_OutputResultsCommon import *
from .openTEPES_OutputResultsRawDump import *
from .openTEPES_OutputResultsInvestment import *
from .openTEPES_OutputResultsGeneration import *
from .openTEPES_OutputResultsStorage import *
from .openTEPES_OutputResultsHydrogen import *
from .openTEPES_OutputResultsHeat import *
from .openTEPES_OutputResultsNetwork import *
from .openTEPES_OutputResultsEconomic import *
from .openTEPES_OutputResultsSummary import *
6 changes: 3 additions & 3 deletions openTEPES/_output_parity_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
snapshots can then be compared: numbers are compared with a small tolerance,
text labels exactly.

It is meant for safely changing the output code. If you reorganise
``openTEPES_OutputResults.py``, the result files should stay the same. To
check that, take a snapshot before and after the change and compare them:
It is meant for safely changing the output code. If you reorganise the
``openTEPES_OutputResults*.py`` modules, the result files should stay the
same. To check that, take a snapshot before and after the change and compare:

# before the change
python -m openTEPES._output_parity_test snapshot <case_output_folder> before.pkl
Expand Down
Loading
Loading