Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
3.1.0b1 - 2026-10-06¶
Added¶
- Python 3.15 support: the
cp312-abi3wheels cover it, and a free-threadedcp315twheel is built alongsidecp314t. Android (cp315-android_*) and Pyodide (cp315-pyodide_*) wheels are added to the build targets too. CI runs the RK4 engines on 3.15 and 3.15t. The Stable ABI for free-threaded builds (abi3t, PEP 803) is not used yet, as Cython does not support it in a release (cython/cython#7399).
Removed¶
- Breaking: Python 3.11 is no longer supported; the minimum is now Python 3.12 (
requires-python = ">=3.12"). The stable-ABI wheels ofpy_ballisticcalc.extsare now built againstPy_LIMITED_API=0x030C0000and taggedcp312-abi3-*instead ofcp311-abi3-*. Thetyping_extensionsdependency is dropped (typing.overrideis used).
Changed¶
- The six Cython integration engines (Euler, RK4, Velocity Verlet, Cash-Karp, Dormand-Prince, Tsitouras) now live in a
single extension module,
py_ballisticcalc_exts.engines, instead of one module each. The type stubs are merged intoengines.pyi. Import the classes frompy_ballisticcalc_extsas before; the per-engine submodules (py_ballisticcalc_exts.rk4_engine, etc.) no longer exist. The unusedstd::function::target[T]()declaration is removed frombase_engine.pxd. py_ballisticcalc.extsis now compiled with C++ exceptions disabled (-fno-exceptions,/EHs-c-on MSVC) and-O3on GCC/Clang, matching bclibc's noexcept API. The.pxddeclarations usenoexceptinstead ofexcept +, and a fewlibcppcalls that made Cython emittry/catchwere replaced (vectoriteration by index;reserve/resize/emplace_backvianoexceptshims instd_vector.pxd;std::sin/std::cos/std::isfinite/std::numeric_limits<double>::quiet_NaN()via a sharednoexceptstd_math.pxdinstead oflibcpp.cmath,libc.math.NANand Python'smath.isfinite). RTTI is disabled too (-fno-rtti,/GR-): the Cash-Karp/Dormand-Prince/Tsitouras engines now hold their integrator as acdefattribute and giveintegrate_funcastd::reference_wrapperto it (std_functional.pxd) instead of recovering it viastd::function::target<T>(), which needs RTTI. Behavior change: an allocation failure (std::bad_alloc) in these paths now aborts instead of raisingMemoryError.
3.0.0 - 2026-10-03¶
Stable release, covering the whole arc from 2.3.1 through 3.0.0-rc.4. Highlights below; full details are in
the beta/rc entries beneath this one. Built against bclibc v2.0.0,
itself the stable release of the same noexcept/BCLIBC_Result rework rc.4 already pinned.
Changed¶
- New entry point model. Engines register in groups
py_ballisticcalc.engines.<engine>(python,cython,scipy) and are selected as<engine>+<method>/<engine>.<method>(e.g.Calculator(engine="cython+rk4")). The old flatpy_ballisticcalcgroup (euler_engine,cythonized_*_engine, ...) still loads, but now emits aDeprecationWarning. - BREAKING:
HitResultreads fromrecords, not the oldtrajectorytable.__len__/__iter__/__getitem__,dataframe()andplot()now walk the exact chronological event stream (every ZERO/MACH/APEX/MRT row in its own position) instead of folding event flags onto the nearest scheduled sample. The old fixed-cardinality presentation view is still available, renamed tosamples;HitResult.trajectoryis kept as a deprecated alias forrecords. py_ballisticcalc.exts's Cython bindings follow bclibc's exception-freeBCLIBC_Result/BCLIBC_ErrorAPI; thetry/except +shims are gone, replaced by oneraise_engine_error(). Python-visible exception types and messages are unchanged.bclibcpinned through its whole noexcept rework, up to v2.0.0.
Added¶
- Three new adaptive Cython engines:
cython+rkck(Cash-Karp),cython+dopri(Dormand-Prince 5(4)) andcython+tsitouras(Tsitouras 5(4)) — each owns its own tolerances/step counts per instance (no shared thread-local state), 2-14x faster thancython+rk4on the standard benchmark shot depending on method and call. - SciPy factory engines (
scipy+rk23/rk45/dop853/radau/bdf/lsoda) viaSciPyIntegrationEngineFactory. examples/tiny_bclibc_ctypes: single/double-precision engines driving bclibc'stiny_bclibcC engine over ctypes, including a filtered-trajectory-streaming API so filtering and derived fields run in the compiled library, not per-step in Python.
Fixed¶
HitResult.samplesno longer folds an event's flag onto the nearest scheduled sample unconditionally — only onto one judged the same instant within a relative tolerance, so a coarse sampling schedule no longer produces nonsensical combined flags on an unrelated row.BaseIntegrationEngine._zero_angle's per-iteration convergence check now honors a configuredcZeroFindingAccuracyinstead of a hardcoded constant two lines above it.interpolate()/index_at_distance()/get_at_distance()/get_at_time()now search the exactrecordsstream instead of the old presentation table, so they can no longer miss or misattribute an event-only row.
Removed¶
examples/tiny_bclibc_wasm, moved to the standalone tiny-bclibc-wasm-py package, which now registers and tests its own engines.
3.0.0-rc.4 - 2026-10-01¶
Changed¶
py_ballisticcalc.exts: the Cython bindings follow bclibc's new exception-free API (BCLIBC_Result<T>/BCLIBC_Errorinstead of C++ exceptions). The inline C++ shims in the.pxdfiles (py_engine_*,py_traj_seq_*,PyEngineError, the per-enginepy_*_set_*_toleranceand theBCLIBC_ShotProps_*wrappers ofpy_bind.cpp) are gone: the bclibc methods are declared directly, asnoexcept, in the.pxdfiles, and a failure is raised in one place,raise_engine_error()(exceptions.pxd). The newresult.pxddeclaresBCLIBC_Result/BCLIBC_Error. Python-visible behaviour is unchanged:OutOfRangeError,ZeroFindingError,InterceptionErrorandSolverRuntimeErrorcome from the solver payloads, every other error maps exactly as Cython's defaultexcept +did (ValueError,IndexError,RuntimeError), and the fixed per-call types are kept (ZeroDivisionErrorforinterpolateandupdate_stability_coefficient,IndexErrorforBaseTrajSeq[...],ValueErrorfordrag_by_mach).- Pin
bclibctov2.0.0-rc.4, its exception-free (noexcept) revision (ballistics-lab/bclibc#40), further bumped to pick upBCLIBC_Error's payload accessor rename topayload<P>()(it collided with Python'siskeyword) and a default-constructibleBCLIBC_Resultfor Cython (ballistics-lab/bclibc#41).
3.0.0-rc.3 - 2026-09-25¶
Chores¶
- bump to
bclibc@v2.0.0-rc.3
Removed¶
examples/tiny_bclibc_wasm: moved to tiny-bclibc-wasm astiny_bclibc.pybc. That package now registers the engines itself (tiny_bclibc_wasm+tsitouras-dp,tiny_bclibc_wasm+tsitouras-sp) and tests them with this project's suite, so py-ballisticcalc no longer depends on it, and its CI no longer runs the wasm engine.
3.0.0-rc.2 - 2026-09-23¶
Added¶
examples/tiny_bclibc_wasm:TinyBclibcWasmDoubleIntegrationEngine/TinyBclibcWasmSingleIntegrationEngine, the WebAssembly counterpart ofexamples/tiny_bclibc_ctypes, made for Pythonista on iOS. It runs bclibc's import-free tiny_bclibc.wasmbuild (tiny_bclibc/build_wasm.sh) inside a JavaScript engine: JavaScriptCore'sJSContextviaobjc_utilon iOS, WebKitGTK's JavaScriptCore via PyGObject on Linux (run_example_jsc.py), or Node. The same JS glue runs on all three hosts, and one engine call is one JS round trip. The double-precision engine passes the full suite and returns results bit-identical to the native ctypes engine; the single-precision one has the same known float32 exceptions as its ctypes twin. Building the modules needs a bclibc submodule that includestiny_bclibc/build_wasm.sh.
3.0.0-rc.1 - 2026-09-22¶
Fixed¶
cython.rkck/cython.dopri/cython.tsitouras: bumped thebclibcsubmodule to pick up itsBCLIBC_CashKarpIntegrator/BCLIBC_DormandPrinceIntegrator/BCLIBC_TsitourasIntegratorclasses, replacing the old thread-local tolerance/step-count API (BCLIBC_cashKarpSetRelativeTolerance/BCLIBC_cashKarpGetStats/etc.). Each engine instance now owns its own integrator (obtained viastd::function::target()onceintegrate_funchas taken a copy of it), instead of every instance of the same adaptive engine on a thread sharing onethread_localtolerance/step-count pair — a latent bug that could silently mix up tolerances or accepted/rejected counts between engine instances run concurrently on the same thread (these modules are allfreethreading_compatible). No change to the public Python API:relative_tolerance,absolute_tolerance, andget_step_stats()behave the same as before.Calculator._EngineLoader._is_legacy_namenow@functools.caches like its sibling lookups (_get_entries_by_group,_get_engine_entries,_load_by_name) instead of being the one method in that class that re-walkediter_engines()(generator,seen-set, per-entryengine_id()) on every call — wasted, repeated work when manyCalculatorinstances are created in a loop with the sameengine=string, sinceentry_points()itself doesn't change within a process.vector.py/unit.py: removed 15 of 22# type: ignore[override]comments on arithmetic dunder overrides (Temperature.__mul__/__rmul__/__truediv__/__rtruediv__/__imul__/__itruediv__/_units_to_raw_delta/__radd__/__rsub__,Vector.__radd__/__iadd__/__sub__/__isub__/__imul__/__neg__) that no longer suppress any pyright diagnostic — confirmed by removing each individually and re-runningpyrightclean. The remaining 7 (Temperature.__add__/__sub__/__iadd__/__isub__,Vector.__mul__/__add__/__rmul__) do still suppress a realreportIncompatibleMethodOverride(theirNumber/Vectorparameter types are narrower thantuple's/GenericDimension's own) and were left in place.
CI¶
.pre-commit-config.yaml: theuv-lock/uv-lock-extshooks now runuv lock --checkinstead ofuv lock --upgrade.--upgradere-resolved to whatever was newest on PyPI at CI run time, so a transitive dependency (not even a direct one —platformdirs/virtualenv, pulled in bypre-commit/uvthemselves) getting a new release between commits failedpre-commit.yml's push-triggered runs with no code change involved (its auto-commit-fixes step only runs onpull_requestevents, so apushrun just fails outright — see bclibc-functors CI run 35705018507 for an example).--checkonly verifiesuv.lockstill matchespyproject.toml's own constraints; routine dependency bumps are already Dependabot's job (.github/dependabot.yml, weekly).
3.0.0-beta.3 - 2026-09-21¶
Added¶
- New entry point model: engines are registered in groups
py_ballisticcalc.engines.<engine>(python,cython,scipy) with the integration method as the entry point name, and are selected with<engine>+<method>or<engine>.<method>(e.g.Calculator(engine="cython+rk4"),"scipy+dop853"). The direct<module>:<factory>path is still supported, including the call form"py_ballisticcalc:SciPyIntegrationEngineFactory(method=DOP853)". SciPyIntegrationEngineFactory(method)and ready-madeSciPy<Method>IntegrationEnginefactories (RK23, RK45, DOP853, Radau, BDF, LSODA) inpy_ballisticcalc.engines.- Typed configs
DormandPrinceEngineConfigandTsitourasEngineConfig(type stubs) alongsideCashKarpEngineConfig; all three adaptive Cython engines now expose the same stub API. scripts/bench_report.py: builds the benchmark page frombenchmarks/benchmarks.csv— grouped log-scale bar charts of mean time and of speedup vspython.rk4(docs/concepts/bench.svg,docs/concepts/bench_speedup.svg) plus a results table (docs/concepts/bench.md, added to the docs nav).- Benchmarks for all
scipy+…methods (RK23, RK45, DOP853, Radau, BDF, LSODA),python+eulerandpython+verlet.
Changed¶
_EngineLoader.iter_engines()deduplicates entries by target; new_EngineLoader.engine_id(ep).scripts/benchmark.py,examples/performance_check.py, tests and CI use the new engine names.README.mdanddocs/: engine tables show the per-engine speed for the new names, with a separate row and description for everyscipy+…method and a per-engine "Tests" badge column; the engine test badges above the table were moved into it.- Engine speed figures in the tables were re-measured (Find Zero / Trajectory vs
python+rk4): Cython adaptive engines 2567x / 326x,scipy+rk455.4x / 9.8x, and so on.cython+verletis not re-measured yet. - SciPy engine: cut Python-side overhead per call without changing tolerances (no per-call
Velocityobject inget_density_and_mach_for_altitude, safeguarded Newton instead ofbrentqfor the range-step interpolation, plain math instead of numpy in the terminating events, scalar indexing indiff_eq,warnings.simplefilterapplied once at import). A 2000 m RK45 trajectory takes ~11 ms instead of ~19 ms. As a side effect DOP853's zero-angle solver no longer stalls just abovecZeroFindingAccuracy, which removes the repeatedFailed to find zero angle using Newton methodwarnings.
Deprecated¶
- The legacy flat entry point group
py_ballisticcalc(euler_engine,rk4_engine,scipy_engine,cythonized_*_engine, ...). Loading an engine by these names emits aDeprecationWarning.
3.0.0-beta.2 - 2026-09-21¶
Added¶
cythonized_tsitouras_engine(py_ballisticcalc_exts.CythonizedTsitourasIntegrationEngine): a compiled Tsitouras 5(4) ("Tsit5") adaptive engine, wrapping bclibc's newBCLIBC_integrateTsitouras. Structurally identical tocythonized_dopri_engine(same 7-stage FSAL shape, same SciPy RK45-style component scaling and controller); coefficients verified againstARKODE_TSITOURAS_7_4_5in SUNDIALS/ARKODE. Measured againstcythonized_rkck_engineandcythonized_dopri_engineacross a sweep of shot profiles: accepted+rejected step counts come out within 1-2 steps of each other for smooth, well-conditioned trajectories atrtol=atol=1e-6— no consistent win, despite Tsitouras' smaller leading truncation-error coefficient.
Changed¶
- Bumped the
bclibcsubmodule to pick up its own newBCLIBC_integrateTsitouras, an FFI unknown-method-fallback fix (unrecognized/out-of-range method now falls back to RK4, not Euler), andtiny_bclibc's internal switch from Cash-Karp to Tsitouras as its baked-in adaptive core (examples/tiny_bclibc's two engines pick this up automatically). See bclibc's CHANGELOG for details. Further bumped to pick up a fix aligningtiny_bclibc's per-stage Tsitouras accumulation order with the C++ engine's (identity-test diffs on a simple shot now at the double-precision noise floor, down from ~1e-9) --TinyBclibcSingleIntegrationEngine's docstring here has been updated to match (two new single-precision-only marginal test failures,test_wind_lag_rule/test_multiple_wind, same precision-floor class as the rest of that list). Bumped once more to pick up a genuine fix (not just closer rounding) fortest_hitresult.py::test_flags, previouslyTinyBclibcDoubleIntegrationEngine's one known failure:tiny_bclibc's MACH-crossing interpolation linearly interpolated the mach ratio itself instead of reconstructing it from Hermite-derived velocity and a linearly-interpolated speed of sound like the C++ engine does, costing real accuracy right at MACH crossings (worst in the transonic region).test_flagsnow passes underTinyBclibcDoubleIntegrationEngine; its docstring's known-issue note has been removed.
3.0.0-beta.1 - 2026-09-17¶
Changed¶
- Pin
bclibctov2.0.0-beta.7, including the zero-point result API and its corrected WASM export metadata. cythonized_rkck_engine: Cash-Karp's adaptive error controller now hasscipy.integrate.solve_ivpsemantics.absolute_toleranceis a single scalar (default1e-6) used independently for all six position/velocity state components; it replaces the former hidden, unequal position and velocity floors. Each component usesatol + rtol * max(abs(y), abs(y_new)), and the controller accepts/rejects steps using the RMS of those six scaled errors.relative_toleranceremains scalar and defaults to1e-6.
Added¶
cythonized_dopri_engine, a compiled Dormand--Prince 5(4) adaptive engine. Its component scaling and controller factors follow SciPy RK45, whilecythonized_rkck_enginedeliberately retains its historical controller.cythonized_rkck_engine(py_ballisticcalc_exts.CythonizedCashKarpIntegrationEngine): a Cython engine wrapping bclibc's new Cash-Karp adaptive RK45 integrator (Numerical Recipes'rkck, embedded 4th/5th-order error estimate). Grows its internal step up to 64x the configured base step during smooth flight and shrinks it on error-estimate rejection, needing far fewer accepted steps than fixed-step RK4 for comparable accuracy — benchmarked at 2.1x (Trajectory) / 14.3x (Zero, which integrates repeatedly per Newton iteration, so per-call step-count savings compound) faster thancythonized_rk4_engineon the standard 2000m G7 benchmark shot. Exposes arelative_toleranceconfig option (default1e-6, empirically Pareto-optimal on the one shot profile measured so far — see the engine's__init__docstring andtests/test_cashkarp.py::test_cashkarp_accuracy_across_tolerancesfor the data; height/ velocity accuracy plateaus below this, but event-root distance accuracy is not monotonic in tolerance) and aget_step_stats()method returning this instance's own(accepted, rejected)step counts from its most recentintegrate()call.examples/tiny_bclibc/: single- and double-precisionBaseIntegrationEnginesubclasses (TinyBclibcSingleIntegrationEngine/TinyBclibcDoubleIntegrationEngine) driving bclibc'stiny_bclibcC99 engine via ctypes andtiny_bclibc_integrate_stream, a filtered-trajectory-streaming API added totiny_bclibcfor this purpose: both the integration and its range-step/APEX/MACH/ZERO filtering and derived-field computation run in the compiled library, with Python's callback firing once per output row rather than once per raw integration step (atiny_bclibc_integrate_rawraw-per-step variant was tried first and is ~20-34x slower — kept intiny_bclibcas a small, generically useful primitive, but not used by these engines).tiny_bclibc_integrate/_streamnow runtiny_bclibc's own Cash-Karp adaptive core (seebclibc's CHANGELOG) rather than fixed-step RK4;zero_angle/find_apex/find_max_rangeare stillBaseIntegrationEngine's own unmodified Python implementations, repeatedly calling_integrate.CMakeLists.txtbuilds both precisions from thebclibcgit submodule already vendored for the Cython engine atpy_ballisticcalc.exts/py_ballisticcalc_exts/external/bclibc. Running both engines against the full pytest suite separates precision effects from logic bugs: the double-precision engine passes the entire suite identically tork4_engine/cythonized_rk4_engine(373 passed, 8 skipped); the single-precision engine differs on 13 of 381 tests, all traceable to float32's representable precision (8 comparetiny_bclibc_integrate_stream'srange_limit_ft/range_step_ftrequest fields, themselvesreal_t, against a double-precision-tight tolerance; the remainder compare values within a few ULPs of float32's precision floor, including two whose adaptive step-size selection is itself sensitive to sub-ULP differences between two numerically-equivalent drag models — seeTinyBclibcSingleIntegrationEngine's docstring for the verified mechanism behind each). Not wired intopy_ballisticcalc's own entry points/public API — it depends on a natively-compiled library the package does not ship or build itself.
Changed¶
HitResult.records/_common.py's_sort_rows(formerly_coalesce_rows): a scheduled RANGE-step sample and a physical event (ZERO/MACH/APEX) that happen to fall at nearly the same instant are no longer merged into one row with combined flags — they are kept as independent records, only re-sorted into chronological order (each was already correctly positioned relative to its own neighbors; only cross-interval ordering needed restoring).HitResult.samples's own "annotate the closest scheduled sample with each event's flag" projection already performs the equivalent view generically from independent records (seedocs/concepts/trajectory_data.md), so pre-merging here duplicated — and could conflict with — that step. Matches the same fix applied toTrajectoryDataFilter.record_stepand bclibc's C++BCLIBC_TrajectoryDataFilter::handle_step(project issue #350).-
BREAKING:
HitResult.__len__/__iter__/__getitem__,dataframe(), andplot()now read fromrecords(the exact chronological stream) instead of the oldtrajectorypresentation table. Concretely:len(hit_result),for row in hit_result, andhit_result[i]now include every exact ZERO/MACH/APEX/MRT event row in its own chronological position, rather than folding its flag onto the nearest RANGE-scheduled sample. Renamed the oldtrajectorypresentation view (fixed cardinality tracking the requested RANGE/TIME schedule regardless of solver internals — seetest_cashkarp_tolerance_controls_adaptive_step_count) tosamples, so it stays available under an explicit name for anyone comparing output across engines or tolerances.HitResult.trajectoryitself is kept as a deprecated alias forrecords(see Deprecated below) so existing attribute access keeps working, just with a different row count/order than before if event flags were requested; code that indexed.trajectoryassuming the fixed RANGE/TIME schedule should switch to.samples. -
HitResult.base_data(pure-Python engines:euler/rk4/velocity_verlet) is a flatlist[BaseTrajData]again — one entry per accepted-step point, matching both the pre-records/samples/eventsshape and bclibc's ownBCLIBC_BaseTrajSeq(also a flat buffer of points). It had briefly becomelist[TrajectoryStep](each step'sstart/endpair materialized up front) during this same unreleased work —TrajectoryStepis a computational helper for one interval, not a storage shape, and pre-pairing every entry duplicated each interior point across its neighboring pairs for no benefit over building aTrajectoryStepon demand fromzip(base_data, base_data[1:])at query time, whichHitResult.interpolate()/get_at()now do.TrajectoryDataFilter.record_step()similarly takes(start, end)directly instead of a pre-builtTrajectoryStep, constructing one internally; callers (the three pure-Python engines) no longer need to importTrajectoryStepat all. Cython/dense_outputengines are unaffected — they already exposed their ownCythonizedBaseTrajSeq(a flat buffer with its ownget_at()), neverTrajectoryStep. HitResult.__init__drops thetrajectory=keyword-only compatibility spelling introduced earlier in this same unreleased work:recordsis now an ordinary, required positional parameter.HitResultis built internally by the engines (always positionally) and is not meant to be constructed directly by user code, so a back-compat spelling for a former public dataclass constructor had no real caller to protect.
Deprecated¶
HitResult.trajectory: alias forHitResult.recordsretained for source compatibility (emitsDeprecationWarning). Use.recordsfor the exact chronological stream, or.samplesfor the deterministic scheduled-sample table (what.trajectoryused to mean before this release'srecords/samples/eventssplit).
Fixed¶
- fix: pure python
start_from_timeregression py_ballisticcalc/trajectory_data.py:HitResult.samplesannotated the nearest scheduled sample with every event's flag unconditionally, with no check that the two were actually close. With a coarse schedule (e.g.trajectory_step == trajectory_range, leaving only the launch and terminal samples) andflags=TrajFlag.ALL, every event in between — APEX, ZERO, MACH — landed on whichever endpoint bisection preferred, producing one row with a nonsensical combined flag (e.g.ZERO_DOWN|RANGE|APEX) that misrepresented that endpoint's own state as every event's.samplesnow annotates a sample only whenmath.isclose()(using the new_SAME_INSTANT_REL_TOL/_SAME_INSTANT_ABS_TOLconstants) judges the sample and the event to be the same instant to floating-point precision — the genuine case this exists for (e.g. a RANGE sample requested at the same distance a zero was set for, reached by two different numerical paths that agree to solver residual) — rather than merely the closest of however many samples happen to exist. A relative tolerance (not the engines' own tolerance knobs, which use incompatible units — feet forcZeroFindingAccuracy, a state-error rtol forrelative_tolerance, a step-size multiplier forcStepMultiplier, none of them a time tolerance) keeps the check independent of flight-time scale and any specific engine's precision, while staying far tighter than the gap between any two physically distinct trajectory events.samples' cardinality is unaffected either way (it always equals the requested RANGE/TIME schedule's), so cross-engine/tolerance comparisons relying on that (seetest_cashkarp_tolerance_controls_adaptive_step_count) still hold.py_ballisticcalc/trajectory_data.py:HitResult.interpolate(),index_at_distance(),get_at_distance(), andget_at_time()searchedself.trajectory— the presentation table that projects events onto nearby scheduled samples — instead ofself.records, the exact chronological stream. Sincetrajectorycan omit an event-only row (folding its flag onto a neighboring sample instead), these lookups could silently miss or misattribute the point a caller asked for. Also:interpolate()raisedValueErrorfor fewer than 3 bracketing points; it now raisesArithmeticErrorfor consistency with its other failure paths, and additionally falls back to linear interpolation when exactly 2 points bracket the target (previously an unconditional raise).py_ballisticcalc/trajectory_data.py:TrajectoryStep.at_x()now assigns the exact queried downrange target to the result'sposition.xinstead of re-deriving it from the bisection-converged Hermite sample (TrajectoryStep.at_value, whichrecord_step's RANGE-step sampling used directly before this). Invisible at Python's double precision (residual is far below any existing tolerance) — the same pattern intiny_bclibc's C port of this logic measurably missed an exact RANGE-step target once run in single precision (seebclibc's CHANGELOG), so this keeps all three implementations (this one, bclibc's C++, andtiny_bclibc's C99) consistent rather than relying on double precision to hide it here too.py_ballisticcalc/engines/base_engine.py:BaseIntegrationEngine._zero_angle's convergence check inside its damped-Newton iteration loop comparedheight_error_ftagainst the hardcoded module-levelcZeroFindingAccuracyconstant (5e-6ft) instead ofself._config.cZeroFindingAccuracy(already used correctly two lines above, for the initialheight_error_ft, and after the loop, for the final success/failure check) — so a caller who configured a loosercZeroFindingAccuracystill had the loop itself hold out for5e-6ft every iteration. No effect under the default config (5e-6ft either way). Found while addingTinyBclibcSingleIntegrationEngineabove: float32 cannot represent position to5e-6ft at typical zero distances, so_zero_angle's primary method always exhausted its iteration budget and fell back to the ~10-50x more expensive guaranteed method (_find_zero_angle, which itself requires a_find_max_rangegolden-section search first) — fixing this bug (so the configured, looser tolerance is honored throughout) plus givingTinyBclibcSingleIntegrationEnginea1e-3ft default (matchingtiny_bclibc's ownTINY_BCLIBC_SINGLE_PRECISIONzero-finding tolerance) cut aset_weapon_zerocall at 2000m from ~2.2s to ~36ms on the affected shot.
CI¶
.github/workflows/pypi-publish.yml: theCache cibuildwheel/pyodide toolchainstep (actions/cache@v6on~/.cache/cibuildwheel) is no longer Pyodide-only — it now runs for everymatrix.cibw_platformexceptlinux(macOS, Windows, Android also download interpreters/toolchains thatCIBW_CACHE_PATHcovers: CPython/PyPy/GraalPy installers, nuget packages, the Android NDK), keyed permatrix.artifact_keyinstead of one sharedcibw-pyodide-<runner.os>key; Linux is excluded because manylinux/musllinux builds fetch their Docker base images viadocker pull, whichCIBW_CACHE_PATHdoes not cover. The "Build binary python package" step now also setsCIBW_CACHE_PATH: ~/.cache/cibuildwheelexplicitly (kept in sync with the cache step'spath) instead of relying on cibuildwheel's platform-specific default cache locationuv lock --upgrade(bothuv.lockandpy_ballisticcalc.exts/uv.lock) — routine dependency bump surfaced bypre-commit'suv-lock/uv-lock-extshooks once thebclibcsubmodule was initialized locally (cython, ruff, scipy, scipy-stubs, filelock, dependency-groups, pymdown-extensions, pyzmq, vcs-versioning, appnope)
2.3.1 - 2026-07-06¶
Added¶
- Native
py_ballisticcalc.extswheels for Android (cp313-android_*/cp314-android_*,arm64_v8a+x86_64) via a newcibuildwheel[android]-driven CI job —.github/workflows/pypi-publish.yml,[tool.cibuildwheel.android]inpy_ballisticcalc.exts/pyproject.toml(#339) - Native
py_ballisticcalc.extswheels for Pyodide/WebAssembly (cp313-pyodide_*/cp314-pyodide_*,wasm32) viacibuildwheel[pyodide], with its own~/.cache/cibuildwheelcache keyed onpyproject.toml's hash —[tool.cibuildwheel.pyodide]inpy_ballisticcalc.exts/pyproject.toml,.github/workflows/pypi-publish.yml(#340); confirmed against the actual2.3.1b2PyPI release:cp313built against Pyodide xbuildenv0.29.4(wheel tagcp313-cp313-pyemscripten_2025_0_wasm32),cp314against a newer, not-yet-stable xbuildenv (tagcp314-cp314-pyemscripten_2026_0_wasm32) — each Pyodide release pins one exact CPython+Emscripten ABI (seesetup.pycomment below), so a wheel only installs under the matching Pyodide version examples/pyodide/index.html: standalone, browser-only interactive REPL demoingpy_ballisticcalc/py_ballisticcalc.extsrunning entirely client-side under Pyodide — no server, no install. Rebuilds the official Pyodideconsole.htmlterminal (jQuery Terminal +pyodide.console.PyodideConsole, top-levelawait,Ctrl+C, tab-completion) pinned to the Pyodide build matching thecp313wasm32 wheel above, then on load feeds a.338LM/300gr SMK example (micropip.install(..., pre=True)since2.3.1b2is a pre-release →cythonized_rk4_enginezero + 1000 m trajectory) into the live console as a normal, narrated transcript rather than running it silently, leavingcalc/shot/weapon/ammo/resultin scope for further exploration; four<script src>tags moved from<head>to the end of<body>so the loading spinner paints immediately instead of being blocked behind the Pyodide/jQuery Terminal downloadsdocs/pyodide(symlink toexamples/pyodide/,not_in_nav: pyodide/*inmkdocs.ymlto silence the resulting "omitted from nav" warning) and a new "Interactive Web REPL" nav entry linking straight to the rawpyodide/index.htmlpage (deliberately not wrapped in a.mddoc page or an<iframe>: an iframe embed of a full Pyodide runtime was noticeably laggy, and a raw asset is excluded fromsitemap.xml, which keeps Material'snavigation.instant.previewhover-card from trying to render it) —mkdocs.yml,docs/index.md,README.md[![powered by pyodide]][Pyodide]badge inREADME.md, alongside a new (previously unbadged)[![powered by bclibc]][bclibc]— both use a base64-embedded logo (bclibc: its own circular mark; pyodide: the white-on-transparentlogo-quadratic-dark.svgfrom pyodide/pyodide-artwork, chosen over the wordmarklogo-dark.svg/logo-light.svgvariants because shields.io badge logos need a roughly square aspect ratio) so the icon renders standalone without depending on an external stylesheet or the shields.io logo-service's own icon set
Changed¶
py_ballisticcalc.exts/setup.py: cross-compile target detection (IS_EMSCRIPTEN/IS_ANDROID/IS_IOS, viasysconfig.get_platform()rather thanplatform.system(), which reports the host OS under cross-compilation) now also disables the stable-ABI (abi3) build for Android and iOS, not just Emscripten — Android's Bionicdlopenrequires every dependency to resolve to a literallibpythonX.Y.soat load time (no lazy glibc-style symbol resolution), so anabi3wheel only looks version-independent while actually hardcoding a dependency on one CPython point release; confirmed empirically (acp311-abi3-androidwheel built against acp313Android crossenv failed to import on 3.14 withlibrary libpython3.13.so not found); also adds an Emscripten-specific compiler-flags branch (-std=c99/-std=c++11, noCC/CXXoverride sincepyodide-buildalready setsemcc/em++, no-Wl,-strip-allsinceem++'s linker wrapper doesn't reliably support it) (#339).github/workflows/pypi-publish.ymlwheel-build matrix: every entry now carries a descriptivename(shown in the Actions UI as "Build wheels on ubuntu-latest / Pyodide WebAssembly" etc.), anartifact_key(upload-artifact names are nowdist-wheels-<key>instead ofdist-<os>, since Android/Pyodide/Linux-x64 all shareos: ubuntu-latest), and an explicitcibw_platform(--platform ${{ matrix.cibw_platform || 'auto' }}, output moved from./distto./wheelhouseto avoid colliding with the sdist job's./dist); macOS runners bumpedmacos-15/macos-15-intel→macos-26/macos-26-intel(also inpytest-{rk4,cythonized-rk4,scipy}-engine.yml, plus added to thepytest-manual.ymlworkflow_dispatchOS dropdown);SETUPTOOLS_SCM_OVERRIDESenv var (and matchingCIBW_ENVIRONMENT_PASSentry, needed for container-based builds like manylinux which don't otherwise inherit the host env) now setslocal_scheme = "no-local-version"on push/TestPyPI runs, since PyPI/TestPyPI reject local version segments (+g<hash>) that build-only runs keep for traceability (#339).github/dependabot.yml: the two separateuvecosystem blocks (root +py_ballisticcalc.exts/) merged into one block withdirectories: ["/", "py_ballisticcalc.exts/"]; the freed-up per-py_ballisticcalc.extsblock repurposed to annpmecosystem entry watchingpy_ballisticcalc.exts/pyodide/(the pyodide/emscripten cross-build toolchain pulls in node/npm dependencies) (#340).gitignore:**/wheelhouse(newcibuildwheeloutput dir, see above),**/.pyodide_build,**/node_modules/(#340)README.md:[![pyright]]badge replaced by[![Pre-commit]]— pyright has run as a.pre-commit-config.yamlhook via the consolidatedpre-commit.ymlworkflow since2.3.0, so the standalonepyright.ymlbadge/workflow link was already stale; engine-comparison table column widths realigned (padding only, no content change — had gone ragged after2.3.0added thecythonized_verlet_enginerow without matching the other rows' width)
CI¶
.github/workflows/pypi-publish.yml:Cache cibuildwheel/pyodide toolchainstep (actions/cache@v6on~/.cache/cibuildwheel) added for the Pyodide job only — the Pyodide xbuildenv/Emscripten SDK download is the slowest part of that build and doesn't change between runs unlesspy_ballisticcalc.exts/pyproject.tomldoes (#340)
2.3.0 - 2026-07-24¶
Added¶
cythonized_verlet_engine(py_ballisticcalc_exts.CythonizedVelocityVerletIntegrationEngine): compiled counterpart to the existing pure-Pythonverlet_engine, wired up the same way ascythonized_rk4_engine/cythonized_euler_engine(velocity_verlet_engine.pxd/.pyx/.pyi,setup.pysource/dependency registration,py_ballisticcalc_exts/__init__.pyexport,[project.entry-points.py_ballisticcalc]inpy_ballisticcalc.exts/pyproject.toml) against bclibc'sBCLIBC_integrateVELOCITY_VERLET(include/bclibc/velocity_verlet.hpp, released in bclibcv1.1.7); produces identical trajectories toverlet_enginebit-for-bit on a standard test shot and passes the fullpytestsuite (373 passed, 2 skipped) andpy_ballisticcalc.exts/tests; benchmarked at 100x (Trajectory) / 157x (Find Zero) faster thanrk4_engine— added to the engine comparison table inREADME.md/docs/concepts/engines.md.github/workflows/pytest-cythonized-verlet-engine.yml: minimal-matrix CI (ubuntu-latest×3.11, push + PR) forcythonized_verlet_engine, mirroringpytest-cythonized-euler-engine.yml— no full cross-OS/version matrix since it shares its integration/data-structure core withcythonized_rk4_engine(which keeps the full matrix);README.mdbadge added.github/workflows/pytest-manual.yml: addedcythonized_verlet_engineto theengine_nameworkflow_dispatchdropdown (itsoptions:list is hand-maintained, unlikepytest-reusable.yml/ci-helpers.sh, which key off a genericcythonized_*prefix and needed no change);.github/workflows/coverage.ymlalso needed no change — it discovers engines dynamically viaCalculator.iter_engines(), confirmed by simulating itsdiscover-engines/teststeps locally (engine listed,--extra extsinstalled, coverage run passes)
Changed¶
- bclibc submodule bumped to
v1.1.7— adds thevelocity_verletintegration method (see Added) .pylintrc(643 lines, essentially unmodifiedpylint --generate-rcfileoutput) replaced by[tool.pylint.main]/[tool.pylint.design]/[tool.pylint."messages control"]inpyproject.toml; diffed against a freshly generated default rcfile of the same pylint version to isolate the actual overrides (fail-under=9.0vs default9.5,max-positional-arguments=6vs default5,ignore,py-version, and thedisable=list — which, despite reading like--generate-rcfile's own suggested defaults, is not applied unless present in a config file: without it, pylint's score onpy_ballisticcalcdrops from 9.57/10 to 7.89/10); verified byte-for-byte equivalent pylint findings/score before deleting.pylintrc[tool.ruff] extend-exclude(pyproject.toml) gainedpy_ballisticcalc.exts/py_ballisticcalc_exts/external/bclibc—ruff check --fixwas reaching into the vendored bclibc submodule (a separate upstream repo) and rewriting its typing (Optional[X]→X | None, etc.); confirmed[tool.ruff.lint].excludedoes not control file discovery the way top-levelexclude/extend-excludedo (ruff still walked and linted files under it when onlylint.excludewas set), so the fix went into the shared top-level exclude insteadpy_ballisticcalc/interface.py:from py_ballisticcalc import RK4IntegrationEngine→from py_ballisticcalc.engines import RK4IntegrationEngine— the former was a real (if currently-working) circular import:py_ballisticcalc/__init__.pyimportsinterface.py, which imported back from the partially-initializedpy_ballisticcalcpackage itself; it only worked because__init__.pyhappens to import.enginesbefore.interface, leaving the whole package's importability fragile to any future reordering of that file (caught by pylint'scyclic-import, whichruff'sPL*rule subset does not implement)py_ballisticcalc/engines/base_engine.py,rk4.py,engines/scipy_engine.py,unit.py,vector.py,generics/engine.py,interpolation.py,constants.py,drag_tables.py, and severalpy_ballisticcalc.extsscripts: broadruff check --fixcleanup — typing modernization (Optional[X]/List[X]/Dict[X]→X | None/list[X]/dict[X]),set()/tuple()literal rewrites,.keys()removal, nested-ifmerges (only where no attachedelifwould change semantics — a first attempt at mergingbase_engine.py'sZERO_UP/ZERO_DOWNcrossing-detection nesting did change semantics and was reverted after it brokeTestTrajectoryDataFilter.test_zero_up_then_zero_down_ordering),yield-loop →yield from,%-format → f-string,ClassVarannotations onUnit's per-subclass_conversion_factors(RUF012), and shebang scripts (cythonization-report.py,setup.py,uconv.py) marked executable (EXE001); a handful of findings were suppressed with# noqa+ rationale instead of "fixed" where the suggestion didn't match the code's actual intent:PreferredUnitsMeta.__repr__'sgetattr(cls, "__dataclass_fields__")(pyright can't see the attribute@dataclassinjects onto the metaclass at runtime — ruff's own--fixhad silently rewritten this to direct attribute access, which is what brokepyright),Vector.__iadd__/__isub__(immutableNamedTuple: genuinely returns a new instance, notself, contraPYI034),ConfigTingenerics/engine.py(public name re-exported fromgenerics/__init__.py, contraPLC0105's naming convention), and both__all__sequences inconstants.py/interpolation.py(deliberately grouped by category with comments, contraRUF022's alphabetical sort)py_ballisticcalc/engines/rk4.py,engines/scipy_engine.py: closures created inside per-iteration loops (acceleration()in RK4,x_minus_target()in SciPy's root-finding) now bind their loop-scoped variable (k_m,x_target) as a default argument instead of relying on the closure — both were already correct at runtime (fully invoked before the next iteration reassigns the variable) but trippedB023; the default-argument form makes that safety explicit rather than incidental
Fixed¶
.github/workflows/pre-commit.yml: checkout step was missingsubmodules: recursive, so the consolidated pre-commit workflow (see CI) failed itsuv-synchook on every run —py_ballisticcalc.exts' Cython build needsbclibc's vendored C++ sources, which a non-recursive checkout never fetches
Removed¶
.pylintrc— migrated intopyproject.toml(see Changed)
CI¶
.github/workflows/pyright.yml,ruff.yml,pylint.ymlremoved; all three now run as.pre-commit-config.yamlhooks (uv-pyright,uv-ruff-check,uv-ruff-format,uv-pylint) via the existing.github/workflows/pre-commit.yml(pre-commit run --all-files), instead of three separate workflows each reinstalling the project.pre-commit-config.yaml:uv-pylinthook scoped topy_ballisticcalconly (args: [..., "py_ballisticcalc"],pass_filenames: false) instead of linting whatever.py/.pyifiles changed anywhere in the repo with no path filter — the old form ran pylint againstpy_ballisticcalc.exts/tests/scripts that were never in its scope, always failing thefail-under=9.0gate locally (score dropped to 8.71) even for cleanpy_ballisticcalc-only changes.pre-commit-config.yaml: addeduv-lock-extshook (uv lock --upgrade --project py_ballisticcalc.exts), mirroring the existinguv-lockhook for the main package'suv.lock
2.3.0rc2 - 2026-07-21¶
Changed¶
- bclibc submodule bumped to
v1.1.5—BCLIBC_BaseTrajSeq::get_at()(src/traj_data.cpp) silently extrapolated instead of raising whenkey_valuewas outside the trajectory's range, sincebisect_center_idx_buf()/find_target_index()clamp their bracket index to[1, n-2]regardless of how farkey_valuefalls outside the sequence, andinterpolate_at()only ever validated the index, never the value;get_at()now checkskey_valueagainst[min(buffer[0][key], buffer[n-1][key]), max(...)](± a1e-9epsilon) before searching and throwsstd::out_of_range(surfaces as PythonIndexError) if it falls outside (bclibc#19); also fixes a broken artifact-path check inpre-commit-check.shand adds a CTest-based C++ unit test target — surfaced while investigating #305 below
Fixed¶
HitResult.get_at()(trajectory_data.py): raisedArithmeticError: Trajectory does not reach ...when the requested value was a floating-point epsilon beyond an existing trajectory point (e.g. from meter→inch conversion artifacts) instead of returning that point; the forward-search loop's bracket condition (curr_val < key_value <= next_val) excludes anykey_valuepast the bracket it can find, sotarget_idxstayed-1and the error was raised before theepsiloncheck could ever run, regardless of how largeepsilonwas passed — this affected not just the first/last trajectory points but also interior local extrema (e.g. the trajectory apex) for non-monotonic attributes like height, since an extremum epsilon-overshoot falls outside every bracket the same way an endpoint epsilon-overshoot does; fix: ontarget_idx == -1, scan the whole trajectory for its closest point by key-attribute value (min(traj, key=...)) and return it if withinepsilon— only reached on this error-fallback path, so the O(n) scan has no cost on the normal path — resolves #305; pure-Python fix —py_ballisticcalc_exts' internalCythonizedBaseTrajSeq.get_at()(bclibc) uses a different binary-search-and-always-interpolate algorithm not affected by this specific bug, but investigating it surfaced the opposite problem there (see bclibc submodule bump tov1.1.5above)
Tests¶
TestIssue305added totests/test_issues.py— regression tests forHitResult.get_at(): a value a float-epsilon past the last trajectory point resolves to that last point, a value a float-epsilon before the first point resolves to the first point, a value a float-epsilon past an interior local extremum (trajectory apex) resolves to that extremum, and a value genuinely far out of range still correctly raisesArithmeticErrorpy_ballisticcalc.exts/tests/test_get_at_functions.py: addedCythonizedBaseTrajSeq.get_at()regression tests mirroringTestIssue305for the Cython/bclibc path — epsilon past/before the first and last points still resolve to those points, and values genuinely far out of range now raiseIndexErrorper the bclibcv1.1.5fix
CI¶
.github/workflows/coverage.yml: added top-levelconcurrency: {group: "${{ github.workflow }}-${{ github.ref }}", cancel-in-progress: true}, matching every other workflow in the repo — a new push/PR update now cancels the previous in-flight coverage run on the same ref instead of both running to completion.github/workflows/coverage.yml:discover-enginesandmergejobs now skip whengithub.actor == 'dependabot[bot]'(testskips too, transitively, via itsneeds: discover-engines) — dependency-bump PRs don't need a fresh full-matrix coverage run, andmerge's existingif: ${{ !cancelled() }}would otherwise still have attempted to run (and fail, with no coverage data to combine) even withdiscover-engines/testskipped, since a skipped job isn't a cancelled onepytest-euler-engine.yml,pytest-verlet-engine.yml,pytest-cythonized-euler-engine.yml:test_full_matrixjob (6-OS ×3.11/3.14/3.14tmatrix, PR-only) removed;test_minimal_matrix(ubuntu-latest×3.11only) now runs unconditionally on bothpushandpull_requestinstead of push-only — these three engines share their integration/data-structure code withrk4_engine/cythonized_rk4_engine(which keep the full cross-OS/version matrix), differing only in integration formula, so cross-platform/cross-version testing of that shared core would just be duplicated CI cost; the minimal matrix still catches regressions specific to each engine's own logic
2.3.0rc1 - 2026-07-20¶
Fixed¶
- CHANGELOG.md: the
v2.3.0b5Codecov entry'sif: ${{ !cancelled() }}GitHub Actions expression wasn't escaped for Jinja, so mkdocs/pymdown-extensionstried to evaluate it as a template expression when rendering the docs site; wrapped in...
2.3.0b5 - 2026-07-20¶
Added¶
- Codecov integration:
.github/workflows/coverage.ymlrewritten as a 3-job pipeline —discover-engines(lists engines viaCalculator.iter_engines()),test(matrix over discovered engines, each writing an isolated.coverage.<engine>data file),merge(coverage combine/xml/html, thencodecov/codecov-action@v7upload);mergeruns even if one engine's tests fail (if: ${{ !cancelled() }}) so coverage is still reported CODECOV_TOKENrepository secret added; README[coverage]badge now links to the live codecov.io report instead of the localcoverage.svg
Changed¶
_EngineLoader(interface.py):_get_entries_by_group()and new_load_by_name()wrapped infunctools.cache—entry_points().select(...)was re-scanning all installed distributions on every string-keyedCalculator(engine="...")construction (~4.4ms/call); now cached per-process (~0.0085ms/call on repeat calls, ~500x) — mainly benefits code that repeatedly loads an engine by name, e.g. the class-scopedloaded_engine_instancepytest fixture andCalculator.__setstate__on unpickle- Dependencies update to fix CVEs
Fixed¶
ScipyIntegrationEngine(engines/scipy_engine.py): 12 pyrightreportPossiblyUnboundVariablefindings onnp/root_scalar/minimize_scalar/solve_ivp— these are optional third-party imports (try/except ImportError), but every use is only reachable afterSciPyIntegrationEngine.__init__has already verified_HAS_NUMPY/_HAS_SCIPY, an invariant the type checker can't see across function boundaries; fixed by also importing them unconditionally underif TYPE_CHECKING:(never executed at runtime, only informs the type checker that the names are bound) — no runtime behavior changeScipyWindSock.__init__(engines/scipy_engine.py):windsparameter was typedWind | Sequence[Wind | None], which (a) allowedNoneitems inside the sequence even though nothing ever produces or handles those, and (b) didn't even include bareNoneas a valid top-level value despite the method explicitly handlingwinds is None; corrected toWind | Sequence[Wind] | None, matching the real caller (props.winds, typedSequence[Wind]) and the existingNone-handling codeColoredFormatter.__init__(logger.py):styleparameter had no type annotation, so pyright widened it tostrand flagged thesuper().__init__()call as incompatible withlogging.Formatter'sLiteral['%', '{', '$']; annotated asLiteral["%", "{", "$"] = "%"GenericDimension.from_raw(unit.py:840): parameter was namedunit, whileTemperature.from_raw's override (unit.py:1242) and theGenericDimensionProtocoldeclaration both useunits; pyright flagged the override as LSP-incompatible (reportIncompatibleMethodOverride); renamed the base method's parameter tounitsto matchVelocityVerletIntegrationEngine._integrate(engines/velocity_verlet.py):data_filter.finalize()was called with no argument, defaultingtermination_reasontoNoneand silently no-opping (unlikerk4.py/euler.py, which passtermination_reasonthrough); on anyRangeErrorpath (MaximumDropReached,MinimumAltitudeReached, incomplete/vertical shots) the real final trajectory point was never appended, soHitResult.trajectory[-1]stayed pinned at the muzzle point (time=0.0); fixed by passingtermination_reasonintofinalize(), matching the other engines
Removed¶
coverage.svg(root) — was only ever regenerated by a commented-outcoverage-badgestep; the README[coverage]badge now points at codecov.io instead- Orphaned unused
[coverage]reference definition (pointing at a non-raw.githubusercontent.com, already-brokencoverage.svgURL) removed frompy_ballisticcalc.exts/README.md
CI¶
- Type checker swapped from
mypytopyright, recommended by Serhiy Yevtushenko:[tool.mypy]→[tool.pyright]inpyproject.toml(include = ["py_ballisticcalc"]);mypy>=2.3.0→pyright>=1.1.411in both dev dependency groups;.pre-commit-config.yamluv-mypyhook →uv-pyright;.github/workflows/mypy.ymlrenamed topyright.yml;docs/contributing.mdcommand updated; stale.mypy_cache/entries removed from both.gitignorefiles;.pylintrc'sspelling-ignore-comment-directivesmypy:→pyright:;# Make mypy happycomments inbase_engine.py/scipy_engine.pyreworded generically.reportUnsupportedDunderAlldisabled in[tool.pyright]—__init__.py/drag_tables.pybuild__all__dynamically fromglobals()/a generated list by design, which pyright can't statically verify .pre-commit-config.yaml:astral-sh/uv-pre-commit's pinneduv-lockhook (rev: 0.11.29) replaced with a locallanguage: systemhook runninguv lock --checkdirectly, placed right beforeuv-sync— avoids a second, independently-pinneduvversion diverging from whateveruvis actually used to run the rest of the hooks and CI.github/workflows/pytest-verlet-engine.ymladded —verlet_enginehad no dedicated pytest-*-engine.yml (onlycoverage.ymlexercised it); new file mirrors the euler/rk4/scipy pattern (full matrix onpull_request, minimal matrix onpush,fail-fast: false).github/workflows/pytest-manual.yml:verlet_engineadded to theengine_namechoice-input options.github/workflows/pytest-reusable.yml:astral-sh/setup-uvnow setsenable-cache: truewith an explicitcache-dependency-globcoveringpy_ballisticcalc.exts/**and.gitmodules(not justuv.lock/pyproject.toml) — a cache hit now skips rebuilding the Cython extension entirely when its source tree is unchanged from a prior run, e.g. acrosscythonized_euler_engine/cythonized_rk4_enginelegs on the same runner or on job re-runs/retriestest_full_matrix/test_minimal_matrixjobname:shortened toFull/Minimalacrosspytest-{euler,rk4,scipy,verlet}-engine.ymlandpytest-cythonized-{euler,rk4}-engine.yml; the two cythonized files'test_minimal_matrixpreviously had no explicitname:at all.gitignore:.coverageglob widened to.coverage*and**/coverage.xmladded, to also ignore per-engine coverage data files (.coverage.<engine>) and generated XML reports from the newcoverage.yml
2.3.0b4 - 2026-06-25¶
Changed¶
- bclibc submodule reference updated from
v1.1.2tov1.1.4— no additional wrapper-consolidation work of its own; picks up bclibc's interimv1.1.3fix (Ridder's-methodf_nextordering, seev2.3.0b3below) plus further upstreamv1.1.4changes
2.3.0b3 - 2026-06-22¶
Changed¶
py_ballisticcalc.exts(bind.pyx):BCLIBC_ShotProps_from_pyobjectis now a thin field mapper — fillsBCLIBC_Shotwith natural-unit values and delegates all physics/unit conversions toBCLIBC_Shot::to_shot_props()in C++ (cant cos/sin, CIPM-2007 atmosphere, Coriolis trig, PCHIP drag curve, wind sock assembly); Step 2 of bclibc-wrapper-consolidationpy_ballisticcalc.exts(base_types.pxd): addedBCLIBC_Shotcppclass declarationpy_ballisticcalc.exts(base_types.pxd):BCLIBC_Corioliscppclass extended with@staticmethod from_lat_az(lat_deg, muzzle_velocity_fps, az_deg)py_ballisticcalc.exts(base_types.pxd):BCLIBC_Atmospherecppclass extended with@staticmethod from_conditions(t_c, p_hpa, alt_ft, humidity)py_ballisticcalc.exts(bind.pyx): addedBCLIBC_Coriolis_from_lat_azandBCLIBC_Atmosphere_from_conditionsCython helpers (available for callers that hold Python domain objects directly;_from_pyobjectvariants retained for the same reason)- bclibc submodule bumped to
v1.1.2— addsBCLIBC_Shot/to_shot_props(),BCLIBC_Coriolis::from_lat_az(),BCLIBC_Atmosphere::from_conditions()(Steps 1–2); renames allBC*struct/enum types inbclibc_ffi.htoBCLIBCFFI_*and addsBCLIBCFFI_*_shot()entry points in the C FFI layer (Step 3a, breaking for C FFI consumers); no effect on Cython path (base_types.hppBCLIBC_*types are unchanged)
Fixed¶
_find_zero_angle()(base_engine.py): units mismatch in Ridder's bracket-convergence checks —cZeroFindingAccuracyis a height tolerance in feet but was also used as an angle tolerance in radians (if abs(next_angle - mid_angle) < cZeroFindingAccuracy); with the default5e-6the angle bracket had to narrow to5e-6 rad ≈ 0.0003°, causing oscillation and exhaustingcMaxIterationson certain high-elevation geometries; additionally, the Python implementation lacked explicit height-error checks (|f_mid|,|f_next|) that the C++ version has, so convergence was never declared even when the bullet already crossed the sight line within tolerance; fix: introduce a separateangle_tol = 1e-7 radfor bracket convergence, add|f_mid| < cZeroFindingAccuracyand|f_next| < cZeroFindingAccuracyheight checks, and evaluatef_nextbefore the angle-step exit — resolves #204- bclibc submodule bumped to
v1.1.3—src/engine.cpp,tiny_bclibc/include/tiny_bclibc/engine.h: same Ridder's-method fix —f_nextnow evaluated before theangle_tol/kRiddersAngleTolcheck in both the C++ and C implementations CythonizedBaseTrajData.__str__unpacked(name, value)from an iterator that yields scalar values — raisedValueErroron any callVacuum.__init__(conditions.py):Atmo.__init__usespressure or standard_pressure(...)so passingpressure=0(falsy) stores_p0 = 1013.25 hPainstead of0;Vacuum.__init__corrected_pressureand_density_ratiobut not_p0; any C++ wrapper reading_p0directly (e.g. Dart FFI, WASM) would receive 1013.25 hPa for vacuum; fix: explicitly setself._p0 = 0.0inVacuum.__init__aftersuper().__init__()BCLIBC_ShotProps_from_pyobject(bind.pyx):shot_info.windswas accessed twice (once forlen(), once for the for-loop);Shot.windsreturnstuple(self._winds)— a new tuple on every call; under free-threading a concurrent mutation of_windsbetween the two accesses could makewind_count > winds_vec.size(), causing an out-of-bounds read inBCLIBC_Shot::to_shot_props(); fix: cachewinds_py = shot_info.windsonce before both usesBCLIBC_ShotProps_from_pyobject(bind.pyx):shot_info.ammo,.ammo.dm,.atmo, and.weaponwere traversed multiple times each without being cached incdef objectlocals, violating the function's own documented invariant ("Cython may forget to add DECREF") — each chain traversal creates a temporary reference that Cython may not release on exception paths; fix: cache all intermediates in namedcdef objectlocals at the top of the function
Tests¶
TestIssue204added totests/test_issues.py— parametrized regression suite covering 9 high-elevation target points from issue #204 that previously raisedZeroFindingError; tests bothzero_angle()(iterative + Ridder's fallback) andfind_zero_angle()(direct Ridder's); all engines run withcStepMultiplier=5.0to keep runtime reasonable on high-elevation trajectories
CI¶
py_ballisticcalc.exts/pyproject.toml: removedenable = ["cpython-freethreading"]— no longer a valid enable group in cibuildwheel 4.x; removedcp313t-*frombuildselectors — Python 3.13t is no longer available in cibuildwheel 4.1.0 (left preview stage); final selectors:cp311-* cp314t-*test_full_matrixinpytest-cythonized-rk4-engine.ymlandpytest-cythonized-euler-engine.yml: changedfail-fastfromfalsetotrue— stops the 24-job matrix on the first failure instead of running all jobs to completioncibuildwheelbuild selectors:cp311-* cp313t-* cp314t-*;cp313t-*requiresenable = ["cpython-freethreading"]in cibuildwheel 3.4 (flag is deprecated butcp313tis silently skipped without it)- Cythonized engine test full matrix:
3.11, 3.14(abi3 boundary versions) +3.13t, 3.14t(distinct free-threaded ABIs);3.12,3.13removed — abi3 binary is identical across standard CPython versions pypi-publish.yml: extractedbuild-sdistjob — pure Python wheel and exts sdist built once onubuntu-latest; removed from per-OSbuildmatrix jobs to eliminate 5× redundant platform-independent builds
2.3.0b2 - 2026-05-13¶
Changed¶
BaseEngineConfigDictfields changed fromT | NonetoT—total=Falsealready provides optionality; explicitNonevalues no longer acceptedwith_no_minimum_velocityandwith_max_drop_zerodecorators typed withParamSpec,Concatenate, andfunctools.wraps— decorated methods now preserve their full signatures for static type checkersTrajectoryDataFilter.recordsclass-level mutable default= []removed; annotation-only declaration left, instance assigned in__init__isinstance(x, (A, B))replaced withisinstance(x, A | B)union syntax (PEP 604) acrossunit.py,vector.py,munition.py,trajectory_data.py,engines/scipy_engine.py- Implicit type aliases annotated with
TypeAliasininterface.py,munition.py,interpolation.py,trajectory_data.py typing.Selfmerged into existingfrom typing import (...)block inunit.py; duplicatefrom typing importblocks merged intrajectory_data.pyandgenerics/engine.pyimportlib.metadatacompatibility shim removed from_EngineLoader._get_entries_by_group—entry_points().select()used directly (available since Python 3.9)from __future__ import annotationsremoved fromengines/base_engine.py,visualize/plot.py, andshot.py;shot.ShotProps.from_shotreturn type updated toSelftyping.Selfmoved fromtyping_extensionsto stdlibtypingininterface.pytomllibimported directly from stdlib (Python 3.11+), version guard removedCallablemoved fromtypingtocollections.abcinhelpers.py
Removed¶
- Python 3.10 support EOL - removed all references to Python 3.10, updated CI and dependencies
CI¶
py_ballisticcalc.extswheels now target the Python stable ABI (cp311-abi3-*): one binary per platform/architecture is compatible with CPython 3.11 and all later standard releases; free-threaded Python 3.13t / 3.14t is built as separate version-specific wheelscibuildwheelreduced tocp311-* cp314t-*— eliminates redundant per-interpreter builds for standard CPython;cp313t-*dropped (deprecated in cibuildwheel 3.4, removed in next minor),cp314t-*requires noenableflag- Cythonized engine test full matrix reduced from 6 to 3 Python versions (
3.11, 3.14, 3.14t);3.12,3.13,3.13tremoved — abi3 binary is identical across standard versions, boundary versions provide sufficient coverage CIBW_ENVIRONMENT_PASS: SETUPTOOLS_SCM_PRETEND_VERSIONadded to the publish workflow — previously the version override was not forwarded intocibuildwheelbuild containers, causing+gHASHlocal version suffixes that PyPI rejectsuv auditpre-commit hook added — checks for known vulnerabilities in locked dependencies before each commituv lock --upgrade— all dev/docs dependencies updated; resolves 18 Dependabot security alerts (Pillow, urllib3, tornado, CairoSVG, fonttools, requests, virtualenv, Pygments, pymdown-extensions)pypi-publish.ymltrigger changed fromrelease: publishedtopush: tags: v*;create-releasejob added — generates and creates a draft GitHub Release automatically on tag push.github/actions/gen_release_notes/— new composite action with a Python script that builds formatted release notes fromCHANGELOG.md; supports optional> introand### Upgrade Notessections; auto-collects contributors viagit log.mailmapadded — normalizes git author aliases to GitHub usernames for consistent contributor attribution
2.2.10 - 2026-04-30¶
Changed¶
- bclibc C++ engine is now a git submodule (previously vendored sources)
- bump bclibc to v1.0.4
BCLIBC_Curve_fromPylistinpy_bind.cppnow delegates tobuild_pchip_curve_from_arrays— the universal PCHIP builder extracted intobase_types.hpp/base_types.cpp; eliminates code duplication between the Cython and FFI pathsBCLIBCFFI_CATCHmacro replaced withffi_call<F>template — same catch logic, no hiddenreturn, fullcatch(...)coverage for non-std exceptions across the FFI boundarytry_get_exactsignature changed fromvoid(exception-as-control-flow) tobool; call sites inget_atconverted fromtry/catchblocks to plainifchecks — removes overhead on the hot interpolation path
Fixed¶
- bclibc: undefined behavior in
find_zero_angle— missingreturnafter Ridder's loop - bclibc:
~BCLIBC_TrajectoryDataFilterdestructor no longer throws (wrapped intry/catch) - bclibc:
BCLIBC_BaseTrajData::operator[]bounds check used wrong constant and>instead of>= - bclibc:
BCLIBC_TrajectoryData::interpolatebounds check used>instead of>=, allowingFLAGkey - bclibc:
try_get_exactrefactored from exception-as-control-flow to returningbool - bclibc: memory leak in
BCLIBCFFI_integratewhentoCloop throws aftermalloc - bclibc: infinite loop guard added for
calc_step <= 0in Euler and RK4 integrators - bclibc:
BCLIBC_ShotPropsconstructor no longer propagatesdomain_errorfrom stability coefficient calculation - bclibc:
BCLIBCFFI_CATCHmacro replaced with type-safeffi_call<F>template (addscatch(...)for non-std exceptions)
2.2.9 - 2026-03-16¶
Changed¶
- Updated CI dependencies versions
- Updated a list of package dependencies in pyproject.toml
Fixed¶
- Fixed
Unit.Jouleconversion factor inEnergyclass returning incorrect foot-pounds to Joules values. - Types annotations codestyle fix
- Security fix for vulnerability described at (https://github.com/o-murphy/py-ballisticcalc/security/dependabot/1)
2.2.8 - 2026-01-26¶
Added¶
Calculatornow can be used as a context manager
Changed¶
Calculator- improved initialisation and type annotations- engines
__init__signature adjusted for consistency
Fixed¶
- Type annotations fix in
unit.py EngineProtocol- type annotations fixCalculator- type annotations fix- Type annotations modernized to Python 3.10+ style (PEP 604, PEP 585)
Optional[X]→X | NoneUnion[X, Y]→X | YList[X]→list[X],Dict[K, V]→dict[K, V],Tuple[X, Y]→tuple[X, Y]- Removed unused typing imports
- Updated .pyi stub files for Cython extensions
- Added type guards in
uconv.py
2.2.7 - 2025-12-26¶
Changed¶
- C++ headers includes refactoring
- Redundant Null Pointer Check Removal in
BCLIBC_Coriolis - Better
BCLIBC_WindSockinitialization in Cython/C++ BCLIBC_WindSock_from_pylistrenamed toBCLIBC_WindSock_from_pytuple- C++ to Python Exception bridge improved, avoids multiple rethrows, uses
dynamic_cast - C++ to Python Exception bridge
many_exception_handlerrenamed toexception_dispatch BCLIBC_BaseTrajData::get_key_valreplaced withBCLIBC_BaseTrajData::operator[]BCLIBC_BaseTrajSeq::get_key_valreplaced withBCLIBC_BaseTrajSeq::operator[]BCLIBC_TrajectoryData::get_key_valreplaced withBCLIBC_TrajectoryData::operator[]
Fix¶
- Docstrings fix according to Issue #299
2.2.6.post1 - 2025-12-14¶
Changed¶
- Removed unnecessary atmosphere precalculation in C++ rk4 integrator
- Removed unnecessary 'edited' trigger from pypi-publish.yml
2.2.6 – 2025-12-13¶
Changed¶
- Optimized C++ based RK4 integrator performance
- V3dT uses square check instead of sqrt for magnitude calculation for performance
Fixed¶
- C++ based TrajectoryDataFilter initialization fix
Compatibility¶
- Fully compatible with v2.2.3 (no breaking changes).
- Rebuilding wheels is recommended for distributors.
2.2.5 – 2025-12-11¶
Added¶
- Improvements in integrator termination control and integration helpers.
- CI/CD enhancements including reusable wheel builds and better version extraction.
Changed¶
- Major internal migration to a C++-centric engine design.
- Simplified Cython bindings and improved Python-level ergonomics.
- Significant memory usage optimizations.
- Thread-safety improvements in core engine components.
- Updated documentation, README, and contributor guidelines.
Fixed¶
- Eliminated redundant copying in internal data pipelines.
- Fixed documentation build issues.
- Numerous bug fixes across the engine, wrappers, and integration logic.
Compatibility¶
- Fully compatible with v2.2.3 (no breaking changes).
- Rebuilding wheels is recommended for distributors.
- Thread-safety improvements may positively affect multi-threaded usage.
2.2.5rc3 - 2025-12-10¶
Changed¶
- Refactoring and features improvements
- Attempted to use std::function for better functionality
Fixed¶
- Made type annotations better and more accurate
2.2.5rc2 - 2025-12-02¶
Added¶
- C++-level
GenericTerminatorfor better control CythonizedBaseIntegrationEngine.integrate_raw_atmethod to integrate by key_attribute and target_value- Thread safety with
std::recursive_mutexforBCLIBC_Enginefields
Changed¶
- Control integrator termination from external handlers
- Much more safe
BCLIBC_EssentialTerminatorsusage - Refactored termination control from outer
BCLIBC_EssentialTerminators
Fixed¶
- C++ memory usage optimization
2.2.5rc1 - 2025-11-28¶
Added¶
- Automatic version detection during build/install using
setuptools-scm - RAII initialization to prevent memory leaks in trajectory filters
- Dense output support for trajectory handling
Changed¶
- Major refactor: Fully ported ballistic engine from C/Cython to C++ with thin Cython wrappers
- Cython now provides only a thin interface with most computations in native C++
BaseTrajSeq,TrajectoryDataFilter,V3dT,BCLIBC_Coriolis,BCLIBC_Wind, andBCLIBC_ShotPropsrefactored as C++ classes/enum classes- Optimized solvers (rk4, euler) and trajectory computations
- Enhanced trajectory data filtering and interpolation
- Unified exception handling in apex, range, and zero-finding calculations
Fixed¶
- Memory management across engine and trajectory structures
- Pickling support for
Calculatorinterface - Log level issues
- TDF caching and timestep handling
- Numerical stability including look angles near 90°
Improved¶
- Performance through optimized internal data structures using
std::vector - Reduced complexity and duplication in Cython wrappers
- Partial compile-time logging disabling to reduce overhead
- Zero-finding and engine computations robustness
2.2.5b2 - 2025-11-27¶
Fixed¶
interface::Calculatorcustom serialization methods for pickling
2.2.5b1 - 2025-11-11¶
Added¶
- C++ TrajectoryDataFilter implementation
Changed¶
- Ported TDF to C++
- Refactored C++ Engine wrapper
- Wrapped
Engine.release_trajectoryin C++
Fixed¶
- macOS builds compatibility
- TDF exceptions handling
CythonizedBaseIntegrationEngine.integrateerrors check- Removed useless arguments from integratefunc
2.2.4rc1 - 2025-11-09¶
Added¶
- Namespace organization improvements
- Helper functions for better code organization
Changed¶
- Multiple refactoring passes (Refactor2C_4, Refactor2C_5, Refactor2C_6)
- C-level optimizations for better performance
- Cythonized and C sources updates
Fixed¶
- Log level configuration
- Setup process
- Conftest configuration
- TDF cache interpolation for efficiency
- TDF time step check
- Annotations in TDF
BCLIBC_Coriolis_adjustRangefunction- Memory management with memset
2.2.3 - 2025-10-19¶
Added¶
- Python 3.14 and 3.14t support
- ARM wheel building support
- Manual pytest workflow runner for GitHub Actions
- Thread safety improvements for
interface.Calculator
Changed¶
- Dropped support for Python 3.9
- Updated CI workflows to include Python 3.14
- Refactored
WindSock_tto use built-in structure - Reduced
.ipynbdependency footprint for VS Code and PyCharm - Updated reusable pytest workflows with exit code checks
Fixed¶
- Dependency issues for new Python versions
Coriolis_t_from_pyobjecthandlingShotProps_t_freeResourcesto safely NULLIFY internal pointers.pylintrcissues- Dependabot configuration
Removed¶
- Deprecated runners (macos-13)
- Deprecated workflows (cibuildwheel_test.yml)
- Deprecated
interface.Calculator.cdmproperty
2.2.2 - 2025-10-16¶
Added¶
- Valgrind-based workflows for leak detection
- New benchmarking details in documentation
CBaseTrajSeq_t_lenhelper function- Enhanced test helpers and internal data structure creation utilities
Changed¶
- Moved integration routines (
_integrate_rk4,_integrate_euler) fully to C CBaseTrajSeqnow wraps pure-CCBaseTrajSeq_t- Replaced Python-level calls with direct C invocations
- Improved
BaseTrajDataTstructure and method access
Fixed¶
- Multiple memory leaks in trajectory and curve management
- Enhanced C++ compatibility with
extern "C"declarations - Typos and mkdocs build issues
termination_reasonchecks in C integrators
Removed¶
- Redundant pointer dereferences and casts
- Redundant imports and legacy declarations
2.2.1 - 2025-10-08¶
Added¶
- Import guards for optional dependencies (pandas, numpy, scipy)
- Version-matching guards in
exts
Changed¶
- Updated hooks and contribution guides
- Refreshed code examples for clarity
Fixed¶
- Engine loading to prevent import errors when scientific libraries are missing
- Mypy and ruff checks now pass cleanly
Removed¶
- Redundant include guard from
bind.c
2.2.0 - 2025-10-03¶
Added¶
- New RK4 (Runge-Kutta 4) integrator
- Coriolis effect support in trajectory calculations
- Python 3.13 support
- Entry points for engines
- Override for
getCalcStepin RK4 engine
Changed¶
- Set RK4 engine as default
- Refactored integration with SciPy
- General Cythonization of critical paths
- Some components rewritten directly in C
- Standardized slant terminology
- Restructured and prettified documentation
Fixed¶
- RK4 engine implementation
- Type annotations and configuration
- Tests and logger cleanup
Improved¶
- Zeroing features and refinements with Cython implementation
- Documentation expanded with user guides and explanations
2.2.0rc2 - 2025-09-25¶
Changed¶
- Extended documentation
Fixed¶
- Various issues from v2.2.0rc1
2.2.0rc1 - 2025-09-15¶
Added¶
- Entry points for engines
- RK4 integrator with passing tests
- New tests for zeroing
- Coriolis effect implementation
Changed¶
- Refactored
trajectory_calc.py - Cythonized engines refactoring
- Inline vector operations optimization
- Restructured and prettified documentation
- Updated README
Fixed¶
- Test fixes and logger cleanup
- Various SciPy engine configuration issues
2.2.0b7 - 2025-08-26¶
Added¶
- Piecewise Cubic Hermite Interpolation (PCHIP) to trajectory data
Optional[RangeError]toHitResult- Implemented new
dense_outputflag - Cython-specific unit tests
- Enhanced documentation with standardized docstrings
Changed¶
- RK4 engines run with larger
DEFAULT_STEPfor faster results - Replaced
EngineProtocol.trajectory()with.integrate() - Rewrote
TrajectoryDataFilterto interpolate for all requested points - Rewrote chunks of Cython for better test compatibility
- Renamed
density_factortodensity_ratio - Renamed
PreferredUnits.defaults()to.restore_defaults()
Deprecated¶
helpers.py::must_fire()- useCalculator.fire(raise_range_error=True)insteadextra_dataflag
Fixed¶
- Multiple issues including #199, #202, #203, #209, #211, and #35
Removed¶
- Dev dependencies from standard install
2.2.0b6 - 2025-08-09¶
Added¶
- ZeroStudy.ipynb with zeroing/fire-solution performance study
- BenchmarkEngines.md analysis
- Enhanced zeroing features
Changed¶
- RK4 engine set as default after fixes
- Standardized slant terminology in README
- Cythonized engines refactoring
- Inline vector operations
Fixed¶
- RK4 engine implementation
- mkdocs build
Improved¶
- All engines now support
.find_max_range()for any angle
2.2.0b5 - 2025-07-04¶
Changed¶
- Type annotations updates
- Ruff CI setup
Fixed¶
- SciPy engine annotations
- SciPy configuration issues
2.2.0b4 - 2025-07-01¶
Added¶
- RK4
getCalcStepoverride
Changed¶
- SciPy integration improvements
2.2.0b3.post1 - 2025-06-25¶
Changed¶
- SciPy integration refinements
2.2.0b3 - 2025-06-25¶
Changed¶
- SciPy integration improvements
2.2.0b2 - 2025-06-20¶
Changed¶
- SciPy integration enhancements
2.2.0b1 - 2025-06-13¶
Added¶
- RK4 integrator implementation
Fixed¶
- Test suite improvements
- InterfaceConfigDict usages
2.1.1b3 - 2025-06-10¶
Added¶
- Python 3.13 support
2.1.1b2 - 2025-06-04¶
Changed¶
- Refactored
trajectory_calc.py
2.1.1b1 - 2025-06-03¶
Added¶
- Entry points for engines
2.1.0 - 2025-05-22¶
Added¶
- Vacuum atmosphere implementation
- Helper utilities and test helpers
add_time_of_flight_axismethod- Enhanced documentation with docstrings
- Automatic CI with UV for better performance
Changed¶
- Switched to powder temperature and sensitivity options
- Boostrap Cython modules
- Refactored munition cythonization
- Refactored wind_vector
- Configuration bind refactoring for cythonic TrajectoryCalc
Fixed¶
- Atmospheric model issues
- Vector performance (changed to NamedTuple)
- Velocity for temperature calculations
- Exception handling improvements
- Incomplete shot handling
- Trajectories that bend backwards
_init_trajectoryissues- Issue #130
Improved¶
- Performance optimizations with high-optimized Euler
- CI pipelines with UV
- Type annotations with Mypy
2.1.0rc2 - 2025-04-27¶
Fixed¶
- Partial fix to issue #155
2.1.0rc1 - 2025-04-13¶
Fixed¶
- Completed implementation for issue #164
2.1.0b7 - 2025-03-25¶
Added¶
- Vacuum Atmo implementation
- Unit test for issue #160
Changed¶
- Vector changed to NamedTuple for better performance
- Switched CI to UV for performance boost
Fixed¶
- All atmospheric model issues from #157
- Simple fix to #160
- CI pipelines
- Mypy typing issues
2.1.0b6 - 2025-02-18¶
Changed¶
- Various improvements and refinements
2.1.0b5 - 2025-02-06¶
Added¶
- Incomplete shot handling
2.1.0b4 - 2025-01-25¶
Added¶
- Helper utilities and test helpers
add_time_of_flight_axismethod- Trusted publishers support
Changed¶
- Configuration bind refactoring for cythonic TrajectoryCalc
Fixed¶
- Issue #141
- Trajectories that bend backwards