ballistic-solver 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+ pull_request:
7
+ branches: [ "main" ]
8
+
9
+ jobs:
10
+ build-test:
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [windows-latest, ubuntu-latest, macos-latest]
15
+ config: [Release]
16
+ runs-on: ${{ matrix.os }}
17
+
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Configure (CMake)
23
+ run: cmake -S . -B build -DCMAKE_BUILD_TYPE=${{ matrix.config }}
24
+
25
+ - name: Build
26
+ run: cmake --build build --config ${{ matrix.config }} -j
27
+
28
+ - name: Test (ctest)
29
+ run: ctest --test-dir build -C ${{ matrix.config }} --output-on-failure
@@ -0,0 +1,208 @@
1
+ name: Release Native + PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+ inputs:
9
+ tag:
10
+ description: "v0.3.2"
11
+ required: true
12
+ type: string
13
+
14
+ permissions:
15
+ contents: write
16
+ id-token: write
17
+
18
+ env:
19
+ TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
20
+
21
+ jobs:
22
+ native:
23
+ name: Build native (${{ matrix.os }})
24
+ if: >
25
+ (github.event_name == 'push' && startsWith(github.ref_name, 'v')) ||
26
+ (github.event_name == 'workflow_dispatch' && startsWith(inputs.tag, 'v'))
27
+ strategy:
28
+ fail-fast: false
29
+ matrix:
30
+ os: [ubuntu-latest, macos-latest, windows-latest]
31
+ runs-on: ${{ matrix.os }}
32
+
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ with:
36
+ fetch-depth: 0
37
+ ref: refs/tags/${{ env.TAG }}
38
+
39
+ - name: Configure (CMake Release)
40
+ run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
41
+
42
+ - name: Build
43
+ run: cmake --build build --config Release --parallel
44
+
45
+ - name: Test
46
+ run: ctest --test-dir build -C Release --output-on-failure
47
+
48
+ - name: Package native (Linux/macOS)
49
+ if: runner.os != 'Windows'
50
+ shell: bash
51
+ run: |
52
+ set -euo pipefail
53
+ mkdir -p dist
54
+ cp -f include/ballistic_solver_c_api.h dist/
55
+
56
+ if [[ "${RUNNER_OS}" == "Linux" ]]; then
57
+ libpath="$(find build -maxdepth 10 -type f -name "libballistic_solver.so" -print -quit)"
58
+ else
59
+ libpath="$(find build -maxdepth 10 -type f -name "libballistic_solver.dylib" -print -quit)"
60
+ fi
61
+
62
+ if [[ -z "${libpath:-}" ]]; then
63
+ echo "ERROR: shared library not found under build/"
64
+ exit 1
65
+ fi
66
+
67
+ cp -f "$libpath" dist/
68
+ os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')"
69
+ (cd dist && zip -r "../ballistic_solver_${TAG}_${os}.zip" .)
70
+
71
+ - name: Package native (Windows)
72
+ if: runner.os == 'Windows'
73
+ shell: pwsh
74
+ run: |
75
+ New-Item -ItemType Directory -Force dist | Out-Null
76
+ Copy-Item -Force include\ballistic_solver_c_api.h dist\
77
+
78
+ $dll = Get-ChildItem -Path build -Recurse -Filter ballistic_solver.dll -ErrorAction SilentlyContinue | Select-Object -First 1
79
+ if (-not $dll)
80
+ {
81
+ Write-Error "ERROR: ballistic_solver.dll not found under build/"
82
+ exit 1
83
+ }
84
+
85
+ Copy-Item -Force $dll.FullName dist\
86
+ Compress-Archive -Path dist\* -DestinationPath "ballistic_solver_$($env:TAG)_windows.zip" -Force
87
+
88
+ - uses: actions/upload-artifact@v4
89
+ with:
90
+ name: native-${{ runner.os }}
91
+ path: ballistic_solver_*.zip
92
+ if-no-files-found: error
93
+
94
+ py_wheels:
95
+ name: Build python wheels (${{ matrix.os }})
96
+ if: >
97
+ (github.event_name == 'push' && startsWith(github.ref_name, 'v')) ||
98
+ (github.event_name == 'workflow_dispatch' && startsWith(inputs.tag, 'v'))
99
+ strategy:
100
+ fail-fast: false
101
+ matrix:
102
+ os: [ubuntu-latest, macos-latest, windows-latest]
103
+ runs-on: ${{ matrix.os }}
104
+
105
+ steps:
106
+ - uses: actions/checkout@v4
107
+ with:
108
+ fetch-depth: 0
109
+ ref: refs/tags/${{ env.TAG }}
110
+
111
+ - name: Build wheels (cibuildwheel)
112
+ uses: pypa/cibuildwheel@v3.3.1
113
+ with:
114
+ package-dir: .
115
+ output-dir: wheelhouse
116
+ env:
117
+ CIBW_BUILD: "cp310-* cp311-* cp312-*"
118
+ CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux_*"
119
+ CIBW_ARCHS_MACOS: "universal2"
120
+ CIBW_BUILD_VERBOSITY: "3"
121
+ CIBW_TEST_COMMAND: >-
122
+ python -c "import ballistic_solver; print('import_ok')"
123
+
124
+ - uses: actions/upload-artifact@v4
125
+ with:
126
+ name: pywheels-${{ runner.os }}
127
+ path: wheelhouse/*.whl
128
+ if-no-files-found: error
129
+
130
+ sdist:
131
+ name: Build python sdist
132
+ if: >
133
+ (github.event_name == 'push' && startsWith(github.ref_name, 'v')) ||
134
+ (github.event_name == 'workflow_dispatch' && startsWith(inputs.tag, 'v'))
135
+ runs-on: ubuntu-latest
136
+
137
+ steps:
138
+ - uses: actions/checkout@v4
139
+ with:
140
+ fetch-depth: 0
141
+ ref: refs/tags/${{ env.TAG }}
142
+
143
+ - uses: actions/setup-python@v5
144
+ with:
145
+ python-version: "3.12"
146
+
147
+ - name: Build sdist
148
+ shell: bash
149
+ run: |
150
+ set -euo pipefail
151
+ python -m pip install -U pip build
152
+ python -m build --sdist
153
+
154
+ - uses: actions/upload-artifact@v4
155
+ with:
156
+ name: pysdist
157
+ path: dist/*.tar.gz
158
+ if-no-files-found: error
159
+
160
+ publish:
161
+ name: Publish (PyPI + GitHub Release assets)
162
+ needs: [native, py_wheels, sdist]
163
+ if: >
164
+ (github.event_name == 'push' && startsWith(github.ref_name, 'v')) ||
165
+ (github.event_name == 'workflow_dispatch' && startsWith(inputs.tag, 'v'))
166
+ runs-on: ubuntu-latest
167
+
168
+ steps:
169
+ - uses: actions/checkout@v4
170
+
171
+ - uses: actions/download-artifact@v4
172
+ with:
173
+ path: artifacts
174
+ merge-multiple: true
175
+
176
+ - name: Collect PyPI dist
177
+ shell: bash
178
+ run: |
179
+ set -euo pipefail
180
+ mkdir -p dist
181
+ find artifacts -type f \( -name "*.whl" -o -name "*.tar.gz" \) -exec cp {} dist/ \;
182
+ ls -la dist
183
+
184
+ - name: Publish to PyPI
185
+ uses: pypa/gh-action-pypi-publish@release/v1
186
+ with:
187
+ packages-dir: dist
188
+
189
+ - name: Create release if missing
190
+ env:
191
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
192
+ shell: bash
193
+ run: |
194
+ set -euo pipefail
195
+ if gh release view "${TAG}" > /dev/null 2>&1; then
196
+ echo "Release ${TAG} already exists."
197
+ else
198
+ gh release create "${TAG}" --title "${TAG}" --generate-notes
199
+ fi
200
+
201
+ - name: Upload assets to GitHub Release
202
+ env:
203
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
204
+ shell: bash
205
+ run: |
206
+ set -euo pipefail
207
+ gh release upload "${TAG}" artifacts/*.zip --clobber
208
+ gh release upload "${TAG}" dist/* --clobber
@@ -0,0 +1,60 @@
1
+ # Build outputs
2
+ build/
3
+ out/
4
+ cmake-build-*/
5
+ CMakeFiles/
6
+ CMakeCache.txt
7
+ cmake_install.cmake
8
+ Makefile
9
+ compile_commands.json
10
+ **/dist/
11
+
12
+ # Object / binary / library files
13
+ *.o
14
+ *.obj
15
+ *.exe
16
+ *.out
17
+ *.a
18
+ *.lib
19
+ *.so
20
+ *.dll
21
+ *.dylib
22
+ *.whl
23
+ *.zip
24
+
25
+ # Debug symbols / misc
26
+ *.pdb
27
+ *.ilk
28
+ *.dSYM/
29
+ *.log
30
+
31
+ # IDE/editor
32
+ .vscode/
33
+ .idea/
34
+ *.user
35
+ *.suo
36
+ *.vcxproj*
37
+ *.filters
38
+ *.xcodeproj/
39
+ *.xcworkspace/
40
+
41
+ # OS
42
+ .DS_Store
43
+ Thumbs.db
44
+
45
+ # Visual Studio
46
+ .vs/
47
+
48
+ # .NET build outputs
49
+ **/bin/
50
+ **/obj/
51
+
52
+ # Python
53
+ __pycache__/
54
+ *.py[cod]
55
+ **/*.egg-info/
56
+ .venv/
57
+ venv/
58
+
59
+ # Assets
60
+ assets/
@@ -0,0 +1,108 @@
1
+ cmake_minimum_required(VERSION 3.20)
2
+
3
+ project(ballistic_solver
4
+ VERSION 0.3.0
5
+ LANGUAGES C CXX
6
+ )
7
+
8
+ set(CMAKE_C_STANDARD 11)
9
+ set(CMAKE_C_STANDARD_REQUIRED ON)
10
+ set(CMAKE_C_EXTENSIONS OFF)
11
+
12
+ set(CMAKE_CXX_STANDARD 17)
13
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
14
+ set(CMAKE_CXX_EXTENSIONS OFF)
15
+
16
+ add_library(ballistic_solver SHARED
17
+ src/ballistic_solver_c_api.cpp
18
+ )
19
+
20
+ target_include_directories(ballistic_solver
21
+ PUBLIC
22
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
23
+ $<INSTALL_INTERFACE:include>
24
+ )
25
+
26
+ target_compile_definitions(ballistic_solver PRIVATE
27
+ BALLISTIC_SOLVER_EXPORTS
28
+ BALLISTIC_SOLVER_ABI_VERSION=2
29
+ BALLISTIC_SOLVER_VERSION_STRING=\"${PROJECT_VERSION}\"
30
+ )
31
+
32
+ if (MSVC)
33
+ target_compile_options(ballistic_solver PRIVATE /W4 /utf-8)
34
+ else()
35
+ target_compile_options(ballistic_solver PRIVATE -Wall -Wextra -Wpedantic)
36
+ endif()
37
+
38
+ add_executable(ballistic_solver_example
39
+ examples/c/basic.c
40
+ )
41
+ target_link_libraries(ballistic_solver_example PRIVATE ballistic_solver)
42
+
43
+ include(CTest)
44
+ if (BUILD_TESTING)
45
+ add_executable(smoke_test
46
+ tests/smoke_test.c
47
+ )
48
+ target_link_libraries(smoke_test PRIVATE ballistic_solver)
49
+
50
+ add_test(
51
+ NAME smoke_test
52
+ COMMAND $<TARGET_FILE:smoke_test>
53
+ )
54
+ endif()
55
+
56
+ include(GNUInstallDirs)
57
+
58
+ install(TARGETS ballistic_solver
59
+ EXPORT ballistic_solverTargets
60
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
61
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
62
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
63
+ )
64
+
65
+ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
66
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
67
+ )
68
+
69
+ # Minimal find_package(CONFIG) support
70
+ set(_pkgdir ${CMAKE_INSTALL_LIBDIR}/cmake/ballistic_solver)
71
+
72
+ install(EXPORT ballistic_solverTargets
73
+ FILE ballistic_solverTargets.cmake
74
+ NAMESPACE ballistic_solver::
75
+ DESTINATION ${_pkgdir}
76
+ )
77
+
78
+ include(CMakePackageConfigHelpers)
79
+
80
+ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ballistic_solverConfig.cmake"
81
+ "include(\"\${CMAKE_CURRENT_LIST_DIR}/ballistic_solverTargets.cmake\")\n"
82
+ )
83
+
84
+ write_basic_package_version_file(
85
+ "${CMAKE_CURRENT_BINARY_DIR}/ballistic_solverConfigVersion.cmake"
86
+ VERSION ${PROJECT_VERSION}
87
+ COMPATIBILITY SameMajorVersion
88
+ )
89
+
90
+ install(FILES
91
+ "${CMAKE_CURRENT_BINARY_DIR}/ballistic_solverConfig.cmake"
92
+ "${CMAKE_CURRENT_BINARY_DIR}/ballistic_solverConfigVersion.cmake"
93
+ DESTINATION ${_pkgdir}
94
+ )
95
+
96
+ set_target_properties(ballistic_solver PROPERTIES OUTPUT_NAME "ballistic_solver")
97
+
98
+ if(DEFINED SKBUILD_PLATLIB_DIR)
99
+ set(BS_NATIVE_DIR "${SKBUILD_PLATLIB_DIR}/ballistic_solver/_native")
100
+ else()
101
+ set(BS_NATIVE_DIR "${CMAKE_INSTALL_LIBDIR}")
102
+ endif()
103
+
104
+ install(TARGETS ballistic_solver
105
+ RUNTIME DESTINATION "${BS_NATIVE_DIR}" # Windows .dll
106
+ LIBRARY DESTINATION "${BS_NATIVE_DIR}" # Linux .so / macOS .dylib
107
+ ARCHIVE DESTINATION "${BS_NATIVE_DIR}" # .lib
108
+ )
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 <ujinf74>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.4
2
+ Name: ballistic-solver
3
+ Version: 0.3.2
4
+ License-Expression: MIT
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+
8
+ <img width="2024" height="512" alt="banner" src="https://github.com/user-attachments/assets/f4e57e3f-f584-4938-a321-e9dd83dbbac3" />
9
+
10
+ **ballistic-solver** is a native C/C++ numerical solver that computes launch angles to intercept **moving targets** under **gravity** and **quadratic air drag**.
11
+ Unlike vacuum / closed-form approaches, this solver **simulates the projectile** and **solves the intercept numerically**, targeting robustness for real-time simulations even when trajectories are strongly curved.
12
+
13
+ ---
14
+
15
+ ## Quick start
16
+
17
+ ### Python (wheel)
18
+
19
+ ```bash
20
+ pip install ballistic-solver
21
+ ````
22
+
23
+ Requires Python **>= 3.10**.
24
+
25
+ ```python
26
+ import ballistic_solver as bs
27
+
28
+ result = bs.solve(
29
+ relPos0=(120, 30, 5),
30
+ relVel=(2, -1, 0),
31
+ v0=90,
32
+ kDrag=0.002,
33
+ )
34
+
35
+ print(result["theta"], result["phi"], result["miss"])
36
+ print(result["success"], result["status"], result["message"])
37
+ ```
38
+
39
+ #### Signature (actual binding)
40
+
41
+ ```python
42
+ solve(relPos0, relVel, v0, kDrag, arcMode=0, params=None) -> dict
43
+ ```
44
+
45
+ * `arcMode` accepts `0/1` or `"low"/"high"` (case-insensitive).
46
+ * **Important:** the `arcMode` argument **overrides** `params.arcMode` even if `params` is provided.
47
+
48
+ Returned dict keys include:
49
+
50
+ * `success`, `theta`, `phi`, `miss`, `tStar`, `relMissAtStar`
51
+ * `status`, `message`
52
+ * `iterations`, `acceptedSteps`, `lastLambda`, `lastAlpha`
53
+
54
+ ### C ABI (stable interface)
55
+
56
+ ```c
57
+ void ballistic_inputs_init(BallisticInputs* in);
58
+ int32_t ballistic_solve(const BallisticInputs* in, BallisticOutputs* out);
59
+ ```
60
+
61
+ See `ballistic_solver_c_api.h` for `BallisticInputs/Outputs` definitions and defaults.
62
+
63
+ ---
64
+
65
+ ## Demo (Unity)
66
+
67
+ Highly curved trajectories under strong air drag, still converging to a hit against moving targets.
68
+
69
+ [https://github.com/user-attachments/assets/dcaf7479-cb94-477a-b71e-470a5b4c6004](https://github.com/user-attachments/assets/dcaf7479-cb94-477a-b71e-470a5b4c6004)
70
+
71
+ ---
72
+
73
+ ## Key properties
74
+
75
+ * Moving targets supported
76
+ * Strong air resistance (quadratic drag) supported
77
+ * Robust in strongly nonlinear regimes (no analytic assumptions)
78
+ * Best-effort result returned even without perfect convergence
79
+ * Explicit success / failure reporting (+ diagnostic message)
80
+ * Stable C ABI for multi-language use
81
+ * Header-only C++ core
82
+ * Low / High arc selection (since v0.2.0)
83
+
84
+ ---
85
+
86
+ ## Arc mode (since v0.2.0)
87
+
88
+ C ABI convention:
89
+
90
+ * `arcMode = 0` → Low
91
+ * `arcMode = 1` → High
92
+
93
+ High arc example:
94
+
95
+ [https://github.com/user-attachments/assets/4334ed87-597e-4ad4-b21e-c1a1a17e8cd8](https://github.com/user-attachments/assets/4334ed87-597e-4ad4-b21e-c1a1a17e8cd8)
96
+
97
+ ---
98
+
99
+ ## How it works (high level)
100
+
101
+ 1. Simulate projectile motion using RK4 integration with drag (+ wind in core/C ABI)
102
+ 2. Track the closest approach between projectile and target
103
+ 3. Express the miss at closest approach as an angular residual
104
+ 4. Solve the nonlinear system using damped least squares (Levenberg–Marquardt)
105
+ 5. Accelerate Jacobian updates with Broyden-style refinement
106
+ 6. Return the best solution found
107
+
108
+ Failure cases are explicitly detected and reported.
109
+
110
+ ---
111
+
112
+ ## Advanced parameters (C ABI)
113
+
114
+ `BallisticInputs` exposes additional controls (all have defaults via `ballistic_inputs_init`):
115
+
116
+ * `g` (gravity)
117
+ * `wind[3]` (wind vector)
118
+ * `dt` (RK4 step)
119
+ * `tMax` (max sim time)
120
+ * `tolMiss` (hit tolerance)
121
+ * `maxIter` (LM iterations)
122
+
123
+ Note: The current Python binding exposes many tuning parameters via `BallisticParams`,
124
+ but does **not** expose `wind` as a Python field.
125
+
126
+ ---
127
+
128
+ ## Status codes (SolveStatus)
129
+
130
+ `BallisticOutputs.status` / Python `result["status"]` corresponds to the core enum:
131
+
132
+ * `0` = Ok
133
+ * `1` = InvalidInput
134
+ * `2` = InitialResidualFailed
135
+ * `3` = JacobianFailed
136
+ * `4` = LMStepSingular
137
+ * `5` = ResidualFailedDuringSearch
138
+ * `6` = LineSearchRejected
139
+ * `7` = LambdaTriesExhausted
140
+ * `8` = MaxIterReached
141
+
142
+ `message` contains a short diagnostic string.
143
+
144
+ ---
145
+
146
+ ## Using prebuilt binaries
147
+
148
+ Download the archive for your platform from **Releases**.
149
+
150
+ Each release contains:
151
+
152
+ * Shared library
153
+
154
+ * Windows: `ballistic_solver.dll`
155
+ * Linux: `libballistic_solver.so`
156
+ * macOS: `libballistic_solver.dylib`
157
+ * C ABI header: `ballistic_solver_c_api.h`
158
+
159
+ ---
160
+
161
+ ## C# / Unity usage
162
+
163
+ A C# P/Invoke example is available in:
164
+
165
+ ```text
166
+ examples/dotnet/
167
+ ```
168
+
169
+ On Windows, place `ballistic_solver.dll` next to the executable
170
+ (or ensure it is discoverable via PATH),
171
+ then call `ballistic_solve` via `DllImport`.
172
+
173
+ ---
174
+
175
+ ## Build from source
176
+
177
+ ```bash
178
+ cmake -S . -B build
179
+ cmake --build build -j
180
+ ctest --test-dir build
181
+ ```
182
+
183
+ The shared library target is `ballistic_solver`.
184
+
185
+ ---
186
+
187
+ ## ABI notes
188
+
189
+ * Plain C layout across the ABI boundary
190
+ * Fixed-size arrays only
191
+ * No dynamic allocation across the boundary
192
+
193
+ ---
194
+
195
+ ## License
196
+
197
+ MIT License