Skip to content

Hoist invariant sum() out of calculate_force_terms inner loop (~16% faster) - #111

Merged
ibell merged 1 commit into
masterfrom
force-terms-sum-hoist
Jun 11, 2026
Merged

Hoist invariant sum() out of calculate_force_terms inner loop (~16% faster)#111
ibell merged 1 commit into
masterfrom
force-terms-sum-hoist

Conversation

@ibell

@ibell ibell commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

Scroll.calculate_force_terms recomputed sum(_slice) on every iteration of its for CVkey: for theta: double loop, inside the condition if len(error_messages) == sum(_slice). Since _slice = list(range(self.Itheta+1)) is invariant across both loops, this summed ~Itheta integers ~671,000 times per representative scroll run — about 18% of total solve time.

This hoists it to a single n_slice_sum = sum(_slice) before the loops.

Impact

  • ~16% faster end-to-end on examples/scroll_compressor.py (16.4s → 13.7s), interpreted/default build — no compilation, no API change.
  • Bit-identical results: volumetric efficiency (91.95998551975654 %), adiabatic efficiency (74.61003492585036 %), mass flow (55.175673362688805 g/s), and the force/moment outputs all match to full precision before and after.

How it was found

Native stack sampling of the unprofiled solve showed builtins.sum at ~18% of solve time; cProfile masks this because its per-call overhead inflates the millions of cheap delegation calls instead. cProfile's call graph (print_callers) then pinned the caller to calculate_force_terms.

Note (not changed here)

The condition len(error_messages) == sum(_slice) looks like it may have been intended as len(...) == len(_slice) (i.e. "every force evaluation for this CV failed, so print the unique errors"). This PR preserves the existing behavior exactly and only removes the redundant recomputation; the suspected logic question is left for a separate decision.

Test Plan

  • examples/scroll_compressor.py runs and all reported quantities are bit-identical to master
  • ~16% wall-clock reduction confirmed across repeated runs

🤖 Generated with Claude Code

calculate_force_terms recomputed `sum(_slice)` on every iteration of its
`for CVkey: for theta:` double loop, in the condition
`if len(error_messages) == sum(_slice)`. `_slice = list(range(self.Itheta+1))`
is invariant over both loops, so this summed ~Itheta integers ~671k times per
representative scroll run -- about 18% of total solve time.

Hoist it to a single `n_slice_sum = sum(_slice)` before the loops. The result
is bit-identical (all reported quantities -- volumetric/adiabatic efficiency,
mass flow, forces -- match to full precision) and cuts ~16% off the wall-clock
of examples/scroll_compressor.py (16.4s -> 13.7s), with no compilation and no
API change.

Found via native sampling (cProfile's per-call overhead masks it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ibell
ibell merged commit 50d793c into master Jun 11, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant