ball op• Data kinds: signal × points × table → table
• Call: import fullseye as fs; fs.ledger.fit_spin_aero(t, p, bp: 'dict', *, omega0=None, fit_cl: 'bool' = False, omega_max: 'float' = 1000.0, iters: 'int' = 25, dt: 'float' = 0.001) -> 'dict' (to call the implementation directly, import ballistics; ballistics.fit_spin_aero(t, p, bp: 'dict', *, omega0=None, fit_cl: 'bool' = False, omega_max: 'float' = 1000.0, iters: 'int' = 25, dt: 'float' = 0.001) -> 'dict'; from the registry, opsdrive.get("fit_spin_aero"))
• GPU: none (CPU reference implementation only)
Reads drag and spin together (fit_spin with a free C_d): fits the drag + Magnus equations of motion to a trajectory (t_i, p_i) (an interval without bounces) with the 10 parameters (p₀, v₀, ω, C_d) by Levenberg–Marquardt (fit_cl=True frees the lift coefficient scale κ too, for 11 parameters).
The model C_d is a constant (no Re dependence is fitted); the lift is the spin-ratio empirical formula C_L = κ·S/(1 + 2S) (S = rω/|v|) with the scale κ at 1 by default. ★A bp with a numeric (constant) C_L is rejected (ValueError): the Magnus force of a constant C_L has a magnitude independent of ω, so the |ω| column vanishes and the spin magnitude cannot be read (measured: fit_spin with a constant C_L, given a trajectory of a true 150 rad/s, moves |ω| only to 10.3 rad/s).
What cannot be read is reported in the return value: drag is along −v and Magnus along ω × v (perpendicular to v), so the force directions separate orthogonally, but (a) with a short flight in which the speed hardly drops, C_d is entangled with v₀, (b) ω and the lift scale κ cannot be separated when the spin ratio S varies little over the flight (where S is small C_L ≈ κS, so the force is close to the product κ·ω, and for a slow ball with S = 0.49-0.56, where S moves by only 14 %, C_L(S) is effectively seen at a single point), and (c) the component of ω parallel to v produces no force. These show up in cond (condition number of the scaled Jacobian), corr (correlation matrix of the estimates, in the order of param_names), cov_unit (covariance of the estimates per 1 m of position noise = the linearised Cramér–Rao bound; multiply by σ² for noise σ [m]), corr_cd_omega (correlation of C_d with the magnitude of ω⊥), corr_cd_v0 (correlation of C_d with the magnitude of the initial velocity), omega_axial_std_unit (standard deviation of the component of ω parallel to v, per 1 m of noise) and omega_perp_std_unit (standard deviation of each of the two perpendicular components of ω). When it is readable: in the linearisation of the same model, the ω⊥ standard deviation with fit_cl=True (noise 1.5 mm) is 628 rad/s (419 %) at v₀ = 6 m/s, 76 (50 %) at 15 m/s and 35 (12 %) at 25 m/s with ω = 300 rad/s: the degeneracy is a conclusion for conditions where S barely moves, not "unreadable from the trajectory in principle"; fast balls and flights that decelerate a lot can be read. When the rank of the Jacobian drops (e.g. in vacuum) cond is None, not inf, and rank_deficient is True. corr_cd_omega is None when ω⊥ = 0 (no direction; NaN is never returned). Return value {"p0", "v0", "omega", "omega_perp", "cd", "cl_scale", "rms", "cond", "rank_deficient", "corr", "param_names", "cov_unit", "corr_cd_omega", "corr_cd_v0", "omega_axial_std_unit", "omega_perp_std_unit", "iters", "t0", "at_bound"}. A trajectory made with the same model is recovered to 1e-6 (gate). ★If the world is a different model (C_d depends on Re, C_L has a dip), the constant-fitted C_d, ω and C_L become effective values over the interval and are biased (the bias is measured in the PoC). The bias is not specific to a dip: when the fitted C_L model misses the world's coefficient, ω is dragged along, amplified 1.5 to 2.7 times the coefficient error (in a world without a dip, scaling the coefficient by 0.6 / 0.8 / 1.2 makes the κ = 1 fit miss ω by 58 / 34 / +53 %). Whether the fit is off shows in rms. omega_max is the same as in fit_spin (upper bound on the spin magnitude). Convergence is slow when omega0 is far from the truth, the trajectory is short or fit_cl=True, and some samples do not reach iters on the flat valley (if the returned iters equals the argument, the run was cut off).
Limits: with fit_cl=True the |ω| and κ columns are nearly parallel when the spin ratio S varies little over the flight (cond large, corr ≈ ±1; a slow ball with S = 0.49-0.56 gives an ω⊥ standard deviation of 419 % of ω, a fast one at v₀ = 25 m/s gives 12 %) — then read ω from the ball's pattern (balltrack.spin_from_markers) and take the lift scale from the trajectory instead (fit_aero with ω known). A constant-C_L bp and fewer than 12 samples raise ValueError; a rank-deficient Jacobian returns cond = None (never inf) and corr_cd_omega = None when ω⊥ = 0.
• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).
• Operator provenance and references — the sources of the research/methods this op family came from.
• The canonical algorithm (author, year) and its uses are named in the family usage guide above.
• poc_cd_spin_joint — py -3.11 examples/poc_cd_spin_joint.py
table as input)course_layout · course_occupancy · course_contains · world_build · world_camera · world_move · lidar_scan · rss_longitudinal_same
ball)ball_params · impact_params · flight_vacuum · flight_ode · flight_simulate · flight_state_at · magnus_lift_coefficient · drag_coefficient_sphere
*Provenance: ballistics.py — DRIVE operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.