Educational Python simulator for baseball pitch aerodynamics, spin, drag, Magnus force, and pitch movement.
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pitchphys-0.2.0.tar.gz.
File metadata
- Download URL: pitchphys-0.2.0.tar.gz
- Upload date:
- Size: 270.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9ed67d7058c616b0f3681b526957aa38954c8d3a847f6945e3e2b0e353d6f4f
|
|
| MD5 |
f943a4936591d7bf647fe19c111ebd55
|
|
| BLAKE2b-256 |
57d2fa65279d622f2d76faeaeea47db774d14e973256c2ce5ed444843d46a153
|
Provenance
The following attestation bundles were made for pitchphys-0.2.0.tar.gz:
Publisher:
release.yml on jman4162/pitchphys
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pitchphys-0.2.0.tar.gz -
Subject digest:
d9ed67d7058c616b0f3681b526957aa38954c8d3a847f6945e3e2b0e353d6f4f - Sigstore transparency entry: 1501018967
- Sigstore integration time:
-
Permalink:
jman4162/pitchphys@886b92f7f2b504339b8167b73af8e1540fc3292f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jman4162
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@886b92f7f2b504339b8167b73af8e1540fc3292f -
Trigger Event:
push
-
Statement type:
File details
Details for the file pitchphys-0.2.0-py3-none-any.whl.
File metadata
- Download URL: pitchphys-0.2.0-py3-none-any.whl
- Upload date:
- Size: 58.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43a3f42032dbce120ab55d58521a522eb3ee7318165c62c8f64200dc9879a9c0
|
|
| MD5 |
ae93a42c5a2c2a8c33f486778767df93
|
|
| BLAKE2b-256 |
cf55c93573303ff767159f07d7d7621b2608884b1722d541dc8a5764f8e45b6c
|
Provenance
The following attestation bundles were made for pitchphys-0.2.0-py3-none-any.whl:
Publisher:
release.yml on jman4162/pitchphys
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pitchphys-0.2.0-py3-none-any.whl -
Subject digest:
43a3f42032dbce120ab55d58521a522eb3ee7318165c62c8f64200dc9879a9c0 - Sigstore transparency entry: 1501019058
- Sigstore integration time:
-
Permalink:
jman4162/pitchphys@886b92f7f2b504339b8167b73af8e1540fc3292f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jman4162
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@886b92f7f2b504339b8167b73af8e1540fc3292f -
Trigger Event:
push
-
Statement type: