Environment and weather¶
The Environment dataclass holds air density, dynamic viscosity, gravity, and wind. It's what simulate(pitch, env=env) reads to compute drag and Magnus forces. Three constructors cover the common cases.
Sea-level standard¶
from pitchphys import Environment
env = Environment.sea_level()
# Environment(air_density_kg_m3=1.225, dynamic_viscosity_pa_s=1.81e-5,
# gravity_m_s2=9.80665, wind_m_s=array([0., 0., 0.]))
This is the default if you don't pass env to simulate(). ISA standard atmosphere at 15 °C, sea-level pressure.
Coors Field¶
Denver's ~5,200 ft elevation gives ~18% less air density than sea level. A fastball at Coors loses less speed to drag (plate speed ~88 mph vs ~86 mph at sea level for a 95 mph release) AND has less Magnus force (since both drag and Magnus scale linearly with density). The net effect on IVB is a wash for fastballs but matters a lot for breaking pitches.
This is a hardcoded approximation. For exact local conditions, use from_weather below.
From weather¶
env = Environment.from_weather(
temp_c=25.0, # game-time temperature
pressure_pa=98_000.0, # actual atmospheric pressure
humidity=0.6, # relative humidity (0..1)
)
Builds an Environment from real weather data. Uses:
- Ideal-gas law for dry-air density: \(\rho_d = P_d / (R_d T)\)
- Tetens equation for water-vapor saturation pressure: \(P_\text{sat}(T) = 610.78 \, e^{17.27 T_c / (T_c + 237.3)}\) Pa
- Sutherland's law for dynamic viscosity vs temperature: \(\mu(T) = \mu_\text{ref} (T / T_\text{ref})^{1.5} (T_\text{ref} + S) / (T + S)\)
The humid-air density is a mixture of dry-air and water-vapor partial pressures.
Counter-intuitive fact: humid air is LESS dense than dry air¶
Most people guess that humid air is "thicker" — it isn't. Water vapor has molar mass ~18 g/mol, while the nitrogen/oxygen mix that makes up most of dry air averages ~29 g/mol. So replacing dry-air molecules with water-vapor molecules at the same pressure and temperature lowers the average molar mass and therefore the density. At 30 °C and sea-level pressure, going from 0% to 100% relative humidity drops the density by ~1%.
dry = Environment.from_weather(temp_c=30.0, pressure_pa=101_325.0, humidity=0.0)
wet = Environment.from_weather(temp_c=30.0, pressure_pa=101_325.0, humidity=1.0)
print(f"Dry: {dry.air_density_kg_m3:.4f} kg/m³")
print(f"Humid: {wet.air_density_kg_m3:.4f} kg/m³")
# Dry: 1.1644 kg/m³
# Humid: 1.1517 kg/m³ (~1% less)
A 1% drop in air density means ~1% less drag and ~1% less Magnus break — small, but not zero. Hot, humid weather (a typical August game) gives slightly less break than cold, dry weather.
Temperature sensitivity (constant pressure)¶
Higher temperature → lower density (ideal gas). Sutherland's law also bumps viscosity slightly, but the density effect dominates for trajectory work.
| Temp | Density (kg/m³) | Δ vs 15 °C |
|---|---|---|
| 0 °C | 1.290 | +5.3% |
| 15 °C | 1.225 | (ref) |
| 25 °C | 1.184 | −3.4% |
| 35 °C | 1.146 | −6.4% |
Pressure sensitivity (constant temperature)¶
Density is linear in pressure. Coors Field (~83 kPa) is at 82% of sea-level pressure, so density ~82% of sea-level value (~1.00 kg/m³). High-pressure systems give slightly more drag than low-pressure systems.
Wind¶
import numpy as np
env = Environment(
air_density_kg_m3=1.225,
dynamic_viscosity_pa_s=1.81e-5,
gravity_m_s2=9.80665,
wind_m_s=np.array([0.0, -5.0, 0.0]), # 5 m/s headwind
)
Wind enters drag through the relative velocity: \(\mathbf{v}_\text{rel} = \mathbf{v}_\text{ball} - \mathbf{v}_\text{wind}\). A 5 m/s (~11 mph) headwind makes the ball "feel" 5 m/s faster, scaling drag by ((v + 5) / v)^2.
Vertical wind components (the vz axis) are physically unusual but technically supported. A crosswind component (vx) creates an asymmetry that the Magnus force on a vertical-spin pitch wouldn't otherwise have.
Putting it all together¶
The Drag + Environment page of the Streamlit app lets you toggle gravity / drag / Magnus on or off independently while varying temperature, pressure, humidity, and wind — a useful way to build intuition for how each environmental knob shifts the trajectory.