Skip to content

Commit eae1f85

Browse files
authored
Merge branch 'main' into simple-frictional-inelastic
2 parents 1fc4abc + 29572ba commit eae1f85

36 files changed

+1500
-81
lines changed

docs/examples/30_degree_rule.pct.py

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
#
7676
# We'll start with a table. Since we don't want collisions with cushions to interfere with our trajectory, let's make an unrealistically large $10\text{m} \times 10\text{m}$ [Table](../autoapi/pooltool/index.rst#pooltool.Table).
7777

78-
# %% trusted=true
78+
# %%
7979
import pooltool as pt
8080

8181
table_specs = pt.objects.BilliardTableSpecs(l=10, w=10)
@@ -85,20 +85,20 @@
8585
# %% [markdown]
8686
# Next, we'll create two [Ball](../autoapi/pooltool/index.rst#pooltool.Ball) objects.
8787

88-
# %% trusted=true
88+
# %%
8989
cue_ball = pt.Ball.create("cue", xy=(2.5, 1.5))
9090
obj_ball = pt.Ball.create("obj", xy=(2.5, 3.0))
9191

9292
# %% [markdown]
9393
# Next, we'll need a [Cue](../autoapi/pooltool/index.rst#pooltool.Cue).
9494

95-
# %% trusted=true
95+
# %%
9696
cue = pt.Cue(cue_ball_id="cue")
9797

9898
# %% [markdown]
9999
# Finally, we'll need to wrap these objects up into a [System](../autoapi/pooltool/index.rst#pooltool.System). We'll call this our system *template*, with the intention of reusing it for many different shots.
100100

101-
# %% trusted=true
101+
# %%
102102
system_template = pt.System(
103103
table=table,
104104
cue=cue,
@@ -112,7 +112,7 @@
112112
#
113113
# So in the function call below, `pt.aim.at_ball(system, "obj", cut=30)` returns the angle `phi` that the cue ball should be directed at such that a cut angle of 30 degrees with the object ball is achieved.
114114

115-
# %% trusted=true
115+
# %%
116116
# Creates a deep copy of the template
117117
system = system_template.copy()
118118

@@ -122,7 +122,7 @@
122122
# %% [markdown]
123123
# Now, we [simulate](../autoapi/pooltool/index.rst#pooltool.simulate) the shot and then [continuize](../autoapi/pooltool/evolution/continuize/index.html#pooltool.evolution.continuize.continuize) it to store ball state data (like coordinates) in $10\text{ms}$ timestep intervals.
124124

125-
# %% trusted=true
125+
# %%
126126
# Create a default physics engine and overwrite ball-ball model with frictionless, elastic model.
127127
engine = pt.physics.PhysicsEngine()
128128
engine.resolver.ball_ball = pt.physics.get_ball_ball_model(pt.physics.BallBallModel.FRICTIONLESS_ELASTIC)
@@ -144,7 +144,7 @@
144144
#
145145
# Since that can't be embedded into the documentation, we'll instead plot the trajectory of the cue ball and object ball by accessing ther historical states.
146146

147-
# %% trusted=true
147+
# %%
148148
cue_ball = system.balls["cue"]
149149
obj_ball = system.balls["obj"]
150150
cue_history = cue_ball.history_cts
@@ -154,7 +154,7 @@
154154
# %% [markdown]
155155
# The [BallHistory](../autoapi/pooltool/objects/index.rst#pooltool.objects.BallHistory) holds the ball's historical states, each stored as a [BallState](../autoapi/pooltool/objects/index.rst#pooltool.objects.BallState) object. Each attribute of the ball states can be concatenated into numpy arrays with the [BallHistory.vectorize](../autoapi/pooltool/objects/index.rst#pooltool.objects.BallHistory.vectorize) method.
156156

157-
# %% trusted=true
157+
# %%
158158
rvw_cue, s_cue, t_cue = cue_history.vectorize()
159159
rvw_obj, s_obj, t_obj = obj_history.vectorize()
160160

@@ -165,12 +165,12 @@
165165
# %% [markdown]
166166
# We can grab the xy-coordinates from the `rvw` array by with the following.
167167

168-
# %% trusted=true
168+
# %%
169169
coords_cue = rvw_cue[:, 0, :2]
170170
coords_obj = rvw_obj[:, 0, :2]
171171
coords_cue.shape
172172

173-
# %% trusted=true editable=true slideshow={"slide_type": ""} tags=[]
173+
# %% editable=true slideshow={"slide_type": ""} tags=[]
174174
import plotly.graph_objects as go
175175
import plotly.io as pio
176176

@@ -203,28 +203,28 @@
203203
#
204204
# As mentioned before, the carom angle is the angle between the cue ball velocity right before collision, and the cue ball velocity post-collision, once the ball has stopped sliding on the cloth. Hidden somewhere in the system **event list** one can find the events corresponding to these precise moments in time:
205205

206-
# %% trusted=true
206+
# %%
207207
system.events[:6]
208208

209209
# %% [markdown]
210210
# Programatically, we can pick out these two events of interest with event selection syntax.
211211
#
212212
# Since there is only one ball-ball collision, it's easy to select with [filter_type](../autoapi/pooltool/events/index.rst#pooltool.events.filter_type):
213213

214-
# %% trusted=true
214+
# %%
215215
collision = pt.events.filter_type(system.events, pt.EventType.BALL_BALL)[0]
216216
collision
217217

218218
# %% [markdown]
219219
# To get the event when the cue ball stops sliding, we can similarly try filtering by the sliding to rolling transition event:
220220

221-
# %% trusted=true
221+
# %%
222222
pt.events.filter_type(system.events, pt.EventType.SLIDING_ROLLING)
223223

224224
# %% [markdown]
225225
# But there are many sliding to rolling transition events, and to make matters worse, they are shared by both the cue ball and the object ball. What we need is the **first** **sliding to rolling** transition that the **cue ball** undergoes **after** the **ball-ball** collision. We can achieve this multi-criteria query with [filter_events](../autoapi/pooltool/events/index.rst#pooltool.events.filter_events):
226226

227-
# %% trusted=true
227+
# %%
228228
transition = pt.events.filter_events(
229229
system.events,
230230
pt.events.by_time(t=collision.time, after=True),
@@ -236,16 +236,13 @@
236236
# %% [markdown]
237237
# Now, we can dive into these two events and pull out the cue ball velocities we need to calculate the carom angle.
238238

239-
# %% trusted=true
239+
# %%
240240
# Velocity prior to impact
241-
for agent in collision.agents:
242-
if agent.id == "cue":
243-
# agent.initial is a copy of the Ball before resolving the collision
244-
velocity_initial = agent.initial.state.rvw[1, :2]
241+
velocity_initial = collision.get_ball("cue", initial=True).vel[:2]
245242

246243
# Velocity post sliding
247-
# We choose `final` here for posterity, but the velocity is the same both before and after resolving the transition.
248-
velocity_final = transition.agents[0].final.state.rvw[1, :2]
244+
# We choose the "final" here for posterity, but the velocity is the same both before and after resolving the transition.
245+
velocity_final = transition.get_ball("cue", initial=False).vel[:2]
249246

250247
carom_angle = pt.ptmath.utils.angle_between_vectors(velocity_final, velocity_initial)
251248

@@ -259,7 +256,7 @@
259256
# We calculated the carom angle for a single cut angle, 30 degrees. Let's write a function called `get_carom_angle` so we can do that repeatedly for different cut angles.
260257

261258

262-
# %% trusted=true
259+
# %%
263260
def get_carom_angle(system: pt.System) -> float:
264261
assert system.simulated
265262

@@ -283,7 +280,7 @@ def get_carom_angle(system: pt.System) -> float:
283280
# `get_carom_angle` assumes the passed system has already been simulated, so we'll need another function to take care of that. We'll cue stick speed and cut angle as parameters.
284281

285282

286-
# %% trusted=true
283+
# %%
287284
def simulate_experiment(V0: float, cut_angle: float) -> pt.System:
288285
system = system_template.copy()
289286
phi = pt.aim.at_ball(system, "obj", cut=cut_angle)
@@ -296,7 +293,7 @@ def simulate_experiment(V0: float, cut_angle: float) -> pt.System:
296293
# We'll also want the ball hit fraction:
297294

298295

299-
# %% trusted=true
296+
# %%
300297
import numpy as np
301298

302299
def get_ball_hit_fraction(cut_angle: float) -> float:
@@ -306,7 +303,7 @@ def get_ball_hit_fraction(cut_angle: float) -> float:
306303
# %% [markdown]
307304
# With these functions, we are ready to simulate how carom angle varies as a function of cut angle.
308305

309-
# %% trusted=true
306+
# %%
310307
import pandas as pd
311308

312309
data = {
@@ -329,7 +326,7 @@ def get_ball_hit_fraction(cut_angle: float) -> float:
329326
# %% [markdown]
330327
# From this dataframe we can make some plots. On top of the ball-hit fraction, plot, I'll create a box between a $1/4$ ball hit and a $3/4$ ball hit, since this is the carom angle range that the 30-degree rule is defined with respect to.
331328

332-
# %% trusted=true editable=true slideshow={"slide_type": ""} tags=["nbsphinx-thumbnail"]
329+
# %% editable=true slideshow={"slide_type": ""} tags=["nbsphinx-thumbnail"]
333330
import matplotlib.pyplot as plt
334331

335332
x_min = 0.25
@@ -354,7 +351,7 @@ def get_ball_hit_fraction(cut_angle: float) -> float:
354351
#
355352
# For your reference, here is the same plot but with cut angle $\phi$ as the x-axis:
356353

357-
# %% trusted=true
354+
# %%
358355
fig, ax = plt.subplots()
359356
ax.scatter(frame['phi'], frame['theta'], color='#1f77b4')
360357
ax.set_title('Carom Angle vs Cut Angle', fontsize=20)
@@ -378,7 +375,7 @@ def get_ball_hit_fraction(cut_angle: float) -> float:
378375
# Since pooltool's baseline physics engine makes the same assumptions, we should expect the results to be the same. Let's directly compare:
379376

380377

381-
# %% trusted=true
378+
# %%
382379
def get_theoretical_carom_angle(phi) -> float:
383380
return np.atan2(np.sin(phi) * np.cos(phi), (np.sin(phi) ** 2 + 2 / 5))
384381

@@ -425,7 +422,7 @@ def get_theoretical_carom_angle(phi) -> float:
425422
#
426423
# Interestingly, the carom angle is independent of the speed:
427424

428-
# %% trusted=true
425+
# %%
429426
for V0 in np.linspace(1, 4, 20):
430427
system = simulate_experiment(V0, 30)
431428
carom_angle = get_carom_angle(system)
@@ -434,7 +431,7 @@ def get_theoretical_carom_angle(phi) -> float:
434431
# %% [markdown]
435432
# This doesn't mean that the trajectories are the same though. Here are the trajectories:
436433

437-
# %% trusted=true
434+
# %%
438435
import numpy as np
439436
import plotly.graph_objects as go
440437

docs/examples/straight_shot.pct.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def create_system(d, D):
115115
obj_ball = create_object_ball(cue_ball, d)
116116

117117
return pt.System(
118-
cue=pt.Cue.default(),
118+
cue=pt.Cue(cue_ball_id="CB"),
119119
balls=(cue_ball, obj_ball),
120120
table=table,
121121
)

pooltool/ani/animate.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,8 @@ def create_system(self):
506506

507507
game = get_ruleset(game_type)()
508508
game.players = [
509-
Player("Player"),
509+
Player("Player 1"),
510+
Player("Player 2"),
510511
]
511512

512513
table = Table.from_game_type(game_type)

0 commit comments

Comments
 (0)