Skip to content

Commit 9ba9489

Browse files
authored
Merge pull request #246 from TemoaProject/feat/stochastics
Temoa v4 stochastics implementation
2 parents ac21b26 + 03f34d3 commit 9ba9489

34 files changed

Lines changed: 533 additions & 3330 deletions

docs/source/computational_implementation.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ The Temoa model code is organized into clear, purpose-driven packages:
389389
* ``monte_carlo`` - :doc:`monte_carlo` (Uncertainty quantification)
390390
* ``myopic`` - Sequential decision making with limited foresight
391391
* ``single_vector_mga`` - Focused MGA on specific variables ([!] untested in v4.0)
392-
* ``stochastics`` - Stochastic programming capabilities ([!] untested in v4.0)
392+
* ``stochastics`` - :doc:`stochastics` (Stochastic programming capabilities)
393393

394394
* ``temoa._internal`` - Internal utilities (not part of public API)
395395

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ Temoa Project Documentation
66
Documentation
77
monte_carlo
88
unit_checking
9+
stochastics

docs/source/stochastics.rst

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
2+
.. _stochastics:
3+
4+
Stochastic Programming
5+
======================
6+
7+
The Stochastics extension in Temoa v4 provides support for stochastic programming using the `mpi-sppy <https://github.com/Pyomo/mpi-sppy>`_ library. This allows for decision-making under uncertainty by considering multiple scenarios simultaneously and finding an optimal "first-stage" decision that minimizes the expected cost over all scenarios.
8+
9+
Stochastic programming is particularly useful for modeling uncertainties in future costs, demands, or resource availability.
10+
11+
Dependencies
12+
------------
13+
14+
The stochastics extension requires the ``mpi-sppy`` package. You can install it using ``uv``:
15+
16+
.. code-block:: bash
17+
18+
uv add mpi-sppy
19+
20+
Or using ``pip``:
21+
22+
.. code-block:: bash
23+
24+
pip install mpi-sppy
25+
26+
Configuration
27+
-------------
28+
29+
To run Temoa in stochastic mode, you need to modify your main configuration TOML file and provide an additional stochastic configuration file.
30+
31+
Main Configuration TOML
32+
~~~~~~~~~~~~~~~~~~~~~~~
33+
34+
Set the ``scenario_mode`` to ``"stochastic"`` and add a ``[stochastic]`` section:
35+
36+
.. code-block:: toml
37+
38+
scenario_mode = "stochastic"
39+
40+
# ... other standard options ...
41+
42+
[stochastic]
43+
# Path to the stochastic configuration file, relative to this file
44+
stochastic_config = "stochastic_config.toml"
45+
46+
Stochastic Configuration TOML
47+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48+
49+
The stochastic configuration file defines the scenarios, their probabilities, and the data perturbations associated with each scenario.
50+
51+
.. code-block:: toml
52+
53+
# Define the scenarios
54+
[scenarios]
55+
# Each key is a scenario name, and the value is its probability
56+
# Probabilities must sum to 1.0
57+
low_cost = 0.5
58+
high_cost = 0.5
59+
60+
# Define perturbations for a specific scenario
61+
[[perturbations]]
62+
scenario = "low_cost"
63+
table = "cost_variable"
64+
# Filter specifies which rows in the table to perturb
65+
filter = { tech = "IMPHCO1" }
66+
# Action can be "multiply", "add", or "set" (defaults to "set")
67+
action = "multiply"
68+
value = 0.5
69+
70+
[[perturbations]]
71+
scenario = "high_cost"
72+
table = "cost_variable"
73+
filter = { tech = "IMPHCO1" }
74+
action = "multiply"
75+
value = 1.5
76+
77+
Perturbation Options
78+
^^^^^^^^^^^^^^^^^^^^
79+
80+
Currently, the following fields are required for each perturbation:
81+
82+
* **scenario**: The name of the scenario to which this perturbation applies.
83+
* **table**: The Temoa parameter (database table) to perturb (e.g., ``cost_variable``, ``demand``, ``capacity_factor_process``).
84+
* **filter**: A dictionary of column-value pairs used to identify specific rows. Since the extension uses the dynamic manifest from ``HybridLoader``, any column belonging to the table's index can be used for filtering.
85+
* **action**: The operation to perform. Supported values:
86+
* ``multiply``: Multiply the base value by ``value``.
87+
* ``add``: Add ``value`` to the base value.
88+
* ``set``: Replace the base value with ``value``.
89+
* **value**: The numeric value used in the perturbation action.
90+
91+
How it Works
92+
------------
93+
94+
When running in stochastic mode, Temoa:
95+
96+
1. Loads the base data from the input database.
97+
2. Identifies the "first-stage" variables. In the current implementation, all decisions in the first time period are considered first-stage.
98+
3. Orchestrates multiple scenario runs using the ``mpi-sppy`` Extensive Form (EF) solver.
99+
4. For each scenario, the ``scenario_creator`` applies the specified perturbations to the base data and builds a Pyomo model instance.
100+
5. The EF solver binds the first-stage variables across all scenarios (non-anticipativity constraints) and optimizes the total expected cost.
101+
6. The terminal output reports the Stochastic Expected Value.
102+
103+
Limitations
104+
-----------
105+
106+
* **Two-Stage Only**: While ``mpi-sppy`` supports multi-stage stochastic programming, the current Temoa integration is tailored for two-stage problems where the first time period constitutes the first stage.
107+
* **Result Persistence**: Currently, only the expected objective value and summary logs are produced. Detailed per-scenario result persistence to the database is under development.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencies = [
3030
"rich>=14.2.0",
3131
"tomlkit>=0.12.0",
3232
"pint>=0.25.2",
33+
"mpi-sppy>=0.12.1",
3334
]
3435

