pooltool¶
The top-level API for the pooltool library¶
Important and highly used objects are placed in this top-level API. For example,
System
can be imported directly from the top module:
>>> import pooltool as pt
>>> system = pt.System.example()
Alternatively, it can be imported directly from its source location:
>>> from pooltool.system.datatypes import System
>>> system = System.example()
If the object you’re looking for isn’t in this top-level API, search for it in the submodules listed below. Relatedly, if you believe that an objects deserves to graduate to the top-level API, your input is valuable and such changes can be considered.
Subpackages¶
Submodules¶
Overview¶
An Enum of event types |
|
An Enum for supported game types |
|
This class runs the pooltool application |
|
An interface for viewing shots from within python. |
|
A billiards ball |
|
Ball parameters and physical constants |
|
A cue stick |
|
A table. |
|
An Enum describing the table type |
|
A player |
|
A storage for System objects |
|
A class representing the billiards system. |
|
Run a simulation on a system and return it |
|
Generate Ball objects based on a given blueprint and table dimensions. |
|
Generate a ball rack. |
|
Retrieve a ruleset class |
Classes¶
- class pooltool.EventType(value)[source]¶
An Enum of event types
- NONE¶
The null event.
- BALL_BALL¶
A ball-ball collision.
- BALL_LINEAR_CUSHION¶
A ball collision with a linear cushion segment.
- BALL_CIRCULAR_CUSHION¶
A ball collision with a circular cushion segment.
- BALL_POCKET¶
A ball pocket “collision”. This marks the point at which the ball crosses the point of no return.
- STICK_BALL¶
A cue-stick ball collision.
- SPINNING_STATIONARY¶
A ball transition from spinning to stationary.
- ROLLING_STATIONARY¶
A ball transition from rolling to stationary.
- ROLLING_SPINNING¶
A ball transition from rolling to spinning.
- SLIDING_ROLLING¶
A ball transition from sliding to rolling.
Bases:
pooltool.utils.strenum.StrEnum
Methods:
- class pooltool.GameType(value)[source]¶
An Enum for supported game types
- EIGHTBALL¶
- NINEBALL¶
- THREECUSHION¶
- SNOOKER¶
- SANDBOX¶
- SUMTOTHREE¶
- class pooltool.Game(config=ShowBaseConfig.default())[source]¶
This class runs the pooltool application
Bases:
Interface
Methods:
- class pooltool.ShotViewer(config=ShowBaseConfig.default())[source]¶
An interface for viewing shots from within python.
Important
For instructions on how to use the interactive interface, see The Interface.
Important
Only one instance of this class can be created per python process. You’ll receive a runtime error if you try. Instead, create one instance and use it for the lifetime of your python process.
For usage, see
show()
.Bases:
Interface
Methods:
- show(shot_or_shots: pooltool.system.datatypes.System | pooltool.system.datatypes.MultiSystem, title: str = '', camera_state: pooltool.ani.camera.CameraState | None = None)[source]¶
Opens the interactive interface for one or more shots.
Important
For instructions on how to use the interactive interface, see The Interface.
- Parameters:
shot_or_shots (Union[pooltool.system.datatypes.System, pooltool.system.datatypes.MultiSystem]) --
The shot or collection of shots to visualize. This can be a single
pooltool.system.datatypes.System
object or apooltool.system.datatypes.MultiSystem
object containing multiple systems.Note
If a multisystem is passed, the systems can be scrolled through by pressing n (next) and p (previous).
title (str) -- The title to display in the visualization. Defaults to an empty string.
camera_state (Optional[pooltool.ani.camera.CameraState]) -- The initial camera state that the visualization is rendered with.
Example
This example visualizes a single shot.
>>> import pooltool as pt >>> system = pt.System.example()
Make sure the shot is simulated, otherwise it will make for a boring visualization:
>>> pt.simulate(system, inplace=True)
Create a
ShotViewer
object:>>> gui = pt.ShotViewer()
Now visualize the shot:
>>> gui.show(system)
(Press escape to exit the interface and continue script execution)
Example
This example explains the order in which events and script execution happens.
import pooltool as pt system = pt.System.example() pt.simulate(system, inplace=True) # This line takes a view seconds to execute. It will generate a visible # window. Once the window has been generated, script execution continues gui = pt.ShotViewer() # When this line is called, the window is populated with an animated # scene of the shot. gui.show(system) # This line will not execute until <esc> is pressed while the window is # active. print('script continues') # For subsequent calls to `show`, you must use the same `ShotViewer` # object: gui.show(system)
- class pooltool.Ball(id: str, state: BallState = BallState.default, params: BallParams = BallParams.default, ballset: BallSet | None = None, initial_orientation: BallOrientation = BallOrientation.random, history: BallHistory = BallHistory.factory, history_cts: BallHistory = BallHistory.factory)[source]¶
A billiards ball
This class represents a billiards ball. It stores its parameters (mass, radius, etc.), it’s state (coordinates, velocity, spin, etc), its history (a time-resolved trajectory of its state), amongst other things.
- state¶
The ball’s state.
This is the current state of the ball.
See also
See the Important section in
Ball
for a description of the role ofstates
during simulation.
- params¶
The ball’s physical parameters.
The physical parameters of the ball.
- ballset¶
The ball set that the ball belongs to.
Important if rendering the ball in a scene.
See also
See
Ball.set_ballset()
for details
- Type:
- initial_orientation¶
The initial rendered orientation of the ball.
Important if rendering the ball in a scene.
This is the orientation of the ball at \(t = 0\).
- history¶
The ball’s state history
The historical states of the ball from \(t_{initial}\) to \(t_{final}\).
See also
See the Important section in
Ball
for a description of the role ofhistory
during simulation.
- history_cts¶
The ball’s continuous state history
The historical states of the ball from \(t_{initial}\) to \(t_{final}\) densely sampled with respect to time.
See also
See
pooltool.evolution.event_based.continuize.continuize()
for a details about continuizing a simulated system.See the Important section in
Ball
for a description of the role ofhistory_cts
during simulation.
Important
To instantiate this class, consider using the
create()
constructor. Or, use functions withinpooltool.layouts
to generate entire collection of balls. Or, of course, construct as normal with__init__
.Important
The following explains how a
Ball
object is modified when its parent system is simulated (pooltool.evolution.event_based.simulate.simulate()
).At the start of the simulation process,
state
represents the ball state at \(t = 0\). A copy ofstate
is appended tohistory
.For each timestep of the simulation,
state
is used to inform how the system should advance forward in time. Once determined,state
is updated to reflect the ball’s new state. A copy ofstate
is appended tohistory
.When the simulation is finished,
state
represents the final resting state of the ball. So too doeshistory[-1]
.Finally, if the system is continuized (see
pooltool.evolution.continuize.continuize()
),history_cts
is populated. Otherwise it remains empty.- property xyz¶
The displacement (from origin) vector of the ball.
A shortcut for
self.state.rvw[0]
.
- property vel¶
The velocity vector of the ball.
A shortcut for
self.state.rvw[1]
.
- property avel¶
The angular velocity vector of the ball.
A shortcut for
self.state.rvw[2]
.
Methods:
- set_ballset(ballset: pooltool.objects.ball.sets.BallSet) None [source]¶
Update the ballset
- Raises:
ValueError -- If the ball ID doesn’t match to a model name of the ballset.
See also
See
pooltool.objects.ball.sets
for details about ball sets.See
pooltool.system.datatypes.System.set_ballset()
for setting the ballset for all the balls in a system.
- copy(drop_history: bool = False) Ball [source]¶
Create a copy
- Parameters:
drop_history (bool) -- If True, the returned copy
history
andhistory_cts
attributes are both set to emptyBallHistory
objects.- Return type:
- static create(id: str, *, xy: Sequence[float] | None = None, ballset: pooltool.objects.ball.sets.BallSet | None = None, **kwargs) Ball [source]¶
Create a ball using keyword arguments.
This constructor flattens the tunable parameter space, allowing one to construct a
Ball
without directly instancing objects like likepooltool.objects.balls.params.BallParams
andBallState
.- Parameters:
xy (Optional[Sequence[float]]) -- The x and y coordinates of the ball position.
ballset (Optional[pooltool.objects.ball.sets.BallSet]) -- A ballset.
**kwargs -- Arguments accepted by
pooltool.objects.balls.params.BallParams
- Return type:
- class pooltool.BallParams(m: float = 0.170097, R: float = 0.028575, u_s: float = 0.2, u_r: float = 0.01, u_sp_proportionality: float = 0.4444444444444444, e_c: float = 0.85, f_c: float = 0.2, g: float = 9.81)[source]¶
Ball parameters and physical constants
- u_sp_proportionality¶
The spinning coefficient of friction, with R factored out (default = 0.01).
See also
For the coefficient of spinning friction, use the property
u_sp()
.
- Type:
- e_c¶
The cushion coefficient of restitution (default = 0.85).
Note
This is a potentially model-dependent ball-cushion parameter and should be placed elsewhere, either as a model parameter or as a cushion segment parameter.
- Type:
- f_c¶
The cushion coefficient of friction (default = 0.2).
Note
This is a potentially model-dependent ball-cushion parameter and should be placed elsewhere, either as a model parameter or as a cushion segment parameter.
- Type:
Most of the default values (SI units) are taken from or based off of https://billiards.colostate.edu/faq/physics/physical-properties/.
Some of the parameters aren’t truly ball parameters, e.g. the gravitational constant. However, it is nice to be able to tune such parameters on a ball-by-ball basis, so they are included here.
Methods:
- u_sp() float ¶
Coefficient of spinning friction
This is equal to
u_sp_proportionality
*R
Cached Property Note
This is a cached property, and should be accessed as an attribute, not as a method call.
- Return type:
- copy() BallParams [source]¶
Return a copy
Note
Since the class is frozen and its attributes are immutable, this just returns
self
.
- Return type:
- classmethod default(game_type: pooltool.game.datatypes.GameType = GameType.EIGHTBALL) BallParams [source]¶
Return prebuilt ball parameters based on game type
- Parameters:
game_type (pooltool.game.datatypes.GameType) -- What type of game is being played?
- Returns:
The prebuilt ball parameters associated with the passed game type.
- Return type:
- classmethod prebuilt(name: PrebuiltBallParams) BallParams [source]¶
Return prebuilt ball parameters based on name
- Parameters:
name (PrebuiltBallParams) -- A
PrebuiltBallParams
member.- Return type:
All prebuilt ball parameters are named with the
PrebuiltBallParams
Enum. This constructor takes a prebuilt name and returns the corresponding ball parameters.See also
PrebuiltBallParams
- class pooltool.Cue(id: str = 'cue_stick', V0: float = 2.0, phi: float = 0.0, theta: float = 0.0, a: float = 0.0, b: float = 0.25, cue_ball_id: str = 'cue', specs: CueSpecs = CueSpecs.default)[source]¶
A cue stick
- V0¶
The impact speed.
Units are m/s.
Warning: This is the speed of the cue stick upon impact, not the speed of the ball upon impact.
- Type:
- phi¶
The directional strike angle.
The horizontal direction of the cue’s orientation relative to the table layout. Specified in degrees.
If you imagine facing from the head rail (where the cue is positioned for a break shot) towards the foot rail (where the balls are racked),
\(\phi = 0\) corresponds to striking the cue ball to the right
\(\phi = 90\) corresponds to striking the cue ball towards the foot rail
\(\phi = 180\) corresponds to striking the cue ball to the left
\(\phi = 270\) corresponds to striking the cue ball towards the head rail
\(\phi = 360\) corresponds to striking the cue ball to the right
- Type:
- theta¶
The cue inclination angle.
The vertical angle of the cue stick relative to the table surface. Specified in degrees.
\(\theta = 0\) corresponds to striking the cue ball parallel with the table (no massé)
\(\theta = 90\) corresponds to striking the cue ball downwards into the table (max massé)
- Type:
- a¶
The amount and direction of side spin.
\(a = -1\) is the rightmost side of ball
\(a = +1\) is the leftmost side of the ball
- Type:
- b¶
The amount of top/bottom spin.
\(b = -1\) is the bottom-most side of the ball
\(b = +1\) is the top-most side of the ball
- Type:
- specs¶
The cue specs.
Methods:
- copy() Cue [source]¶
Create a copy
Note
specs
is shared betweenself
and the copy, but that’s ok because it’s frozen and has no mutable attributes.- Return type:
- set_state(V0: float | None = None, phi: float | None = None, theta: float | None = None, a: float | None = None, b: float | None = None, cue_ball_id: str | None = None) None [source]¶
Set the cueing parameters
- Parameters:
If any arguments are
None
, they will be left untouched--they will not be set to None.
- class pooltool.Table(cushion_segments: CushionSegments, pockets: Dict[str, Pocket], table_type: TableType, model_descr: TableModelDescr | None = None, height: float = 0.708, lights_height: float = 1.99)[source]¶
A table.
While a table can be constructed by passing all of the following initialization parameters, there are many easier ways, all of which are detailed in the Table Specification </resources/table_specs> resource.
- cushion_segments¶
The table’s linear and circular cushion segments.
- pockets¶
The table’s pockets.
- Type:
- table_type¶
An Enum specifying the type of table.
- height¶
The height of the playing surface (measured from the ground).
This is just used for visualization.
- Type:
- lights_height¶
The height of the table lights (measured from the playing surface).
This is just used for visualization.
- Type:
- property w: float¶
The width of the table.
Warning
This assumes the table follows the layout similar to this diagram. Specifically, it must have the linear cushion segments with IDs
"3"`
and"12"
.- Return type:
- property l: float¶
The length of the table.
Warning
This assumes the table follows the layout similar to this diagram. Specifically, it must have the linear cushion segments with IDs
"9"`
and"18"
.- Return type:
Methods:
- static from_table_specs(specs: pooltool.objects.table.specs.TableSpecs) Table [source]¶
Build a table from a table specifications object
- Parameters:
specs (pooltool.objects.table.specs.TableSpecs) --
A valid table specification.
- Returns:
A table matching the specifications of the input.
pooltool.objects.table.specs.PocketTableSpecs
hastable_type
set to pooltool.objects.table.specs.TableType.POCKETpooltool.objects.table.specs.BilliardTableSpecs
hastable_type
set to pooltool.objects.table.specs.TableType.BILLIARDpooltool.objects.table.specs.SnookerTableSpecs
hastable_type
set to pooltool.objects.table.specs.TableType.SNOOKER
- Return type:
- classmethod prebuilt(name: pooltool.objects.table.collection.TableName) Table [source]¶
Create a default table based on name
- Parameters:
name (pooltool.objects.table.collection.TableName) -- The name of the prebuilt table specs.
- Returns:
A prebuilt table.
- Return type:
- classmethod default(table_type: pooltool.objects.table.specs.TableType = TableType.POCKET) Table [source]¶
Create a default table based on table type
A default table is associated to each table type.
- Parameters:
table_type (pooltool.objects.table.specs.TableType) -- The type of table.
- Returns:
The default table for the given table type.
- Return type:
- classmethod from_game_type(game_type: pooltool.game.datatypes.GameType) Table [source]¶
Create a default table based on table type
A default table is associated with each game type.
- Parameters:
game_type (pooltool.game.datatypes.GameType) -- The game type.
- Returns:
The default table for the given game type.
- Return type:
- class pooltool.Player(name: str, ai: AIPlayer | None = None)[source]¶
A player
- ai¶
Not implemented yet…
- Type:
- class pooltool.MultiSystem(multisystem: List[System] = UNKNOWN)[source]¶
A storage for System objects
Houses a collection of systems, for example, shots taken sequentially in a game.
- multisystem¶
A list of System objects (default =
[]
)- Type:
Example
This example illustrates the basics of multisystems.
First, make a system and evolve it.
>>> import pooltool as pt >>> import numpy as np >>> system = pt.System.example() >>> system.strike(phi=90) >>> pt.simulate(system, inplace=True)
Now add it to a multisystem.
>>> multisystem = pt.MultiSystem() >>> multisystem.append(system)
Now copy the system, reset it’s history, strike it differently, simulate it, and add it to the mulisystem:
>>> next_system = multisystem[-1].copy() >>> next_system.strike(phi=0) >>> pt.simulate(next_system, inplace=True) >>> multisystem.append(next_system)
The multisystem has a length,
>>> len(multisystem) 2
supports basic indexing,
>>> multisystem[0].t 6.017032496778012
and can be iterated through:
>>> for shot in multisystem: print(len(shot.events)) 15 10
Now visualize the multisystem:
>>> gui = pt.ShotViewer() >>> gui.show(multisystem, title="Press 'n' for next, 'p' for previous")
Methods:
- append(system: System) None [source]¶
Append a system to the multisystem
This appends
system
tomultisystem
.
- save(path: pooltool.serialize.serializers.Pathish) None [source]¶
Save the multisystem to file in a serialized format.
Supported file extensions:
.json
.msgpack
- Parameters:
path (pooltool.serialize.serializers.Pathish) -- Either a
pathlib.Path
object or a string. The extension should match the supported filetypes mentioned above.
See also
To load a multisystem, see
load()
.To save/load single systems, see
System.save()
andSystem.load()
- classmethod load(path: pooltool.serialize.serializers.Pathish) MultiSystem [source]¶
Load a multisystem from a file in a serialized format.
Supported file extensions:
.json
.msgpack
- Parameters:
path (pooltool.serialize.serializers.Pathish) -- Either a pathlib.Path object or a string representing the file path. The extension should match the supported filetypes mentioned above.
- Returns:
The deserialized MultiSystem object loaded from the file.
- Return type:
See also
To save a multisystem, see
save()
.To save/load single systems, see
System.save()
andSystem.load()
- class pooltool.System(cue: Cue, table: Table, balls: Dict[str, Ball], t: float = 0.0, events: List[Event] = UNKNOWN)[source]¶
A class representing the billiards system.
This class holds:
a collection of balls (
pooltool.objects.ball.datatypes.Ball
)a cue stick (
pooltool.objects.cue.datatypes.Cue
)a table (
pooltool.objects.table.datatypes.Table
)
Together, these objects, referred to as the system, fully describe the billiards system.
This object is a mutable object that can be evolved over the course of system’s evolution. When a billiards system is simulated, a list of
pooltool.events.datatypes.Event
objects is stored in this class.This class also stores the duration of simulated time elapsed as
t
, measured in seconds.- cue¶
A cue stick.
- table¶
A table.
- balls¶
A dictionary of balls.
Warning
Each key must match each value’s
id
(e.g.{"2": Ball(id="1")}
is invalid).- Type:
- t¶
The elapsed simulation time. If the system is in the process of being simulated,
t
is updated to be the number of seconds the system has evolved. After being simulated,t
remains at the final simulation time.- Type:
- events¶
The sequence of events in the simulation. Like
t
, this is updated incrementally as the system is evolved. (default =[]
)- Type:
Examples
Constructing a system requires a cue, a table, and a dictionary of balls:
>>> import pooltool as pt >>> pt.System( >>> cue=pt.Cue.default(), >>> table=pt.Table.default(), >>> balls={"1": pt.Ball.create("1", xy=(0.2, 0.3))}, >>> )
If you need a quick system to experiment with, call
example()
:>>> import pooltool as pt >>> system = pt.System.example()
You can simulate this system and introspect its attributes:
>>> pt.simulate(system, inplace=True) >>> system.simulated True >>> len(system.events) 14 >>> system.cue <Cue object at 0x7fb838080190> ├── V0 : 1.5 ├── phi : 95.07668213305062 ├── a : 0.0 ├── b : -0.3 └── theta : 0.0
This
system
can also be visualized in the GUI:>>> gui = pt.ShotViewer() >>> gui.show(system)
- property continuized¶
Checks if all balls have a non-empty continuous history.
- Returns:
True if all balls have a non-empty continuous history, False otherwise.
- Return type:
See also
For a proper definition of continuous history, please see
pooltool.objects.ball.datatypes.Ball.history_cts
.
- property simulated¶
Checks if the simulation has any events.
If there are events, it is assumed that the system has been simulated.
- Returns:
True if there are events, False otherwise.
- Return type:
Methods:
- set_ballset(ballset: pooltool.objects.ball.sets.BallSet) None [source]¶
Sets the ballset for each ball in the system.
Important only if rendering the system in a scene and you are manually creating balls (rather than relying on built-in utilities in
pooltool.layouts
)In this case, you need to manually associate a
pooltool.objects.ball.sets.BallSet
to the balls in the system, so that the proper model skin can be applied to each. That’s what this method does.- Parameters:
ballset (pooltool.objects.ball.sets.BallSet) -- The ballset to be assigned to each ball.
- Raises:
ValueError -- If any ball’s ID does not correspond to a model name associated with the ball set.
See also
See
pooltool.objects.ball.sets
for details about ball sets.See
pooltool.objects.ball.datatypes.Ball.set_ballset()
for setting the ballset of an individual ball.
- reset_history()[source]¶
Resets the history for all balls, clearing events and resetting time.
Operations that this method performs:
Additionally for each ball in
self.balls
,(1)
pooltool.objects.ball.datatypes.Ball.history
is set toBallHistory()
(2)pooltool.objects.ball.datatypes.Ball.history_cts
is set toBallHistory()
(3) Thet
attribute ofpooltool.objects.ball.datatypes.Ball.state
is set to0.0
Calling this method thus erases any history.
- reset_balls()[source]¶
Resets balls to their initial states based on their history
This sets the state of each ball to the ball’s initial historical state (i.e. before evolving the system). It doesn’t erase the history.
Example
This example shows that calling this method resets the ball’s states to before the system is simulated.
First, create a system and store the cue ball’s state
>>> import pooltool as pt >>> system = pt.System.example() >>> cue_ball_initial_state = system.balls["cue"].state.copy() >>> cue_ball_initial_state BallState(rvw=array([[0.4953 , 0.9906 , 0.028575], [0. , 0. , 0. ], [0. , 0. , 0. ]]), s=0, t=0.0)
Now simulate the system and assert that the cue ball’s new state has changed:
>>> pt.simulate(system, inplace=True) >>> assert system.balls["cue"].state != cue_ball_initial_state
But after resetting the balls, the cue ball state once again matches the state before simulation.
>>> system.reset_balls() >>> assert system.balls["cue"].state == cue_ball_initial_state
The system history is not erased:
>>> system.simulated True >>> len(system.events) 14 >>> system.t 5.193035203405666
- stop_balls()[source]¶
Change ball states to stationary and remove all momentum
This method removes all kinetic energy from the system by:
Setting the velocity and angular velocity vectors of each ball to <0, 0, 0>
Setting the balls’ motion states to stationary (i.e. 0)
- strike(**kwargs) None [source]¶
Set cue stick parameters
This is merely an alias for
pooltool.objects.cue.datatypes.Cue.set_state()
- Parameters:
kwargs -- **kwargs Cue stick parameters.
- randomize_positions(ball_ids: List[str] | None = None, niter=100) bool [source]¶
Randomize ball positions on the table--ensure no overlap
This “algorithm” initializes a random state, and checks that all the balls are non-overlapping. If any are, a new state is initialized and the process is repeated. This is an inefficient algorithm, in case that needs to be said.
- is_balls_overlapping() bool [source]¶
Determines if any balls are overlapping.
- Returns:
True if any balls overlap, False otherwise.
- Return type:
- copy() System [source]¶
Creates a deep-ish copy of the system.
This method generates a copy of the system with a level of deep copying that is contingent on the mutability of the objects within the system. Immutable objects, frozen data structures, and read-only numpy arrays (
array.flags["WRITEABLE"] = False
) remain shared between the original and the copied system.TLDR For all intents and purposes, mutating the system copy will not impact the original system, and vice versa.
- Returns:
A deepcopy of the system.
- Return type:
Example
>>> import pooltool as pt >>> system = pt.System.example() >>> system_copy = pt.System.example() >>> pt.simulate(system, inplace=True) >>> system.simulated True >>> system_copy.simulated False
- save(path: pooltool.serialize.serializers.Pathish, drop_continuized_history: bool = False) None [source]¶
Save a System to file in a serialized format.
Supported file extensions:
.json
.msgpack
- Parameters:
path (pooltool.serialize.serializers.Pathish) -- Either a
pathlib.Path
object or a string. The extension should match the supported filetypes mentioned above.drop_continuized_history (bool) -- If True,
pooltool.objects.ball.datatypes.Ball.history_cts
is wiped before the save operation, which can save a lot of disk space and increase save/load speed. If loading (deserializing) at a later time, thehistory_cts
for each ball can be easily regenerated (see Examples).
Example
An example of saving to, and loading from, JSON:
>>> import pooltool as pt >>> system = pt.System.example() >>> system.save("case1.json") >>> loaded_system = pt.System.load("case1.json") >>> assert system == loaded_system
You can also save simulated systems:
>>> pt.simulate(system, inplace=True) >>> system.save("case2.json")
Simulated systems contain the events of the shot, so they’re larger:
- $ du -sh case1.json case2.json
12K case1.json 68K case2.json
Example
JSON may be human readable, but MSGPACK is faster:
>>> import pooltool as pt >>> system = pt.System.example() >>> pt.simulate(system, inplace=True) >>> print("saving:") >>> %timeit system.save("readable.json") >>> %timeit system.save("fast.msgpack") >>> print("loading:") >>> %timeit pt.System.load("readable.json") >>> %timeit pt.System.load("fast.msgpack") saving: 5.4 ms ± 470 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 725 µs ± 55.8 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each) loading: 3.16 ms ± 38.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 1.9 ms ± 15.2 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
Example
If the system has been continuized (see
pooltool.evolution.continuize.continuize()
), disk space and save/load times can be decreased by usingdrop_continuized_history
:>>> import pooltool as pt >>> system = pt.System.example() >>> # simulate and continuize the results >>> pt.simulate(system, continuous=True, inplace=True) >>> print("saving") >>> %timeit system.save("no_drop.json") >>> %timeit system.save("drop.json", drop_continuized_history=True) >>> print("loading") >>> %timeit pt.System.load("no_drop.json") >>> %timeit pt.System.load("drop.json") saving 36 ms ± 803 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 7.59 ms ± 342 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) loading 18.3 ms ± 1.15 ms per loop (mean ± std. dev. of 7 runs, 100 loops each) 3.14 ms ± 30.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
- $ du -sh drop.json no_drop.json
68K drop.json
584K no_drop.json
However, the loaded system is no longer continuized. If you need it to be, call
pooltool.evolution.continuize.continuize()
:>>> loaded_system = pt.System.load("drop.json") >>> assert loaded_system != system >>> pt.continuize(loaded_system, inplace=True) >>> assert loaded_system == system
See also
Load systems with
load()
.
- classmethod load(path: pooltool.serialize.serializers.Pathish) System [source]¶
Load a System from a file in a serialized format.
Supported file extensions:
.json
.msgpack
- Parameters:
path (pooltool.serialize.serializers.Pathish) -- Either a
pathlib.Path
object or a string representing the file path. The extension should match the supported filetypes mentioned above.- Returns:
The deserialized System object loaded from the file.
- Return type:
- Raises:
AssertionError -- If the file specified by path does not exist.
ValueError -- If the file extension is not supported.
Examples:
Please refer to the examples in
save()
.See also
Save systems with
save()
.
- classmethod example() System [source]¶
A simple example system
This system features 2 balls (IDs “1” and “cue”) on a pocket table. The cue stick parameters are set to pocket the “1” ball in the top-left pocket.
Example
The system can be constructed and introspected like so:
>>> import pooltool as pt >>> system = pt.System.example() >>> system.balls["cue"].xyz array([0.4953 , 0.9906 , 0.028575]) >>> system.balls["1"].xyz array([0.4953 , 1.4859 , 0.028575]) >>> system.cue <Cue object at 0x7f7a2641ce40> ├── V0 : 1.5 ├── phi : 95.07668213305062 ├── a : 0.0 ├── b : -0.3 └── theta : 0.0
It can be simulated and visualized:
>>> pt.simulate(system, inplace=True) >>> gui = pt.ShotViewer() >>> gui.show(system)
- Return type:
Functions¶
- pooltool.simulate(shot: pooltool.system.datatypes.System, engine: pooltool.physics.engine.PhysicsEngine | None = None, inplace: bool = False, continuous: bool = False, dt: float | None = None, t_final: float | None = None, quartic_solver: pooltool.ptmath.roots.quartic.QuarticSolver = QuarticSolver.HYBRID, include: Set[pooltool.events.EventType] = INCLUDED_EVENTS, max_events: int = 0) pooltool.system.datatypes.System [source]¶
Run a simulation on a system and return it
- Parameters:
shot (pooltool.system.datatypes.System) -- The system you would like simulated. The system should already have energy, otherwise there will be nothing to simulate.
engine (Optional[pooltool.physics.engine.PhysicsEngine]) -- The engine holds all of the physics. You can instantiate your very own
pooltool.physics.engine.PhysicsEngine
object, or you can modify~/.config/pooltool/physics/resolver.json
to change the default engine.inplace (bool) -- By default, a copy of the passed system is simulated and returned. This leaves the passed system unmodified. If inplace is set to True, the passed system is modified in place, meaning no copy is made and the returned system is the passed system. For a more practical distinction, see Examples below.
continuous (bool) -- If True, the system will not only be simulated, but it will also be “continuized”. This means each ball will be populated with a ball history with small fixed timesteps that make it ready for visualization.
dt (Optional[float]) -- The small fixed timestep used when continuous is True.
t_final (Optional[float]) -- If set, the simulation will end prematurely after the calculation of an event with
event.time > t_final
.quartic_solver (pooltool.ptmath.roots.quartic.QuarticSolver) -- Which QuarticSolver do you want to use for solving quartic polynomials?
include (Set[pooltool.events.EventType]) -- Which EventType are you interested in resolving? By default, all detected events are resolved.
max_events (int) -- If this is greater than 0, and the shot has more than this many events, the simulation is stopped and the balls are set to stationary.
- Returns:
The simulated system.
- Return type:
Examples
Standard usage:
>>> # Simulate a system >>> import pooltool as pt >>> system = pt.System.example() >>> simulated_system = pt.simulate(system) >>> assert not system.simulated >>> assert simulated_system.simulated
The returned system is simulated, but the passed system remains unchanged.
You can also modify the system in place:
>>> # Simulate a system in place >>> import pooltool as pt >>> system = pt.System.example() >>> simulated_system = pt.simulate(system, inplace=True) >>> assert system.simulated >>> assert simulated_system.simulated >>> assert system is simulated_system
Notice that the returned system _is_ the simulated system. Therefore, there is no point catching the return object when inplace is True:
>>> # Simulate a system in place >>> import pooltool as pt >>> system = pt.System.example() >>> assert not system.simulated >>> pt.simulate(system, inplace=True) >>> assert system.simulated
You can continuize the ball trajectories with continuous
>>> # Simulate a system in place >>> import pooltool as pt >>> system = pt.simulate(pt.System.example(), continuous=True) >>> for ball in system.balls.values(): assert len(ball.history_cts) > 0
- pooltool.generate_layout(blueprint: List[BallPos], table: pooltool.objects.table.datatypes.Table, ballset: pooltool.objects.ball.sets.BallSet | None = None, ball_params: pooltool.objects.ball.datatypes.BallParams | None = None, spacing_factor: float = 0.001, seed: int | None = None) Dict[str, pooltool.objects.ball.datatypes.Ball] [source]¶
Generate Ball objects based on a given blueprint and table dimensions.
The function calculates the absolute position of each ball on the table using the translations provided in the blueprint relative to table anchors. It then randomly assigns ball IDs to each position, ensuring no ball ID is used more than once.
- Parameters:
blueprint (List[BallPos]) -- A list of ball positions represented as BallPos objects, which describe their location relative to table anchors or other positions.
table (pooltool.objects.table.datatypes.Table) -- A Table. This must exist so the rack can be created with respect to the table’s dimensions.
ball_params (Optional[pooltool.objects.ball.datatypes.BallParams]) -- A BallParams object, which all balls will be created with. This contains info like ball radius.
spacing_factor (float) -- This factor adjusts the spacing between balls to ensure they do not touch each other directly. Instead of being in direct contact, each ball is allocated within a larger, virtual radius defined as
(1 + spacing_factor) * R
, whereR
represents the actual radius of the ball. Within this expanded radius, the ball’s position is determined randomly, allowing for a controlled separation between each ball. The spacing_factor therefore dictates the degree of this separation, with higher values resulting in greater distances between adjacent balls. Setting this to 0 is not recommended.seed (Optional[int]) -- Set a seed for reproducibility. That’s because getting a rack involves two random procedures. First, some ball positions can be satisfied with many different ball IDs. For example, in 9 ball, only the 1 ball and 9 ball are predetermined, the positions of the other balls are random. The second source of randomnness is from spacing_factor.
- Returns:
A dictionary mapping ball IDs to their respective Ball objects, with their absolute positions on the table.
- Return type:
Notes
The table dimensions are normalized such that the bottom-left corner is (0.0, 0.0) and the top-right corner is (1.0, 1.0).
- pooltool.get_rack(game_type: pooltool.game.datatypes.GameType, table: pooltool.objects.table.datatypes.Table, ball_params: pooltool.objects.ball.datatypes.BallParams | None = None, ballset: pooltool.objects.ball.sets.BallSet | None = None, spacing_factor: float = 0.001) Dict[str, pooltool.objects.ball.datatypes.Ball] [source]¶
Generate a ball rack.
This function ultimately delegates to
generate_layout()
.- Parameters:
game_type (pooltool.game.datatypes.GameType) -- The game type being played. This will determine what rack is returned.
table (pooltool.objects.table.datatypes.Table) -- A table. This must exist so the rack can be created with respect to the table’s dimensions.
ball_params (Optional[pooltool.objects.ball.datatypes.BallParams]) -- Ball parameters that all balls will be created with.
spacing_factor (float) -- This factor adjusts the spacing between balls to ensure they do not touch each other directly. Instead of being in direct contact, each ball is allocated within a larger, virtual radius defined as
(1 + spacing_factor) * R
, whereR
represents the actual radius of the ball. Within this expanded radius, the ball’s position is determined randomly, allowing for a controlled separation between each ball. Thespacing_factor
therefore dictates the degree of this separation, with higher values resulting in greater distances between adjacent balls. Setting this to 0 is not recommended.
- Returns:
A dictionary mapping ball IDs to their respective Ball objects, with their absolute positions on the table.
- Return type:
- pooltool.get_ruleset(game: pooltool.game.datatypes.GameType) Type[datatypes.Ruleset] [source]¶
Retrieve a ruleset class
- Parameters:
game (pooltool.game.datatypes.GameType) -- The game type.
- Returns:
An uninitialized class object representing a game.
- Return type:
Type[Ruleset]