Purpose: Execute rendered simulation scripts across multiple frameworks (PyMDP, RxInfer.jl, ActiveInference.jl, JAX, DisCoPy, PyTorch, NumPyro).
Pipeline Step: Step 12: Execution (12_execute.py)
Category: Simulation / Execution
Status: ✅ Production Ready
Version: 1.6.0
Last Updated: 2026-05-05
- Execute Python simulation scripts (PyMDP, JAX, DisCoPy)
- Execute Julia simulation scripts (RxInfer.jl, ActiveInference.jl)
- Capture simulation results and logs
- Handle execution errors gracefully
- Generate execution reports
- Multi-framework execution support
- Skip vs fail: JAX, NumPyro, PyTorch, and DisCoPy are core dependencies; if the environment is incomplete, scripts are skipped (not run) and reported as "skipped" — they do not count as execution failures. Repair with
uv sync. Julia backends still require a local Julia install. - Graceful degradation when frameworks unavailable
- Automatic PyMDP package detection (distinguishes correct vs wrong package variants)
- Path collection with deduplication (prevents nested directory issues)
- Comprehensive error logging
- Result capture and validation
- Execution timeout handling
summaries/execution_summary.json(slim aggregate): Per-script rows omit bulk fields (stdout/stderrbodies,simulation_data); lengths and pointers remain. Consumers needing full detail passexecution_summary_detail=True/--execution-summary-detailto also writesummaries/execution_summary_detail.json.PipelineArguments/ main orchestrator: Step 12 subprocess commands omit--backendunlessdistributedis true, and omit--execution-benchmark-repeatswhen the value is 1 (avoids implying benchmarking when repeats are disabled).- PyMDP subprocess environment: Defaults
TF_CPP_MIN_LOG_LEVEL=3for the child process when unset (quieter captured stderr). SetGNN_JAX_PLATFORM(e.g.cpu) on the host to pin JAX device selection for PyMDP runs; when unset, JAX uses normal platform discovery. - Configurable script-level concurrency: local process workers by default, or Ray/Dask when
distributed=True
process_execute(target_dir: Path, output_dir: Path, verbose: bool = False, frameworks: str = "all", **kwargs) -> Union[bool, int]
Description: Main execution function called by orchestrator (12_execute.py). Executes rendered simulation scripts across multiple frameworks.
Parameters:
target_dir(Path): Directory containing rendered scripts (typically output from Step 11)output_dir(Path): Output directory for execution resultsverbose(bool): Enable verbose logging (default: False)frameworks(str): Frameworks to execute ("all", "lite", or comma-separated list, default: "all")"all": Execute all configured frameworks"lite": Execute PyMDP, JAX, DisCoPy, and BNLearn- Comma-separated:
"pymdp,jax"for specific frameworks
timeout(int): Execution timeout per script in seconds (default: 3600)render_output_dir(Optional[Path]): Explicit Step 11 output directory to search. This is the safest way to keep Step 12 scoped to an isolated pipeline run.execution_workers(int): Number of rendered scripts to execute concurrently.1preserves serial execution; values above1use local process workers unlessdistributed=True.distributed(bool): Execute scripts through the distributed dispatcher when enabled (default: False)backend(str): Distributed execution backend, defaultray**kwargs: Additional framework-specific options
Returns: Union[bool, int] - True when execution succeeds or there is nothing to execute, False on execution failure, and integer exit-style values where the caller uses them.
Example:
from execute import process_execute
from pathlib import Path
import logging
logger = logging.getLogger(__name__)
success = process_execute(
target_dir=Path("output/11_render_output"),
output_dir=Path("output/12_execute_output"),
verbose=True,
frameworks="pymdp,jax",
timeout=600,
render_output_dir=Path("output/11_render_output"),
execution_workers=2,
)execute_simulation_from_gnn(gnn_file: Path, framework: str, output_dir: Path, **kwargs) -> Dict[str, Any]
Description: Execute simulation for specific GNN file and framework.
Parameters:
gnn_file(Path): Path to GNN fileframework(str): Framework to use ("pymdp", "rxinfer", "activeinference_jl", "jax", "discopy")output_dir(Path): Output directory for execution results**kwargs: Framework-specific execution options
Returns: Dict[str, Any] - Execution results dictionary with:
success(bool): Whether execution succeededreturn_code(int): Process return codestdout(str): Standard outputstderr(str): Standard errorduration(float): Execution duration in secondsoutput_files(List[Path]): Generated output files
execute_script_safely(script_path: Union[str, Path], timeout: int = 3600, capture_output: bool = True, cwd: Optional[Union[str, Path]] = None, env: Optional[Dict[str, str]] = None) -> Dict[str, Any]
Description: Run a single Python script via subprocess.run and return a uniform envelope. Rejects non-.py paths up front and converts every failure mode (missing file, timeout, non-zero exit, unknown exception) into a dict so callers never need to differentiate.
Returns: Dict[str, Any] with keys:
success(bool),script_path(str),return_code(int),stdout(str),stderr(str),duration_seconds(float), anderror/error_typeon failure.
Example:
from execute import execute_script_safely
result = execute_script_safely("output/11_render_output/.../model_pymdp.py", timeout=120)
if not result["success"]:
print(f"{result['error_type']}: {result.get('error') or result['stderr']}")execute_rendered_simulators(target_dir: Path, output_dir: Path, logger: logging.Logger, recursive: bool = False, verbose: bool = False, **kwargs) -> bool
Description: Iterate over the ExecutorFrameworkSpec registry for every supported framework runner (PyMDP, RxInfer.jl, DisCoPy, ActiveInference.jl, JAX, NumPyro, PyTorch) and write a summary JSON + markdown report under output_dir / "12_execute_output" / "summaries" /. Missing optional dependencies are recorded as "SKIPPED" instead of failures.
Framework availability is assessed at execution time by the processor rather than a single standalone function. Key detection utilities:
execute.pymdp.package_detector.detect_pymdp_installation()— Detect which PyMDP package variant is installed.execute.pymdp.package_detector.validate_pymdp_for_execution()— Validate PyMDP is ready for execution.- MCP tool:
execute.get_health_status— Exposes framework availability via MCP (seeexecute/mcp.py).
Module: execute.pymdp.package_detector
Functions:
detect_pymdp_installation() -> Dict[str, Any]: Detect which PyMDP package variant is installed- Returns detection results including
correct_package,wrong_package,has_agent,has_mdp_solver
- Returns detection results including
is_correct_pymdp_package() -> bool: Check if correct package (inferactively-pymdp) is installedget_pymdp_installation_instructions() -> str: Get actionable installation instructionsvalidate_pymdp_for_execution() -> Dict[str, Any]: Validate PyMDP is ready for execution- Returns
readystatus, detection results, and installation instructions
- Returns
Usage:
from execute.pymdp.package_detector import detect_pymdp_installation, is_correct_pymdp_package
detection = detect_pymdp_installation()
if detection.get("wrong_package"):
print("Wrong PyMDP package installed - install inferactively-pymdp")
elif not detection.get("correct_package"):
print("PyMDP not installed - install inferactively-pymdp")simulation_engine(str): Engine to use for execution (default:"auto")"auto": Automatically select best available engine"pymdp": Use PyMDP for Python simulations"rxinfer": Use RxInfer.jl for Julia simulations"activeinference_jl": Use ActiveInference.jl"jax": Use JAX framework"discopy": Use DisCoPy for categorical diagrams
timeout(int): Execution timeout in seconds (default:3600)capture_output(bool): Capture stdout/stderr (default:True)render_output_dir(Path): Render output directory to search before default discoveryexecution_workers(int): Number of rendered scripts to execute concurrently. This parallelizes model/script runs, not timesteps within a single simulation.distributed(bool): Route scripts through the distributed dispatcher instead of the local process poolbackend(str): Dispatcher backend, defaultray
julia_path(str): Path to Julia executable (default: auto-detect)python_env(str): Python environment to use (default: current environment)jax_device(str): JAX device to use (default:"cpu", options:"cpu","gpu")
subprocess- Script executionjson- Result serialization
inferactively-pymdp- PyMDP simulation engine (package name:inferactively-pymdp, recovery: skip PyMDP)- Note: The correct package name is
inferactively-pymdp, notpymdp - The execute module automatically detects wrong package variants
- Note: The correct package name is
julia- Julia runtime (recovery: skip Julia scripts)jax- JAX framework (recovery: skip JAX)
from execute import process_execute
success = process_execute(
target_dir=Path("input/gnn_files"),
output_dir=Path("output/12_execute_output"),
simulation_engine="auto"
)uv run python src/12_execute.py \
--target-dir input/gnn_files/pymdp_scaling_study \
--output-dir output/pymdp_scaling_pipeline \
--frameworks pymdp \
--timeout 1200 \
--render-output-dir output/pymdp_scaling_pipeline/11_render_output \
--execution-workers 2When --render-output-dir is provided, Step 12 searches that directory for executable scripts and still filters by --frameworks. This prevents execution from reading stale artifacts in the default output/11_render_output directory.
Use --distributed --backend ray or --distributed --backend dask only when Ray/Dask is installed and the run should use that dispatcher. Without --distributed, Step 12 uses local process workers when --execution-workers is greater than 1.
execution_results.json- Execution results summaryexecution_report.md- Human-readable reportexecution_logs/*.log- Per-script execution logssimulation_data/*.json- Simulation output data
output/12_execute_output/
├── execution_results/
│ ├── execution_results.json
│ ├── execution_report.md
│ └── execution_logs/
│ ├── pymdp_simulation.log
│ ├── rxinfer_simulation.log
│ └── activeinference_simulation.log
└── simulation_data/
└── results_*.json
Use the current output/*/00_pipeline_summary/pipeline_execution_summary.json and Step 12 summaries for exact timing, memory, and pass/fail counts. Do not treat stale benchmark numbers in documentation as current measurements.
- PyMDP: ~1-5 seconds
- RxInfer.jl: ~10-20 seconds (JIT compilation)
- ActiveInference.jl: ~10-15 seconds
- JAX: ~2-8 seconds (with GPU)
- DisCoPy: ~1-3 seconds
- PyMDP unavailable: Log warning, skip PyMDP scripts
- Julia unavailable: Log warning, skip Julia scripts
- JAX unavailable: Log warning, skip JAX scripts
- Script errors: Capture stderr, continue with other scripts
- Timeout: configurable per script, defaulting to 3600s in
process_execute
- Dependency Errors: Framework not installed
- Syntax Errors: Generated code has errors
- Runtime Errors: Simulation crashes
- Timeout Errors: Execution exceeds limit
- Input: Receives rendered simulation scripts from Step 11 (render)
- Output: Generates execution results for Step 13 (llm analysis), Step 16 (analysis), and Step 23 (report generation)
- Dependencies: Requires rendered code from
11_render.pyoutput. Use--render-output-dirfor isolated pipeline runs.
- render/: Consumes rendered simulation scripts
- llm/: Provides execution results for LLM analysis
- analysis/: Provides execution data for statistical analysis
- report/: Provides execution summaries for reports
- PyMDP: Executes Python Active Inference simulations
- Julia Runtime: Executes Julia simulation scripts (RxInfer.jl, ActiveInference.jl)
- JAX: Executes JAX-based simulations
- DisCoPy: Executes categorical diagram computations
11_render.py (Code generation)
↓
12_execute.py (Script execution; optional explicit render_output_dir)
↓
├→ 13_llm.py (LLM analysis of results)
├→ 16_analysis.py (Statistical analysis)
├→ 23_report.py (Execution reports)
└→ output/12_execute_output/ (Execution results)
src/tests/execute/test_execute_overall.pysrc/tests/execute/test_execute_pymdp_integration.pysrc/tests/execute/test_execute_pymdp_package.py
Measure on demand:
uv run --extra dev python -m pytest src/tests/test_execute*.py \
--cov=src/execute --cov-report=term-missing- Multi-framework execution
- Error handling and recovery
- Result capture and validation
- Timeout handling
execute.run_simulation- Execute simulation scriptexecute.validate_environment- Validate execution environmentexecute.get_health_status- Get framework health statusexecute.analyze_error- Analyze execution errors
@mcp_tool("execute.run_simulation")
def run_simulation_tool(script_path: str, framework: str) -> Dict[str, Any]:
"""Execute simulation script"""
# Implementationsrc/execute/mcp.py- MCP tool registrations
Symptom: Julia scripts fail to execute
Cause: Julia not installed or not in PATH
Solution:
- Install Julia:
brew install julia(macOS) or download from julialang.org - Verify Julia installation:
julia --version - Check Julia is in PATH:
which julia - Install required Julia packages if needed
Symptom: Execution fails with import errors
Cause: Required packages not installed in environment
Solution:
- Install framework dependencies:
uv pip install inferactively-pymdp jax - Note: The correct PyMDP package name is
inferactively-pymdp, notpymdp - For Julia: Install packages via
julia -e 'using Pkg; Pkg.add("RxInfer")' - Check framework-specific requirements in documentation
Symptom: Error message "Wrong pymdp package installed. Found 'pymdp' with MDP/MDPSolver"
Cause: The wrong pymdp package (with MDP/MDPSolver) is installed instead of inferactively-pymdp
Solution:
- Uninstall wrong package:
uv pip uninstall pymdp - Install correct package:
uv pip install inferactively-pymdp - Or use setup module:
python src/1_setup.py --install_optional --optional_groups pymdp - The execute module automatically detects wrong package variants and provides clear error messages
Symptom: Scripts timeout before completion
Cause: Simulation too complex or timeout too short
Solution:
- Increase timeout:
--timeout 3600(1 hour) - Simplify model complexity
- Use faster frameworks (JAX) for large models
- Process models individually instead of batch
Features:
- Multi-framework execution support
- Graceful degradation when frameworks unavailable
- Comprehensive error logging
- Result capture and validation
- Execution timeout handling
Known Issues:
- None currently
- Next Version: Broader distributed execution coverage
- Future: Real-time execution monitoring
Last Updated: 2026-05-05 Maintainer: GNN Pipeline Team Status: ✅ Production Ready Version: 1.6.0 Architecture Compliance: ✅ 100% Thin Orchestrator Pattern