3536

requirements-dev.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ mdit-py-plugins==0.5.0
7171
# via myst-parser
7272
mdurl==0.1.2
7373
# via markdown-it-py
74+
mpi-sppy==0.12.1
75+
# via temoa (pyproject.toml)
7476
multiprocess==0.70.18
7577
# via salib
7678
myst-parser==4.0.1
@@ -83,6 +85,7 @@ numpy==2.3.1
8385
# contourpy
8486
# highspy
8587
# matplotlib
88+
# mpi-sppy
8689
# pandas
8790
# salib
8891
# scipy
@@ -121,7 +124,9 @@ pygments==2.19.2
121124
# rich
122125
# sphinx
123126
pyomo==6.9.2
124-
# via temoa (pyproject.toml)
127+
# via
128+
# temoa (pyproject.toml)
129+
# mpi-sppy
125130
pyparsing==3.2.3
126131
# via matplotlib
127132
pytest==8.4.1
@@ -149,6 +154,7 @@ salib==1.5.1
149154
scipy==1.16.0
150155
# via
151156
# temoa (pyproject.toml)
157+
# mpi-sppy
152158
# salib
153159
seaborn==0.13.2
154160
# via temoa (pyproject.toml)

requirements.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ matplotlib==3.9.2
3737
# seaborn
3838
mdurl==0.1.2
3939
# via markdown-it-py
40+
mpi-sppy==0.12.1
41+
# via temoa (pyproject.toml)
4042
multiprocess==0.70.18
4143
# via salib
4244
networkx==3.5
@@ -47,6 +49,7 @@ numpy==2.3.1
4749
# contourpy
4850
# highspy
4951
# matplotlib
52+
# mpi-sppy
5053
# pandas
5154
# salib
5255
# scipy
@@ -77,7 +80,9 @@ pygments==2.19.2
7780
# pytest
7881
# rich
7982
pyomo==6.9.2
80-
# via temoa (pyproject.toml)
83+
# via
84+
# temoa (pyproject.toml)
85+
# mpi-sppy
8186
pyparsing==3.2.3
8287
# via matplotlib
8388
pytest==8.4.1
@@ -97,6 +102,7 @@ salib==1.5.1
97102
scipy==1.16.0
98103
# via
99104
# temoa (pyproject.toml)
105+
# mpi-sppy
100106
# salib
101107
seaborn==0.13.2
102108
# via temoa (pyproject.toml)

