pitchphys
An educational Python simulator for baseball pitch aerodynamics, spin, drag, Magnus force, and pitch movement.
pitchphys is a point-mass trajectory simulator that lets you experiment with the dominant first-order physics of a pitched baseball: gravity, drag, and the Magnus effect. Force terms are toggleable so you can see exactly how each one shapes the trajectory. v0.2 adds an interactive 3D Plotly viz layer, a 5-page Streamlit app, and three tutorial notebooks.
Install matrix
| Goal | Install command |
|---|---|
| Core engine only (numpy + scipy) | pip install pitchphys |
| Engine + 2D matplotlib plots | pip install pitchphys[viz] |
| Engine + 2D + 3D Plotly + Streamlit app | pip install pitchphys[app] |
| Local development (everything + tests) | pip install -e ".[dev,viz,app]" |
Quickstart
from pitchphys import simulate
from pitchphys.presets import four_seam
traj = simulate(four_seam(speed_mph=95, spin_rpm=2400))
print(traj.break_metrics())
Interactive app
After pip install pitchphys[app]:
pitchphys-app # opens http://localhost:8501
# or, from a source checkout:
streamlit run app/streamlit_app.py
The app has five pages:
- Pitch Playground — every slider, animated 3D, full break-metrics row.
- Magnus Explorer —
v,ω, andω × v̂vectors at release. - Fastball vs Curveball — two pitches side by side.
- Active Spin / Gyro — sweep active fraction at fixed spin rate.
- Drag + Environment — gravity / drag / Magnus toggles, weather, wind.
Deploy your own copy to Streamlit Cloud
The repo is preconfigured for one-click deployment:
- Fork or clone this repo to your GitHub account.
- Sign in at https://share.streamlit.io with your GitHub account.
- Click Create app → pick this repo, branch
main, and main file pathapp/streamlit_app.py. - Click Deploy. Build takes ~3 minutes the first time (plotly + scipy wheels).
Streamlit Cloud reads requirements.txt (single line .[app] — installs the package with the [app] extra) and runtime.txt (Python 3.12). All app pages, 3D Plotly viz, and physics models run without further configuration.
Notebooks
Three tutorial notebooks under notebooks/ walk through the core physics. Click the badge to open in Google Colab — each notebook auto-installs pitchphys in the Colab session, no setup needed.
Note: Colab currently runs Python 3.11; if a future Colab downgrade breaks the install, run the notebooks locally via jupyter lab notebooks/.
Approximate output for a high-spin 95 mph four-seamer at sea level (default LyuAeroModel + 1.5 s spin-decay τ):
| metric | value |
|---|---|
release_speed_mph |
95.0 |
plate_speed_mph |
~86.5 |
flight_time_s |
~0.41 |
induced_vertical_break_in |
~+20.5 (positive = "rises" vs spinless) |
total_drop_in |
~24.7 |
horizontal_break_in |
~0 (pure 12:00 backspin) |
magnus_break_z_in |
~+19 |
To run the test suite, lint, and type checks:
pytest -q
ruff check . && ruff format --check .
mypy src
If import pitchphys fails after pip install -e . because the install lives under ~/Documents (which inherits a macOS "hidden" flag that Python's site module skips), run:
find .venv -name "*.pth" -exec chflags nohidden {} \;
The package's tests don't need this — pytest's pythonpath = ["src"] config in pyproject.toml works around it.
What's modeled (v0.2)
Engine (v0.1.5)
- Gravity, drag, and Magnus lift on a point mass
- Active spin vs gyro spin (the part of the spin that actually moves the ball)
- Drag crisis at low spin via the Lyu 2022 wind-tunnel fit (
LyuAeroModel, default formodel="magnus") - Spin decay
omega(t) = omega_0 * exp(-t/τ)with default τ = 1.5 s;spin_decay_tau_s=Noneto disable - Weather-driven air properties via
Environment.from_weather(temp_c, pressure_pa, humidity)(ideal gas + Tetens humidity + Sutherland viscosity) - Pluggable aerodynamic models (
LyuAeroModel,NathanLiftModel,SimpleMagnusModel,ConstantAeroModel,UserDefinedAeroModel) - Pedagogical pitch presets (four-seam, curveball, slider, sinker, changeup)
- Break metrics in baseball-friendly units (inches, mph, feet) over an SI core
Interactive layer (v0.2)
- 2D Matplotlib helpers (side / catcher / top / compare-pitches)
- 3D Plotly helpers (
trajectory_3d,compare_pitches_3d,add_spin_axis_arrow,add_force_vectors) - Animated ball flight (
animate_pitch,animate_pitches) with play/pause/scrub - 5-page Streamlit app launchable via
pitchphys-app - 3 tutorial notebooks under
notebooks/
Physics provenance
The default aerodynamic model is calibrated against published baseball wind-tunnel and motion-capture data:
LyuAeroModel(default): drag crisisCd(Re, S)and seam-averaged liftCl(S)from Lyu, Smith, Elliott & Kensrud (2022), "The dependence of baseball lift and drag on spin," Proc IMechE Part P. PDF inreferences/LyuDragLiftSpin.pdf.NathanLiftModel: bilinearCl(S) = 1.5·SforS<0.1and0.09 + 0.6·SforS≥0.1from Sawicki, Hubbard & Stronge (2003), "How to hit home runs," Am. J. Phys. 71:1152–1162. Validated by Nathan (2008), Am. J. Phys. 76:119–124 (PDF:references/ajpfeb08.pdf). The four-pitch deflection regression in Nathan's Table I is checked intests/test_aero_nathan.py.SimpleMagnusModel: pedagogicalCl = min(a·S, cl_max)(Watts & Ferrer 1987 baseline). Kept as a transparent comparison.
What's deferred
- Statcast import and seam-shifted-wake toy model (v0.3)
- Numba / JAX backends, parameter fitting (v0.4+)
See SPEC_DRAFT.md for the full design rationale and roadmap.
Important caveat
pitchphys is an educational point-mass trajectory simulator. It uses empirical aerodynamic models and simplified force terms. Real baseball flight depends on ball variation, seam geometry, atmospheric conditions, release conditions, spin axis, active spin, and non-Magnus effects such as seam-shifted wake. Use it to learn, explore, and prototype — not as a definitive pitch-design engine.
Coordinate conventions (catcher view)
12 (backspin)
|
9 ----+---- 3 (clock-tilt notation; degrees clockwise from 12)
|
6 (topspin)
For a right-handed pitcher:
- 12:00 → spin axis along +x (catcher's right) → upward Magnus on a +y-traveling pitch
- 6:00 → axis along -x (topspin)
- 3:00 → axis along -z (sidespin)
- 9:00 → axis along +z (sidespin)
throwing_hand="L" mirrors clock tilt across the 12-6 axis. World-frame axes: x = catcher's right, y = toward home plate, z = up.
License
MIT.
Release files for pitchphys 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pitchphys-0.2.0.tar.gz | 270.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pitchphys-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 329.2 kB
Release files / pitchphys-0.2.0.tar.gz
| Download URL | pitchphys-0.2.0.tar.gz |
|---|---|
| Size | 270.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d9ed67d7058c616b0f3681b526957aa38954c8d3a847f6945e3e2b0e353d6f4f
|
|
BLAKE2b-256 checksum How to use checksums |
57d2fa65279d622f2d76faeaeea47db774d14e973256c2ce5ed444843d46a153
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 11, 2026.
Transparency logRelease files / pitchphys-0.2.0-py3-none-any.whl
| Download URL | pitchphys-0.2.0-py3-none-any.whl |
|---|---|
| Size | 58.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
43a3f42032dbce120ab55d58521a522eb3ee7318165c62c8f64200dc9879a9c0
|
|
BLAKE2b-256 checksum How to use checksums |
cf55c93573303ff767159f07d7d7621b2608884b1722d541dc8a5764f8e45b6c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 11, 2026.
Transparency log