Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
logger = logging.getLogger(__name__)


def _solve(ball: Ball, cushion: Cushion) -> tuple[Ball, Cushion]:
def _solve(ball: Ball, cushion: Cushion, omega_ratio: float) -> tuple[Ball, Cushion]:
rvw = ball.state.rvw.copy()

logger.debug(f"v={rvw[1]}, w={rvw[2]}")
Expand Down Expand Up @@ -55,8 +55,8 @@ def _solve(ball: Ball, cushion: Cushion) -> tuple[Ball, Cushion]:
beta_n=beta_n,
mu=ball.params.f_c,
e_n=ball.params.e_c,
k_n=1e3, # TODO: cushion params
eta_squared=(beta_t_by_beta_n / 1.7**2), # TODO: cushion params
k_n=1e3, # arbitrary: collision outcome depends only on omega_ratio, not k_n.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just a small note: if we ever want to use the spring deformation to approximate cushion deformation and duration, this parameter affects that duration.

eta_squared=(beta_t_by_beta_n / omega_ratio**2),
)

Dv_n = (v_n_f - v_n_0) / beta_n
Expand Down Expand Up @@ -93,23 +93,54 @@ def _solve(ball: Ball, cushion: Cushion) -> tuple[Ball, Cushion]:

@attrs.define
class StrongeCompliantLinear(CoreBallLCushionCollision):
"""Ball-cushion collision resolver using Stronge's compliant collision model.

This model accounts for the compliant (spring-like) nature of cushion deformation
during collision.

Attributes:
omega_ratio:
Frequency ratio omega_t/omega_n controlling collision compliance, must be in
range (1, 2). Higher values = stiffer cushion, lower values = softer.

Notes:
Architecturally, omega_ratio represents a cushion material property (Poisson's
ratio) and should ideally be a cushion attribute rather than a model parameter.
However, it is exposed here as a model parameter because

(1) It's intuitive to adjust, ranging between [1, 2], and representing the
ratio of spring coefficients between tangential and normal components.

(2) This is the first model requiring cushion material properties, so we
defer adding cushion attributes until needed by multiple models.

When cushion material properties are added to cushion segments, we should add
Poisson ratio as a cushion parameter, and use
``poisson_ratio_from_omega_ratio()`` and ``omega_ratio_from_poisson_ratio()`` to
convert to/from omega_ratio.
"""

omega_ratio: float = 1.7
model: BallLCushionModel = attrs.field(
default=BallLCushionModel.STRONGE_COMPLIANT, init=False, repr=False
)

def solve(
self, ball: Ball, cushion: LinearCushionSegment
) -> tuple[Ball, LinearCushionSegment]:
return _solve(ball, cushion)
return _solve(ball, cushion, self.omega_ratio)


@attrs.define
class StrongeCompliantCircular(CoreBallCCushionCollision):
"""See :class:`StrongeCompliantLinear`."""

omega_ratio: float = 1.7
model: BallCCushionModel = attrs.field(
default=BallCCushionModel.STRONGE_COMPLIANT, init=False, repr=False
)

