From 29572ba462899b168556145d10b52428748fc8af Mon Sep 17 00:00:00 2001 From: Evan Kiefl Date: Sat, 4 Jan 2025 12:04:58 -0800 Subject: [PATCH] Update examples (#175) --- docs/examples/30_degree_rule.pct.py | 57 ++++++++++++++--------------- docs/examples/straight_shot.pct.py | 2 +- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/docs/examples/30_degree_rule.pct.py b/docs/examples/30_degree_rule.pct.py index 7b08d7b9..a92e21f0 100644 --- a/docs/examples/30_degree_rule.pct.py +++ b/docs/examples/30_degree_rule.pct.py @@ -75,7 +75,7 @@ # # 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). -# %% trusted=true +# %% import pooltool as pt table_specs = pt.objects.BilliardTableSpecs(l=10, w=10) @@ -85,20 +85,20 @@ # %% [markdown] # Next, we'll create two [Ball](../autoapi/pooltool/index.rst#pooltool.Ball) objects. -# %% trusted=true +# %% cue_ball = pt.Ball.create("cue", xy=(2.5, 1.5)) obj_ball = pt.Ball.create("obj", xy=(2.5, 3.0)) # %% [markdown] # Next, we'll need a [Cue](../autoapi/pooltool/index.rst#pooltool.Cue). -# %% trusted=true +# %% cue = pt.Cue(cue_ball_id="cue") # %% [markdown] # 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. -# %% trusted=true +# %% system_template = pt.System( table=table, cue=cue, @@ -112,7 +112,7 @@ # # 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. -# %% trusted=true +# %% # Creates a deep copy of the template system = system_template.copy() @@ -122,7 +122,7 @@ # %% [markdown] # 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. -# %% trusted=true +# %% # Create a default physics engine and overwrite ball-ball model with frictionless, elastic model. engine = pt.physics.PhysicsEngine() engine.resolver.ball_ball = pt.physics.get_ball_ball_model(pt.physics.BallBallModel.FRICTIONLESS_ELASTIC) @@ -144,7 +144,7 @@ # # 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. -# %% trusted=true +# %% cue_ball = system.balls["cue"] obj_ball = system.balls["obj"] cue_history = cue_ball.history_cts @@ -154,7 +154,7 @@ # %% [markdown] # 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. -# %% trusted=true +# %% rvw_cue, s_cue, t_cue = cue_history.vectorize() rvw_obj, s_obj, t_obj = obj_history.vectorize() @@ -165,12 +165,12 @@ # %% [markdown] # We can grab the xy-coordinates from the `rvw` array by with the following. -# %% trusted=true +# %% coords_cue = rvw_cue[:, 0, :2] coords_obj = rvw_obj[:, 0, :2] coords_cue.shape -# %% trusted=true editable=true slideshow={"slide_type": ""} tags=[] +# %% editable=true slideshow={"slide_type": ""} tags=[] import plotly.graph_objects as go import plotly.io as pio @@ -203,7 +203,7 @@ # # 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: -# %% trusted=true +# %% system.events[:6] # %% [markdown] @@ -211,20 +211,20 @@ # # 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): -# %% trusted=true +# %% collision = pt.events.filter_type(system.events, pt.EventType.BALL_BALL)[0] collision # %% [markdown] # To get the event when the cue ball stops sliding, we can similarly try filtering by the sliding to rolling transition event: -# %% trusted=true +# %% pt.events.filter_type(system.events, pt.EventType.SLIDING_ROLLING) # %% [markdown] # 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): -# %% trusted=true +# %% transition = pt.events.filter_events( system.events, pt.events.by_time(t=collision.time, after=True), @@ -236,16 +236,13 @@ # %% [markdown] # Now, we can dive into these two events and pull out the cue ball velocities we need to calculate the carom angle. -# %% trusted=true +# %% # Velocity prior to impact -for agent in collision.agents: - if agent.id == "cue": - # agent.initial is a copy of the Ball before resolving the collision - velocity_initial = agent.initial.state.rvw[1, :2] +velocity_initial = collision.get_ball("cue", initial=True).vel[:2] # Velocity post sliding -# We choose `final` here for posterity, but the velocity is the same both before and after resolving the transition. -velocity_final = transition.agents[0].final.state.rvw[1, :2] +# We choose the "final" here for posterity, but the velocity is the same both before and after resolving the transition. +velocity_final = transition.get_ball("cue", initial=False).vel[:2] carom_angle = pt.ptmath.utils.angle_between_vectors(velocity_final, velocity_initial) @@ -259,7 +256,7 @@ # 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. -# %% trusted=true +# %% def get_carom_angle(system: pt.System) -> float: assert system.simulated @@ -283,7 +280,7 @@ def get_carom_angle(system: pt.System) -> float: # `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. -# %% trusted=true +# %% def simulate_experiment(V0: float, cut_angle: float) -> pt.System: system = system_template.copy() phi = pt.aim.at_ball(system, "obj", cut=cut_angle) @@ -296,7 +293,7 @@ def simulate_experiment(V0: float, cut_angle: float) -> pt.System: # We'll also want the ball hit fraction: -# %% trusted=true +# %% import numpy as np def get_ball_hit_fraction(cut_angle: float) -> float: @@ -306,7 +303,7 @@ def get_ball_hit_fraction(cut_angle: float) -> float: # %% [markdown] # With these functions, we are ready to simulate how carom angle varies as a function of cut angle. -# %% trusted=true +# %% import pandas as pd data = { @@ -329,7 +326,7 @@ def get_ball_hit_fraction(cut_angle: float) -> float: # %% [markdown] # 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. -# %% trusted=true editable=true slideshow={"slide_type": ""} tags=["nbsphinx-thumbnail"] +# %% editable=true slideshow={"slide_type": ""} tags=["nbsphinx-thumbnail"] import matplotlib.pyplot as plt x_min = 0.25 @@ -354,7 +351,7 @@ def get_ball_hit_fraction(cut_angle: float) -> float: # # For your reference, here is the same plot but with cut angle $\phi$ as the x-axis: -# %% trusted=true +# %% fig, ax = plt.subplots() ax.scatter(frame['phi'], frame['theta'], color='#1f77b4') ax.set_title('Carom Angle vs Cut Angle', fontsize=20) @@ -378,7 +375,7 @@ def get_ball_hit_fraction(cut_angle: float) -> float: # Since pooltool's baseline physics engine makes the same assumptions, we should expect the results to be the same. Let's directly compare: -# %% trusted=true +# %% def get_theoretical_carom_angle(phi) -> float: return np.atan2(np.sin(phi) * np.cos(phi), (np.sin(phi) ** 2 + 2 / 5)) @@ -425,7 +422,7 @@ def get_theoretical_carom_angle(phi) -> float: # # Interestingly, the carom angle is independent of the speed: -# %% trusted=true +# %% for V0 in np.linspace(1, 4, 20): system = simulate_experiment(V0, 30) carom_angle = get_carom_angle(system) @@ -434,7 +431,7 @@ def get_theoretical_carom_angle(phi) -> float: # %% [markdown] # This doesn't mean that the trajectories are the same though. Here are the trajectories: -# %% trusted=true +# %% import numpy as np import plotly.graph_objects as go diff --git a/docs/examples/straight_shot.pct.py b/docs/examples/straight_shot.pct.py index dc1e3e57..85f7680d 100644 --- a/docs/examples/straight_shot.pct.py +++ b/docs/examples/straight_shot.pct.py @@ -115,7 +115,7 @@ def create_system(d, D): obj_ball = create_object_ball(cue_ball, d) return pt.System( - cue=pt.Cue.default(), + cue=pt.Cue(cue_ball_id="CB"), balls=(cue_ball, obj_ball), table=table, )