Skip to content

Conversation

@ParticularlyPythonicBS
Copy link
Member

@ParticularlyPythonicBS ParticularlyPythonicBS commented Jan 7, 2026

Revamped stochastics extension for Temoa v4 using mpi-sppy, adds mpi-sppy as a core dependency

Core Integration

  • modes.py: Added STOCHASTIC mode to TemoaMode
  • config.py: Updated TemoaConfig to support stochastic_config files and handled TOML section filtering in build_config
  • temoa_sequencer.py: Enabled routing to the new StochasticSequencer

Stochastic Components

  • stochastic_config.py : New class for parsing stochastic scenarios and data perturbations from TOML.
  • stochastic_sequencer.py : Orchestrates mpi-sppy runs, initializing the ExtensiveForm solver and managing the scenario lifecycle.
  • scenario_creator.py: Bridges Temoa and mpi-sppy. It performs:
    • Base data loading via HybridLoader
    • Scenario-specific data perturbations.
    • Model instance building.
    • Identification and attachment of first-stage variables and costs.

Summary by CodeRabbit

  • New Features

    • Added a TOML-driven stochastic workflow: config loader, scenario creator, and sequencer to run scenario-based optimization and report stochastic expected value.
  • Dependencies

    • Added mpi-sppy to enable MPI/extensive-form stochastic solves.
  • Documentation

    • Added new stochastic docs and navigation entries.
  • Removals

    • Removed legacy/experimental stochastic utilities and generation scripts.
  • Tests

    • Added an automated stochastic system test and supporting test config/data.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

Walkthrough

Adds first-class stochastic support: new TOML-parsed dataclasses, a scenario_creator that loads and perturbs DB data, and a StochasticSequencer that runs mpi-sppy ExtensiveForm solves; integrates a STOCHASTIC mode and tests; removes many legacy stochastic scripts and adds mpi-sppy as a dependency and docs.

Changes

Cohort / File(s) Summary
Documentation
docs/source/computational_implementation.rst, docs/source/index.rst, docs/source/stochastics.rst
Added stochastics docs and toctree entry; updated wording and installation/config guidance.
Dependencies
pyproject.toml, requirements-dev.txt, requirements.txt
Added runtime/dev dependency mpi-sppy>=0.12.1 and updated comments in requirements files.
Core config & sequencer
temoa/core/config.py, temoa/core/modes.py, temoa/_internal/temoa_sequencer.py
Added stochastic_config handling to TemoaConfig, new TemoaMode.STOCHASTIC, and integration to instantiate/start StochasticSequencer.
New stochastic implementation
temoa/extensions/stochastics/stochastic_config.py, temoa/extensions/stochastics/scenario_creator.py, temoa/extensions/stochastics/stochastic_sequencer.py
New dataclasses and TOML parser for stochastic config, scenario_creator that builds and perturbs Pyomo instances from DB data, and StochasticSequencer that orchestrates mpi-sppy EF solves.
Legacy removal
temoa/extensions/stochastics/* (many files)
Large deletions: legacy EF tools, scenario-tree generators, hard-coded option/data files, scripts, READMEs, and PBS/job scripts removed. Review for API/behavioral regressions where callers referenced these modules.
Tests & test configs
tests/conftest.py, tests/test_full_runs.py, tests/testing_configs/config_utopia_stochastic.toml, tests/testing_data/utopia_stochastic_config.toml
Test DB mapping adjusted to allow None source; added stochastic utopia system test, test TOML, and stochastic-config TOML with scenarios/perturbations.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TemoaSequencer
    participant StochasticSequencer
    participant mpi_sppy as mpi-sppy
    participant ScenarioCreator
    participant Database

    User->>TemoaSequencer: start(config mode=STOCHASTIC)
    TemoaSequencer->>StochasticSequencer: instantiate(config)
    StochasticSequencer->>StochasticSequencer: load StochasticConfig.from_toml()
    StochasticSequencer->>StochasticSequencer: build options & scenario list
    StochasticSequencer->>mpi_sppy: init ExtensiveForm(options, scenario_creator)
    loop for each scenario
        mpi_sppy->>ScenarioCreator: scenario_creator(name, temoa_config, stoch_config)
        ScenarioCreator->>Database: load base tables via HybridLoader
        ScenarioCreator->>ScenarioCreator: apply perturbations (filter/action)
        ScenarioCreator->>mpi_sppy: return Pyomo ConcreteModel (probability, first-stage info)
    end
    mpi_sppy->>mpi_sppy: solve extensive form
    mpi_sppy->>StochasticSequencer: return objective (expected value)
    StochasticSequencer-->>User: expose/store stochastic result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Temoa v4 stochastics implementation' directly and specifically summarizes the main objective of the PR, which is to implement stochastic programming capabilities for Temoa v4.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

@ParticularlyPythonicBS ParticularlyPythonicBS changed the title adding mpi-sppy dependency for stochastics Temoa v4 stochastics implementation Jan 7, 2026
@ParticularlyPythonicBS ParticularlyPythonicBS marked this pull request as ready for review January 8, 2026 16:08
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🤖 Fix all issues with AI agents
In @pyproject.toml:
- Line 33: Update the dependency spec for mpi-sppy in pyproject.toml from
"mpi-sppy>=0.12.1" to "mpi-sppy>=0.13.1" (replace the existing version token),
then run your test suite and CI to validate compatibility; review the mpi-sppy
changelog between 0.12.1 and 0.13.1 for any API or behavior changes that may
require code updates in places that import or call mpi-sppy (search for
imports/usages of mpi_sppy in the repo) and adjust code accordingly if tests or
changelog indicate breaking or behavioral changes.

In @temoa/extensions/stochastics/scenario_creator.py:
- Line 82: The code constructs periods with sorted(list(instance.time_optimize))
which unnecessarily wraps the iterable in list(); change the expression to
sorted(instance.time_optimize) inside the function where periods is defined (the
variable periods and the attribute instance.time_optimize are the targets) to
remove the redundant list() call while preserving behavior.
- Around line 30-39: Replace the f-string in the logger.warning call inside the
perturbation loop so it uses lazy logging formatting; locate the loop over
stoch_config.perturbations in scenario_creator.py (the variable p and the check
target_param is None) and change the logging invocation to pass a format string
with placeholders and the variables p.table and scenario_name as arguments
(e.g., use "Table %s not found in data_dict for scenario %s" with p.table and
scenario_name) to avoid constructing the message when the warning level is
disabled.
- Around line 81-87: The probability lookup uses a nested dict accessor but
scenarios are a flat mapping; replace the nested .get usage with a flat lookup:
use stoch_config.scenarios.get(scenario_name, 1.0) to set
instance._mpisppy_probability, and update the StochasticConfig type hint
(stochastic_config.py line where scenarios is declared) from dict[str, dict[str,
float]] to dict[str, float] so the types match the TOML structure and the
probability values are read correctly.
- Around line 14-28: The scenario_creator function lacks type annotations and
doesn't handle DB connection errors; add type annotations for scenario_name
(str) and kwargs (e.g., **kwargs: Any or use explicit parameters temoa_config:
TemoaConfig, stoch_config: StochasticConfig) and validate presence of
'temoa_config' and 'stoch_config' before use; wrap the sqlite3.connect(...) call
in a try/except to catch sqlite3.Error (or Exception), log a clear message
including temoa_config.input_database and the caught exception, and re-raise or
raise a custom RuntimeError; keep usage of HybridLoader(db_connection=con,
config=temoa_config) and data_dict =
hybrid_loader.create_data_dict(myopic_index=None) unchanged except for the safe
connection handling.

In @temoa/extensions/stochastics/stochastic_config.py:
- Around line 15-18: The scenarios field on the StochasticConfig dataclass is
incorrectly typed as dict[str, dict[str, float]]; change the type hint to
dict[str, float] (scenario_name -> probability) and update its comment
accordingly so it matches the TOML loader and the expected structure used
elsewhere (see StochasticConfig.scenarios and related usage in
scenario_creator.py around the scenario handling code).

In @temoa/extensions/stochastics/stochastic_sequencer.py:
- Around line 61-66: Remove the unused local variable by calling
ef.solve_extensive_form() without assigning its return (or assign to _ if
preferred) and keep setting self.objective_value from ef.get_objective_value();
replace the f-string logger.info(f"Stochastic Expected Value: {obj_val}") with
lazy logging logger.info("Stochastic Expected Value: %s", obj_val) to avoid
unnecessary string interpolation when logging is disabled—references: the
results variable, ef.solve_extensive_form(), obj_val, self.objective_value, and
logger.info.
- Around line 28-33: The ImportError handling in the top-level import block
currently logs a message and raises a new RuntimeError without chaining; change
it to preserve the original exception context by capturing the ImportError as e
and re-raising with exception chaining (e.g., raise RuntimeError("mpi-sppy not
found") from e) or by re-raising the original ImportError; also consider using
logger.exception instead of logger.error to include the traceback when logging
before the raise. Apply this change around the import block that references
mpisppy.utils.sputils, mpisppy.opt.ef.ExtensiveForm, and the logger used there.
- Around line 19-22: The current loading of the stochastic config
(self.config.stochastic_config -> StochasticConfig.from_toml) lacks validation
and error handling; before calling StochasticConfig.from_toml, verify the
referenced path exists and is readable, and wrap the call in a try/except that
catches FileNotFoundError, toml decoding errors (e.g., toml.TomlDecodeError or
equivalent), and general ValueError/Exception from the parser, rethrowing a
ValueError with a clear message that includes the source path and the underlying
error details so users see whether the file is missing or contains invalid TOML.
- Line 45: The code assumes scenarios exist when building all_scenario_names
from self.stoch_config.scenarios; add a pre-check (before creating
ExtensiveForm) to validate that self.stoch_config.scenarios is non-empty and
raise a clear error (e.g., ValueError or RuntimeError) with an actionable
message like "No scenarios found in stoch_config.scenarios; provide at least one
scenario before constructing the ExtensiveForm" so users get a helpful error
instead of mpi-sppy failing later; reference the all_scenario_names assignment
and the subsequent ExtensiveForm construction when adding this guard.

In @tests/testing_configs/config_utopia_stochastic.toml:
- Around line 13-15: Remove the unused [execution] section: delete the block
containing [execution] and solver = "appsi_highs"; then add save_duals = false
at the top level (same placement as other test configs) so the setting is
actually read by the loader (temoa/core/config.py pops/discards [execution]).
Ensure no other references to solver = "appsi_highs" remain in this config.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac21b26 and 3fa4f54.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (33)
  • docs/source/computational_implementation.rst
  • docs/source/index.rst
  • docs/source/stochastics.rst
  • pyproject.toml
  • requirements-dev.txt
  • requirements.txt
  • temoa/_internal/temoa_sequencer.py
  • temoa/core/config.py
  • temoa/core/modes.py
  • temoa/extensions/README.txt
  • temoa/extensions/stochastics/EVPI.py
  • temoa/extensions/stochastics/README.txt
  • temoa/extensions/stochastics/ReferenceModel.py
  • temoa/extensions/stochastics/VSS.py
  • temoa/extensions/stochastics/generate_scenario_tree-nonhomogenous.py
  • temoa/extensions/stochastics/generate_scenario_tree.py
  • temoa/extensions/stochastics/jobTemoa.pbs
  • temoa/extensions/stochastics/legacy_files/ef_writer_script_old.py
  • temoa/extensions/stochastics/legacy_files/scenariomodels.py
  • temoa/extensions/stochastics/options/README.txt
  • temoa/extensions/stochastics/options/__init__.py
  • temoa/extensions/stochastics/options/iew2012-nonhomogenized-markov.py
  • temoa/extensions/stochastics/options/iew2012.py
  • temoa/extensions/stochastics/options/utopia_coal_vs_nuc.py
  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/stochastics/stochastic_config.py
  • temoa/extensions/stochastics/stochastic_sequencer.py
  • temoa/extensions/stochastics/stochastics_README.txt
  • temoa/extensions/stochastics/temoa_stochastic.py
  • tests/conftest.py
  • tests/test_full_runs.py
  • tests/testing_configs/config_utopia_stochastic.toml
  • tests/testing_data/utopia_stochastic_config.toml
💤 Files with no reviewable changes (16)
  • temoa/extensions/stochastics/options/README.txt
  • temoa/extensions/README.txt
  • temoa/extensions/stochastics/stochastics_README.txt
  • temoa/extensions/stochastics/generate_scenario_tree.py
  • temoa/extensions/stochastics/legacy_files/ef_writer_script_old.py
  • temoa/extensions/stochastics/README.txt
  • temoa/extensions/stochastics/EVPI.py
  • temoa/extensions/stochastics/options/iew2012-nonhomogenized-markov.py
  • temoa/extensions/stochastics/VSS.py
  • temoa/extensions/stochastics/temoa_stochastic.py
  • temoa/extensions/stochastics/generate_scenario_tree-nonhomogenous.py
  • temoa/extensions/stochastics/options/utopia_coal_vs_nuc.py
  • temoa/extensions/stochastics/ReferenceModel.py
  • temoa/extensions/stochastics/jobTemoa.pbs
  • temoa/extensions/stochastics/legacy_files/scenariomodels.py
  • temoa/extensions/stochastics/options/iew2012.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T22:55:02.519Z
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 186
File: temoa/_internal/temoa_sequencer.py:60-63
Timestamp: 2025-11-03T22:55:02.519Z
Learning: In `TemoaSequencer.__init__`, when a `mode_override` is provided, it must be written back to `self.config.scenario_mode` (not just stored in `self.temoa_mode`) because `HybridLoader.create_data_dict()` has strict consistency checks that validate `config.scenario_mode` matches the presence/absence of `myopic_index`. Without this synchronization, MYOPIC mode would fail with a RuntimeError when the loader is instantiated.

Applied to files:

  • temoa/_internal/temoa_sequencer.py
🧬 Code graph analysis (2)
temoa/_internal/temoa_sequencer.py (2)
temoa/extensions/stochastics/stochastic_sequencer.py (1)
  • StochasticSequencer (12-69)
temoa/core/modes.py (1)
  • TemoaMode (9-20)
temoa/extensions/stochastics/stochastic_sequencer.py (3)
temoa/core/config.py (1)
  • TemoaConfig (32-344)
temoa/extensions/stochastics/stochastic_config.py (2)
  • StochasticConfig (16-38)
  • from_toml (21-38)
temoa/extensions/stochastics/scenario_creator.py (1)
  • scenario_creator (14-101)
🪛 Ruff (0.14.10)
temoa/extensions/stochastics/scenario_creator.py

14-14: Missing type annotation for **kwargs

(ANN003)


37-37: Logging statement uses f-string

(G004)


82-82: Unnecessary list() call within sorted()

Remove the inner list() call

(C414)

temoa/extensions/stochastics/stochastic_sequencer.py

17-17: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)


20-20: Avoid specifying long messages outside the exception class

(TRY003)


32-32: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


33-33: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


33-33: Avoid specifying long messages outside the exception class

(TRY003)


61-61: Local variable results is assigned to but never used

Remove assignment to unused variable results

(F841)


66-66: Logging statement uses f-string

(G004)

🔇 Additional comments (24)
docs/source/index.rst (1)

9-9: LGTM! Documentation navigation updated correctly.

The stochastics entry is properly added to the documentation table of contents, aligning with the new stochastic programming functionality introduced in this PR.

requirements.txt (1)

40-41: LGTM! Dependency additions are consistent and correctly generated.

The mpi-sppy dependency and its transitive dependency references are properly included. The autogenerated file correctly reflects the pyproject.toml changes with exact version pinning (==0.12.1) and updated dependency comments.

Also applies to: 52-52, 83-85, 105-105

requirements-dev.txt (1)

74-75: LGTM! Dev requirements correctly updated.

The mpi-sppy dependency additions and transitive dependency comment updates are consistent with the main requirements file and properly reflect the pyproject.toml changes for the development environment.

Also applies to: 88-88, 127-129, 157-157

temoa/core/modes.py (1)

20-20: LGTM! Clean enum extension for stochastic mode.

The STOCHASTIC mode is properly added with sequential numbering and a clear comment. This cleanly extends the TemoaMode enum to support the new stochastic optimization functionality introduced in this PR.

docs/source/computational_implementation.rst (1)

392-392: LGTM! Documentation update accurately reflects the new stochastic implementation.

The removal of the "[!] untested in v4.0" qualifier is appropriate given that this PR introduces comprehensive stochastic programming capabilities with test coverage.

tests/conftest.py (2)

95-95: LGTM! Addition of stochastic test database entry is appropriate.

The None source for utopia_stochastic.sqlite correctly indicates that this database will be created without initial data scripts, which aligns with the stochastic testing workflow introduced in this PR.


99-100: LGTM! Conditional handling for None source is correct.

The implementation properly handles the None case by creating an empty list, allowing test databases to be created with only the schema and no initial data scripts. This is essential for supporting stochastic test scenarios.

tests/testing_data/utopia_stochastic_config.toml (3)

1-3: LGTM! Scenario definitions are well-structured.

The two scenarios with equal probabilities (0.5 each) sum to 1.0, which is mathematically correct for a probability distribution. The descriptive names (low_coal and high_coal) clearly indicate the test case intent.


13-19: LGTM! Perturbation for high coal scenario is well-defined.

The second perturbation appropriately increases the cost by 50% (multiply by 1.5) for the high_coal scenario. Together with the first perturbation (0.5x), this creates a symmetric test case exploring both low and high coal cost scenarios.


5-11: IMPHCO1 technology exists and perturbation configuration is valid.

The IMPHCO1 technology is defined in the utopia test database as an imported coal supply technology with cost_variable data, confirming this perturbation configuration will function correctly.

temoa/_internal/temoa_sequencer.py (3)

36-36: LGTM! Import statement is correctly placed.

The import of StochasticSequencer is appropriately positioned with other extension sequencer imports and follows the established pattern in this module.


71-71: LGTM! Member initialization follows established patterns.

The stochastic_sequencer member is properly typed and initialized, consistent with how other mode-specific sequencers and results are stored in the class (e.g., pf_results, pf_solved_instance).


194-196: LGTM! STOCHASTIC mode implementation follows established patterns.

The implementation correctly:

  • Instantiates StochasticSequencer with the config
  • Stores the sequencer instance for test access (as seen in test_full_runs.py)
  • Invokes start() to execute the stochastic workflow

This follows the same pattern used for other modes like MYOPIC and MGA.

tests/testing_configs/config_utopia_stochastic.toml (2)

1-8: LGTM! Main configuration section is correctly structured.

The configuration appropriately sets:

  • scenario_mode = "stochastic" to activate the new stochastic workflow
  • Input/output paths to the testing directories
  • appsi_highs as the solver
  • Time sequencing consistent with utopia test data

10-11: LGTM! Stochastic configuration reference is correct.

The stochastic_config path correctly points to the stochastic scenario definitions using a relative path. Based on the AI summary, TemoaConfig includes path resolution support for stochastic_config, so this should work as intended.

tests/test_full_runs.py (3)

8-8: LGTM!

The addition of Any to the typing imports is appropriate for the new stochastic test's type hints.


37-37: LGTM!

The stochastic_files list follows the established pattern for test configuration, consistent with myopic_files and mc_files.


127-146: LGTM!

The stochastic test implementation is sound:

  • Defensive check for stochastic_sequencer existence before accessing attributes.
  • Appropriate use of pytest.approx with relative tolerance for numerical comparison.
  • Consistent with existing test patterns.

The hardcoded expected objective value (34389.9878) is reasonable for regression testing. Consider adding a brief comment explaining the source or derivation of this value for future maintainers.

docs/source/stochastics.rst (1)

1-106: LGTM! Clear and comprehensive documentation.

The documentation provides excellent coverage of the stochastic programming feature, including installation, configuration, execution flow, and limitations. The examples are helpful and the structure is well-organized.

temoa/core/config.py (3)

57-57: LGTM!

The addition of the stochastic_config parameter to the __init__ signature properly supports the new stochastic mode configuration.


148-148: LGTM!

The assignment of self.stochastic_config correctly stores the configuration path for later use.


233-246: LGTM!

The stochastic section handling properly:

  • Extracts the stochastic configuration from the TOML data
  • Resolves the relative path correctly using config_file.parent
  • Removes non-top-level sections to prevent unexpected kwargs

The implementation is clean and follows the existing pattern for section handling.

temoa/extensions/stochastics/stochastic_config.py (2)

6-13: LGTM!

The Perturbation dataclass is well-structured with appropriate types and clear field names.


20-38: LGTM with note on documentation inconsistency.

The from_toml implementation correctly loads the stochastic configuration from TOML. The method properly handles missing sections with defaults and constructs Perturbation objects with appropriate field mapping.

Note: The code reads 'table' from perturbations (line 31), but the documentation examples use 'variable'. This inconsistency has been flagged separately in the documentation review.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🤖 Fix all issues with AI agents
In @temoa/extensions/stochastics/scenario_creator.py:
- Line 36: The logger.exception call is passing the exception object explicitly
which is redundant; edit the logger.exception invocation that currently reads
logger.exception("Failed to connect to database %s: %s",
temoa_config.input_database, e) so it only formats the database name (e.g.,
logger.exception("Failed to connect to database %s",
temoa_config.input_database)), allowing logging.exception to automatically
include the exception traceback.
- Line 14: Move the "from typing import Any" import so it is grouped with the
other top-level imports (place it near the top of
temoa/extensions/stochastics/scenario_creator.py, e.g., immediately after other
standard/library imports) to keep import ordering consistent; ensure the symbol
Any is still available for use in functions or classes in this module after
relocating the import.
- Around line 78-85: The code in scenario_creator.py silently ignores unknown
perturbation actions when handling p.action in the block that updates
target_param (using idx_tuple, current_val, p.value); add explicit validation of
p.action against allowed_actions = {'multiply','add','set'} and if p.action is
not in that set raise a clear ValueError (or log and raise) that includes the
invalid p.action and identifying info about the perturbation (e.g., parameter
name or index) so misconfigurations fail fast and are easy to debug.

In @temoa/extensions/stochastics/stochastic_config.py:
- Around line 6-13: The Perturbation dataclass defines a column field that is
never used by the scenario matching logic; either remove the unused column
attribute from Perturbation or update the matching code in the scenario creation
routine (the loop that checks perturbation.table and perturbation.filter) to
also honor perturbation.column when deciding if a perturbation applies. If you
remove column, delete the attribute and any code/tests that reference it; if you
implement it, update the matching conditional to check the target column
(Perturbation.column) against the current column context and adjust any
constructors/serializers and tests accordingly.

In @temoa/extensions/stochastics/stochastic_sequencer.py:
- Around line 28-32: The exception log call in the try/except around
StochasticConfig.from_toml(sc_path) is redundant: remove the explicit exception
object from the logger.exception call so it relies on logging.exception's
automatic exception capture; update the message to include only sc_path (e.g.,
call logger.exception("Failed to load stochastic config from %s", sc_path)) and
keep re-raising the ValueError as-is; look for the try/except that sets
self.stoch_config and references StochasticConfig.from_toml, sc_path, and
logger.exception to make this change.
- Around line 47-50: Replace the magic numbers in the options dict ("loglevel":
50 if self.config.silent else 10) with the logging module constants
(logging.CRITICAL and logging.DEBUG) and ensure the logging module is imported;
update the options assignment (where "solver": self.config.solver_name is set)
to use "loglevel": logging.CRITICAL if self.config.silent else logging.DEBUG so
intent is explicit.
- Around line 76-81: Capture and validate the SolverResults returned by
ef.solve_extensive_form() instead of discarding it; check that
results.solver.status and results.solver.termination_condition indicate a
successful/optimal solve (or use Pyomo's
check_optimal_termination()/assert_optimal_termination()) before calling
ef.get_objective_value(); only then assign to self.objective_value and call
logger.info, otherwise log or raise an error referencing the non-optimal
termination and avoid using ef.get_objective_value() on a failed solve.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fa4f54 and 2c9623e.

📒 Files selected for processing (4)
  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/stochastics/stochastic_config.py
  • temoa/extensions/stochastics/stochastic_sequencer.py
  • tests/testing_configs/config_utopia_stochastic.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T22:55:02.519Z
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 186
File: temoa/_internal/temoa_sequencer.py:60-63
Timestamp: 2025-11-03T22:55:02.519Z
Learning: In `TemoaSequencer.__init__`, when a `mode_override` is provided, it must be written back to `self.config.scenario_mode` (not just stored in `self.temoa_mode`) because `HybridLoader.create_data_dict()` has strict consistency checks that validate `config.scenario_mode` matches the presence/absence of `myopic_index`. Without this synchronization, MYOPIC mode would fail with a RuntimeError when the loader is instantiated.

Applied to files:

  • temoa/extensions/stochastics/stochastic_sequencer.py
🧬 Code graph analysis (2)
temoa/extensions/stochastics/stochastic_sequencer.py (3)
temoa/core/config.py (1)
  • TemoaConfig (32-344)
temoa/extensions/stochastics/stochastic_config.py (2)
  • StochasticConfig (16-44)
  • from_toml (21-44)
temoa/extensions/stochastics/scenario_creator.py (1)
  • scenario_creator (16-110)
temoa/extensions/stochastics/scenario_creator.py (5)
temoa/core/config.py (1)
  • TemoaConfig (32-344)
temoa/extensions/stochastics/stochastic_config.py (1)
  • StochasticConfig (16-44)
temoa/data_io/hybrid_loader.py (2)
  • HybridLoader (70-930)
  • data_portal_from_data (145-155)
temoa/_internal/run_actions.py (1)
  • build_instance (121-155)
temoa/components/costs.py (1)
  • period_cost_rule (316-516)
🪛 Ruff (0.14.10)
temoa/extensions/stochastics/stochastic_sequencer.py

17-17: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)


20-20: Avoid specifying long messages outside the exception class

(TRY003)


24-24: Avoid specifying long messages outside the exception class

(TRY003)


26-26: Avoid specifying long messages outside the exception class

(TRY003)


31-31: Redundant exception object included in logging.exception call

(TRY401)


32-32: Avoid specifying long messages outside the exception class

(TRY003)


43-43: Avoid specifying long messages outside the exception class

(TRY003)


53-56: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/scenario_creator.py

16-16: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)


25-25: Avoid specifying long messages outside the exception class

(TRY003)


36-36: Redundant exception object included in logging.exception call

(TRY401)


37-37: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: setup and test (macos-latest, 3.12)
  • GitHub Check: setup and test (windows-latest, 3.12)
  • GitHub Check: setup and test (macos-latest, 3.13)
  • GitHub Check: setup and test (windows-latest, 3.13)
🔇 Additional comments (2)
tests/testing_configs/config_utopia_stochastic.toml (1)

1-12: LGTM! Well-structured test configuration.

The stochastic test configuration is complete and properly structured. The relative path for stochastic_config will be resolved correctly by the config loading logic.

temoa/extensions/stochastics/scenario_creator.py (1)

97-108: No action needed. attach_root_node from mpi-sppy explicitly handles empty variable lists gracefully. When first_stage_vars is empty (i.e., no v_new_capacity entries for the first period), the node will simply have no first-stage nonanticipative variables, which is valid behavior per mpi-sppy's design.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 Fix all issues with AI agents
In @temoa/extensions/stochastics/scenario_creator.py:
- Around line 73-83: The code recreates allowed_actions inside the perturbation
loop; move the set declaration out of the loop to avoid repeated allocation:
define allowed_actions = {'multiply', 'add', 'set'} once before the loop that
iterates perturbations (the loop using variable p) and then remove the in-loop
declaration, keeping the subsequent checks that use p.action and the assignments
to target_param[idx_tuple] with current_val unchanged.
- Around line 62-71: The comprehension building index_map can silently truncate
if index_cols and idx_tuple lengths differ; update the comprehension in the loop
over target_param to use zip(index_cols, idx_tuple, strict=True) so mismatched
lengths raise immediately (i.e., change "{col: val for col, val in
zip(index_cols, idx_tuple)}" to use strict=True) and run tests to ensure this
code path handles any length errors; reference symbols: target_param,
index_cols, idx_tuple, index_map, p.filter.

In @temoa/extensions/stochastics/stochastic_config.py:
- Around line 24-30: The scenarios probabilities parsed into the local variable
scenarios should be validated to ensure they form a proper distribution; after
the loop that builds scenarios (the for loop over scenarios_raw in
stochastic_config.py) compute total_prob = sum(scenarios.values()) and warn if
total_prob is outside an acceptable tolerance (e.g., 0.99–1.01). Add
module-level logging (import logging and logger = logging.getLogger(__name__))
and call logger.warning with the sum and a helpful message if the check fails to
alert users to misconfigured probabilities.
- Around line 33-40: The loop that constructs Perturbation objects from
perturbations_data can raise unclear KeyError if a required field is missing; in
the loop around Perturbation(...) (the block creating scenario=, table=,
filter=, value=), validate that keys 'scenario', 'table', 'filter', and 'value'
exist (or wrap the Perturbation construction in try/except KeyError) and raise a
clearer exception (e.g., ValueError) that includes the perturbation index or the
offending dict and the missing key name so callers get actionable context.

In @temoa/extensions/stochastics/stochastic_sequencer.py:
- Around line 88-90: Initialize the objective_value attribute in the
StochasticSequencer.__init__ to avoid AttributeError when accessed before
start() or after a failed solve; add an assignment like self.objective_value =
None (typed as float | None if using typing) inside __init__ so the attribute
always exists and callers can check for None before using it.
- Around line 29-33: The except block that catches exceptions from
StochasticConfig.from_toml(sc_path) should chain the original exception to
preserve context: change the handler to capture the exception (e.g., except
Exception as e:) and re-raise the ValueError using "from e" so the original
traceback is retained; keep the existing logger.exception call
(logger.exception("Failed to load stochastic config from %s", sc_path)) and then
raise ValueError(f"Error parsing stochastic config {sc_path}") from e, referring
to StochasticConfig.from_toml and self.stoch_config in stochastic_sequencer.py.
- Around line 8-9: Remove the no-op TYPE_CHECKING block and its unused import:
delete the "if TYPE_CHECKING: pass" block from the module and remove
"TYPE_CHECKING" from the "from typing import ..." import statement (or delete
the import entirely if that line becomes unused); then run the linter/format to
ensure there are no remaining unused-import errors.
- Around line 48-51: The call to solve_extensive_form() in StochasticSequencer
is missing the solver_options argument; use the already-built options dict (or
load solver options from the stochastic config like the Monte Carlo/MGA
extensions do) and pass it as solver_options=options when calling
solve_extensive_form(); ensure the config field name matches (e.g.,
self.config.solver_options or similar) and merge any default keys (solver,
loglevel) into that dict before passing.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c9623e and 24dc591.

📒 Files selected for processing (4)
  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/stochastics/stochastic_config.py
  • temoa/extensions/stochastics/stochastic_sequencer.py
  • tests/testing_configs/config_utopia_stochastic.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T22:55:02.519Z
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 186
File: temoa/_internal/temoa_sequencer.py:60-63
Timestamp: 2025-11-03T22:55:02.519Z
Learning: In `TemoaSequencer.__init__`, when a `mode_override` is provided, it must be written back to `self.config.scenario_mode` (not just stored in `self.temoa_mode`) because `HybridLoader.create_data_dict()` has strict consistency checks that validate `config.scenario_mode` matches the presence/absence of `myopic_index`. Without this synchronization, MYOPIC mode would fail with a RuntimeError when the loader is instantiated.

Applied to files:

  • temoa/extensions/stochastics/stochastic_sequencer.py
  • temoa/extensions/stochastics/scenario_creator.py
🪛 Ruff (0.14.10)
temoa/extensions/stochastics/stochastic_sequencer.py

18-18: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)


21-21: Avoid specifying long messages outside the exception class

(TRY003)


25-25: Avoid specifying long messages outside the exception class

(TRY003)


27-27: Avoid specifying long messages outside the exception class

(TRY003)


33-33: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


33-33: Avoid specifying long messages outside the exception class

(TRY003)


44-44: Avoid specifying long messages outside the exception class

(TRY003)


54-57: Avoid specifying long messages outside the exception class

(TRY003)


86-86: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/scenario_creator.py

15-15: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)


24-24: Avoid specifying long messages outside the exception class

(TRY003)


43-43: Avoid specifying long messages outside the exception class

(TRY003)


64-64: Unnecessary dict comprehension (rewrite using dict())

Rewrite using dict()

(C416)


64-64: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


76-76: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: setup and test (windows-latest, 3.13)
  • GitHub Check: setup and test (windows-latest, 3.12)
  • GitHub Check: setup and test (macos-latest, 3.12)
  • GitHub Check: setup and test (macos-latest, 3.13)
🔇 Additional comments (3)
tests/testing_configs/config_utopia_stochastic.toml (1)

1-12: LGTM!

The stochastic test configuration is well-structured. The relative path for stochastic_config is appropriate for the test directory layout, and all necessary fields for stochastic mode are present.

temoa/extensions/stochastics/scenario_creator.py (1)

15-24: Well-structured mpi-sppy integration.

The scenario creator correctly implements the mpi-sppy callback pattern: loading base data, applying scenario-specific perturbations, building the Pyomo instance, and attaching the root node with first-stage variables and cost. The identification of first-stage decisions (capacity investments in the first period) aligns with standard stochastic programming practice.

Also applies to: 85-107

temoa/extensions/stochastics/stochastic_sequencer.py (1)

13-16: Good orchestration of the stochastic workflow.

The StochasticSequencer cleanly separates configuration validation in __init__ from execution in start(). The dynamic import pattern for mpi-sppy allows graceful handling when the optional dependency is missing. The ExtensiveForm construction follows the expected mpi-sppy API.

Also applies to: 35-38, 68-77

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @temoa/extensions/stochastics/stochastic_sequencer.py:
- Around line 49-54: The current shallow update of the options dict (the
variables options and solver_options in stochastic_sequencer.py) will overwrite
nested dicts; implement a deep merge instead by adding a helper (e.g.,
deep_merge) that recursively merges nested dicts and then replace
options.update(solver_options) with options = deep_merge(options,
solver_options); ensure the helper preserves non-dict values from override and
returns a new dict so existing references to options are not mutated
unexpectedly.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24dc591 and 040da8c.

📒 Files selected for processing (4)
  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/stochastics/stochastic_config.py
  • temoa/extensions/stochastics/stochastic_sequencer.py
  • tests/testing_configs/config_utopia_stochastic.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T22:55:02.519Z
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 186
File: temoa/_internal/temoa_sequencer.py:60-63
Timestamp: 2025-11-03T22:55:02.519Z
Learning: In `TemoaSequencer.__init__`, when a `mode_override` is provided, it must be written back to `self.config.scenario_mode` (not just stored in `self.temoa_mode`) because `HybridLoader.create_data_dict()` has strict consistency checks that validate `config.scenario_mode` matches the presence/absence of `myopic_index`. Without this synchronization, MYOPIC mode would fail with a RuntimeError when the loader is instantiated.

Applied to files:

  • temoa/extensions/stochastics/stochastic_sequencer.py
  • temoa/extensions/stochastics/scenario_creator.py
🧬 Code graph analysis (1)
temoa/extensions/stochastics/stochastic_sequencer.py (3)
temoa/core/config.py (1)
  • TemoaConfig (32-344)
temoa/extensions/stochastics/stochastic_config.py (2)
  • StochasticConfig (20-69)
  • from_toml (26-69)
temoa/extensions/stochastics/scenario_creator.py (1)
  • scenario_creator (19-119)
🪛 Ruff (0.14.10)
temoa/extensions/stochastics/stochastic_config.py

61-61: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/stochastic_sequencer.py

16-16: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)


20-20: Avoid specifying long messages outside the exception class

(TRY003)


24-24: Avoid specifying long messages outside the exception class

(TRY003)


26-26: Avoid specifying long messages outside the exception class

(TRY003)


32-32: Avoid specifying long messages outside the exception class

(TRY003)


42-42: Avoid specifying long messages outside the exception class

(TRY003)


57-60: Avoid specifying long messages outside the exception class

(TRY003)


89-89: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/scenario_creator.py

19-19: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)


28-28: Avoid specifying long messages outside the exception class

(TRY003)


47-47: Avoid specifying long messages outside the exception class

(TRY003)


74-74: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: setup and test (macos-latest, 3.13)
  • GitHub Check: setup and test (windows-latest, 3.12)
  • GitHub Check: setup and test (macos-latest, 3.12)
  • GitHub Check: setup and test (windows-latest, 3.13)
🔇 Additional comments (12)
tests/testing_configs/config_utopia_stochastic.toml (1)

1-12: LGTM! Configuration is well-structured.

The stochastic configuration file is properly structured with all necessary fields for a stochastic run, including the appropriate solver and path to the stochastic configuration.

temoa/extensions/stochastics/stochastic_config.py (3)

1-8: LGTM! Clean imports.

The imports are appropriate and well-organized for parsing TOML configuration files.


10-23: LGTM! Well-defined dataclasses.

The dataclass definitions appropriately model the stochastic configuration structure with clear field types and sensible defaults.


25-69: LGTM! Robust TOML parsing with good error handling.

The from_toml method correctly:

  • Handles both dictionary and direct float probability specifications
  • Validates that probabilities sum to approximately 1.0 with reasonable tolerance
  • Provides clear error messages for missing required fields in perturbations
  • Defaults the action field to 'set' when not specified

The static analysis hint about TRY003 (long exception messages) can be safely ignored here, as the descriptive error messages aid debugging.

temoa/extensions/stochastics/stochastic_sequencer.py (2)

16-33: LGTM! Thorough configuration validation.

The __init__ method properly validates the stochastic configuration:

  • Ensures stochastic_config is provided
  • Validates the path exists and is a file
  • Handles loading errors with clear context

The detailed error messages (flagged by static analysis TRY003) are appropriate for configuration validation scenarios.


34-96: LGTM! Well-structured stochastic solver orchestration.

The start method correctly:

  • Lazily imports mpi-sppy with appropriate error handling
  • Merges solver options from both config and stochastic config
  • Validates that at least one scenario exists before constructing ExtensiveForm
  • Checks for optimal termination before accessing objective value
  • Extracts and stores the objective value for downstream use

The TODO for TableWriter integration is appropriately noted for future work.

temoa/extensions/stochastics/scenario_creator.py (6)

1-31: LGTM! Proper mpi-sppy scenario creator interface.

The function signature correctly implements the mpi-sppy scenario creator pattern:

  • Uses **kwargs to receive framework-provided arguments (the Any type hint is appropriate here)
  • Validates required configuration objects are present
  • Properly types the extracted configurations for downstream use

33-47: LGTM! Proper data loading with good error handling.

The base data loading correctly:

  • Uses context manager for database connection
  • Calls HybridLoader.create_data_dict with myopic_index=None (appropriate for stochastic mode)
  • Builds the table index map from the manifest for perturbation matching
  • Handles connection and loading errors with clear context

The index column extraction (item.columns[:-1]) assumes tables have at least one index column, which aligns with the data model design.


49-75: LGTM! Robust perturbation validation.

The perturbation setup properly:

  • Defines allowed actions that match the stochastic config documentation
  • Filters perturbations by scenario name
  • Logs warnings for missing tables or manifest entries (allowing partial perturbations)
  • Validates actions strictly, raising clear errors for invalid operations

76-94: LGTM! Excellent perturbation application logic.

The perturbation application is well-implemented:

  • Creates a list copy for safe iteration while modifying the dictionary
  • Uses strict=True in zip() (line 78) to ensure index columns and tuple lengths match—excellent defensive programming!
  • Implements clear filter matching logic
  • Correctly applies multiply, add, and set operations

95-119: LGTM! Correct mpi-sppy integration.

The instance building and root node attachment properly:

  • Converts the modified data dictionary to a Pyomo data portal
  • Builds the Temoa model instance silently (appropriate for scenario creation)
  • Identifies the first period and sets the scenario probability
  • Collects first-stage variables (new capacity decisions for the first period)
  • Calculates first-stage cost using Temoa's period_cost_rule
  • Attaches the root node with correct arguments for mpi-sppy

106-110: Verify behavior when no first-stage capacity decisions exist.

The code collects first-stage variables by filtering v_new_capacity for the first period. If a scenario has no new capacity decisions in the first period, first_stage_vars would be empty.

Please verify whether:

  • This is a valid scenario configuration (some models may not require first-period investments)
  • This should be treated as an error condition requiring validation

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @temoa/extensions/stochastics/scenario_creator.py:
- Around line 73-74: The check for p.action against allowed_actions in
scenario_creator.py is redundant because Perturbation.__post_init__ in
stochastic_config.py already validates actions; remove the conditional that
raises ValueError (the p.action not in allowed_actions branch) and also remove
the now-unused allowed_actions definition referenced on line 50 so validation
relies on Perturbation.__post_init__ only.

In @temoa/extensions/stochastics/stochastic_config.py:
- Around line 54-68: Perturbations may reference nonexistent scenarios so update
the loop that builds perturbations (perturbations_data -> perturbations,
constructing Perturbation) to validate that p['scenario'] exists in the
scenarios mapping before appending; if p['scenario'] is missing or not a key in
scenarios, raise a ValueError identifying the perturbation index and the invalid
scenario name to fail fast and avoid silent ignores. Ensure you still catch
KeyError for other missing fields as before but perform the scenario membership
check (using the scenarios dict) prior to creating the Perturbation.

In @temoa/extensions/stochastics/stochastic_sequencer.py:
- Around line 85-92: The call to ef.solve_extensive_form is passing the full
options dict instead of only solver-specific settings; change the argument to
use the previously extracted solver_options variable (used earlier on line 59)
so that ef.solve_extensive_form receives only solver-specific params; update the
call where ef (ExtensiveForm instance) invokes solve_extensive_form to pass
solver_options rather than options.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 040da8c and 3da806e.

📒 Files selected for processing (5)
  • docs/source/stochastics.rst
  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/stochastics/stochastic_config.py
  • temoa/extensions/stochastics/stochastic_sequencer.py
  • tests/testing_configs/config_utopia_stochastic.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T22:55:02.519Z
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 186
File: temoa/_internal/temoa_sequencer.py:60-63
Timestamp: 2025-11-03T22:55:02.519Z
Learning: In `TemoaSequencer.__init__`, when a `mode_override` is provided, it must be written back to `self.config.scenario_mode` (not just stored in `self.temoa_mode`) because `HybridLoader.create_data_dict()` has strict consistency checks that validate `config.scenario_mode` matches the presence/absence of `myopic_index`. Without this synchronization, MYOPIC mode would fail with a RuntimeError when the loader is instantiated.

Applied to files:

  • temoa/extensions/stochastics/stochastic_sequencer.py
  • temoa/extensions/stochastics/scenario_creator.py
🧬 Code graph analysis (2)
temoa/extensions/stochastics/stochastic_sequencer.py (3)
temoa/core/config.py (1)
  • TemoaConfig (32-344)
temoa/extensions/stochastics/stochastic_config.py (2)
  • StochasticConfig (27-76)
  • from_toml (33-76)
temoa/extensions/stochastics/scenario_creator.py (1)
  • scenario_creator (19-134)
temoa/extensions/stochastics/scenario_creator.py (5)
temoa/_internal/run_actions.py (1)
  • build_instance (121-155)
temoa/components/costs.py (1)
  • period_cost_rule (316-516)
temoa/data_io/hybrid_loader.py (1)
  • data_portal_from_data (145-155)
temoa/core/config.py (1)
  • TemoaConfig (32-344)
temoa/extensions/stochastics/stochastic_config.py (1)
  • StochasticConfig (27-76)
🪛 Ruff (0.14.10)
temoa/extensions/stochastics/stochastic_sequencer.py

17-17: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)


21-21: Avoid specifying long messages outside the exception class

(TRY003)


25-25: Avoid specifying long messages outside the exception class

(TRY003)


27-27: Avoid specifying long messages outside the exception class

(TRY003)


33-33: Avoid specifying long messages outside the exception class

(TRY003)


54-54: Avoid specifying long messages outside the exception class

(TRY003)


69-72: Avoid specifying long messages outside the exception class

(TRY003)


101-101: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/scenario_creator.py

19-19: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)


28-28: Avoid specifying long messages outside the exception class

(TRY003)


47-47: Avoid specifying long messages outside the exception class

(TRY003)


74-74: Avoid specifying long messages outside the exception class

(TRY003)


125-125: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/stochastic_config.py

21-23: Avoid specifying long messages outside the exception class

(TRY003)


68-68: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: setup and test (macos-latest, 3.12)
  • GitHub Check: setup and test (windows-latest, 3.13)
  • GitHub Check: setup and test (macos-latest, 3.13)
  • GitHub Check: setup and test (windows-latest, 3.12)
🔇 Additional comments (9)
tests/testing_configs/config_utopia_stochastic.toml (1)

1-12: LGTM!

The stochastic test configuration is well-structured. The relative path for stochastic_config correctly points to the sibling testing_data directory, and the configuration options are appropriate for a stochastic test run.

docs/source/stochastics.rst (1)

1-107: LGTM!

The documentation is comprehensive and accurately reflects the implementation. It clearly explains the configuration structure, perturbation options, and workflow. The limitations section appropriately sets expectations about current capabilities.

temoa/extensions/stochastics/scenario_creator.py (3)

19-28: LGTM!

The function signature and kwargs validation are well-implemented. The early validation of required configuration objects ensures clear error messages if the function is called incorrectly.


112-125: First-stage variables limited to v_new_capacity.

The implementation only considers v_new_capacity variables for the first period as first-stage decisions. This aligns with the documented two-stage limitation. Consider adding a comment or documentation note if other capacity-related variables (like retirement decisions) should also be included in future extensions.


39-44: Column convention is correct and well-documented.

The assumption that the last column in each LoadItem is always the value (or set element) and preceding columns are indices is explicitly documented in the LoadItem class docstring: "The last column is assumed to be the value for a Param." This convention is consistently applied across all manifest items—Sets use a single column (index extraction yields an empty list), and Params use the last column as the value with preceding columns as indices. The code correctly implements this with item.columns[:-1].

temoa/extensions/stochastics/stochastic_config.py (1)

10-23: LGTM!

The Perturbation dataclass is well-designed with clear field definitions and early validation in __post_init__. The validation ensures invalid actions fail at parse time rather than during scenario execution.

temoa/extensions/stochastics/stochastic_sequencer.py (3)

17-33: LGTM!

The __init__ method properly validates the stochastic configuration path and handles errors gracefully. The sequential existence and file-type checks provide clear error messages.


35-44: LGTM!

The _deep_merge helper correctly handles recursive dictionary merging for nested solver options.


94-105: LGTM!

The termination check correctly uses Pyomo's check_optimal_termination and extracts the termination condition for error reporting. The objective value retrieval and logging are well-implemented.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In @temoa/extensions/stochastics/scenario_creator.py:
- Around line 27-28: The scenario_creator function currently requires
'temoa_config' and 'stoch_config' via kwargs and validates them at runtime;
change the function signature to accept explicit typed parameters (e.g.,
scenario_creator(temoa_config: TemoaConfig, stoch_config: StochConfig, *args,
**kwargs)) so callers and IDEs get type safety and clear contracts, update
internal references to use these parameters directly, add a thin shim or
backward-compatible check to still accept kwargs when invoked by mpi-sppy
(preserve compatibility by extracting them from kwargs if present), and update
any callers/tests to pass the new arguments.
- Around line 72-90: Loop over target_param in scenario_creator code currently
handles p.action == 'multiply'|'add'|'set' but lacks a defensive else; update
the block inside the if match branch (the code manipulating target_param
entries) to include an explicit else that handles unexpected p.action
values—e.g., raise a clear ValueError or log and skip—so that unknown actions in
Perturbation are caught at runtime; reference the loop using target_param,
idx_tuple, current_val and the Perturbation instance p to locate where to add
the else.

In @temoa/extensions/stochastics/stochastic_sequencer.py:
- Around line 35-44: The _deep_merge function currently mutates the passed-in
base dict in place and also returns it, which can surprise callers; either make
the behavior explicit in the docstring (state that _deep_merge mutates base in
place and returns the same dict) or convert it to a pure function: create a deep
copy of base (e.g., via copy.deepcopy), recursively merge update into that new
dict (mirroring the existing recursion in _deep_merge), and return the new dict
without modifying the original base; if you keep the in-place semantics, remove
the misleading return or keep it but document it clearly in the docstring
referencing the _deep_merge signature and the base/update parameters.
- Around line 17-33: Add an explicit return type annotation to the constructor:
update the __init__ method signature to include "-> None" to match modern Python
typing conventions; locate the __init__ in class StochasticSequencer (the block
that references self.config, self.objective_value, sc_path, and calls
StochasticConfig.from_toml) and add the return type only—no other logic changes.
- Line 63: The current 'loglevel' assignment sets logging.CRITICAL when
self.config.silent is true and logging.DEBUG otherwise; change this to a more
balanced default by using logging.INFO (or logging.WARNING if you prefer less
verbosity) instead of logging.DEBUG when not silent. Locate the dict entry with
the key 'loglevel' (the line containing "'loglevel': logging.CRITICAL if
self.config.silent else logging.DEBUG,") and update the else branch to
logging.INFO (i.e., "'loglevel': logging.CRITICAL if self.config.silent else
logging.INFO,") so normal runs emit informational messages rather than full
debug output.
- Around line 59-66: The code redundantly merges solver-specific settings into
the framework options: remove the self._deep_merge(options, solver_options) call
so the options dict only contains framework-level keys ('solver' and 'loglevel')
and leave solver_options passed exclusively to solve_extensive_form(...). Update
around the construction of options (the variables options, solver_options and
the self._deep_merge call) so ExtensiveForm gets framework options only and
solve_extensive_form is given solver_options unchanged.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3da806e and 7ebb459.

📒 Files selected for processing (6)
  • docs/source/computational_implementation.rst
  • docs/source/stochastics.rst
  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/stochastics/stochastic_config.py
  • temoa/extensions/stochastics/stochastic_sequencer.py
  • tests/testing_configs/config_utopia_stochastic.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T22:55:02.519Z
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 186
File: temoa/_internal/temoa_sequencer.py:60-63
Timestamp: 2025-11-03T22:55:02.519Z
Learning: In `TemoaSequencer.__init__`, when a `mode_override` is provided, it must be written back to `self.config.scenario_mode` (not just stored in `self.temoa_mode`) because `HybridLoader.create_data_dict()` has strict consistency checks that validate `config.scenario_mode` matches the presence/absence of `myopic_index`. Without this synchronization, MYOPIC mode would fail with a RuntimeError when the loader is instantiated.

Applied to files:

  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/stochastics/stochastic_sequencer.py
🧬 Code graph analysis (1)
temoa/extensions/stochastics/stochastic_sequencer.py (3)
temoa/core/config.py (1)
  • TemoaConfig (32-344)
temoa/extensions/stochastics/stochastic_config.py (2)
  • StochasticConfig (27-83)
  • from_toml (33-83)
temoa/extensions/stochastics/scenario_creator.py (1)
  • scenario_creator (19-130)
🪛 Ruff (0.14.10)
temoa/extensions/stochastics/scenario_creator.py

19-19: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)


28-28: Avoid specifying long messages outside the exception class

(TRY003)


47-47: Avoid specifying long messages outside the exception class

(TRY003)


121-121: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/stochastic_config.py

21-23: Avoid specifying long messages outside the exception class

(TRY003)


60-63: Avoid specifying long messages outside the exception class

(TRY003)


75-75: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/stochastic_sequencer.py

17-17: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)


21-21: Avoid specifying long messages outside the exception class

(TRY003)


25-25: Avoid specifying long messages outside the exception class

(TRY003)


27-27: Avoid specifying long messages outside the exception class

(TRY003)


33-33: Avoid specifying long messages outside the exception class

(TRY003)


54-54: Avoid specifying long messages outside the exception class

(TRY003)


69-72: Avoid specifying long messages outside the exception class

(TRY003)


101-101: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: setup and test (windows-latest, 3.13)
  • GitHub Check: setup and test (macos-latest, 3.13)
  • GitHub Check: setup and test (windows-latest, 3.12)
  • GitHub Check: setup and test (macos-latest, 3.12)
🔇 Additional comments (15)
docs/source/computational_implementation.rst (1)

392-392: LGTM! Documentation reference updated appropriately.

The update correctly removes the "untested" qualifier and adds a proper documentation cross-reference to the new stochastics guide, aligning with the stochastic implementation introduced in this PR.

docs/source/stochastics.rst (2)

1-107: Excellent documentation for the stochastics extension!

The documentation is comprehensive and well-structured, covering installation, configuration, usage patterns, and current limitations. The TOML examples are clear and the perturbation options are well-documented.


14-24: No changes needed. The installation commands are correct and properly documented.

The uv add mpi-sppy syntax is valid per the official uv package manager documentation. The mpi-sppy package is a legitimate PyPI dependency (currently at version 0.13.1) and is already listed in the project's pyproject.toml. Both installation methods shown (via uv add and pip install) are standard and correct.

tests/testing_configs/config_utopia_stochastic.toml (2)

1-12: Test configuration looks appropriate.

The configuration properly sets up a stochastic test scenario with reasonable solver and output settings for testing purposes.


11-12: Stochastic config file path is correct and valid.

The file tests/testing_data/utopia_stochastic_config.toml exists and contains valid TOML configuration with properly defined scenarios and perturbations.

temoa/extensions/stochastics/scenario_creator.py (3)

34-47: Excellent error handling for database operations.

The try-except block properly catches connection/loading errors, logs context, and re-raises with a helpful message. The context manager ensures proper connection cleanup.


108-122: Good validation of first-stage variables.

The code correctly identifies first-stage variables and raises a clear error if none are found, which is essential for stochastic optimization to function properly.


95-129: First-stage identification aligns with mpi-sppy requirements.

The implementation correctly identifies v_new_capacity[*, *, first_period] as first-stage variables, sets _mpisppy_probability, and calls attach_root_node() as required by mpi-sppy for two-stage problems. Since perturbations only modify parameter values (not model structure), all scenarios will have identical variable indices. Consider adding an explicit comment or setting do_not_check_variable_probabilities=True in the attach_root_node() call to document this assumption, though the current implementation is functionally correct.

temoa/extensions/stochastics/stochastic_config.py (4)

10-23: Excellent validation in Perturbation.__post_init__.

The dataclass properly validates the action field against allowed values, preventing invalid actions from being created. This validation complements the action handling in scenario_creator.py.


45-52: Appropriate floating-point tolerance for probability validation.

The 0.99 to 1.01 range accounts for floating-point arithmetic imprecision while still catching significant probability distribution errors. The warning message is helpful for users.


56-75: Robust perturbation validation with clear error messages.

The validation properly checks:

  • Scenario existence before creating perturbations
  • Required fields with helpful KeyError handling
  • Index tracking for debugging (line 61 includes index in error message)

The detailed error messages (flagged by static analysis) are appropriate here as they help users diagnose configuration issues.


32-83: Well-designed TOML parser with flexible schema support.

The from_toml method handles:

  • Flexible probability formats (float or dict with 'probability' key)
  • Sensible defaults (line 70: action defaults to 'set')
  • Comprehensive validation with informative error messages
temoa/extensions/stochastics/stochastic_sequencer.py (3)

1-9: LGTM!

The imports are well-organized and appropriate for integrating mpi-sppy with Temoa's configuration system.


74-90: LGTM!

The ExtensiveForm construction correctly passes scenario names, the creator function, and necessary kwargs for scenario instantiation.


94-105: Robust error handling and termination verification.

The code properly validates optimal termination before accessing the objective value and provides detailed error information on failure. The extraction and logging of the stochastic expected value is clean.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @temoa/extensions/stochastics/stochastic_config.py:
- Around line 32-83: The from_toml classmethod currently allows an empty
scenarios dict; add a validation after building scenarios that raises a
ValueError if scenarios is empty (e.g., if not scenarios:) with a clear message
like "No scenarios defined; at least one scenario is required for stochastic
optimization", so callers of StochasticConfig.from_toml will get a clear error
instead of silently proceeding; update the method around the existing "Validate
probability distribution" block and reference the scenarios variable and the
from_toml classmethod in your change.

In @temoa/extensions/stochastics/stochastic_sequencer.py:
- Around line 17-33: The constructor __init__ of the StochasticSequencer
validates and loads stochastic_config but should have an explicit return type
and can improve message lengths or use a custom exception; add a -> None
annotation to __init__ and (optionally) replace the long ValueError messages
with either shorter messages or raise a custom StochasticConfigError (define a
new exception class) while keeping the existing checks
(config.stochastic_config, sc_path.exists(), sc_path.is_file()) and keep logging
the original exception when calling StochasticConfig.from_toml(sc_path) via
logger.exception before re-raising.
- Around line 35-43: The _deep_merge method in stochastic_sequencer.py is unused
dead code; remove the entire _deep_merge(self, base: dict[str, Any], update:
dict[str, Any]) method definition from the class, and if that removal leaves any
now-unused imports (e.g., Any from typing), remove those imports as well; ensure
no other code references _deep_merge before deleting.
- Around line 45-105: The options dict passed to ExtensiveForm in start()
currently includes an undocumented 'loglevel' key; remove 'loglevel' from the
options dict construction and only include the documented 'solver' key (use
self.config.solver_name). If you need to control mpi-sppy logging instead,
configure it via mpi-sppy’s documented mechanism or pass logging via
solver_options (already derived from self.stoch_config.solver_options) rather
than adding 'loglevel' to options; update the code that builds options
(referencing options, self.config.silent, and self.config.solver_name) and
ensure only supported keys are passed to ExtensiveForm.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ebb459 and b491d93.

📒 Files selected for processing (6)
  • docs/source/computational_implementation.rst
  • docs/source/stochastics.rst
  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/stochastics/stochastic_config.py
  • temoa/extensions/stochastics/stochastic_sequencer.py
  • tests/testing_configs/config_utopia_stochastic.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T22:55:02.519Z
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 186
File: temoa/_internal/temoa_sequencer.py:60-63
Timestamp: 2025-11-03T22:55:02.519Z
Learning: In `TemoaSequencer.__init__`, when a `mode_override` is provided, it must be written back to `self.config.scenario_mode` (not just stored in `self.temoa_mode`) because `HybridLoader.create_data_dict()` has strict consistency checks that validate `config.scenario_mode` matches the presence/absence of `myopic_index`. Without this synchronization, MYOPIC mode would fail with a RuntimeError when the loader is instantiated.

Applied to files:

  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/stochastics/stochastic_sequencer.py
🧬 Code graph analysis (2)
temoa/extensions/stochastics/scenario_creator.py (4)
temoa/_internal/run_actions.py (1)
  • build_instance (121-155)
temoa/components/costs.py (1)
  • period_cost_rule (316-516)
temoa/data_io/hybrid_loader.py (2)
  • HybridLoader (70-930)
  • data_portal_from_data (145-155)
temoa/extensions/stochastics/stochastic_config.py (1)
  • StochasticConfig (27-83)
temoa/extensions/stochastics/stochastic_sequencer.py (3)
temoa/core/config.py (1)
  • TemoaConfig (32-344)
temoa/extensions/stochastics/stochastic_config.py (2)
  • StochasticConfig (27-83)
  • from_toml (33-83)
temoa/extensions/stochastics/scenario_creator.py (1)
  • scenario_creator (19-130)
🪛 Ruff (0.14.10)
temoa/extensions/stochastics/scenario_creator.py

19-19: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)


28-28: Avoid specifying long messages outside the exception class

(TRY003)


47-47: Avoid specifying long messages outside the exception class

(TRY003)


121-121: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/stochastic_config.py

21-23: Avoid specifying long messages outside the exception class

(TRY003)


60-63: Avoid specifying long messages outside the exception class

(TRY003)


75-75: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/stochastic_sequencer.py

17-17: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)


21-21: Avoid specifying long messages outside the exception class

(TRY003)


25-25: Avoid specifying long messages outside the exception class

(TRY003)


27-27: Avoid specifying long messages outside the exception class

(TRY003)


33-33: Avoid specifying long messages outside the exception class

(TRY003)


53-53: Avoid specifying long messages outside the exception class

(TRY003)


66-69: Avoid specifying long messages outside the exception class

(TRY003)


98-98: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: setup and test (macos-latest, 3.12)
  • GitHub Check: setup and test (windows-latest, 3.13)
  • GitHub Check: setup and test (macos-latest, 3.13)
  • GitHub Check: setup and test (windows-latest, 3.12)
🔇 Additional comments (12)
temoa/extensions/stochastics/stochastic_config.py (2)

10-23: LGTM! Clean validation logic.

The Perturbation dataclass is well-structured with appropriate field validation in __post_init__. The action validation ensures only supported operations are used.


26-30: LGTM! Proper use of default factories.

The dataclass correctly uses field(default_factory=...) for mutable default values, following Python best practices.

docs/source/computational_implementation.rst (1)

392-392: LGTM! Documentation accurately reflects the new stochastics implementation.

The update appropriately removes the "[!] untested in v4.0" qualifier and adds a documentation reference, aligning with the newly implemented and tested stochastic programming capabilities.

docs/source/stochastics.rst (1)

1-107: LGTM! Comprehensive and clear documentation.

The stochastics documentation is well-structured and covers all essential aspects:

  • Clear dependency installation instructions
  • Detailed configuration examples
  • Comprehensive perturbation option descriptions
  • Transparent workflow explanation
  • Honest limitations disclosure

This will help users effectively utilize the new stochastic programming capabilities.

temoa/extensions/stochastics/scenario_creator.py (6)

19-31: LGTM! Appropriate kwargs validation.

The function correctly validates required kwargs before proceeding. The use of typing.Any for **kwargs is acceptable here since this is a callback function for mpi-sppy with a flexible signature pattern.


49-90: LGTM! Solid perturbation logic with good defensive programming.

The perturbation application is well-implemented:

  • Line 72 correctly creates a copy with list(target_param.items()) to avoid modification during iteration
  • Line 74 uses strict=True in zip() for safety
  • Proper warning logs for missing tables/manifests
  • All three action types are handled

One observation: an empty p.filter dict would match all entries in the table, which might be intentional for broad perturbations but could also be an error. Consider whether this should be documented or validated.


91-93: LGTM! Clean instance construction.

The instance building correctly uses the modified data dictionary and appropriately silences verbose output during scenario creation.


95-107: LGTM! Proper first-stage setup.

The code correctly identifies the first period and sets the scenario probability following mpi-sppy conventions. The warning for missing probabilities is good defensive programming.


108-128: Clarify first-stage variable selection: capacity investments vs. all first-period decisions.

The code collects only v_new_capacity variables with deployment period equal to first_period as first-stage decisions. However, the documentation (stochastics.rst) states: "all decisions in the first time period are considered first-stage."

These statements conflict. The current implementation intentionally selects only capacity investments as first-stage, excluding other first-period decision variables such as v_capacity, v_flow_out, v_retired_capacity, and v_annual_retirement.

Note: The loop variable p in for r, t, p in instance.v_new_capacity represents the deployment vintage (year), not the optimization period—this naming could be clarified.

Please confirm whether:

  1. Only capacity investments should be first-stage (design choice), and documentation should be updated, or
  2. All first-period decisions should be first-stage (as documentation implies), and the code should be expanded to include other decision variables.

33-47: No actionable issue found. Single-column items in the manifest are Sets (model.regions, techs, commodities), not Parameters. Since perturbations only apply to Parameters, and all Parameters in the manifest have at least 2 columns (index + value), the edge case of an empty index list does not occur in practice. The code is safe as-is.

tests/testing_configs/config_utopia_stochastic.toml (1)

1-12: LGTM! Test configuration is well-structured.

The stochastic test configuration correctly sets up the scenario mode and references the stochastic config file. The relative path ../testing_data/utopia_stochastic_config.toml resolves correctly from the config file's directory, and the referenced configuration file exists.

temoa/extensions/stochastics/stochastic_sequencer.py (1)

1-9: LGTM: Imports are clean and appropriate.

All imports are used and properly organized.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @temoa/extensions/stochastics/scenario_creator.py:
- Around line 83-89: Add a defensive else branch to the action handling block
that logs and raises on unexpected actions: after the existing if/elif chain
that checks p.action == 'multiply' / 'add' / 'set' (where you modify
target_param[idx_tuple]), add an else that calls logger.error('Invalid action %s
for perturbation', p.action) and raises ValueError(f'Invalid action:
{p.action}'); this complements the existing Perturbation.__post_init__
validation and protects against future changes or bypassed validation.
- Around line 99-106: The code currently defaults missing scenario probabilities
to 1.0 which can break normalization; in scenario_creator.py change the behavior
in the block that retrieves prob from stoch_config.scenarios for scenario_name
so that if prob is None you log an error with available keys (use logger.error
and list(stoch_config.scenarios.keys())) and raise a ValueError indicating the
missing scenario instead of assigning 1.0, leaving assignment to
instance._mpisppy_probability only after a valid prob is obtained.

In @temoa/extensions/stochastics/stochastic_sequencer.py:
- Around line 93-94: The TODO about integrating TableWriter in
temoa/extensions/stochastics/stochastic_sequencer.py should be tracked as a
formal issue: create a repo issue describing the work (update handle_results or
add a new handle_stochastic_results to persist stochastics results via
TableWriter), include acceptance criteria (schema, persistence behavior, tests),
and then replace the inline TODO with a short note referencing the new issue
number/URL and the target symbols (TableWriter, handle_results,
StochasticSequencer) so future PRs can link to the tracker.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b491d93 and 53b89e6.

📒 Files selected for processing (6)
  • docs/source/computational_implementation.rst
  • docs/source/stochastics.rst
  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/stochastics/stochastic_config.py
  • temoa/extensions/stochastics/stochastic_sequencer.py
  • tests/testing_configs/config_utopia_stochastic.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T22:55:02.519Z
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 186
File: temoa/_internal/temoa_sequencer.py:60-63
Timestamp: 2025-11-03T22:55:02.519Z
Learning: In `TemoaSequencer.__init__`, when a `mode_override` is provided, it must be written back to `self.config.scenario_mode` (not just stored in `self.temoa_mode`) because `HybridLoader.create_data_dict()` has strict consistency checks that validate `config.scenario_mode` matches the presence/absence of `myopic_index`. Without this synchronization, MYOPIC mode would fail with a RuntimeError when the loader is instantiated.

Applied to files:

  • temoa/extensions/stochastics/stochastic_sequencer.py
  • temoa/extensions/stochastics/scenario_creator.py
🪛 Ruff (0.14.10)
temoa/extensions/stochastics/stochastic_config.py

21-23: Avoid specifying long messages outside the exception class

(TRY003)


61-64: Avoid specifying long messages outside the exception class

(TRY003)


76-76: Avoid specifying long messages outside the exception class

(TRY003)


82-82: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/stochastic_sequencer.py

21-21: Avoid specifying long messages outside the exception class

(TRY003)


25-25: Avoid specifying long messages outside the exception class

(TRY003)


27-27: Avoid specifying long messages outside the exception class

(TRY003)


33-33: Avoid specifying long messages outside the exception class

(TRY003)


43-43: Avoid specifying long messages outside the exception class

(TRY003)


55-58: Avoid specifying long messages outside the exception class

(TRY003)


87-87: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/scenario_creator.py

19-19: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)


28-28: Avoid specifying long messages outside the exception class

(TRY003)


47-47: Avoid specifying long messages outside the exception class

(TRY003)


121-121: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: setup and test (windows-latest, 3.13)
  • GitHub Check: setup and test (macos-latest, 3.12)
  • GitHub Check: setup and test (windows-latest, 3.12)
  • GitHub Check: setup and test (macos-latest, 3.13)
🔇 Additional comments (11)
docs/source/computational_implementation.rst (1)

392-392: LGTM!

The documentation update correctly reflects that stochastics is now tested and implemented in v4, with proper Sphinx doc reference formatting consistent with other extensions.

temoa/extensions/stochastics/scenario_creator.py (3)

19-19: LGTM - **kwargs: Any is appropriate here.

The static analysis warning about Any in kwargs can be safely ignored. The **kwargs pattern is required for compatibility with mpi-sppy's scenario creator interface, and the function immediately validates and extracts the required typed arguments on lines 27-31.


108-128: LGTM - First-stage variable identification is correct.

The logic correctly identifies first-stage variables as all v_new_capacity entries for the first period, appropriately errors if none are found, and properly attaches the root node with the period cost expression for mpi-sppy.


39-44: No changes needed.

The assumption that the value column is always the last one is not an unvalidated assumption—it is the explicit design contract documented in the LoadItem class docstring: "The last column is assumed to be the value for a Param." This contract is consistently applied across all 50+ LoadItem definitions in component_manifest.py, confirming that columns[:-1] correctly extracts the index columns by removing the final value column.

Likely an incorrect or invalid review comment.

tests/testing_configs/config_utopia_stochastic.toml (1)

1-12: LGTM - Test configuration is well-structured.

The configuration correctly sets up a stochastic scenario with appropriate settings and references the stochastic configuration file using a relative path.

temoa/extensions/stochastics/stochastic_config.py (2)

55-77: Perturbation validation is thorough and well-implemented.

The validation correctly:

  • Ensures referenced scenarios exist before creating perturbations
  • Provides clear error messages with context (index, scenario name, available options)
  • Defaults the action field to 'set' as documented
  • Catches missing required fields with informative messages

The Ruff TRY003 warnings about long messages can be safely ignored here, as the detailed error messages significantly improve user experience when configuration errors occur.


78-88: LGTM - Final validation ensures configuration validity.

The code appropriately validates that at least one scenario is defined and returns a properly constructed StochasticConfig instance with all parsed data.

temoa/extensions/stochastics/stochastic_sequencer.py (4)

17-33: LGTM: Well-structured initialization with proper validation.

The path validation and error handling are solid. The exception chaining with detailed logging will help with debugging configuration issues.


39-45: LGTM: Appropriate lazy import for optional dependency.

The lazy import of mpi-sppy with clear error messaging is the right approach for an optional dependency.


78-91: LGTM: Robust error handling for optimization termination.

The termination check using pyo.check_optimal_termination() with defensive attribute access (lines 83-84) is well-implemented and will provide clear diagnostics on solve failures. The subsequent call to ef.get_objective_value() correctly retrieves the extensive form objective value from mpi-sppy.


47-76: The code correctly follows the mpi-sppy API pattern:

  1. ExtensiveForm correctly receives only the base options dict with the 'solver' key (lines 50-52)
  2. Solver-specific options are correctly passed exclusively to solve_extensive_form(solver_options=...) (line 78)

This separation aligns with mpi-sppy's API design, where ExtensiveForm.__init__ expects minimal options and solve_extensive_form() accepts solver-specific configurations. No changes required.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 Fix all issues with AI agents
In @temoa/core/config.py:
- Around line 233-246: The code silently ignores a present 'stochastic' section
when its 'stochastic_config' key is missing, which can cause confusing runtime
errors later in StochasticSequencer; update the block handling 'stochastic' (the
stoch_data / stochastic_config logic) to explicitly validate presence of
'stochastic_config' and either raise a clear ValueError (e.g.,
"stochastic_config missing in [stochastic] section") or log a warning and set
data['stochastic_config']=None so downstream code can handle it; ensure when
present you still resolve the path relative to config_file.parent as currently
done.

In @temoa/extensions/stochastics/scenario_creator.py:
- Around line 95-106: The code currently defaults missing scenario probability
to 1.0 which can corrupt expected-value calculations; instead, when
stoch_config.scenarios.get(scenario_name) returns None, raise a clear exception
(e.g., ValueError or KeyError) mentioning the missing scenario_name and that
probabilities are missing/invalid, and do not set instance._mpisppy_probability
in that case; update the block around stoch_config.scenarios, scenario_name,
logger, and instance._mpisppy_probability to perform this validation and raise
the error rather than defaulting.
- Around line 34-47: The catch-all Exception handler around the sqlite3.connect
block gives a misleading "Failed to connect to database" message even when
errors come from HybridLoader.create_data_dict or manifest processing; update
the try/except to either narrow the try to only the connection call or change
the exception handling to log the real error context: move the
sqlite3.connect(...) into its own small try/except (or use a separate try around
HybridLoader(...) and create_data_dict(...)), and in the except log both
temoa_config.input_database and the caught exception via logger.exception while
raising a RuntimeError that includes the original exception message (or a more
generic message that reflects which operation failed), referencing HybridLoader,
create_data_dict, manifest, table_index_map and temoa_config.input_database so
the log accurately reflects the failing step.
- Around line 72-90: The loop that applies perturbations over target_param
silently ignores unknown p.action values; update the code in the
scenario_creator loop that handles p.action (the if/elif chain handling
'multiply', 'add', 'set') to handle invalid actions explicitly by either raising
a clear ValueError (including p.action and any identifier like p.name or p) or
emitting a warning via the module logger with the same contextual info; add an
else branch after the existing elifs that performs this validation and stops or
warns so misconfigured perturbations are not silently dropped.
- Around line 19-28: Replace the untyped kwargs in scenario_creator with a
TypedDict describing the required keys: define a TypedDict (e.g.,
ScenarioKwargs) that requires 'temoa_config' and 'stoch_config' (with
appropriate types), import TypedDict from typing, then change the function
signature to accept kwargs: ScenarioKwargs (or a named param like params:
ScenarioKwargs) and update the runtime check to use those typed keys; ensure any
callers are updated to pass the typed mapping/argument and adjust imports
accordingly.

In @temoa/extensions/stochastics/stochastic_config.py:
- Around line 40-44: The code silently defaults dict-style scenarios to
probability 1.0; change the loop over scenarios_raw so that when isinstance(val,
dict) you first check if 'probability' in val and if not emit a warning (e.g.,
warnings.warn or module logger.warning) mentioning the scenario name, then parse
scenarios[name] = float(val.get('probability', 1.0)); update the code
referencing scenarios_raw, scenarios and val.get('probability', 1.0) accordingly
so missing probability keys are logged instead of being silently assumed.

In @temoa/extensions/stochastics/stochastic_sequencer.py:
- Around line 93-94: The TODO in StochasticSequencer (file
stochastic_sequencer.py) indicates result persistence is missing; create a
tracked GitHub issue and implement integration with the existing TableWriter by
either extending the StochasticSequencer.handle_results method or adding a new
method (e.g., persist_stochastic_results) that formats results and calls
TableWriter.write/insert, ensure error handling and idempotency, and add unit
tests exercising result persistence and a short docstring pointing to the new
issue number for follow-up.
- Around line 54-58: Remove the redundant validation that raises ValueError when
self.stoch_config.scenarios is empty in stochastic_sequencer.py; since
StochasticConfig.from_toml already enforces scenarios existence, delete the
if-block that checks self.stoch_config.scenarios and its raise, leaving
construction logic (e.g., ExtensiveForm-related setup) to rely on
StochasticConfig.from_toml's validation.

In @tests/test_full_runs.py:
- Around line 127-146: The test test_stochastic_utopia relies on a hard-coded
expected_obj (34389.9878) that assumes deterministic solver behavior; add a
concise inline comment above the expected_obj definition explaining how that
value was derived (e.g., derived from a baseline run with solver X version Y and
random seed Z or analytic expected value) and note that changes in
solver/version/seed may change the value, referencing expected_obj and
sequencer.stochastic_sequencer.objective_value so future maintainers know why
the constant exists and when to update it.

In @tests/testing_data/utopia_stochastic_config.toml:
- Around line 1-19: The TOML "column" field in perturbations is ignored: add a
column attribute to the Perturbation dataclass and parse it in the code that
reads TOML (the parsing logic in stochastic_config.py around the Perturbation
extraction), then update the perturbation application in scenario_creator.py to
check perturbation.column and only apply the change to matching column names
(or, if column-level behavior is not desired, remove the column entries from the
test file). Make sure to update any constructors/usages of Perturbation and the
unit tests to reflect the new column-aware behavior.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53b89e6 and 03f34d3.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (33)
  • docs/source/computational_implementation.rst
  • docs/source/index.rst
  • docs/source/stochastics.rst
  • pyproject.toml
  • requirements-dev.txt
  • requirements.txt
  • temoa/_internal/temoa_sequencer.py
  • temoa/core/config.py
  • temoa/core/modes.py
  • temoa/extensions/README.txt
  • temoa/extensions/stochastics/EVPI.py
  • temoa/extensions/stochastics/README.txt
  • temoa/extensions/stochastics/ReferenceModel.py
  • temoa/extensions/stochastics/VSS.py
  • temoa/extensions/stochastics/generate_scenario_tree-nonhomogenous.py
  • temoa/extensions/stochastics/generate_scenario_tree.py
  • temoa/extensions/stochastics/jobTemoa.pbs
  • temoa/extensions/stochastics/legacy_files/ef_writer_script_old.py
  • temoa/extensions/stochastics/legacy_files/scenariomodels.py
  • temoa/extensions/stochastics/options/README.txt
  • temoa/extensions/stochastics/options/__init__.py
  • temoa/extensions/stochastics/options/iew2012-nonhomogenized-markov.py
  • temoa/extensions/stochastics/options/iew2012.py
  • temoa/extensions/stochastics/options/utopia_coal_vs_nuc.py
  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/stochastics/stochastic_config.py
  • temoa/extensions/stochastics/stochastic_sequencer.py
  • temoa/extensions/stochastics/stochastics_README.txt
  • temoa/extensions/stochastics/temoa_stochastic.py
  • tests/conftest.py
  • tests/test_full_runs.py
  • tests/testing_configs/config_utopia_stochastic.toml
  • tests/testing_data/utopia_stochastic_config.toml
💤 Files with no reviewable changes (16)
  • temoa/extensions/stochastics/stochastics_README.txt
  • temoa/extensions/stochastics/options/iew2012.py
  • temoa/extensions/stochastics/options/utopia_coal_vs_nuc.py
  • temoa/extensions/stochastics/temoa_stochastic.py
  • temoa/extensions/README.txt
  • temoa/extensions/stochastics/VSS.py
  • temoa/extensions/stochastics/options/README.txt
  • temoa/extensions/stochastics/generate_scenario_tree.py
  • temoa/extensions/stochastics/jobTemoa.pbs
  • temoa/extensions/stochastics/options/iew2012-nonhomogenized-markov.py
  • temoa/extensions/stochastics/legacy_files/ef_writer_script_old.py
  • temoa/extensions/stochastics/ReferenceModel.py
  • temoa/extensions/stochastics/legacy_files/scenariomodels.py
  • temoa/extensions/stochastics/generate_scenario_tree-nonhomogenous.py
  • temoa/extensions/stochastics/EVPI.py
  • temoa/extensions/stochastics/README.txt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T22:55:02.519Z
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 186
File: temoa/_internal/temoa_sequencer.py:60-63
Timestamp: 2025-11-03T22:55:02.519Z
Learning: In `TemoaSequencer.__init__`, when a `mode_override` is provided, it must be written back to `self.config.scenario_mode` (not just stored in `self.temoa_mode`) because `HybridLoader.create_data_dict()` has strict consistency checks that validate `config.scenario_mode` matches the presence/absence of `myopic_index`. Without this synchronization, MYOPIC mode would fail with a RuntimeError when the loader is instantiated.

Applied to files:

  • temoa/extensions/stochastics/stochastic_sequencer.py
  • temoa/_internal/temoa_sequencer.py
  • temoa/extensions/stochastics/scenario_creator.py
🧬 Code graph analysis (4)
temoa/extensions/stochastics/stochastic_sequencer.py (3)
temoa/core/config.py (1)
  • TemoaConfig (32-344)
temoa/extensions/stochastics/stochastic_config.py (2)
  • StochasticConfig (27-88)
  • from_toml (34-88)
temoa/extensions/stochastics/scenario_creator.py (1)
  • scenario_creator (19-130)
temoa/_internal/temoa_sequencer.py (2)
temoa/extensions/stochastics/stochastic_sequencer.py (1)
  • StochasticSequencer (12-94)
temoa/core/modes.py (1)
  • TemoaMode (9-20)
tests/test_full_runs.py (2)
tests/conftest.py (1)
  • system_test_run (191-218)
temoa/_internal/temoa_sequencer.py (1)
  • TemoaSequencer (45-272)
temoa/extensions/stochastics/scenario_creator.py (5)
temoa/_internal/run_actions.py (1)
  • build_instance (121-155)
temoa/components/costs.py (1)
  • period_cost_rule (316-516)
temoa/data_io/hybrid_loader.py (3)
  • HybridLoader (70-930)
  • create_data_dict (161-281)
  • data_portal_from_data (145-155)
temoa/core/config.py (1)
  • TemoaConfig (32-344)
temoa/extensions/stochastics/stochastic_config.py (1)
  • StochasticConfig (27-88)
🪛 Ruff (0.14.10)
temoa/extensions/stochastics/stochastic_config.py

21-23: Avoid specifying long messages outside the exception class

(TRY003)


61-64: Avoid specifying long messages outside the exception class

(TRY003)


76-76: Avoid specifying long messages outside the exception class

(TRY003)


82-82: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/stochastic_sequencer.py

21-21: Avoid specifying long messages outside the exception class

(TRY003)


25-25: Avoid specifying long messages outside the exception class

(TRY003)


27-27: Avoid specifying long messages outside the exception class

(TRY003)


33-33: Avoid specifying long messages outside the exception class

(TRY003)


43-43: Avoid specifying long messages outside the exception class

(TRY003)


55-58: Avoid specifying long messages outside the exception class

(TRY003)


87-87: Avoid specifying long messages outside the exception class

(TRY003)

temoa/extensions/stochastics/scenario_creator.py

19-19: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)


28-28: Avoid specifying long messages outside the exception class

(TRY003)


47-47: Avoid specifying long messages outside the exception class

(TRY003)


121-121: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: setup and test (windows-latest, 3.13)
  • GitHub Check: setup and test (windows-latest, 3.12)
  • GitHub Check: setup and test (macos-latest, 3.12)
  • GitHub Check: setup and test (macos-latest, 3.13)
🔇 Additional comments (20)
temoa/core/modes.py (1)

20-20: LGTM! Clean enum addition.

The STOCHASTIC mode follows the existing sequential numbering pattern and integrates cleanly with the existing modes.

requirements.txt (1)

40-41: LGTM! Dependency tracking is consistent.

The mpi-sppy dependency addition and comment updates properly reflect the dependency chain. The exact version pinning (==0.12.1) in this lock file is appropriate and consistent with the minimum version constraint (>=0.12.1) specified in pyproject.toml.

Also applies to: 52-52, 83-85, 105-105

docs/source/computational_implementation.rst (1)

392-392: LGTM! Documentation properly reflects the new stochastic capabilities.

The update removes the "untested" warning and adds a proper cross-reference to the stochastics documentation, correctly reflecting that stochastic programming is now a supported feature.

requirements-dev.txt (1)

74-75: LGTM! Dev dependency tracking is consistent.

The mpi-sppy dependency additions mirror the runtime requirements and properly track the dependency chain through commented references. The version pinning is consistent with requirements.txt.

Also applies to: 88-88, 127-129, 157-157

pyproject.toml (1)

33-33: mpi-sppy version 0.12.1 is valid and available on PyPI.

Confirmed that version 0.12.1 exists and is part of an active release sequence. The latest version is 0.13.1, and no security advisories were detected. The dependency constraint >=0.12.1 appropriately allows version upgrades.

temoa/extensions/stochastics/scenario_creator.py (2)

1-16: LGTM on imports and module setup.

The imports are well-organized with proper TYPE_CHECKING guards for type hints that are only needed at static analysis time.


108-128: First-stage variable selection logic looks correct.

The implementation correctly identifies first-period capacity decisions as first-stage variables and computes the first-stage cost using period_cost_rule. The validation for empty first-stage variables is appropriate since stochastic optimization requires at least one first-stage decision.

docs/source/index.rst (1)

9-9: LGTM - Documentation navigation updated for stochastics.

The new stochastics page is appropriately added to the table of contents.

tests/conftest.py (1)

95-100: Clarify: The stochastic test uses utopia.sqlite as input, not utopia_stochastic.sqlite.

Looking at config_utopia_stochastic.toml, the input_database is tests/testing_outputs/utopia.sqlite while utopia_stochastic.sqlite is the output database. The (None, 'utopia_stochastic.sqlite') entry creates a schema-only database for output.

This is correct but consider adding a comment for clarity:

📝 Optional: Add clarifying comment
         # Feature tests (separate for temporal consistency)
         ('emissions.sql', 'emissions.sqlite'),
         ('materials.sql', 'materials.sqlite'),
         ('simple_linked_tech.sql', 'simple_linked_tech.sqlite'),
         ('storageville.sql', 'storageville.sqlite'),
+        # Output-only databases (schema only, no data)
         (None, 'utopia_stochastic.sqlite'),
tests/testing_configs/config_utopia_stochastic.toml (1)

1-12: LGTM - Stochastic test configuration is well-structured.

The configuration correctly sets up the stochastic mode and references the appropriate input/output databases and stochastic config file.

temoa/_internal/temoa_sequencer.py (2)

36-36: LGTM - Import and attribute for StochasticSequencer.

The import and attribute declaration follow the established pattern for other sequencer types (MorrisSequencer, MgaSequencer, etc.).

Also applies to: 71-71


194-197: LGTM - STOCHASTIC mode integration follows established patterns.

The implementation correctly:

  1. Creates StochasticSequencer with the config
  2. Stores reference in self.stochastic_sequencer for later access (e.g., retrieving objective_value)
  3. Calls start() to execute the stochastic run

Based on retrieved learnings, the mode_override synchronization (lines 63-66) ensures config.scenario_mode is correctly set before StochasticSequencer is instantiated, which is important for downstream consistency checks.

tests/test_full_runs.py (1)

37-37: LGTM - Stochastic test files list defined.

Follows the established pattern for legacy_config_files, myopic_files, and mc_files.

temoa/core/config.py (1)

57-58: LGTM! Stochastic config parameter integration is clean.

The new stochastic_config parameter follows the existing pattern of optional Path parameters and is correctly stored as an instance attribute.

Also applies to: 148-148

docs/source/stochastics.rst (1)

1-107: LGTM! Comprehensive and well-structured documentation.

The documentation clearly explains the stochastic extension, configuration requirements, perturbation options, and workflow. The limitations section is appropriately honest about current scope (two-stage only, partial result persistence).

temoa/extensions/stochastics/stochastic_config.py (2)

10-23: LGTM! Perturbation dataclass with proper validation.

The __post_init__ validation for allowed actions is clean and provides a helpful error message.


55-76: LGTM! Robust perturbation parsing with good error handling.

The validation that perturbations reference existing scenarios and the clear error messages for missing fields will help users debug configuration issues.

temoa/extensions/stochastics/stochastic_sequencer.py (3)

17-33: LGTM! Robust initialization with comprehensive path validation.

The layered validation (missing config → path exists → is file → parse) provides clear error messages at each failure point.


80-91: LGTM! Proper termination checking and objective extraction.

The code correctly validates optimal termination before accessing the objective value, with appropriate error logging and exception handling for non-optimal cases.


71-78: The code correctly follows the mpi-sppy API. The solver_options dict is properly passed to solve_extensive_form() at the point where Pyomo solver configuration is applied, not at ExtensiveForm construction. No changes needed.

Likely an incorrect or invalid review comment.

Comment on lines +72 to +90
for idx_tuple, current_val in list(target_param.items()):
# Map index tuple to names based on table manifest
index_map = dict(zip(index_cols, idx_tuple, strict=True))

# Check if filter matches
match = True
for filter_key, filter_val in p.filter.items():
if index_map.get(filter_key) != filter_val:
match = False
break

if match:
if p.action == 'multiply':
target_param[idx_tuple] = current_val * p.value
elif p.action == 'add':
target_param[idx_tuple] = current_val + p.value
elif p.action == 'set':
target_param[idx_tuple] = p.value

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing validation for unknown perturbation actions.

If p.action is not one of multiply, add, or set, the perturbation is silently ignored. This could mask configuration errors. Consider logging a warning or raising an error for invalid actions.

🔧 Add validation for unknown actions
                 if match:
                     if p.action == 'multiply':
                         target_param[idx_tuple] = current_val * p.value
                     elif p.action == 'add':
                         target_param[idx_tuple] = current_val + p.value
                     elif p.action == 'set':
                         target_param[idx_tuple] = p.value
+                    else:
+                        logger.warning(
+                            "Unknown perturbation action '%s' for scenario %s, table %s; skipping",
+                            p.action,
+                            scenario_name,
+                            p.table,
+                        )
🤖 Prompt for AI Agents
In @temoa/extensions/stochastics/scenario_creator.py around lines 72 - 90, The
loop that applies perturbations over target_param silently ignores unknown
p.action values; update the code in the scenario_creator loop that handles
p.action (the if/elif chain handling 'multiply', 'add', 'set') to handle invalid
actions explicitly by either raising a clear ValueError (including p.action and
any identifier like p.name or p) or emitting a warning via the module logger
with the same contextual info; add an else branch after the existing elifs that
performs this validation and stops or warns so misconfigured perturbations are
not silently dropped.

Comment on lines +95 to +106
# 4. Attach root node (Stage 1)
periods = sorted(instance.time_optimize)
first_period = periods[0]

prob = stoch_config.scenarios.get(scenario_name)
if prob is None:
logger.warning(
"Scenario '%s' not found in stochastic config probabilities; defaulting to 1.0",
scenario_name,
)
prob = 1.0
instance._mpisppy_probability = prob
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Defaulting missing scenario probability to 1.0 may cause incorrect expected value computation.

When a scenario is not found in stoch_config.scenarios, defaulting to probability 1.0 could lead to probabilities summing to more than 1.0 across all scenarios, producing incorrect expected value calculations in the stochastic optimization. This should likely raise an error instead of defaulting.

🔧 Recommended fix: Raise error for missing scenario
     prob = stoch_config.scenarios.get(scenario_name)
     if prob is None:
-        logger.warning(
-            "Scenario '%s' not found in stochastic config probabilities; defaulting to 1.0",
+        raise ValueError(
+            f"Scenario '{scenario_name}' not found in stochastic config probabilities. "
+            "All scenarios must have defined probabilities."
-            scenario_name,
         )
-        prob = 1.0
     instance._mpisppy_probability = prob
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 4. Attach root node (Stage 1)
periods = sorted(instance.time_optimize)
first_period = periods[0]
prob = stoch_config.scenarios.get(scenario_name)
if prob is None:
logger.warning(
"Scenario '%s' not found in stochastic config probabilities; defaulting to 1.0",
scenario_name,
)
prob = 1.0
instance._mpisppy_probability = prob
# 4. Attach root node (Stage 1)
periods = sorted(instance.time_optimize)
first_period = periods[0]
prob = stoch_config.scenarios.get(scenario_name)
if prob is None:
raise ValueError(
f"Scenario '{scenario_name}' not found in stochastic config probabilities. "
"All scenarios must have defined probabilities."
)
instance._mpisppy_probability = prob
🤖 Prompt for AI Agents
In @temoa/extensions/stochastics/scenario_creator.py around lines 95 - 106, The
code currently defaults missing scenario probability to 1.0 which can corrupt
expected-value calculations; instead, when
stoch_config.scenarios.get(scenario_name) returns None, raise a clear exception
(e.g., ValueError or KeyError) mentioning the missing scenario_name and that
probabilities are missing/invalid, and do not set instance._mpisppy_probability
in that case; update the block around stoch_config.scenarios, scenario_name,
logger, and instance._mpisppy_probability to perform this validation and raise
the error rather than defaulting.

Comment on lines +1 to +19
[scenarios]
low_coal = { probability = 0.5 }
high_coal = { probability = 0.5 }

[[perturbations]]
scenario = "low_coal"
table = "cost_variable"
column = "cost"
filter = { tech = "IMPHCO1" }
action = "multiply"
value = 0.5

[[perturbations]]
scenario = "high_coal"
table = "cost_variable"
column = "cost"
filter = { tech = "IMPHCO1" }
action = "multiply"
value = 1.5
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for .column usage in the stochastics extension
rg -n "\.column" temoa/extensions/stochastics/

Repository: TemoaProject/temoa

Length of output: 304


🏁 Script executed:

#!/bin/bash
# Also search for "column" in the file to catch broader patterns
rg -n "column" temoa/extensions/stochastics/ -i

Repository: TemoaProject/temoa

Length of output: 575


🏁 Script executed:

#!/bin/bash
# Examine scenario_creator.py to verify the perturbation logic
find . -name "scenario_creator.py" -type f

Repository: TemoaProject/temoa

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Search for perturbation-related code that might use column
rg -n "perturbations\|perturbation" temoa/extensions/stochastics/ -A 3 -B 1

Repository: TemoaProject/temoa

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Read scenario_creator.py to see how perturbations are processed
cat -n temoa/extensions/stochastics/scenario_creator.py | head -100

Repository: TemoaProject/temoa

Length of output: 4542


🏁 Script executed:

#!/bin/bash
# Search for Perturbation class or data structure definition
rg -n "class Perturbation|@dataclass.*Perturbation" temoa/extensions/stochastics/ -A 15

Repository: TemoaProject/temoa

Length of output: 1342


🏁 Script executed:

#!/bin/bash
# Look for where TOML perturbations are loaded/parsed
rg -n "perturbations" temoa/extensions/stochastics/ -B 2 -A 5

Repository: TemoaProject/temoa

Length of output: 3636


🏁 Script executed:

#!/bin/bash
# Read the complete Perturbation parsing section from TOML
sed -n '55,75p' temoa/extensions/stochastics/stochastic_config.py

Repository: TemoaProject/temoa

Length of output: 929


🏁 Script executed:

#!/bin/bash
# Check if column is mentioned anywhere in stochastic_config parsing or processing
rg -n "column" temoa/extensions/stochastics/stochastic_config.py

Repository: TemoaProject/temoa

Length of output: 44


Remove the unused column field from perturbations or implement column-specific filtering.

The column field (lines 8, 16) in the TOML perturbations is silently discarded: it is not defined in the Perturbation dataclass and is never extracted during TOML parsing in stochastic_config.py (lines 66-72). The perturbation logic in scenario_creator.py applies modifications to all matching index tuples regardless of column name. Either remove this field from the test config or implement proper column filtering if column-specific perturbations are intended.

🤖 Prompt for AI Agents
In @tests/testing_data/utopia_stochastic_config.toml around lines 1 - 19, The
TOML "column" field in perturbations is ignored: add a column attribute to the
Perturbation dataclass and parse it in the code that reads TOML (the parsing
logic in stochastic_config.py around the Perturbation extraction), then update
the perturbation application in scenario_creator.py to check perturbation.column
and only apply the change to matching column names (or, if column-level behavior
is not desired, remove the column entries from the test file). Make sure to
update any constructors/usages of Perturbation and the unit tests to reflect the
new column-aware behavior.

@jdecarolis jdecarolis merged commit 9ba9489 into unstable Jan 8, 2026
9 checks passed
@jdecarolis jdecarolis deleted the feat/stochastics branch January 8, 2026 19:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Feature Additional functionality refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants