API reference¶
This page is autogenerated from the docstrings of the pitchphys package via mkdocstrings. Every public symbol you can from pitchphys import ... (or import from a submodule) is documented here, in source order.
To see the source code for any function, use the "Source" link on the GitHub repo — mkdocstrings is configured to keep this page focused on signatures and prose.
Top-level entry points¶
simulate ¶
Trajectory integrator built on scipy.integrate.solve_ivp.
The plate-crossing event terminates integration at y == plate_distance_m.
Per-step diagnostics (force decomposition, Re, S, Cd, Cl) are filled in via a
second pass over the accepted timesteps so the RHS stays cheap and we don't
need to dedupe rejected steps.
simulate ¶
simulate(pitch, env=None, ball=None, model='magnus', forces=None, plate_distance_m=DEFAULT_PLATE_DISTANCE_M, method='RK45', rtol=1e-08, atol=1e-10, max_t=2.0, spin_decay_tau_s=1.5, _baselines=True)
Integrate a pitch from release to plate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pitch
|
PitchRelease
|
Initial conditions. |
required |
env
|
Environment | None
|
Atmospheric environment (defaults to sea level). |
None
|
ball
|
Baseball | None
|
Baseball physical properties (defaults to standard). |
None
|
model
|
AeroModel | str
|
Either a string name ( |
'magnus'
|
forces
|
Sequence[str] | None
|
Force names to include (e.g. |
None
|
plate_distance_m
|
float
|
Y-coordinate where the integration terminates. |
DEFAULT_PLATE_DISTANCE_M
|
method
|
Literal['RK45', 'DOP853']
|
|
'RK45'
|
rtol
|
float
|
Solver relative tolerance. |
1e-08
|
atol
|
float
|
Solver absolute tolerance. |
1e-10
|
max_t
|
float
|
Maximum integration time in seconds. |
2.0
|
spin_decay_tau_s
|
float | None
|
Exponential time constant for spin decay,
|
1.5
|
_baselines
|
bool
|
Internal. If True and |
True
|
Returns:
| Type | Description |
|---|---|
TrajectoryResult
|
|
TrajectoryResult
|
timesteps and break-metric properties available. |
simulate_many ¶
Run :func:simulate over a sequence of pitches (sequential in v0.1).
simulate_many ¶
Run :func:simulate over a sequence of pitches (sequential in v0.1).
Data models¶
PitchRelease
dataclass
¶
PitchRelease(speed_m_s, launch_angle_deg, horizontal_angle_deg, release_pos_m, spin_rate_rad_s, spin_axis, active_spin_fraction=1.0, metadata=dict())
Release-time state of a pitched ball.
Internally everything is SI: meters, seconds, kilograms, radians. Use
:meth:from_mph_rpm_axis to construct from baseball-friendly units.
from_mph_rpm_axis
classmethod
¶
from_mph_rpm_axis(speed_mph, spin_rpm, tilt_clock=12.0, active_spin_fraction=0.95, release_height_ft=6.0, release_side_ft=-1.5, extension_ft=6.0, launch_angle_deg=-1.0, horizontal_angle_deg=0.0, throwing_hand='R')
Construct a PitchRelease from baseball-friendly units.
tilt_clock is the clock-face tilt of the active spin axis viewed
from behind home plate (catcher view). See :mod:pitchphys.coordinates
for the convention. 12:00 = pure backspin axis, 6:00 = pure topspin,
etc.
The release position is at world y = 0; the plate sits ahead at
y = DEFAULT_PLATE_DISTANCE_M (55 ft). extension_ft is stored as
metadata in v0.1 but does not shift the release point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
speed_mph
|
float
|
Pitch release speed. |
required |
spin_rpm
|
float
|
Total spin rate (active + gyro). |
required |
tilt_clock
|
float
|
Active-spin clock tilt in hours, 0..12. |
12.0
|
active_spin_fraction
|
float
|
Fraction of spin perpendicular to velocity (the part that produces Magnus break). 0 = pure gyro, 1 = pure active. |
0.95
|
release_height_ft
|
float
|
Height of release above home plate (z, ft). |
6.0
|
release_side_ft
|
float
|
Lateral release offset (x, ft). Negative = catcher's left, which is the typical RHP release side. |
-1.5
|
extension_ft
|
float
|
Pitcher extension off the rubber (informational). |
6.0
|
launch_angle_deg
|
float
|
Angle of velocity above horizontal at release. |
-1.0
|
horizontal_angle_deg
|
float
|
Yaw of velocity around vertical (positive toward +x). |
0.0
|
throwing_hand
|
Literal['R', 'L']
|
|
'R'
|
omega_vec ¶
Angular velocity 3-vector in world frame (rad/s).
If v_hat is given, the omega vector is reconstructed from the
active axis + active_spin_fraction so the perp/parallel decomposition
matches the requested velocity direction. Otherwise we use the stored
spin_axis scaled by spin_rate_rad_s.
Baseball
dataclass
¶
A point-mass baseball.
Defaults are MLB rule midpoints (mass 5.0-5.25 oz, circumference 9.0-9.25 in). Override for different ball lots, training balls, etc.
Environment
dataclass
¶
Environment(air_density_kg_m3=AIR_DENSITY_SEA_LEVEL, dynamic_viscosity_pa_s=DYNAMIC_VISCOSITY_SEA_LEVEL, gravity_m_s2=G_M_S2, wind_m_s=_zero_wind())
Air properties, gravity, and wind.
Wind is a 3-vector in world frame (m/s). Drag uses v_rel = v - wind.
coors_field
classmethod
¶
Approximate Denver / Coors Field air density (~5,200 ft elevation).
The 1.00 kg/m^3 value is approximate; real conditions vary with
temperature and humidity. Use :meth:from_weather for explicit
sensitivity studies.
from_weather
classmethod
¶
Air properties from temperature, pressure, and relative humidity.
Uses the ideal-gas law with the Tetens equation for water-vapor saturation pressure and a humid-air partial-pressure mixture. Dynamic viscosity is computed via Sutherland's law.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
temp_c
|
float
|
Air temperature in degrees Celsius. |
required |
pressure_pa
|
float
|
Total atmospheric pressure in Pa (sea-level standard ≈ 101325). |
required |
humidity
|
float
|
Relative humidity in |
0.0
|
Returns:
| Type | Description |
|---|---|
Environment
|
|
Environment
|
|
Environment
|
keep their defaults. |
Note
Humid air is less dense than dry air at the same pressure because water vapor (M ≈ 18 g/mol) is lighter than the nitrogen/oxygen mix (M ≈ 29 g/mol). At 30 °C, 100 % RH, sea-level pressure, this is ~1 % less dense than dry air.
TrajectoryResult
dataclass
¶
TrajectoryResult(time, position, velocity, acceleration, forces, reynolds_number, spin_factor, cd, cl, plate_distance_m, ball, env, pitch, forces_used, grav_drag_baseline=None, metadata=dict())
Sampled trajectory + per-step diagnostics.
Arrays are indexed along axis 0 by accepted-integrator timesteps. Forces
are stored per name in the forces dict (each value shape (N, 3)).
Break metrics rely on baseline trajectories simulated with subsets of the
forces: gravity_only_baseline (forces=["gravity"]) drives induced
vertical break; grav_drag_baseline (forces=["gravity","drag"]) drives
Magnus break.
total_drop_in
property
¶
Inches the ball ended below the release height (positive = below).
horizontal_break_in
property
¶
Lateral deviation from a straight-line projection of the release velocity.
Positive = catcher's right.
induced_vertical_break_in
property
¶
Vertical movement induced by aerodynamic forces (Statcast §4.1).
Defined as the difference in z between the actual pitch and a
no-force projectile with the same release velocity, evaluated at the
actual pitch's flight time:
z_no_force(t) = z0 + vz0 * t - 0.5 * g * t**2
Positive ⇒ the ball ended above the gravity-only path (the pitch "rises" relative to a spinless ball). Negative ⇒ extra drop. For a pure-gyro pitch with no Magnus contribution to z, IVB ≈ 0 by construction.
non_magnus_break_in
property
¶
Reserved for v0.3 seam-shifted-wake models. Always 0.0 in v0.1.
Aerodynamic models¶
Aerodynamic coefficient models.
AeroModel ¶
Bases: Protocol
Pluggable drag/lift coefficient provider.
Implementations may use any subset of Re, S, and ctx keys
they care about. non_magnus_force returns a 3-vector (N) for any
extra force outside gravity/drag/Magnus; v0.1 ships zeros.
LyuAeroModel
dataclass
¶
Lyu 2022 wind-tunnel C_D(Re, S) and C_L(S) (seam-averaged).
Default for model="magnus". See module docstring for sourcing.
NathanLiftModel
dataclass
¶
Lift model from Nathan 2008 — the Sawicki, Hubbard, Stronge (2003) bilinear.
Implements the Sawicki-Hubbard-Stronge (SHS) bilinear C_L(S):
C_L = 1.5 * S for S < 0.1
C_L = 0.09 + 0.6 * S for S >= 0.1
where S = R * |omega_perp| / |v_rel| is the dimensionless spin factor.
The two pieces agree at S = 0.1 (both give C_L = 0.15), so the
function is continuous.
Nathan's 2008 motion-capture experiment (Am. J. Phys. 76:119-124)
independently measured C_L over v = 50-110 mph and S = 0.1-0.6,
finding "excellent agreement with the SHS parametrization for S < 0.3."
See references/ajpfeb08.pdf Sec. IV.A and Fig. 5; the explicit
formula appears as Eq. (2) of Nathan's arXiv preprint physics/0605041.
Drag is held at a constant C_D = 0.35. Nathan 2008 Fig. 4 shows
the actual C_D has a weak speed dependence and considerable
scatter; for a Re- and spin-dependent drag use LyuAeroModel.
History: v0.1 of this package implemented C_L = 1.5*S/(0.4 + 2.32*S)
in this class — that rational fit was misattributed to Nathan 2008 and
has been replaced (v0.1.5) with the actual SHS bilinear.
SimpleMagnusModel
dataclass
¶
Simple linear-in-spin-factor lift model: Cl = min(a * S, cl_max).
Default for model="magnus". Transparent and pedagogically clear; not
a precise empirical fit.
ConstantAeroModel
dataclass
¶
Constant drag and lift coefficients. Useful for analytic tests.
UserDefinedAeroModel
dataclass
¶
Wrap user-supplied callables as an AeroModel.
Coordinates¶
Coordinate system and spin-axis convention.
This module is the single source of truth for how clock-face spin tilts map to 3D spin-axis vectors. Every other module that needs to interpret a spin axis should call into this module rather than re-deriving the rotation.
World axes (right-handed): x = horizontal, positive to catcher's right y = forward, positive from release toward home plate z = vertical, positive up
Spin tilt is described as a clock face viewed from behind home plate looking toward the pitcher (catcher view). For a right-handed pitcher (RHP):
12 (backspin)
|
9 ----+---- 3 | 6 (topspin)
12:00 -> axis = +x (pure backspin -> +z Magnus break, "lift")
3:00 -> axis = -z (sidespin -> +x Magnus break, catcher's right)
6:00 -> axis = -x (pure topspin -> -z Magnus break, "drop")
9:00 -> axis = +z (sidespin -> -x Magnus break, catcher's left)
The mapping of tilt T (in clock hours) to the 3D axis is a planar rotation in the catcher-view (x, z) plane:
angle_rad = -pi * T / 6 # measured clockwise from 12:00
axis = (cos(angle_rad), 0, sin(angle_rad))
For an LHP, the resulting axis is mirrored across the 12-6 axis (x -> -x).
Magnus force is always proportional to omega x v_hat, so under v_hat = +y
the break direction equals the clock-tilt direction itself: 12:00 break = up,
6:00 break = down, 3:00 break = catcher's right, 9:00 break = catcher's left.
This is intentional (the convention is internally consistent) and is the
mental model users should keep.
clock_tilt_to_axis ¶
Map a clock-face tilt to a 3D unit vector along the active spin axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tilt_clock_hours
|
float
|
Clock tilt in hours, |
required |
throwing_hand
|
Literal['R', 'L']
|
|
'R'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Unit 3-vector |
build_omega ¶
Compose the full 3D angular velocity vector from an active axis + gyro mix.
active_spin_fraction is the share of the spin that is perpendicular to
velocity (and therefore contributes to the Magnus force, SPEC §4.4). The
remaining share is gyro spin aligned with velocity:
omega = spin_rate * (active_spin_fraction * active_axis_hat
+ gyro_fraction * v_hat)
where gyro_fraction = sqrt(1 - active_spin_fraction^2).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spin_rate_rad_s
|
float
|
Spin magnitude in rad/s. |
required |
active_axis
|
ndarray
|
3-vector along the active (perpendicular) spin axis. Need not be a unit vector; it will be normalized. |
required |
active_spin_fraction
|
float
|
In |
required |
v_hat
|
ndarray
|
Unit velocity direction at release. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
3-vector angular velocity in rad/s. |
decompose_omega ¶
Split angular velocity into perpendicular and parallel components.
omega = omega_perp + omega_par where omega_par is along v_hat.
Only omega_perp contributes to the Magnus force (SPEC §8.5).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
omega
|
ndarray
|
3-vector angular velocity. |
required |
v_hat
|
ndarray
|
Unit velocity direction (need not be exactly unit; will be normalized). |
required |
Returns:
| Type | Description |
|---|---|
tuple[ndarray, ndarray]
|
|
Units¶
SI unit conversion helpers.
The simulation core stores meters, seconds, kilograms, radians. These helpers convert at the user-facing boundary so mph/rpm/feet/inches never leak into force calculations.
Visualization — 2D (Matplotlib)¶
2D Matplotlib visualization helpers.
Matplotlib is imported lazily inside each function so the core package can be used without the visualization extra installed.
All views default to feet on both axes; pass units="m" to switch.
side_view ¶
Plot the trajectory from the side (y vs. z).
catcher_view ¶
catcher_view(traj, ax=None, units='ft', show_strike_zone=True, show_trajectory=True, label=None, **kwargs)
Plot the trajectory as seen by the catcher (x vs. z).
top_view ¶
Plot the trajectory from above (y vs. x).
compare_pitches ¶
Overlay multiple trajectories on a shared view.
draw_force_arrows ¶
Annotate force vectors at selected times on an existing axis.
Visualization — 3D (Plotly)¶
3D Plotly visualization helpers.
Plotly is imported lazily inside each function so the core package can be
used without the visualization extra installed. Use only
plotly.graph_objects (no plotly.express) for explicit, composable
trace control.
World-frame axes (right-handed, per :mod:pitchphys.coordinates):
x = catcher's right
y = forward toward home plate
z = vertical, positive up
The default camera puts you behind and slightly above the catcher's left
shoulder so vertical break is visible alongside the trajectory. Aspect
mode is "data" to keep break-magnitude visually faithful.
trajectory_3d ¶
trajectory_3d(traj, *, fig=None, label=None, units='ft', show_release=True, show_plate=True, show_strike_zone=True, color=None, line_kwargs=None)
Plot a single pitch trajectory in 3D.
Returns the (possibly new) plotly.graph_objects.Figure.
compare_pitches_3d ¶
Overlay multiple trajectories in a single 3D scene.
add_spin_axis_arrow ¶
add_spin_axis_arrow(fig, pitch, *, position=None, length_ft=2.0, color='#9467bd', label='spin axis', units='ft')
Draw a 3D arrow along pitch.spin_axis at the given position.
add_force_vectors ¶
add_force_vectors(fig, traj, *, times=(0.1, 0.2, 0.3), forces=('gravity', 'drag', 'magnus'), scale=0.05, units='ft', colors=None)
Annotate the trajectory with force-vector cones at selected times.
scale is in meters per Newton (cone tip is scale * |F| meters
from the ball position). Default 0.05 m/N gives ~5 cm per Newton, a
reasonable visual size for the ~1 N forces typical at MLB speeds.
Animation¶
Animated 3D trajectory using Plotly frames + slider/play.
Plotly figure frames together with updatemenus and sliders
provide a play/pause/scrub experience entirely in the browser. We don't
require a server callback. Frame durations below ~30 ms tend to stutter
in browsers; default is 30 fps which gives 33 ms per frame.
animate_pitch ¶
animate_pitch(traj, *, fps=30, fig=None, units='ft', show_trail=True, show_strike_zone=True, target_frames=None)
Build an animated 3D figure for a single pitch trajectory.
The figure has a Play button and a frame slider. target_frames
overrides the auto frame count derived from fps * flight_time.
animate_pitches ¶
Animate multiple pitches synchronized on a shared time grid.
Each pitch gets a colored ball + trail. All balls advance together over a common normalized time index; pitches with shorter flight reach the plate sooner and freeze at the plate position for remaining frames.
Pitch presets¶
Pedagogical pitch presets — see :mod:pitchphys.presets.pitches.
four_seam ¶
four_seam(speed_mph=95.0, spin_rpm=2400.0, tilt_clock=12.0, active_spin_fraction=0.95, throwing_hand='R', **kwargs)
High-spin backspin fastball. Default Magnus break: straight up.
curveball ¶
curveball(speed_mph=80.0, spin_rpm=2700.0, tilt_clock=6.0, active_spin_fraction=0.85, throwing_hand='R', **kwargs)
Topspin curveball. Default Magnus break: straight down.
slider ¶
slider(speed_mph=85.0, spin_rpm=2500.0, tilt_clock=3.0, active_spin_fraction=0.3, throwing_hand='R', **kwargs)
Gyro-dominant slider. High raw spin, low total break.
Demonstrates SPEC §4.4: spin rate alone does not predict movement.
sinker ¶
sinker(speed_mph=92.0, spin_rpm=2200.0, tilt_clock=7.5, active_spin_fraction=0.85, throwing_hand='R', **kwargs)
Arm-side sinker for RHP. Default break: arm side + drop.
changeup ¶
changeup(speed_mph=84.0, spin_rpm=1700.0, tilt_clock=7.0, active_spin_fraction=0.8, throwing_hand='R', **kwargs)
Off-speed changeup. Lower spin, slight arm-side run + drop.