Summary
apply_thermal_heuristics and the two built-in heuristic functions it calls (find_min_generation_fast, find_num_units_accurate) handle the component and scenario dimensions with plain Python for loops, rather than the array-vectorized approach (xarray/linopy broadcasting) used everywhere else in gems_runner. This is a structural outlier relative to the rest of the codebase and is a plausible bottleneck on studies with a non-trivial number of thermal clusters and/or Monte-Carlo scenarios.
Where
apply_thermal_heuristics (src/gems_runner/simulation/heuristic_runner.py:176-197): nested for component in heuristic_comps: for local_idx in range(len(scenario_ids)):, calling the heuristic function once per (component, scenario) pair, each with its own .sel()/.isel() reads and writes.
find_num_units_accurate (src/gems_runner/simulation/thermal_heuristic.py:160-287): builds a brand-new linopy.Model() and issues a separate solver invocation per call, i.e. per (component, scenario) pair.
find_min_generation_fast (src/gems_runner/simulation/thermal_heuristic.py:74-157): pure-Python offset search, O(window_size × num_timesteps) per call, also invoked once per (component, scenario) pair.
Why it matters
AGENTS.md documents VectorizedBuilderBase / VectorizedLinearExprBuilder as the dominant pattern for anything touching time/scenario/component dimensions elsewhere in gems_runner. The heuristic layer doesn't follow that pattern.
- Overall cost scales as
O(components × scenarios × ...) of interpreted Python and, for accurate, of separate solver process/model-construction overhead — multiplicatively, on top of whatever blocks exist in sequential/parallel resolution mode.
Scope of this issue
This issue is about naming and tracking the lack of vectorization. The actual performance impact has not been measured yet and should be assessed (e.g. profiling a study with a representative number of thermal clusters and scenarios) before deciding whether it's worth addressing. If it turns out to matter, several directions could be pursued — this issue does not prescribe one.
Pointers
src/gems_runner/simulation/heuristic_runner.py (apply_thermal_heuristics)
src/gems_runner/simulation/thermal_heuristic.py (find_min_generation_fast, find_num_units_accurate)
Summary
apply_thermal_heuristicsand the two built-in heuristic functions it calls (find_min_generation_fast,find_num_units_accurate) handle thecomponentandscenariodimensions with plain Pythonforloops, rather than the array-vectorized approach (xarray/linopybroadcasting) used everywhere else ingems_runner. This is a structural outlier relative to the rest of the codebase and is a plausible bottleneck on studies with a non-trivial number of thermal clusters and/or Monte-Carlo scenarios.Where
apply_thermal_heuristics(src/gems_runner/simulation/heuristic_runner.py:176-197): nestedfor component in heuristic_comps: for local_idx in range(len(scenario_ids)):, calling the heuristic function once per(component, scenario)pair, each with its own.sel()/.isel()reads and writes.find_num_units_accurate(src/gems_runner/simulation/thermal_heuristic.py:160-287): builds a brand-newlinopy.Model()and issues a separate solver invocation per call, i.e. per(component, scenario)pair.find_min_generation_fast(src/gems_runner/simulation/thermal_heuristic.py:74-157): pure-Python offset search,O(window_size × num_timesteps)per call, also invoked once per(component, scenario)pair.Why it matters
AGENTS.mddocumentsVectorizedBuilderBase/VectorizedLinearExprBuilderas the dominant pattern for anything touching time/scenario/component dimensions elsewhere ingems_runner. The heuristic layer doesn't follow that pattern.O(components × scenarios × ...)of interpreted Python and, foraccurate, of separate solver process/model-construction overhead — multiplicatively, on top of whatever blocks exist in sequential/parallel resolution mode.Scope of this issue
This issue is about naming and tracking the lack of vectorization. The actual performance impact has not been measured yet and should be assessed (e.g. profiling a study with a representative number of thermal clusters and scenarios) before deciding whether it's worth addressing. If it turns out to matter, several directions could be pursued — this issue does not prescribe one.
Pointers
src/gems_runner/simulation/heuristic_runner.py(apply_thermal_heuristics)src/gems_runner/simulation/thermal_heuristic.py(find_min_generation_fast,find_num_units_accurate)