Skip to content

Conversation

@ParticularlyPythonicBS
Copy link
Member

@ParticularlyPythonicBS ParticularlyPythonicBS commented Jan 6, 2026

On adding a full monte carlo integration test, it fail with "A feasible solution was not found" if it was executed after a standard utopia run in the same process group.
This is because:

  • The HiGHS library (via appsi_highs) initializes internal C++ state during its first solve.
  • On Linux, multiprocessing defaults to fork, which copies this "dirty" memory state into child processes.
  • When a child process tries to solve a different model (like the tweaked MC scenarios) after a primary solve in the parent, the solver enters an inconsistent state and reports infeasibility.

Using the spawn start method, which starts each worker in a fresh environment, definitively resolves the issue.

To strictly enforce this process isolation, the monte carlo execution model is refactored so that MCWorker is a plain Python object instead of inheriting from multiprocessing.Process.
The worker initialiation logic then uses multiprocessing.get_context('spawn').Process to truly isolate worker memory from parent memory.

Additionally we now expose the monte carlo solver options in the basic config.toml with:

[monte_carlo]
run_settings = "path/to/mc_settings.csv"
solver_options = "my_custom_options.toml"  # Optional

The default remains temoa/extensions/monte_carlo/MC_solver_options.toml

The docs have also been updated to detail the monte carlo feature functionality

Summary by CodeRabbit

Release Notes

  • Documentation

    • Added comprehensive Monte Carlo documentation covering configuration, input format, supported adjustments, advanced indexing, parallel execution, and output storage.
  • New Features

    • Enhanced Monte Carlo solver configuration with custom options file support and improved worker process management.
  • Bug Fixes

    • Updated output table field names for improved clarity.
    • Enhanced error handling for missing solver configuration files.
  • Tests

    • Added test coverage for Monte Carlo functionality.

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

@ParticularlyPythonicBS ParticularlyPythonicBS added Maintenance Code quality fixes and deprecation management docs labels Jan 6, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

Warning

Rate limit exceeded

@ParticularlyPythonicBS has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between ea5241e and 9e8014e.

⛔ Files ignored due to path filters (1)
  • tests/testing_data/mc_settings_utopia.csv is excluded by !**/*.csv
📒 Files selected for processing (9)
  • docs/source/computational_implementation.rst
  • docs/source/index.rst
  • docs/source/monte_carlo.rst
  • temoa/_internal/table_writer.py
  • temoa/extensions/monte_carlo/mc_sequencer.py
  • temoa/extensions/monte_carlo/mc_worker.py
  • tests/conftest.py
  • tests/test_full_runs.py
  • tests/testing_configs/config_utopia_mc.toml

Walkthrough

This pull request updates Monte Carlo documentation, refactors multiprocessing architecture in the MC worker and sequencer components, standardizes output table field naming, and introduces comprehensive testing coverage for Monte Carlo functionality including configuration files and validation tests.

Changes

Cohort / File(s) Summary
Documentation Updates
docs/source/computational_implementation.rst, docs/source/index.rst, docs/source/monte_carlo.rst
Added comprehensive Monte Carlo documentation including new monte_carlo.rst guide covering framework, configuration, input formats, adjustments, indexing, parallel execution, outputs, and solver options. Updated index toctree and computational_implementation.rst with Sphinx doc directives and navigation entries.
Multiprocessing Architecture
temoa/extensions/monte_carlo/mc_sequencer.py, temoa/extensions/monte_carlo/mc_worker.py
Refactored worker multiprocessing: switched from Process inheritance to plain class in MCWorker, introduced spawn context for queue initialization, replaced worker.start() with multiprocessing.Process instances. Added custom solver options path resolution with fallback to default, deferred solver options loading from file or resource until runtime. Enhanced error handling for missing solver options files.
Output Format Changes
temoa/_internal/table_writer.py
Renamed tweak record keys: 'iteration'→'run', 'param_name'→'param', 'old_value'→'old_val', 'new_value'→'new_val' in write_tweaks function. Field name changes affect output_mc_delta table structure.
Test Infrastructure
tests/conftest.py, tests/test_full_runs.py, tests/testing_configs/config_utopia_mc.toml
Added utopia_mc.sqlite test database mapping to conftest. Created Monte Carlo test configuration (config_utopia_mc.toml) with seasonal timeslices and appsi_highs solver. Implemented test_mc_utopia function to validate tweaks and objectives in MC output database.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • #227: Modifies Monte Carlo multiprocessing and solver initialization by deferring solver creation and applying options inside workers to avoid pickling issues—directly related to the worker and sequencer refactoring in this PR.

Suggested labels

bugfix

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main changes: updating Monte Carlo functionality to work with v4 restructure and HiGHs solver defaults.

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.

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/monte_carlo/mc_sequencer.py:
- Around line 65-71: The except FileNotFoundError block in mc_sequencer.py
currently logs via logger.error()/warning() without the traceback; update that
handler to call logger.exception (preserving the existing messages and
options_file_path usage) so the traceback is captured for diagnostics, then keep
the fallback behavior setting s_options = {} and all_options = {} unchanged;
locate the except block that handles FileNotFoundError and replace the
logger.error/logger.warning calls with logger.exception while retaining the
conditional message based on options_file_path.

In @temoa/extensions/monte_carlo/mc_worker.py:
- Line 117: The timestamp assignment uses naive datetime.now(); change it to a
timezone-aware call (e.g., datetime.now(tz=timezone.utc)) and add the necessary
import (timezone) so that the tic variable in mc_worker.py is timezone-aware;
this keeps elapsed-time calculations the same but ensures consistency for any
future uses of tic.

In @tests/test_full_runs.py:
- Line 173: The unpacked variable data_name in the tuple assignment for
system_test_run is unused; rename it to _data_name (e.g., change "data_name, _,
_, sequencer = system_test_run" to "_data_name, _, _, sequencer =
system_test_run") to indicate it is intentionally unused and satisfy the linter.
📜 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 7b6af0f and ea5241e.

⛔ Files ignored due to path filters (1)
  • tests/testing_data/mc_settings_utopia.csv is excluded by !**/*.csv
📒 Files selected for processing (9)
  • docs/source/computational_implementation.rst
  • docs/source/index.rst
  • docs/source/monte_carlo.rst
  • temoa/_internal/table_writer.py
  • temoa/extensions/monte_carlo/mc_sequencer.py
  • temoa/extensions/monte_carlo/mc_worker.py
  • tests/conftest.py
  • tests/test_full_runs.py
  • tests/testing_configs/config_utopia_mc.toml
🧰 Additional context used
🧬 Code graph analysis (2)
temoa/extensions/monte_carlo/mc_sequencer.py (2)
temoa/extensions/monte_carlo/mc_worker.py (1)
  • run (57-169)
temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py (1)
  • start (123-318)
tests/test_full_runs.py (1)
tests/conftest.py (1)
  • system_test_run (189-216)
🪛 Ruff (0.14.10)
temoa/extensions/monte_carlo/mc_sequencer.py

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

Replace with exception

(TRY400)

tests/test_full_runs.py

173-173: Unpacked variable data_name is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

temoa/extensions/monte_carlo/mc_worker.py

110-110: Do not call setattr with a constant attribute value. It is not any safer than normal property access.

Replace setattr with assignment

(B010)


117-117: datetime.datetime.now() called without a tz argument

(DTZ005)

⏰ 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 (macos-latest, 3.12)
  • GitHub Check: setup and test (windows-latest, 3.12)
  • GitHub Check: setup and test (windows-latest, 3.13)
🔇 Additional comments (14)
docs/source/index.rst (1)

7-7: LGTM!

The addition of the monte_carlo entry to the toctree properly integrates the new Monte Carlo documentation into the navigation structure.

docs/source/monte_carlo.rst (1)

1-124: Excellent documentation coverage!

The Monte Carlo documentation is comprehensive and well-structured, covering:

  • Framework overview and capabilities
  • Configuration setup with TOML examples
  • CSV input format with clear column descriptions
  • Adjustment types (absolute, relative, substitution) with formulas
  • Advanced indexing features (wildcards and multi-tokens)
  • Parallel execution details
  • Output structure and tweak logging

The field names in the tweak log table (lines 120-124) correctly align with the renamed fields in table_writer.py.

docs/source/computational_implementation.rst (1)

389-389: LGTM!

The update to use a Sphinx doc directive (:doc:monte_carlo``) properly creates a cross-reference to the new Monte Carlo documentation, improving navigation and documentation structure.

temoa/extensions/monte_carlo/mc_worker.py (4)

31-56: Correct refactoring to support spawn-based multiprocessing.

The removal of Process inheritance and the deferred solver initialization (line 50: self.opt = None) align with the PR's objective to fix HiGHS solver initialization issues. By making MCWorker a plain class and initializing the solver in run() (line 70), each worker process starts with a fresh solver instance, avoiding the memory-copy issues that occur with the fork multiprocessing context.


118-128: Appropriate handling of solver-specific interfaces.

The different solve approaches for appsi_ vs non-appsi solvers correctly handle the different configuration methods:

  • appsi solvers use config-based settings (lines 122-123)
  • Other solvers use parameter-based settings (line 128)

This ensures terminal output (tee=True) is enabled for both solver types.


130-144: Good enhancement to error diagnostics.

The addition of solver results status logging (lines 140-143) when an exception occurs provides valuable diagnostic information. The safe attribute checking with hasattr ensures this doesn't cause secondary errors.


163-165: Updated logging format is clear.

The multi-line logging format for non-optimal solves improves readability while maintaining the same information content.

temoa/_internal/table_writer.py (1)

746-750: No action required. The field name changes in the output_mc_delta table have been properly implemented across all integration points:

  1. Database schema (temoa/extensions/monte_carlo/make_deltas_table.sql): Table definition uses the new column names (run, param, old_val, new_val)
  2. Documentation (docs/source/monte_carlo.rst): Updated to document the new field names
  3. Tests (tests/test_full_runs.py): Queries already use the new field names
  4. Code mapping (table_writer.py lines 746-750): Correctly maps from internal ChangeRecord fields to database columns

The ChangeRecord namedtuple in mc_run.py retains the old field names internally (param_name, old_value, new_value), but this is acceptable since the mapping to the new database column names occurs at the table writer boundary.

tests/conftest.py (1)

83-83: LGTM!

The new database mapping follows the established pattern and correctly enables the Monte Carlo test scenario by reusing the same utopia_data.sql source file.

tests/test_full_runs.py (1)

164-203: Test structure looks solid.

The Monte Carlo integration test properly validates:

  1. The output_mc_delta table records parameter tweaks correctly
  2. The output_objective table contains the expected scenario variations
  3. Database connection is properly closed

This addresses the PR objective of adding full Monte Carlo integration testing.

temoa/extensions/monte_carlo/mc_sequencer.py (3)

115-126: Spawn context correctly addresses the HiGHS solver state issue.

Using multiprocessing.get_context('spawn') ensures each worker starts in a fresh Python interpreter state, avoiding the copied solver-initialized memory that caused infeasibility when using the default fork context on Linux. The queues are correctly created via the same context.


147-149: Worker process setup is correct.

Creating processes via ctx.Process(target=w.run, daemon=True) properly separates the MCWorker object from the multiprocessing infrastructure. The daemon=True flag ensures workers are terminated if the main process exits unexpectedly, which is appropriate for this use case since proper shutdown is handled via the 'ZEBRA' signal.


42-51: Path resolution logic is well-implemented.

The code correctly handles both absolute and relative paths for custom solver options, with relative paths resolved against the config file location. This aligns with the PR objective of exposing Monte Carlo solver options in the config.

tests/testing_configs/config_utopia_mc.toml (1)

1-16: LGTM! Configuration structure is correct.

The Monte Carlo test configuration properly sets up the scenario mode, solver, and references an existing run settings file. Using appsi_highs aligns with the PR objective to address HiGHS solver state issues.

Comment on lines 65 to 71
except FileNotFoundError:
logger.warning('Unable to find solver options toml file. Using default options.')
if options_file_path:
logger.error('Unable to find custom solver options file at: %s', options_file_path)
else:
logger.warning('Unable to find default solver options toml file.')
s_options = {}
all_options = {}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using logger.exception for better diagnostics.

When handling FileNotFoundError, using logger.exception would include the traceback, which can be helpful for debugging path resolution issues.

🔎 Proposed fix
         except FileNotFoundError:
             if options_file_path:
-                logger.error('Unable to find custom solver options file at: %s', options_file_path)
+                logger.exception('Unable to find custom solver options file at: %s', options_file_path)
             else:
                 logger.warning('Unable to find default solver options toml file.')
             s_options = {}
📝 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
except FileNotFoundError:
logger.warning('Unable to find solver options toml file. Using default options.')
if options_file_path:
logger.error('Unable to find custom solver options file at: %s', options_file_path)
else:
logger.warning('Unable to find default solver options toml file.')
s_options = {}
all_options = {}
except FileNotFoundError:
if options_file_path:
logger.exception('Unable to find custom solver options file at: %s', options_file_path)
else:
logger.warning('Unable to find default solver options toml file.')
s_options = {}
🧰 Tools
🪛 Ruff (0.14.10)

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

Replace with exception

(TRY400)

🤖 Prompt for AI Agents
In @temoa/extensions/monte_carlo/mc_sequencer.py around lines 65 - 71, The
except FileNotFoundError block in mc_sequencer.py currently logs via
logger.error()/warning() without the traceback; update that handler to call
logger.exception (preserving the existing messages and options_file_path usage)
so the traceback is captured for diagnostics, then keep the fallback behavior
setting s_options = {} and all_options = {} unchanged; locate the except block
that handles FileNotFoundError and replace the logger.error/logger.warning calls
with logger.exception while retaining the conditional message based on
options_file_path.

