Skip to content

Solver migration#327

Merged
ekiefl merged 7 commits into
mainfrom
ek/solver-migration
May 20, 2026
Merged

Solver migration#327
ekiefl merged 7 commits into
mainfrom
ek/solver-migration

Conversation

@ekiefl
Copy link
Copy Markdown
Owner

@ekiefl ekiefl commented May 20, 2026

Co-locate collision-time solvers with their detectors

Summary

pooltool/physics/motion/solve.py was a kitchen-sink module holding collision-time root-finders for every event type, while their corresponding detectors (cache + dispatch + event construction) lived under pooltool/evolution/event_based/detect/. Solver and detector for the same event type sat in distant directories, which made the 2D/3D fork harder to reason about and obscured what code each event type actually owned.

This PR collapses that split. Each detect/<event>.py now owns both its detector wrapper and the math function that powers it. physics/motion/ is deleted. Along the way, the 2D/3D detection fork is simplified — only the event types where the math genuinely diverges (ball-ball, ball-table) keep an explicit fork; the rest now share a single detector that branches per ball state internally.

Closes #322.

Summary by CodeRabbit

  • Refactor

    • Consolidated event detection API by unifying dimension-specific collision detection into single pathways, removing separate 2D/3D variants for more consistent interfaces.
    • Reorganized collision-time calculation logic within the event detection system for improved code organization.
  • Documentation

    • Updated Sphinx AutoAPI generation configuration to reflect changes in package structure.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8101450a-e573-4658-bd1e-8e9a24fe8b99

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Moves collision-time solvers from pooltool/physics/motion/solve.py into corresponding event detector modules in pooltool/evolution/event_based/detect/. Consolidates 2D/3D event-detection function variants into unified get_next_ball_*_event signatures. Updates public API exports and the event dispatcher accordingly.

Changes

Solver Relocation and API Consolidation

Layer / File(s) Summary
Event detector API consolidation and dispatcher refactoring
pooltool/evolution/event_based/detect/__init__.py, pooltool/evolution/event_based/detect/detector.py
Public exports replace 2D/3D-specific event functions with unified get_next_ball_linear_cushion_event, get_next_ball_circular_cushion_event, and get_next_ball_pocket_event. The dispatcher refactors to unconditionally add cushion/pocket candidates while is_3d branching controls only ball-ball and ball-table events.
Ball-ball collision-time solver relocation
pooltool/evolution/event_based/detect/ball_ball.py
ball_ball_collision_time is moved from pooltool/physics/motion/solve.py and implemented locally with Numba JIT. It computes collision time by deriving per-ball relative-motion coefficients, building a quartic equation, and extracting the smallest positive real root.
Ball-table collision-time solver relocation
pooltool/evolution/event_based/detect/ball_table.py
ball_table_collision_time is moved and implemented locally with JIT compilation. It gates on const.airborne and delegates to get_airborne_time for airborne balls; non-airborne balls return np.inf.
Ball-cushion collision-time solvers and API consolidation
pooltool/evolution/event_based/detect/ball_cushion.py
Introduces local Numba-jitted ball_vertical_plane_collision_time and ball_vertical_cylinder_collision_time solvers with special-case non-translating state handling. Consolidates linear/circular cushion detection into unified get_next_ball_linear_cushion_event and get_next_ball_circular_cushion_event functions, adding airborne branching to return infinite collision time for airborne balls.
Ball-pocket collision-time solvers and API consolidation
pooltool/evolution/event_based/detect/ball_pocket.py
Implements ball_pocket_collision_time locally with internal state branching and ball_pocket_collision_time_if_airborne for special airborne geometry handling. Consolidates 2D/3D pocket event functions into unified get_next_ball_pocket_event that always calls ball_pocket_collision_time with internal airborne dispatch.
Support infrastructure parameter updates and exports
pooltool/physics/utils.py, pooltool/ptmath/roots/__init__.py
get_u_vec parameter order changed from (rvw, phi, R, s) to (rvw, R, phi, s) with updated early-return logic. Root-finding helpers get_real_positive_smallest_root and get_real_positive_smallest_roots are exported from pooltool/ptmath/roots/__init__.py.
Test and documentation updates
docs/include_exclude.py, sandbox/airborne_demos.py, tests/evolution/event_based/test_ball_pocket.py, tests/evolution/event_based/test_simulate.py
Tests updated to import relocated collision-time functions and call consolidated event APIs. Docstrings and docs config updated to reference new module locations for ball_pocket_collision_time_if_airborne and get_next_ball_pocket_event.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ekiefl/pooltool#323: Both PRs update ball-pocket collision timing; this PR consolidates airborne logic into a unified ball_pocket_collision_time with internal branching, while PR #323 adds dedicated airborne solver functions.
  • ekiefl/pooltool#299: Both PRs modify event-based detection layers and consolidate 2D/3D event APIs; this PR further moves collision-time logic into the same detector modules and unifies function signatures.
  • ekiefl/pooltool#294: Both PRs update ball-ball, ball-cushion, and ball-pocket collision-time logic and event APIs that the simulation engine depends on.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Solver migration' clearly summarizes the main change of moving collision-time solvers from physics/motion/solve.py into their respective detector modules.
Linked Issues check ✅ Passed The PR successfully co-locates solvers with detectors (#322): moves collision-time math into detect modules, hoists shared helpers to physics/utils.py, and removes the physics/motion/solve.py file.
Out of Scope Changes check ✅ Passed All changes are scoped to the solver migration: moving collision-time logic into detectors, updating related imports, simplifying 2D/3D detection logic, and updating tests to use new locations.
Docstring Coverage ✅ Passed Docstring coverage is 94.44% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ek/solver-migration

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.

@ekiefl
Copy link
Copy Markdown
Owner Author

ekiefl commented May 20, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

❌ Patch coverage is 23.26733% with 155 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.78%. Comparing base (96698cd) to head (7b29854).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ltool/evolution/event_based/detect/ball_cushion.py 23.07% 60 Missing ⚠️
...oltool/evolution/event_based/detect/ball_pocket.py 16.92% 54 Missing ⚠️
pooltool/evolution/event_based/detect/ball_ball.py 16.66% 35 Missing ⚠️
...ooltool/evolution/event_based/detect/ball_table.py 70.00% 3 Missing ⚠️
pooltool/physics/utils.py 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #327      +/-   ##
==========================================
+ Coverage   47.71%   47.78%   +0.06%     
==========================================
  Files         158      157       -1     
  Lines       10700    10638      -62     
==========================================
- Hits         5106     5083      -23     
+ Misses       5594     5555      -39     
Flag Coverage Δ
service 47.78% <23.26%> (+0.06%) ⬆️
service-no-ani 58.28% <23.26%> (+0.19%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@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

🧹 Nitpick comments (3)
pooltool/evolution/event_based/detect/ball_ball.py (2)

37-38: 💤 Low value

Inconsistent numeric literal types between branches.

Line 38 uses integer literals (0, 0, 0, 0) while line 55 uses float literals (0.0, 0.0, 0.0, 0.0). For Numba nopython mode, consistent typing avoids potential type-inference surprises.

Proposed fix
     if s1 == const.spinning or s1 == const.pocketed or s1 == const.stationary:
-        a1x, a1y, b1x, b1y = 0, 0, 0, 0
+        a1x, a1y, b1x, b1y = 0.0, 0.0, 0.0, 0.0
     else:

Also applies to: 54-55

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pooltool/evolution/event_based/detect/ball_ball.py` around lines 37 - 38, The
branch that sets a1x, a1y, b1x, b1y uses integer literals (0,0,0,0) while
another branch uses float literals (0.0,0.0,0.0,0.0), which can break Numba
nopython type inference; update the integer assignments to floating-point
literals (0.0) in the branch that checks s1 == const.spinning or s1 ==
const.pocketed or s1 == const.stationary and any other similar branch (the one
around the other zero assignment) so both use 0.0 for a1x, a1y, b1x, b1y.

27-28: 💤 Low value

Remove unused parameters m1 and m2 from function signature.

These mass parameters are declared and passed by all callers but never referenced in the function body. Collision time is a kinematic property—mass doesn't affect when balls collide, only post-collision dynamics. Removing them requires updating the function signature and all call sites (main code at lines 129-130 and test cases), but improves clarity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pooltool/evolution/event_based/detect/ball_ball.py` around lines 27 - 28,
Remove the unused parameters m1 and m2 from the function in ball_ball.py that
currently declares them (they are never referenced in the body); update the
function signature to omit m1 and m2, remove any related parameter
docs/comments, and update all call sites that pass masses (including the main
caller and tests) to stop supplying those two arguments so signatures match; run
tests/linter to ensure no remaining references.
pooltool/evolution/event_based/detect/ball_pocket.py (1)

128-140: 💤 Low value

Minor inconsistency in threshold handling.

Line 139 uses r1 < 0.0 to reject negative roots, while ball_vertical_plane_collision_time (line 89) uses root.real <= const.EPS to reject near-zero roots. Similarly, line 129 uses a hardcoded 1e-9 while the plane function uses const.EPS for imaginary checks.

This could cause edge-case differences where very small positive roots (e.g., 1e-15 due to floating-point precision) are accepted here but rejected in other solvers. Consider aligning with const.EPS for consistency:

♻️ Suggested alignment with const.EPS
-    atol = 1e-9
-    if abs(roots[0].imag) > atol or abs(roots[1].imag) > atol:
+    if abs(roots[0].imag) > const.EPS or abs(roots[1].imag) > const.EPS:
         return np.inf
-    if r1 < 0.0:
+    if r1 <= const.EPS:
         return np.inf
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pooltool/evolution/event_based/detect/ball_pocket.py` around lines 128 - 140,
The code in ball_pocket.py uses hardcoded thresholds (atol = 1e-9 and r1 < 0.0)
causing inconsistency with ball_vertical_plane_collision_time which uses
const.EPS; replace the hardcoded atol with const.EPS for the imaginary-part
checks on roots and change the negative-root test from r1 < 0.0 to r1 <=
const.EPS so root rejection logic (roots[], r1, r2, and the imaginary checks)
aligns with ball_vertical_plane_collision_time's use of const.EPS.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sandbox/airborne_demos.py`:
- Line 100: The docstring currently references the stale symbol
ball_pocket_collision_time_airborne; update that reference to the migrated
helper name ball_pocket_collision_time_if_airborne in the docstring so it points
to the canonical symbol used in this PR (search for any occurrences of
"ball_pocket_collision_time_airborne" and replace with
"ball_pocket_collision_time_if_airborne" in the airborne_demos docstring and
cross-reference markup).

---

Nitpick comments:
In `@pooltool/evolution/event_based/detect/ball_ball.py`:
- Around line 37-38: The branch that sets a1x, a1y, b1x, b1y uses integer
literals (0,0,0,0) while another branch uses float literals (0.0,0.0,0.0,0.0),
which can break Numba nopython type inference; update the integer assignments to
floating-point literals (0.0) in the branch that checks s1 == const.spinning or
s1 == const.pocketed or s1 == const.stationary and any other similar branch (the
one around the other zero assignment) so both use 0.0 for a1x, a1y, b1x, b1y.
- Around line 27-28: Remove the unused parameters m1 and m2 from the function in
ball_ball.py that currently declares them (they are never referenced in the
body); update the function signature to omit m1 and m2, remove any related
parameter docs/comments, and update all call sites that pass masses (including
the main caller and tests) to stop supplying those two arguments so signatures
match; run tests/linter to ensure no remaining references.

In `@pooltool/evolution/event_based/detect/ball_pocket.py`:
- Around line 128-140: The code in ball_pocket.py uses hardcoded thresholds
(atol = 1e-9 and r1 < 0.0) causing inconsistency with
ball_vertical_plane_collision_time which uses const.EPS; replace the hardcoded
atol with const.EPS for the imaginary-part checks on roots and change the
negative-root test from r1 < 0.0 to r1 <= const.EPS so root rejection logic
(roots[], r1, r2, and the imaginary checks) aligns with
ball_vertical_plane_collision_time's use of const.EPS.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8b176fe0-8ed2-457c-804a-76a365e20a91

📥 Commits

Reviewing files that changed from the base of the PR and between 96698cd and c12f241.

📒 Files selected for processing (14)
  • docs/include_exclude.py
  • pooltool/evolution/event_based/detect/__init__.py
  • pooltool/evolution/event_based/detect/ball_ball.py
  • pooltool/evolution/event_based/detect/ball_cushion.py
  • pooltool/evolution/event_based/detect/ball_pocket.py
  • pooltool/evolution/event_based/detect/ball_table.py
  • pooltool/evolution/event_based/detect/detector.py
  • pooltool/physics/motion/__init__.py
  • pooltool/physics/motion/solve.py
  • pooltool/physics/utils.py
  • pooltool/ptmath/roots/__init__.py
  • sandbox/airborne_demos.py
  • tests/evolution/event_based/test_ball_pocket.py
  • tests/evolution/event_based/test_simulate.py
💤 Files with no reviewable changes (2)
  • pooltool/physics/motion/solve.py
  • docs/include_exclude.py

Comment thread sandbox/airborne_demos.py Outdated
Exercises both Strategy 1 (landing directly inside the pocket) and Strategy 2
(xy trajectory crossing the pocket cylinder mid-fall) in
:func:`pooltool.physics.motion.solve.ball_pocket_collision_time_airborne`.
:func:`pooltool.evolution.event_based.detect.ball_pocket.ball_pocket_collision_time_airborne`.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Docstring references a likely stale function name.

Line 100 points to ball_pocket_collision_time_airborne, but the migrated airborne helper in this PR is ball_pocket_collision_time_if_airborne. Please update the reference so docs/users don’t chase a non-canonical symbol.

Suggested docstring fix
-    :func:`pooltool.evolution.event_based.detect.ball_pocket.ball_pocket_collision_time_airborne`.
+    :func:`pooltool.evolution.event_based.detect.ball_pocket.ball_pocket_collision_time_if_airborne`.
📝 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
:func:`pooltool.evolution.event_based.detect.ball_pocket.ball_pocket_collision_time_airborne`.
:func:`pooltool.evolution.event_based.detect.ball_pocket.ball_pocket_collision_time_if_airborne`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sandbox/airborne_demos.py` at line 100, The docstring currently references
the stale symbol ball_pocket_collision_time_airborne; update that reference to
the migrated helper name ball_pocket_collision_time_if_airborne in the docstring
so it points to the canonical symbol used in this PR (search for any occurrences
of "ball_pocket_collision_time_airborne" and replace with
"ball_pocket_collision_time_if_airborne" in the airborne_demos docstring and
cross-reference markup).

@ekiefl ekiefl merged commit 2a813cf into main May 20, 2026
10 checks passed
@ekiefl ekiefl deleted the ek/solver-migration branch May 20, 2026 06:39
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.

[support-3d] C9. Co-locate collision-time solvers with their detectors

1 participant