``pooltool.system`` =================== .. py:module:: pooltool.system The system container and its associated objects Classes ------- .. autoclass:: MultiSystem .. py:property:: active :type: System .. py:property:: empty :type: bool .. py:property:: max_index .. rubric:: Methods: .. py:method:: reset() -> None .. py:method:: append(system: System) -> None Append a system to the multisystem This appends ``system`` to :attr:`multisystem`. .. py:method:: extend(systems: list[System]) -> None .. py:method:: set_active(i) -> None .. py:method:: save(path: pooltool.serialize.serializers.Pathish) -> None Save the multisystem to file in a serialized format. Supported file extensions: (1) ``.json`` (2) ``.msgpack`` :param path: Either a ``pathlib.Path`` object or a string. The extension should match the supported filetypes mentioned above. .. seealso:: - To load a multisystem, see :meth:`load`. - To save/load single systems, see :meth:`pooltool.system.System.save` and :meth:`pooltool.system.System.load` .. py:method:: load(path: pooltool.serialize.serializers.Pathish) -> MultiSystem :classmethod: Load a multisystem from a file in a serialized format. Supported file extensions: (1) ``.json`` (2) ``.msgpack`` :param path: 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. :rtype: MultiSystem .. seealso:: - To save a multisystem, see :meth:`save`. - To save/load single systems, see :meth:`pooltool.system.System.save` and :meth:`pooltool.system.System.load` .. autoclass:: System .. py:property:: continuized :type: bool Checks if all balls have a non-empty continuous history. :returns: True if all balls have a non-empty continuous history, False otherwise. :rtype: bool .. seealso:: For a proper definition of *continuous history*, please see :attr:`pooltool.objects.Ball.history_cts`. .. py:property:: simulated :type: bool 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. :rtype: bool .. rubric:: Methods: .. py:method:: set_ballset(ballset: pooltool.objects.ball.sets.BallSet) -> None 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 :mod:`pooltool.layouts`) In this case, you need to manually associate a :class:`pooltool.objects.BallSet` to the balls in the system, so that the proper `model skin` can be applied to each. That's what this method does. :param 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. .. seealso:: - See :class:`pooltool.objects.BallSet` for details about ball sets. - See :meth:`pooltool.objects.Ball.set_ballset` for setting the ballset of an individual ball. .. py:method:: reset_history() -> None Resets the history for all balls, clearing events and resetting time. Operations that this method performs: (1) :attr:`t` is set to 0.0 (2) :attr:`events` is set to ``[]`` Additionally, for each ball in :attr:`balls`, (1) :attr:`pooltool.objects.Ball.history` is set to ``BallHistory()`` (2) :attr:`pooltool.objects.Ball.history_cts` is set to ``BallHistory()`` (3) The ``t`` attribute of :attr:`pooltool.objects.Ball.state` is set to ``0.0`` Calling this method thus erases any history. .. py:method:: reset_balls() -> None 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. .. admonition:: Example This example shows that calling this method resets the balls' 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 .. py:method:: stop_balls() -> None Change ball states to stationary and remove all momentum This method removes all kinetic energy from the system by: (1) Setting the velocity and angular velocity vectors of each ball to <0, 0, 0> (2) Setting the balls' motion states to stationary (`i.e.` 0) .. py:method:: strike(**kwargs) -> None Set cue stick parameters This is an alias for :meth:`pooltool.objects.Cue.set_state` :param kwargs: **kwargs Cue stick parameters. .. seealso:: :meth:`pooltool.objects.Cue.set_state` .. py:method:: randomize_positions(ball_ids: list[str] | None = None, niter=100) -> bool Randomize ball positions on the table--ensure no overlap This 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. .. note:: This is a very inefficient algorithm. :param ball_ids: Only these balls will be randomized. :param niter: The number of iterations tried until the algorithm gives up. :returns: True if all balls are non-overlapping. Returns False otherwise. :rtype: bool .. py:method:: is_balls_overlapping() -> bool Determines if any balls are overlapping. :returns: True if any balls overlap, False otherwise. :rtype: bool .. py:method:: copy() -> System 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. :rtype: System .. admonition:: 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 .. py:method:: save(path: pooltool.serialize.serializers.Pathish, drop_continuized_history: bool = False) -> None Save a System to file in a serialized format. Supported file extensions: (1) ``.json`` (2) ``.msgpack`` :param path: Either a ``pathlib.Path`` object or a string. The extension should match the supported filetypes mentioned above. :param drop_continuized_history: If True, :attr:`pooltool.objects.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, the ``history_cts`` will have to be repopulated via simulation (see Examples). .. admonition:: 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 event and ball trajectory data, so they're larger: $ du -sh case1.json case2.json 12K case1.json 68K case2.json .. admonition:: 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) .. admonition:: Example If the system has been continuized (see :func:`pooltool.evolution.continuize`), disk space and save/load times can be decreased by using ``drop_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 :func:`pooltool.evolution.continuize`: >>> loaded_system = pt.System.load("drop.json") >>> assert loaded_system != system >>> pt.continuize(loaded_system, inplace=True) >>> assert loaded_system == system .. seealso:: Load systems with :meth:`load`. .. py:method:: load(path: pooltool.serialize.serializers.Pathish) -> System :classmethod: Load a System from a file in a serialized format. Supported file extensions: (1) ``.json`` (2) ``.msgpack`` :param path: 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. :rtype: System :raises AssertionError: If the file specified by `path` does not exist. :raises ValueError: If the file extension is not supported. Examples: Please refer to the examples in :meth:`save`. .. seealso:: Save systems with :meth:`save`. .. py:method:: example() -> System :classmethod: 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. .. admonition:: 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 ├── 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) >>> pt.show(system)