Comment on lines +93 to +112
# update the solver options
self.opt.options = self.solver_options
if self.solver_name.startswith('appsi_'):
for k, v in self.solver_options.items():
try:
setattr(self.opt.config, k, v)
except (ValueError, AttributeError):
# Key not defined in ConfigDict, skip it
pass

# Handle solver log path for appsi
if self.solver_log_path:
log_location = (
self.solver_log_path
/ f'solver_log_{self.worker_number}_{self.solve_count}.log'
)
try:
setattr(self.opt.config, 'log_file', str(log_location))
except (ValueError, AttributeError):
pass
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider optimizing solver options setup.

The solver options are updated on every iteration (lines 93-112). If the solver options don't change between runs, this work is redundant and could be moved to initialization.

Additionally, for appsi_ solvers, both self.opt.options (line 94) and self.opt.config attributes (lines 96-101) are being set. Verify whether setting opt.options is necessary for appsi solvers, as the subsequent config-based settings may be sufficient.

Regarding the static analysis hint about setattr at line 110: this is a false positive. Dynamic configuration setting via setattr is appropriate here since we're safely handling arbitrary solver options with try-except blocks.

💡 Optional optimization if solver options are constant

If self.solver_options doesn't change between iterations, consider moving the solver configuration to immediately after solver initialization:

 self.opt = SolverFactory(self.solver_name)
+
+# Configure solver options once if they don't change between runs
+self.opt.options = self.solver_options
+if self.solver_name.startswith('appsi_'):
+    for k, v in self.solver_options.items():
+        try:
+            setattr(self.opt.config, k, v)
+        except (ValueError, AttributeError):
+            pass

 while True:
     # wait for a DataPortal object to show up, then get to work
     tic = datetime.now()
     data = self.dp_queue.get()
     toc = datetime.now()
-
-    # update the solver options
-    self.opt.options = self.solver_options
-    if self.solver_name.startswith('appsi_'):
-        for k, v in self.solver_options.items():
-            try:
-                setattr(self.opt.config, k, v)
-            except (ValueError, AttributeError):
-                pass

Only apply this optimization if solver options don't vary per iteration.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.10)

110-110: Do not call setattr with a constant attribute value. It is not any safer than normal property access.

Replace setattr with assignment

(B010)

abstract_model = TemoaModel()
model: TemoaModel = abstract_model.create_instance(data=dp)
model.name = name # set the name from the input
tic = datetime.now()
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Optional: Consider timezone-aware datetime for consistency.

The static analysis tool flags datetime.now() without a tz argument. While this doesn't affect the duration calculation (which is the only use here), using timezone-aware datetimes is a general best practice for consistency.

💡 Optional improvement for timezone-aware datetime
-tic = datetime.now()
+from datetime import timezone
+tic = datetime.now(timezone.utc)

However, since this is only used for calculating elapsed time and not for any time-based logic or storage, the current implementation is acceptable.

📝 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
tic = datetime.now()
tic = datetime.now(timezone.utc)
🧰 Tools
🪛 Ruff (0.14.10)

117-117: datetime.datetime.now() called without a tz argument

(DTZ005)

🤖 Prompt for AI Agents
In @temoa/extensions/monte_carlo/mc_worker.py at line 117, The timestamp
assignment uses naive datetime.now(); change it to a timezone-aware call (e.g.,
datetime.now(tz=timezone.utc)) and add the necessary import (timezone) so that
the tic variable in mc_worker.py is timezone-aware; this keeps elapsed-time
calculations the same but ensures consistency for any future uses of tic.

"""
Test Monte Carlo run logic and output database contents
"""
data_name, _, _, sequencer = system_test_run
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Prefix unused variable with underscore.

The data_name variable is unpacked but never used. Prefix it with an underscore to indicate it's intentionally unused.

🔎 Proposed fix
-    data_name, _, _, sequencer = system_test_run
+    _data_name, _, _, sequencer = system_test_run
📝 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
data_name, _, _, sequencer = system_test_run
_data_name, _, _, sequencer = system_test_run
🧰 Tools
🪛 Ruff (0.14.10)

173-173: Unpacked variable data_name is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🤖 Prompt for AI Agents
In @tests/test_full_runs.py at line 173, The unpacked variable data_name in the
tuple assignment for system_test_run is unused; rename it to _data_name (e.g.,
change "data_name, _, _, sequencer = system_test_run" to "_data_name, _, _,
sequencer = system_test_run") to indicate it is intentionally unused and satisfy
the linter.

@jdecarolis jdecarolis merged commit bc49845 into unstable Jan 7, 2026
9 checks passed
@jdecarolis jdecarolis deleted the tests/mc_full_run branch January 7, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix docs Maintenance Code quality fixes and deprecation management

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants