Skip to content
Open
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Change Log

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

- [CHANGED] update the architecture diagram (`doc/img/openTEPES_architecture.svg` and the rendered `.png`) to show the new `openTEPES_ProblemSolvingStageIter.py` as an implemented (green) box in the solver layer, alongside `ProblemSolving`, `Tuning`, `DualExtraction`, `Persistent` and `Benders`; `resolve.py` stays white as the one solver module still planned. The seven boxes are re-spaced to fit the layer's existing width — no other layer changes.
- [ADDED] a test (`tests/test_direct_run.py`) that runs every `openTEPES_*.py` module as a script (via `runpy`, with `__name__ == "__main__"`) to check that each one still imports cleanly that way — i.e. the `try`/`except ImportError` relative-import guard's fallback works. A normal `import openTEPES` only ever takes the relative-import path, so until now nothing checked the fallback; a broken guard, a missing import, or a circular import in any module would only surface when a user runs the file directly (VS Code "Run Python File"). The test solves no model, so it runs in the fast CI job; it excludes `openTEPES_Main.py`, which has a real `__main__` block that would launch the command-line interface.
- [CHANGED] move the per-stage solve loop out of `openTEPES_run` into a new module `openTEPES_ProblemSolvingStageIter.py`. The `(period, scenario, stage)` loop — which activates one stage's load levels, builds that stage's operation constraints, and calls `ProblemSolving` — now lives in a function `StageIterativeSolving`, together with the post-loop work that rebuilds the full stage and load-level sets, reactivates every constraint, and restores the scenario probabilities. `openTEPES.py` still builds the objective and the investment constraints, sets `First_st` / `Last_st` and the empty `pDuals`, and then calls the new driver. The two solve paths are unchanged: a deterministic solve per scenario when there are no expansion decisions and no system emission or RES-energy limit, and one joint stochastic solve at the last period-scenario otherwise. This is a pure move — the loop body is copied unchanged — so results do not change: the whole solve test suite passes with the same locked costs, and a full-output run of case 9n gives the same total cost (252.20132998 MEUR, matching to 13 significant figures; only the per-unit dispatch tables move, by the amount HiGHS already varies run-to-run when it picks among equally optimal storage schedules). The new module uses the same `try`/`except ImportError` relative-import guard as the other split modules, so "Run Python File" still works on it. This continues the solver-layer split (after the Persistent, Tuning and DualExtraction modules) and gives the later re-solve and decomposition drivers a single place to call the stage loop from.
- [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.
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.
42 changes: 23 additions & 19 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.
5 changes: 3 additions & 2 deletions openTEPES/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
from .openTEPES_InputDuckDBSource import *
from .openTEPES_InputData import *
from .openTEPES_ModelFormulation import *
from .openTEPES_ProblemSolving import *
from .openTEPES_ProblemSolvingBenders import *
from .openTEPES_ProblemSolving import *
from .openTEPES_ProblemSolvingStageIter import *
from .openTEPES_ProblemSolvingBenders import *
from .openTEPES_OutputResultsCommon import *
from .openTEPES_OutputResultsRawDump import *
from .openTEPES_OutputResultsInvestment import *
Expand Down
Loading
Loading