temoa/_internal/temoa_sequencer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from temoa.extensions.monte_carlo.mc_sequencer import MCSequencer
3434
from temoa.extensions.myopic.myopic_sequencer import MyopicSequencer
3535
from temoa.extensions.single_vector_mga.sv_mga_sequencer import SvMgaSequencer
36+
from temoa.extensions.stochastics.stochastic_sequencer import StochasticSequencer
3637
from temoa.model_checking.pricing_check import price_checker
3738

3839
if TYPE_CHECKING:
@@ -67,6 +68,7 @@ def __init__(
6768
# for results catching for perfect_foresight or testing
6869
self.pf_results: pyomo.opt.SolverResults | None = None
6970
self.pf_solved_instance: TemoaModel | None = None
71+
self.stochastic_sequencer: StochasticSequencer | None = None
7072

7173
def _run_preliminary_checks(self) -> None:
7274
"""Runs pre-flight system checks and (optionally) a non-fatal units check.
@@ -189,6 +191,10 @@ def start(self) -> None:
189191
case TemoaMode.MONTE_CARLO:
190192
self._run_monte_carlo()
191193

194+
case TemoaMode.STOCHASTIC:
195+
self.stochastic_sequencer = StochasticSequencer(config=self.config)
196+
self.stochastic_sequencer.start()
197+
192198
case _:
193199
raise NotImplementedError(
194200
f"The scenario mode '{self.temoa_mode}' is not yet implemented."

temoa/core/config.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def __init__(
5454
myopic: dict[str, object] | None = None,
5555
morris: dict[str, object] | None = None,
5656
monte_carlo: dict[str, object] | None = None,
57+
stochastic_config: Path | None = None,
5758
config_file: Path | None = None,
5859
silent: bool = False,
5960
stream_output: bool = False,
@@ -144,6 +145,7 @@ def __init__(
144145
)
145146
self.plot_commodity_network = plot_commodity_network and self.source_trace
146147
self.graphviz_output = graphviz_output
148+
self.stochastic_config = stochastic_config
147149

148150
# warn if output db != input db
149151
if self.input_database.suffix == self.output_database.suffix: # they are both .db/.sqlite
@@ -228,6 +230,21 @@ def build_config(config_file: Path, output_path: Path, silent: bool = False) ->
228230
else:
229231
raise SolverNotAvailableError('No solver name specified in the configuration.')
230232

233+
if 'stochastic' in data:
234+
stoch_data = data.pop('stochastic')
235+
if 'stochastic_config' in stoch_data:
236+
# Resolve relative to config file Path
237+
sc = config_file.parent / stoch_data['stochastic_config']
238+
data['stochastic_config'] = sc
239+
240+
# Pop other sections that are not top-level arguments in __init__
241+
for section in ['execution', 'model', 'pricing', 'monte_carlo_tweaks']:
242+
if section in data:
243+
data.pop(section)
244+
245+
if 'output_path' in data:
246+
data.pop('output_path')
247+
231248
tc = TemoaConfig(output_path=output_path, config_file=config_file, silent=silent, **data)
232249
logger.info('Scenario Name: %s', tc.scenario)
233250
logger.info('Data source: %s', tc.input_database)

temoa/core/modes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ class TemoaMode(Enum):
1717
CHECK = 6 # build and run price check, source trace it
1818
SVMGA = 7 # single-vector MGA
1919
MONTE_CARLO = 8 # MC optimization
20+
STOCHASTIC = 9 # Stochastic optimization

temoa/extensions/README.txt

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)