csar 0.1.1__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.
- csar-0.1.1/.github/workflows/test.yml +41 -0
- csar-0.1.1/.github/workflows/wheels.yml +172 -0
- csar-0.1.1/.gitignore +32 -0
- csar-0.1.1/LICENSE +21 -0
- csar-0.1.1/PKG-INFO +215 -0
- csar-0.1.1/changelog.md +24 -0
- csar-0.1.1/dev.md +224 -0
- csar-0.1.1/justfile +67 -0
- csar-0.1.1/meson.build +48 -0
- csar-0.1.1/pyproject.toml +80 -0
- csar-0.1.1/readme.md +182 -0
- csar-0.1.1/scripts/countries/README.md +29 -0
- csar-0.1.1/scripts/countries/countries.py +82 -0
- csar-0.1.1/scripts/states/README.md +25 -0
- csar-0.1.1/scripts/states/states.py +73 -0
- csar-0.1.1/src/csar/__init__.py +29 -0
- csar-0.1.1/src/csar/convert.py +136 -0
- csar-0.1.1/src/csar/outcomes.py +132 -0
- csar-0.1.1/src/csar/plot.py +113 -0
- csar-0.1.1/src/csar/solver.py +90 -0
- csar-0.1.1/src/cython/_cy.pyx +86 -0
- csar-0.1.1/src/zig/build.zig +43 -0
- csar-0.1.1/src/zig/build.zig.zon +17 -0
- csar-0.1.1/src/zig/c_api.zig +145 -0
- csar-0.1.1/tests/test_bindings.py +478 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: test-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
name: py${{ matrix.python-version }} on ${{ matrix.os }}
|
|
16
|
+
runs-on: ${{ matrix.os }}
|
|
17
|
+
# Non-editable so `uv sync` builds + installs csar as the wheel ships it
|
|
18
|
+
# (no meson-python rebuild-on-import hook, which needs build isolation off).
|
|
19
|
+
env:
|
|
20
|
+
UV_NO_EDITABLE: "1"
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
25
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v6
|
|
29
|
+
|
|
30
|
+
- uses: astral-sh/setup-uv@v8.1.0
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
cache-suffix: test
|
|
34
|
+
|
|
35
|
+
- uses: extractions/setup-just@v4
|
|
36
|
+
|
|
37
|
+
- name: Install
|
|
38
|
+
run: uv sync
|
|
39
|
+
|
|
40
|
+
- name: Test + coverage
|
|
41
|
+
run: just ci-test
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
name: wheels
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
release:
|
|
9
|
+
types: [published]
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: wheels-${{ github.ref }}
|
|
13
|
+
# Don't cancel a release run mid-publish, but for branch pushes and
|
|
14
|
+
# PRs the latest commit should win — old runs just block the queue.
|
|
15
|
+
cancel-in-progress: ${{ github.event_name != 'release' }}
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
sdist:
|
|
19
|
+
name: build sdist
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v6
|
|
23
|
+
|
|
24
|
+
- uses: astral-sh/setup-uv@v8.1.0
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.13"
|
|
27
|
+
cache-suffix: wheels-sdist
|
|
28
|
+
|
|
29
|
+
- name: Build sdist
|
|
30
|
+
run: uv build --sdist
|
|
31
|
+
|
|
32
|
+
- name: Test sdist round-trip
|
|
33
|
+
run: |
|
|
34
|
+
uv venv test-env
|
|
35
|
+
uv pip install --python test-env/bin/python --no-cache dist/*.tar.gz pytest numpy
|
|
36
|
+
test-env/bin/pytest -o addopts=
|
|
37
|
+
|
|
38
|
+
- uses: actions/upload-artifact@v6
|
|
39
|
+
with:
|
|
40
|
+
name: sdist
|
|
41
|
+
path: dist/*.tar.gz
|
|
42
|
+
compression-level: 0
|
|
43
|
+
|
|
44
|
+
# Ask cibuildwheel which wheels it would build for each (platform,
|
|
45
|
+
# arch) given our [tool.cibuildwheel] config, then emit a JSON
|
|
46
|
+
# matrix that the wheels job consumes via `fromJson()`. Saves
|
|
47
|
+
# hand-maintaining a 30-row include list as Python versions evolve.
|
|
48
|
+
discover:
|
|
49
|
+
name: discover build identifiers
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
outputs:
|
|
52
|
+
matrix: ${{ steps.set.outputs.matrix }}
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v6
|
|
55
|
+
- uses: actions/setup-python@v6
|
|
56
|
+
with:
|
|
57
|
+
python-version: "3.13"
|
|
58
|
+
# Keep this version in sync with `pypa/cibuildwheel@vX.Y.Z`
|
|
59
|
+
# below — GHA's `uses:` doesn't accept expression interpolation,
|
|
60
|
+
# so the literal has to live in two places.
|
|
61
|
+
- run: pip install cibuildwheel==3.4.1
|
|
62
|
+
- id: set
|
|
63
|
+
shell: bash
|
|
64
|
+
run: |
|
|
65
|
+
python <<'PY' >> $GITHUB_OUTPUT
|
|
66
|
+
import json
|
|
67
|
+
import subprocess
|
|
68
|
+
|
|
69
|
+
# (runner image, cibuildwheel platform name, arch).
|
|
70
|
+
targets = [
|
|
71
|
+
("ubuntu-latest", "linux", "x86_64"),
|
|
72
|
+
("ubuntu-24.04-arm", "linux", "aarch64"),
|
|
73
|
+
("macos-latest", "macos", "arm64"),
|
|
74
|
+
("windows-latest", "windows", "AMD64"),
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
include = []
|
|
78
|
+
for runner, platform, arch in targets:
|
|
79
|
+
ids = subprocess.run(
|
|
80
|
+
["cibuildwheel",
|
|
81
|
+
f"--platform={platform}",
|
|
82
|
+
f"--archs={arch}",
|
|
83
|
+
"--print-build-identifiers"],
|
|
84
|
+
capture_output=True, text=True, check=True,
|
|
85
|
+
).stdout.split()
|
|
86
|
+
for ident in ids:
|
|
87
|
+
include.append({"os": runner, "only": ident})
|
|
88
|
+
|
|
89
|
+
print(f"matrix={json.dumps({'include': include})}")
|
|
90
|
+
PY
|
|
91
|
+
|
|
92
|
+
wheels:
|
|
93
|
+
# One job per build identifier (matrix expanded from `discover`)
|
|
94
|
+
# so wall-clock approaches max(per-wheel build time) instead of
|
|
95
|
+
# sum. Free for public repos on GitHub Actions runner-minutes.
|
|
96
|
+
name: ${{ matrix.only }}
|
|
97
|
+
needs: discover
|
|
98
|
+
runs-on: ${{ matrix.os }}
|
|
99
|
+
strategy:
|
|
100
|
+
fail-fast: false
|
|
101
|
+
matrix: ${{ fromJson(needs.discover.outputs.matrix) }}
|
|
102
|
+
|
|
103
|
+
steps:
|
|
104
|
+
- uses: actions/checkout@v6
|
|
105
|
+
|
|
106
|
+
# cibuildwheel uses uv as the build frontend; on Linux it
|
|
107
|
+
# bootstraps uv inside the manylinux container, but on macOS /
|
|
108
|
+
# Windows it expects uv on the host PATH. Skip the host install
|
|
109
|
+
# on Linux jobs — saves ~5s × N Linux runners of runner-minutes.
|
|
110
|
+
- if: runner.os != 'Linux'
|
|
111
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
112
|
+
|
|
113
|
+
# Pin uv's cache to C: on Windows so hardlinks work.
|
|
114
|
+
#
|
|
115
|
+
# setup-uv defaults UV_CACHE_DIR to ${RUNNER_TEMP}/setup-uv-cache,
|
|
116
|
+
# which on GitHub-hosted Windows runners is D:\a\_temp\... .
|
|
117
|
+
# cibuildwheel's `build[uv]` frontend creates Python's isolated
|
|
118
|
+
# build env via tempfile.gettempdir(), which on Windows resolves
|
|
119
|
+
# to C:\Users\runneradmin\AppData\Local\Temp\... . NTFS doesn't
|
|
120
|
+
# support hardlinks across drives, so uv falls back to full file
|
|
121
|
+
# copy ("Failed to hard link ... os error 17; falling back to
|
|
122
|
+
# copy"). For build-isolation envs containing a chunky toolchain
|
|
123
|
+
# like ziglang (~50MB, hundreds of small files, PE binaries that
|
|
124
|
+
# Defender deep-scans), that's ~48s per wheel install vs ~8s
|
|
125
|
+
# when hardlinking works.
|
|
126
|
+
#
|
|
127
|
+
# Pinning UV_CACHE_DIR to %LOCALAPPDATA% (always on C:) puts the
|
|
128
|
+
# cache on the same drive as the build env and restores
|
|
129
|
+
# hardlinking.
|
|
130
|
+
#
|
|
131
|
+
# Tracking upstream (acknowledged but not fixed; eifinger himself
|
|
132
|
+
# tried in setup-uv#451 and reverted in #460):
|
|
133
|
+
# https://github.com/astral-sh/setup-uv/issues/227
|
|
134
|
+
# https://github.com/astral-sh/setup-uv/issues/371
|
|
135
|
+
# https://github.com/astral-sh/setup-uv/pull/451
|
|
136
|
+
- if: runner.os == 'Windows'
|
|
137
|
+
shell: pwsh
|
|
138
|
+
run: |
|
|
139
|
+
$cache = Join-Path $env:LOCALAPPDATA 'uv-cache'
|
|
140
|
+
"UV_CACHE_DIR=$cache" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
141
|
+
|
|
142
|
+
- uses: pypa/cibuildwheel@v3.4.1
|
|
143
|
+
with:
|
|
144
|
+
only: ${{ matrix.only }}
|
|
145
|
+
|
|
146
|
+
- uses: actions/upload-artifact@v6
|
|
147
|
+
with:
|
|
148
|
+
name: wheels-${{ matrix.only }}
|
|
149
|
+
path: wheelhouse/*.whl
|
|
150
|
+
compression-level: 0
|
|
151
|
+
|
|
152
|
+
# Publish to PyPI on a published GitHub release, via OIDC trusted
|
|
153
|
+
# publishing (no API tokens). Gated by the `pypi` environment configured
|
|
154
|
+
# on this repo. Runs only on release:published, so pushes/PRs never upload.
|
|
155
|
+
to-pypi:
|
|
156
|
+
name: publish to PyPI
|
|
157
|
+
needs: [sdist, wheels]
|
|
158
|
+
if: github.event_name == 'release' && github.event.action == 'published'
|
|
159
|
+
runs-on: ubuntu-latest
|
|
160
|
+
environment:
|
|
161
|
+
name: pypi
|
|
162
|
+
url: https://pypi.org/p/csar
|
|
163
|
+
permissions:
|
|
164
|
+
id-token: write # required for OIDC trusted publishing
|
|
165
|
+
steps:
|
|
166
|
+
- uses: actions/download-artifact@v6
|
|
167
|
+
with:
|
|
168
|
+
pattern: "{wheels-*,sdist}"
|
|
169
|
+
merge-multiple: true
|
|
170
|
+
path: dist
|
|
171
|
+
|
|
172
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
csar-0.1.1/.gitignore
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
.venv/
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.pyc
|
|
5
|
+
*.pyo
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.mypy_cache/
|
|
8
|
+
.ruff_cache/
|
|
9
|
+
.coverage
|
|
10
|
+
|
|
11
|
+
# Build artifacts
|
|
12
|
+
build/
|
|
13
|
+
dist/
|
|
14
|
+
*.egg-info/
|
|
15
|
+
|
|
16
|
+
# Bundled static library (produced by the zig build)
|
|
17
|
+
src/csar/libcsar.*
|
|
18
|
+
|
|
19
|
+
# Zig build artifacts
|
|
20
|
+
zig-out/
|
|
21
|
+
.zig-cache/
|
|
22
|
+
zig-pkg/
|
|
23
|
+
|
|
24
|
+
# uv lockfile — opt in if you want it tracked
|
|
25
|
+
uv.lock
|
|
26
|
+
|
|
27
|
+
# Script outputs (generated plots), at any depth (incl. explorations/out/)
|
|
28
|
+
scripts/**/out/
|
|
29
|
+
|
|
30
|
+
# OS
|
|
31
|
+
.DS_Store
|
|
32
|
+
.ipynb_checkpoints/
|
csar-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AJ Friend
|
|
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.
|
csar-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: csar
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Conic spherical aspect-ratio solver: tightest enclosing cone of a point set on the unit sphere.
|
|
5
|
+
Keywords: conic,spherical,aspect-ratio,cone,geometry,geodesy,geospatial,optimization,zig
|
|
6
|
+
Author-Email: AJ Friend <ajfriend@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
|
+
Classifier: Programming Language :: Zig
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
24
|
+
Project-URL: Homepage, https://github.com/ajfriend/csar_py
|
|
25
|
+
Project-URL: Repository, https://github.com/ajfriend/csar_py
|
|
26
|
+
Project-URL: Issues, https://github.com/ajfriend/csar_py/issues
|
|
27
|
+
Project-URL: Upstream Zig package, https://github.com/ajfriend/csar_zig
|
|
28
|
+
Requires-Python: >=3.11
|
|
29
|
+
Requires-Dist: numpy>=1.24
|
|
30
|
+
Provides-Extra: plot
|
|
31
|
+
Requires-Dist: matplotlib>=3; extra == "plot"
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# csar: Conic Spherical Aspect Ratio
|
|
35
|
+
|
|
36
|
+
Python bindings for [`csar_zig`](https://github.com/ajfriend/csar_zig),
|
|
37
|
+
a spherical aspect-ratio solver. Given a point set on the unit sphere,
|
|
38
|
+
it finds the tightest ellipsoidal cone enclosing the points and returns
|
|
39
|
+
the cone's axis ratio.
|
|
40
|
+
|
|
41
|
+
A thin Cython binding over a small C-ABI shim that links the upstream
|
|
42
|
+
`csar` Zig package as a static archive — no separate shared library
|
|
43
|
+
ships in the wheel.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
Not on PyPI yet — install directly from GitHub. Point pip/uv at the git
|
|
48
|
+
URL, either the latest `main` or a tagged release:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
# latest main
|
|
52
|
+
pip install git+https://github.com/ajfriend/csar_py.git
|
|
53
|
+
uv pip install git+https://github.com/ajfriend/csar_py.git
|
|
54
|
+
|
|
55
|
+
# a specific tagged release
|
|
56
|
+
pip install git+https://github.com/ajfriend/csar_py.git@v0.1.0
|
|
57
|
+
uv pip install git+https://github.com/ajfriend/csar_py.git@v0.1.0
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
For the optional plotting helper (`csar.plot_cone`), add the `plot` extra:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
pip install "csar[plot] @ git+https://github.com/ajfriend/csar_py.git"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
That path triggers a source build: meson-python pulls the Zig toolchain
|
|
67
|
+
from the `ziglang` PyPI wheel (`python -m ziglang build`), compiles the
|
|
68
|
+
upstream `csar_zig` package into a static archive — fetched over the
|
|
69
|
+
network from the URL pinned in `src/zig/build.zig.zon` — then cythonizes
|
|
70
|
+
`src/cython/_cy.pyx` and links the result against it. No host-level Zig
|
|
71
|
+
or Cython install is required (Python 3.11+).
|
|
72
|
+
|
|
73
|
+
### Local development
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
just test # reinstall + run the test suite
|
|
77
|
+
just reinstall # force a clean rebuild of the Zig extension
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import csar
|
|
84
|
+
|
|
85
|
+
# A sequence of points. Each point is (lat, lng) in degrees by
|
|
86
|
+
# default; pass geo='vec3' to give unit (x, y, z) triples instead.
|
|
87
|
+
pts = [
|
|
88
|
+
(0.0, 0.0),
|
|
89
|
+
(0.0, 90.0),
|
|
90
|
+
(90.0, 0.0),
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
r = csar.solve(pts)
|
|
94
|
+
if isinstance(r, csar.Converged):
|
|
95
|
+
print(r.aspect_ratio) # cross-section axis ratio (>= 1)
|
|
96
|
+
print(r.Q[:, 0]) # unit cone axis (x, y, z) — first column of Q
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Any list/tuple of points works; a NumPy array is also accepted and is
|
|
100
|
+
read as an `(N, k)` array whose **rows are points** (`k` = 2 for the
|
|
101
|
+
`latlng`/`lonlat` families, 3 for `'vec3'`). The `geo` argument picks
|
|
102
|
+
the convention: `'latlng'` (default, h3's `(lat, lng)` order),
|
|
103
|
+
`'lonlat'` (GeoJSON's `(lon, lat)` order), their `_deg`/`_rad`
|
|
104
|
+
variants, or `'vec3'`.
|
|
105
|
+
|
|
106
|
+
Objects implementing `__geo_interface__` (shapely, geopandas, geojson,
|
|
107
|
+
h3 `LatLngPoly`/`LatLngMultiPoly`, …) can be passed directly — their
|
|
108
|
+
vertices are read as GeoJSON `(lon, lat)` degrees, so `geo` is ignored:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
import geopandas as gpd
|
|
112
|
+
|
|
113
|
+
gdf = gpd.read_file('countries.geojson')
|
|
114
|
+
for name, geom in zip(gdf['ADMIN'], gdf.geometry):
|
|
115
|
+
r = csar.solve(geom) # shapely geometry via __geo_interface__
|
|
116
|
+
if isinstance(r, csar.Converged):
|
|
117
|
+
print(name, r.aspect_ratio)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`MultiPoint`, `LineString`, `Polygon` (exterior ring), `MultiPolygon`,
|
|
121
|
+
and a `Feature` wrapping one of those are supported.
|
|
122
|
+
|
|
123
|
+
To visualize the fit, `csar.plot_cone(result, geom)` draws the outline
|
|
124
|
+
gnomonic-projected at the cone axis with the enclosing ellipse overlaid
|
|
125
|
+
— defaulting to north-up, scaled to metres, with labelled axes (needs
|
|
126
|
+
matplotlib — `pip install csar[plot]`):
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
csar.plot_cone(r, geom, title='Chile') # a finished figure from one call
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`scripts/states/` and `scripts/countries/` are full end-to-end examples.
|
|
133
|
+
|
|
134
|
+
`solve` runs all of this through `csar.to_vec3(points, geo=...)`, which
|
|
135
|
+
returns the `(N, 3)` array of unit vectors the solver actually sees.
|
|
136
|
+
Call it directly to inspect how your input maps onto the sphere:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
csar.to_vec3([(0, 0), (0, 90), (90, 0)])
|
|
140
|
+
# array([[1., 0., 0.],
|
|
141
|
+
# [0., 1., 0.],
|
|
142
|
+
# [0., 0., 1.]])
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Outcomes
|
|
146
|
+
|
|
147
|
+
`solve` returns one of three outcome types — `Converged`, `Infeasible`,
|
|
148
|
+
or `DidNotConverge` (collectively `Outcome`) — mirroring the Zig
|
|
149
|
+
`Outcome` tagged union. Each carries **only** the fields meaningful for
|
|
150
|
+
its outcome, so you dispatch on the type rather than guarding nullable
|
|
151
|
+
fields:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
match csar.solve(pts):
|
|
155
|
+
case csar.Converged() as c:
|
|
156
|
+
use(c.aspect_ratio, c.Q) # certified cone
|
|
157
|
+
case csar.Infeasible() as i:
|
|
158
|
+
handle(i.residual) # no enclosing cone exists
|
|
159
|
+
case csar.DidNotConverge() as d:
|
|
160
|
+
retry(d.gap, d.outer_iters) # hit the iteration cap
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
On a `Converged`, `sigma` is the `(3,)` eigenvalue array and `Q` the
|
|
164
|
+
`(3, 3)` eigenbasis (column `i` pairs with `sigma[i]`; column 0 is the
|
|
165
|
+
axis), so the enclosing ellipsoid matrix is
|
|
166
|
+
`A = c.Q @ np.diag(c.sigma) @ c.Q.T`. `DidNotConverge` exposes the same
|
|
167
|
+
`sigma`/`Q`/`gap` for diagnostics but, being uncertified, deliberately
|
|
168
|
+
has no `aspect_ratio`. See the docstrings in `src/csar/__init__.py` for
|
|
169
|
+
the full field reference.
|
|
170
|
+
|
|
171
|
+
## Solver paths
|
|
172
|
+
|
|
173
|
+
`solve` picks its solver via `method=`: `'auto'` (the default) resolves
|
|
174
|
+
to the library's recommended method — currently `'trust'`, a
|
|
175
|
+
trust-region descent that converges on every input family constructed
|
|
176
|
+
to date, including the wide/elongated inputs the original solver
|
|
177
|
+
structurally cannot. `'alternating'` is that original solver, kept for
|
|
178
|
+
continuity and for large dense near-circular inputs where it can still
|
|
179
|
+
be faster. The outcome's `.method` records the concrete path that ran.
|
|
180
|
+
|
|
181
|
+
## Layout
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
.
|
|
185
|
+
├── pyproject.toml — meson-python config, package metadata
|
|
186
|
+
├── meson.build — drives Zig static-archive build + Cython compile
|
|
187
|
+
├── justfile — reinstall / test / wheel / examples / clean
|
|
188
|
+
├── src/
|
|
189
|
+
│ ├── cython/_cy.pyx — Cython binding, exposes _cy.solve
|
|
190
|
+
│ ├── csar/
|
|
191
|
+
│ │ ├── __init__.py — gathers the public API (solve, to_vec3, plot_cone, Outcome…)
|
|
192
|
+
│ │ ├── convert.py — input → (N, 3) unit vectors: to_vec3, geo-interface
|
|
193
|
+
│ │ ├── outcomes.py — Converged/Infeasible/DidNotConverge + build()
|
|
194
|
+
│ │ ├── plot.py — plot_cone (optional; needs matplotlib)
|
|
195
|
+
│ │ └── solver.py — solve(): convert → _cy.solve → build
|
|
196
|
+
│ └── zig/
|
|
197
|
+
│ ├── build.zig — produces libcsar.{a,lib} (static archive)
|
|
198
|
+
│ ├── build.zig.zon — pins the csar_zig dependency
|
|
199
|
+
│ └── c_api.zig — pub export fn csar_solve
|
|
200
|
+
├── scripts/ — examples (run via `just states|countries`)
|
|
201
|
+
│ ├── states/ — US-state aspect ratios (geopandas + csar)
|
|
202
|
+
│ └── countries/ — country aspect ratios (geopandas + csar)
|
|
203
|
+
└── tests/test_bindings.py
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## History
|
|
207
|
+
|
|
208
|
+
`csar` was previously developed as
|
|
209
|
+
[`skar_py`](https://github.com/ajfriend/skar_py) and forked into this fresh
|
|
210
|
+
repository under a new name, since the `skar` name was already taken on PyPI.
|
|
211
|
+
The `skar_py` repository is preserved as-is for its history and provenance.
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT — see [LICENSE](LICENSE).
|
csar-0.1.1/changelog.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to csar. Terse by design — each entry points to the PR or
|
|
4
|
+
commit that carries the full detail.
|
|
5
|
+
|
|
6
|
+
## [0.1.1]
|
|
7
|
+
|
|
8
|
+
- First PyPI release. Enable OIDC trusted-publishing upload in the `wheels`
|
|
9
|
+
workflow (`to-pypi` job, runs on a published GitHub release). No functional
|
|
10
|
+
change from 0.1.0 — the 0.1.0 tag predates the publish job, so this is the
|
|
11
|
+
first version pushed to PyPI.
|
|
12
|
+
|
|
13
|
+
## [0.1.0]
|
|
14
|
+
|
|
15
|
+
- Initial release of `csar` (conic spherical aspect ratio): Python bindings
|
|
16
|
+
for the [`csar_zig`](https://github.com/ajfriend/csar_zig) solver. Given a
|
|
17
|
+
point set on the unit sphere, `csar.solve` returns the tightest enclosing
|
|
18
|
+
ellipsoidal cone as a typed `Converged` / `Infeasible` / `DidNotConverge`
|
|
19
|
+
outcome; `method=` selects the solver path (`'auto'` default, resolving to
|
|
20
|
+
the trust-region method). Ships as a Cython extension with the upstream Zig
|
|
21
|
+
solver statically linked — no separate shared library in the wheel.
|
|
22
|
+
Continues the prototype previously developed as
|
|
23
|
+
[`skar_py`](https://github.com/ajfriend/skar_py), preserved as-is for its
|
|
24
|
+
history and provenance.
|