def solve(
self, ball: Ball, cushion: CircularCushionSegment
) -> tuple[Ball, CircularCushionSegment]:
return _solve(ball, cushion)
return _solve(ball, cushion, self.omega_ratio)
18 changes: 8 additions & 10 deletions pooltool/physics/resolve/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
BallCCushionCollisionStrategy,
BallLCushionCollisionStrategy,
)
from pooltool.physics.resolve.ball_cushion.mathavan_2010.model import (
Mathavan2010Circular,
Mathavan2010Linear,
from pooltool.physics.resolve.ball_cushion.stronge_compliant.model import (
StrongeCompliantCircular,
StrongeCompliantLinear,
)
from pooltool.physics.resolve.ball_pocket import (
BallPocketStrategy,
Expand All @@ -46,7 +46,7 @@
RESOLVER_PATH = pooltool.config.paths.PHYSICS_DIR / "resolver.yaml"
"""The location of the resolver path YAML."""

VERSION: int = 8
VERSION: int = 9


run = Run()
Expand All @@ -71,13 +71,11 @@ def default_resolver() -> Resolver:
c=1.088,
),
),
ball_linear_cushion=Mathavan2010Linear(
max_steps=1000,
delta_p=0.001,
ball_linear_cushion=StrongeCompliantLinear(
omega_ratio=1.8,
),
ball_circular_cushion=Mathavan2010Circular(
max_steps=1000,
delta_p=0.001,
ball_circular_cushion=StrongeCompliantCircular(
omega_ratio=1.8,
),
ball_pocket=CanonicalBallPocket(),
stick_ball=InstantaneousPoint(
Expand Down
84 changes: 75 additions & 9 deletions pooltool/physics/resolve/stronge_compliant.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,51 @@

@jit(nopython=True, cache=const.use_numba_cache)
def normal_tangent_stiffness_ratio(poisson_ratio):
"""Calculate eta_squared from Poisson ratio.

Args:
poisson_ratio: Poisson's ratio of the cushion.

Returns:
eta_squared: Ratio of normal to tangential stiffness.
"""
return (2 - poisson_ratio) / (2 * (1 - poisson_ratio))


def poisson_ratio_from_omega_ratio(
omega_ratio: float, beta_t: float = 3.5, beta_n: float = 1.0
) -> float:
"""Convert from tangential/normal frequency ratio to Poisson ratio.

Args:
omega_ratio: Frequency ratio omega_t/omega_n. Must be in range (1, 2).
beta_t: Tangential mass-matrix coefficient for sphere-half-space collision.
beta_n: Normal mass-matrix coefficient for sphere-half-space collision.

Returns:
Poisson's ratio.
"""
eta_squared = (beta_t / beta_n) / (omega_ratio**2)
return (2 * eta_squared - 2) / (2 * eta_squared - 1)


def omega_ratio_from_poisson_ratio(
poisson_ratio: float, beta_t: float = 3.5, beta_n: float = 1.0
) -> float:
"""Convert Poisson ratio to the tangential/normal frequency ratio.

Args:
poisson_ratio: Poisson's ratio of the cushion.
beta_t: Tangential mass-matrix coefficient for sphere-half-space collision.
beta_n: Normal mass-matrix coefficient for sphere-half-space collision.

Returns:
Frequency ratio omega_t/omega_n.
"""
eta_squared = normal_tangent_stiffness_ratio(poisson_ratio)
return np.sqrt((beta_t / beta_n) / eta_squared)


@jit(nopython=True, cache=const.use_numba_cache)
def t_c_shift(e_n):
return (math.pi / 2) * (1 - 1 / e_n)
Expand Down Expand Up @@ -431,16 +473,40 @@ def collision_duration(t_c, e_n):


def resolve_collinear_compliant_frictional_inelastic_collision(
v_t_0: float, # tangential velocity (must be <= 0)
v_n_0: float, # normal velocity (must be <0)
m: float, # collision effective mass
beta_t: float, # mass-matrix coefficient
beta_n: float, # mass-matrix coefficient
mu: float, # friction coefficient
e_n: float, # coefficient of restitution
k_n: float, # normal spring stiffness
eta_squared: float, # ratio of normal spring stiffness to tangential spring stiffness
v_t_0: float,
v_n_0: float,
m: float,
beta_t: float,
beta_n: float,
mu: float,
e_n: float,
k_n: float,
eta_squared: float,
) -> tuple[float, float]:
"""Resolve a collinear compliant frictional inelastic collision.

Computes the post-collision tangential and normal velocities for a sphere
colliding with a half-space, accounting for friction, compliance (spring-like
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is actually general enough to do any two-body planar collinear collision. The beta_t and beta_n arguments come from the moments of inertia for the two bodies.

deformation), and inelastic energy loss.

Args:
v_t_0: Initial tangential velocity, must be <= 0.
v_n_0: Initial normal velocity, must be < 0.
m: Collision effective mass.
beta_t: Tangential mass-matrix coefficient.
beta_n: Normal mass-matrix coefficient.
mu: Friction coefficient.
e_n: Coefficient of restitution in the normal direction.
k_n: Normal spring stiffness. This value is arbitrary as only the frequency
ratio omega_t/omega_n affects the result, which depends on the ratio
beta_t/beta_n/eta_squared.
eta_squared: Ratio of normal to tangential spring stiffness. Together with
beta_t and beta_n, this determines the frequency ratio omega_t/omega_n,
which must be in the range (1, 2).

Returns:
Final tangential and normal velocities after collision.
"""
assert v_t_0 <= 0
assert v_n_0 < 0

Expand Down