pyturb 0.2.0__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.
Files changed (57) hide show
  1. pyturb-0.2.0/.github/workflows/ci.yml +45 -0
  2. pyturb-0.2.0/.github/workflows/docs.yml +41 -0
  3. pyturb-0.2.0/.github/workflows/gpu.yml +33 -0
  4. pyturb-0.2.0/.github/workflows/release.yml +37 -0
  5. pyturb-0.2.0/.gitignore +223 -0
  6. pyturb-0.2.0/CHANGELOG.md +116 -0
  7. pyturb-0.2.0/CLAUDE.md +73 -0
  8. pyturb-0.2.0/CONTRIBUTING.md +54 -0
  9. pyturb-0.2.0/LICENSE +21 -0
  10. pyturb-0.2.0/PKG-INFO +146 -0
  11. pyturb-0.2.0/README.md +105 -0
  12. pyturb-0.2.0/ROADMAP.md +44 -0
  13. pyturb-0.2.0/benchmarks/RESULTS.md +179 -0
  14. pyturb-0.2.0/benchmarks/bench_compare.py +363 -0
  15. pyturb-0.2.0/benchmarks/bench_frames.py +85 -0
  16. pyturb-0.2.0/benchmarks/bench_suite.py +204 -0
  17. pyturb-0.2.0/docs/api.md +73 -0
  18. pyturb-0.2.0/docs/comparison.md +55 -0
  19. pyturb-0.2.0/docs/concepts.md +63 -0
  20. pyturb-0.2.0/docs/images/validation.png +0 -0
  21. pyturb-0.2.0/docs/index.md +47 -0
  22. pyturb-0.2.0/docs/interop.md +92 -0
  23. pyturb-0.2.0/docs/quickstart.md +72 -0
  24. pyturb-0.2.0/docs/validation.md +50 -0
  25. pyturb-0.2.0/examples/01_screens.py +36 -0
  26. pyturb-0.2.0/examples/02_closed_loop.py +41 -0
  27. pyturb-0.2.0/examples/03_layered_atmosphere.py +38 -0
  28. pyturb-0.2.0/examples/04_off_axis.py +42 -0
  29. pyturb-0.2.0/examples/05_gpu_benchmark.py +22 -0
  30. pyturb-0.2.0/mkdocs.yml +55 -0
  31. pyturb-0.2.0/pyproject.toml +76 -0
  32. pyturb-0.2.0/src/pyturb/__init__.py +97 -0
  33. pyturb-0.2.0/src/pyturb/_accel.py +175 -0
  34. pyturb-0.2.0/src/pyturb/analysis.py +246 -0
  35. pyturb-0.2.0/src/pyturb/atmosphere.py +1103 -0
  36. pyturb-0.2.0/src/pyturb/backend.py +121 -0
  37. pyturb-0.2.0/src/pyturb/benchmark.py +77 -0
  38. pyturb-0.2.0/src/pyturb/extrude.py +725 -0
  39. pyturb-0.2.0/src/pyturb/flow.py +139 -0
  40. pyturb-0.2.0/src/pyturb/fourier.py +296 -0
  41. pyturb-0.2.0/src/pyturb/infinite.py +387 -0
  42. pyturb-0.2.0/src/pyturb/io.py +145 -0
  43. pyturb-0.2.0/src/pyturb/profiles.py +553 -0
  44. pyturb-0.2.0/src/pyturb/py.typed +0 -0
  45. pyturb-0.2.0/src/pyturb/utils.py +183 -0
  46. pyturb-0.2.0/tests/conftest.py +58 -0
  47. pyturb-0.2.0/tests/test_accel.py +71 -0
  48. pyturb-0.2.0/tests/test_analysis.py +108 -0
  49. pyturb-0.2.0/tests/test_atmosphere.py +558 -0
  50. pyturb-0.2.0/tests/test_backend_utils.py +84 -0
  51. pyturb-0.2.0/tests/test_benchmark.py +26 -0
  52. pyturb-0.2.0/tests/test_extrude.py +307 -0
  53. pyturb-0.2.0/tests/test_fourier.py +138 -0
  54. pyturb-0.2.0/tests/test_infinite.py +196 -0
  55. pyturb-0.2.0/tests/test_io.py +219 -0
  56. pyturb-0.2.0/tests/test_profiles.py +86 -0
  57. pyturb-0.2.0/validation/validate.py +160 -0
@@ -0,0 +1,45 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/ruff-action@v3
14
+ with:
15
+ args: check
16
+
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python-version: ["3.9", "3.11", "3.12", "3.13"]
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+ - run: pip install -e ".[test,fits]"
29
+ - run: pytest -q --cov=pyturb --cov-report=xml
30
+ - name: Upload coverage
31
+ if: matrix.python-version == '3.12'
32
+ uses: codecov/codecov-action@v4
33
+ with:
34
+ files: coverage.xml
35
+ continue-on-error: true
36
+
37
+ docs:
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+ - uses: actions/setup-python@v5
42
+ with:
43
+ python-version: "3.12"
44
+ - run: pip install -e ".[docs]"
45
+ - run: mkdocs build --strict
@@ -0,0 +1,41 @@
1
+ name: Docs
2
+
3
+ # Build the mkdocs site and publish it to GitHub Pages on every push to main.
4
+ # Enable Pages once at Settings -> Pages -> Source: GitHub Actions.
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+
10
+ permissions:
11
+ contents: read
12
+ pages: write
13
+ id-token: write
14
+
15
+ concurrency:
16
+ group: pages
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ build:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - uses: actions/setup-python@v5
25
+ with:
26
+ python-version: "3.12"
27
+ - run: pip install -e ".[docs]"
28
+ - run: mkdocs build --strict --site-dir _site
29
+ - uses: actions/upload-pages-artifact@v3
30
+ with:
31
+ path: _site
32
+
33
+ deploy:
34
+ needs: build
35
+ runs-on: ubuntu-latest
36
+ environment:
37
+ name: github-pages
38
+ url: ${{ steps.deployment.outputs.page_url }}
39
+ steps:
40
+ - id: deployment
41
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,33 @@
1
+ name: GPU
2
+
3
+ # The CuPy path is not exercised by the CPU-only `CI` workflow. This job runs
4
+ # the GPU-marked tests (pytest --run-gpu) on a self-hosted runner that has a
5
+ # CUDA GPU + CuPy. It is manual (workflow_dispatch) and, optionally, scheduled;
6
+ # it never blocks a PR, so a repo without a GPU runner is unaffected.
7
+ #
8
+ # To use it: register a self-hosted runner with the labels [self-hosted, gpu]
9
+ # on a CUDA machine, then trigger this workflow from the Actions tab. Locally,
10
+ # the same check is just `pytest --run-gpu` in an env with cupy installed.
11
+
12
+ on:
13
+ workflow_dispatch:
14
+ schedule:
15
+ # Weekly, Mondays 06:00 UTC — a low-frequency regression guard for the
16
+ # CuPy path. Harmless (a no-op) if no self-hosted GPU runner is online.
17
+ - cron: "0 6 * * 1"
18
+
19
+ jobs:
20
+ gpu-test:
21
+ runs-on: [self-hosted, gpu]
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - name: Install with CUDA 12 extras
25
+ run: pip install -e ".[test,fits,cuda12]"
26
+ - name: Show GPU
27
+ run: |
28
+ python -c "import cupy; print('CuPy', cupy.__version__); \
29
+ print(cupy.cuda.runtime.getDeviceProperties(0)['name'])"
30
+ - name: Run GPU-marked tests
31
+ run: pytest -q --run-gpu -m gpu
32
+ - name: Run full suite on the GPU host (CPU + GPU)
33
+ run: pytest -q --run-gpu
@@ -0,0 +1,37 @@
1
+ name: Release
2
+
3
+ # Build and publish to PyPI on a version tag (e.g. v0.2.0), using PyPI
4
+ # trusted publishing (OIDC) — no API token needed. Configure the publisher
5
+ # once at https://pypi.org/manage/project/pyturb/settings/publishing/.
6
+
7
+ on:
8
+ push:
9
+ tags: ["v*"]
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+ - run: pip install build
20
+ - run: python -m build
21
+ - uses: actions/upload-artifact@v4
22
+ with:
23
+ name: dist
24
+ path: dist/
25
+
26
+ publish:
27
+ needs: build
28
+ runs-on: ubuntu-latest
29
+ environment: pypi
30
+ permissions:
31
+ id-token: write # required for trusted publishing
32
+ steps:
33
+ - uses: actions/download-artifact@v4
34
+ with:
35
+ name: dist
36
+ path: dist/
37
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,223 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
219
+
220
+ .vscode/
221
+ # mkdocs strict build output (docs.yml)
222
+ _site/
223
+ trade_study_review/
@@ -0,0 +1,116 @@
1
+ # Changelog
2
+
3
+ All notable changes to pyturb are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/), and the project aims to adhere
5
+ to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.2.0]
8
+
9
+ The "atmosphere" release: pyturb goes from a phase-screen library to a complete,
10
+ benchmarked, GPU-native AO atmosphere.
11
+
12
+ ### Added
13
+
14
+ - **`Atmosphere`** — layered atmosphere summed to pupil OPD, with per-layer
15
+ wind, airmass/zenith scaling, off-axis `directions=`, field-of-view
16
+ oversizing, and integrated `r0` / `seeing` / `theta0` / `tau0` /
17
+ `greenwood_frequency`. Built from named profiles via `from_profile`.
18
+ - **Two frozen-flow engines.** `engine="spectral"` (default): exact sub-pixel
19
+ shift-theorem translation, all layers in one FFT, but periodic. Boiling
20
+ (`tau_boil`) via a spectral AR(1). `engine="extrude"`: Assémat–Wilson row
21
+ extrusion in a wind-aligned ring buffer with rotated sub-pixel sampling —
22
+ unbounded, non-periodic, any wind direction.
23
+ - **`InfinitePhaseScreen`** rewritten with a ring buffer and sub-pixel
24
+ `advance()` (Catmull-Rom / linear), memory bounded over arbitrarily long runs.
25
+ - **Named profiles**: `paranal-median`, `mauna-kea`, `keck`, `las-campanas`,
26
+ `cerro-pachon`, `armazones`, `hv57`, `single-layer`, `two-layer`;
27
+ `discretize_cn2(method=...)` with moment-conserving `"equivalent"`
28
+ (conserves `theta0` and `tau0`), `"centroid"`, and `"optimal_grouping"` —
29
+ the last chooses bin edges by dynamic programming to minimise the
30
+ Cn²-weighted within-group spread of `h^{5/3}`, for MCAO/tomography layer
31
+ compression (Saxenhuber et al. 2017).
32
+ - **`Atmosphere.evolve(dt)`** — single-step, in-seconds frozen-flow stepper
33
+ (mirrors HCIPy's `evolve_until`); repeated calls reproduce `frames(dt)`.
34
+ - **`interp="lanczos"`** (6-tap Lanczos-3) sub-pixel readout on the extruder and
35
+ `InfinitePhaseScreen` — a flatter sub-Nyquist kernel that cuts the extruder's
36
+ finest-scale travel-phase flicker (~10% → ~3.5%) and structure-function
37
+ deficit versus the default cubic, with no change to the extrusion statistics.
38
+ - **`pyturb.analysis`**: Zernike basis/decomposition, Noll (1976) mode
39
+ variances, temporal PSD + power-law fit, angular decorrelation.
40
+ - **I/O**: `pyturb.save` / `pyturb.load` for `.npz` and FITS (optional astropy)
41
+ with metadata; `Atmosphere.metadata`.
42
+ - **Chromatic OPD**: `dispersion="edlen"` (dry air) and `dispersion="ciddor"`
43
+ with a `wet_fraction` water-vapour term for the mid-IR/interferometric
44
+ "wet–dry" problem; `pyturb.air_refractivity` and
45
+ `pyturb.water_vapour_refractivity`.
46
+ - **LGS cone effect**: `Atmosphere(lgs_altitude=...)` on **both** engines — the
47
+ extruder samples its ring buffer on a magnified grid, the spectral engine
48
+ zoom-resamples each layer's screen about the pupil centre by the same factor.
49
+ On the spectral engine the cone now **composes with `tau_boil` boiling**,
50
+ closing the previous cone/boiling mutual exclusivity.
51
+ - **Non-Kolmogorov spectra**: `PhaseScreen(power_law=..., inner_scale=...)`.
52
+ - **Threaded CPU FFT**: `pyturb.set_fft_workers()`.
53
+ - **GPU test path**: GPU tests marked `@pytest.mark.gpu`, run with
54
+ `pytest --run-gpu` (a `device` fixture parameterises statistics tests over
55
+ CPU/GPU); `.github/workflows/gpu.yml` runs them on a self-hosted GPU runner.
56
+ - **`pyturb.benchmark()`** convenience; `benchmarks/bench_suite.py`
57
+ (per-use-case throughput sweep across CPU/GPU) and
58
+ `benchmarks/bench_compare.py` head-to-head vs aotools/soapy/HCIPy;
59
+ `validation/validate.py` gallery.
60
+ - Docs: `docs/comparison.md`, `docs/interop.md`, `docs/validation.md`; examples
61
+ gallery (`examples/01`–`05`).
62
+ - `py.typed` marker; version single-sourced from package metadata.
63
+
64
+ ### Changed
65
+
66
+ - `Atmosphere` output is **OPD in metres** (achromatic); pass `wavelength=` for
67
+ phase. `PhaseScreen` / `InfinitePhaseScreen` still return radians.
68
+ - `discretize_cn2` default method is now `"equivalent"` (moment-conserving).
69
+
70
+ ### Performance
71
+
72
+ - **Spectral engine: collapse the layer axis before the transform.** The
73
+ inverse FFT and subharmonic outer product are linear and shared across
74
+ layers, so `Atmosphere._integrate` now sums the shifted spectra to one
75
+ `(n, n)` array and inverse-FFTs *once* instead of once per layer (and sums
76
+ each subharmonic level's `3x3` coefficients before the shared basis product).
77
+ Identical output; measured on an RTX 5090, 9-layer paranal-median: **CPU
78
+ 25 → 87 fps at 512² (3.4×)**, **GPU 865 → 1232 fps (1.4×)**.
79
+ - **Batch all subharmonic levels into one matmul.** The low-frequency
80
+ subharmonic correction shares one `(3, n)` sinusoid basis across levels and
81
+ layers, so `Atmosphere._integrate`, `PhaseScreen.generate`,
82
+ `FourierFlowScreen.translate` and the boiling update now evaluate every level
83
+ in a couple of batched matmuls instead of a Python loop over levels (which was
84
+ launch-latency bound on the GPU — ~78% of a frame). Identical output.
85
+ Measured on an RTX 5090, 9-layer paranal-median frozen flow: **1,232 → 3,004
86
+ fps at 512² GPU**, **3,234 fps at 256²**; single-layer Monte-Carlo generation
87
+ **14,000 → 31,000 screens/s at 512²** (55,000 → 108,000 at 256²); CPU
88
+ spectral **~87 → ~130 fps at 512²** before the accel extra below.
89
+ - **Fused GPU/CPU extruder readout kernel.** Every layer's ring buffer is a slab
90
+ of one contiguous `(L, cap, W)` array, and the per-frame rotated, sub-pixel,
91
+ per-layer-wind-shifted pupil gather runs in a single pass for the `"cubic"`
92
+ and `"lanczos"` interpolators: a hand-written CUDA kernel on the GPU and a
93
+ fused `prange` Numba kernel on the CPU (see the accel extra below), bit-exact
94
+ with the previous tap-broadcast gather. Measured on an RTX 5090, 9-layer
95
+ paranal-median: **121 → 4,484 fps at 256² GPU (37×)**, **118 → 1,730 fps at
96
+ 512² (15×)**, **50 → 602 fps at 1024²**; the `"lanczos"` readout is now a
97
+ fused kernel too (~334 fps at 512² GPU, from ~120 fps).
98
+ - **Optional Numba CPU acceleration (`pip install pyturb[accel]`).** The CPU
99
+ frozen-flow hot paths — the spectral engine's fused layer sum and the
100
+ extruder's fused bicubic/Lanczos readout — run through Numba when it is
101
+ importable, with a NumPy fallback otherwise (identical results to float
102
+ round-off). Measured on a 32-core CPU, 9-layer paranal-median: spectral
103
+ **~130 → 270 fps at 512²**, extruder **6 → 164 fps at 512² (27×)** and
104
+ **28 → 966 fps at 256² (34×)**.
105
+ - **Geometry-derived extruder buffer sizing.** The shared ring buffer is now
106
+ sized to the largest along-wind/off-axis requirement actually present among
107
+ the layers (each layer's own wind direction and altitude), not a blanket
108
+ every-layer-at-45-degrees-and-max-altitude assumption. Measured (n=512): a
109
+ ground-layer-only atmosphere with `field_of_view=30"` uses ~68% less buffer
110
+ memory; an axis-aligned atmosphere uses ~41% less even at
111
+ `field_of_view=0`. Never worse than before.
112
+
113
+ ## [0.1.0]
114
+
115
+ - Initial release: `PhaseScreen` (FFT + subharmonics) and `InfinitePhaseScreen`
116
+ (Assémat–Wilson extrusion), NumPy/CuPy backends, structure-function tests.
pyturb-0.2.0/CLAUDE.md ADDED
@@ -0,0 +1,73 @@
1
+ # CLAUDE.md
2
+
3
+ Guidance for agents working in this repo. Keep it current if the CI workflow changes.
4
+
5
+ ## Before considering any change done
6
+
7
+ Run these from the repo root (activate an env with the project installed
8
+ editable, e.g. `pip install -e ".[test,fits,docs]"`). All four mirror
9
+ `.github/workflows/ci.yml` exactly — if they pass locally, CI passes.
10
+
11
+ ```bash
12
+ ruff check . # lint (must be clean, zero errors)
13
+ python -m pytest -q --cov=pyturb --cov-report=term-missing # full test suite + coverage
14
+ mkdocs build --strict # docs (only if you touched README/docs/mkdocs.yml)
15
+ python -c "import pyturb" # sanity import after any src/ change
16
+ ```
17
+
18
+ CI additionally runs the test suite on Python 3.9, 3.11, 3.12, and 3.13. If
19
+ you only have one interpreter available, at minimum grep your diff for
20
+ anything that needs Python >=3.10 (`match` statements, `X | Y` type unions
21
+ used at runtime, etc.) — the project floor is `>=3.9`, and code should not
22
+ silently assume a newer numpy either (e.g. `np.trapezoid` requires NumPy
23
+ >= 2.0 and `np.trapz` was removed in a later release; the `numpy>=1.22` floor
24
+ needs a `np.trapezoid if hasattr(np, "trapezoid") else np.trapz` fallback,
25
+ already used in `profiles.py` — note `hasattr`, not `getattr`'s default,
26
+ since `getattr(np, "trapezoid", np.trapz)` still evaluates `np.trapz` eagerly
27
+ and breaks on NumPy releases that no longer have it). If in doubt, spin up a throwaway
28
+ `conda create -n py39check python=3.9` and run the suite there — this has
29
+ caught real bugs before.
30
+
31
+ ## What "done" means here, beyond green tests
32
+
33
+ - **Exercise the actual behavior, not just the code path.** A test that
34
+ calls a function and checks it doesn't throw is not a correctness test.
35
+ Assert on values, statistics, or invariants that would actually catch the
36
+ bug you just fixed or could plausibly introduce. See
37
+ `tests/test_extrude.py::test_finescale_readout_flicker_is_bounded` or
38
+ `tests/test_atmosphere.py::test_boiling_is_scale_dependent_not_uniform`
39
+ for the pattern: characterize the real physical/statistical behavior with
40
+ a bounded assertion, not just "it ran."
41
+ - **Check edge cases the existing tests don't reach**: a single large jump
42
+ vs. many small steps (ring-buffer code in `extrude.py`/`infinite.py` has
43
+ been bitten by this — compaction logic that only gets exercised by tiny
44
+ steps hides bugs that surface on one big one), off-grid/boundary requests,
45
+ values outside a declared range.
46
+ - **If you touch statistical/physical code**, verify against theory or a
47
+ known reference where one exists (structure function vs. von Kármán
48
+ theory, θ₀/τ₀ formulas, a cited profile table) rather than just checking
49
+ the code runs. Don't trust a single-realization measurement — several
50
+ seeds, or an ensemble average, distinguish a real effect from noise.
51
+ - **Match error message quality to the rest of the codebase**: when
52
+ rejecting an invalid combination (see `Atmosphere.__init__`'s many
53
+ `ValueError`s), say *why*, not just *that*. A bare "X requires Y" forces
54
+ the next reader to spelunk the source to find out if it's a permanent
55
+ architectural fact or a gap that might get lifted.
56
+ - **Update docstrings/README/RESULTS.md claims when behavior changes.**
57
+ Several of the bugs found were docs stating something the code didn't
58
+ actually do (or a claim that didn't survive scrutiny, e.g. a benchmark
59
+ ranking within its own noise). A code fix that leaves a stale claim in
60
+ place isn't finished.
61
+
62
+ ## Style notes specific to this repo
63
+
64
+ - Comments and docstrings describe **current** behavior only — never
65
+ "no more X" / "previously Y, now Z" / references to a past bug or a
66
+ specific review. A future reader has no context for what "before" means;
67
+ state what the code does now. (`CHANGELOG.md` is the one place that's
68
+ supposed to narrate change over time.)
69
+ - Type annotations use `from __future__ import annotations` +
70
+ `typing.Optional`/`Union` (not bare `X | Y`), to stay valid on the
71
+ `>=3.9` floor.
72
+ - `ruff` line length is 90 (`pyproject.toml`); wrap before that, don't
73
+ disable the rule.
@@ -0,0 +1,54 @@
1
+ # Contributing to pyturb
2
+
3
+ Thanks for your interest! pyturb aims to be the fastest, GPU-native, and
4
+ statistically-careful way to get atmospheric OPD into an AO workflow. A few
5
+ conventions keep it that way.
6
+
7
+ ## Development setup
8
+
9
+ ```bash
10
+ git clone https://github.com/jacotay7/pyturb
11
+ cd pyturb
12
+ pip install -e ".[test]" # add ",fits" for the FITS I/O tests
13
+ pytest -q
14
+ ```
15
+
16
+ For GPU work, install a CuPy build matching your CUDA toolkit
17
+ (`pip install cupy-cuda12x`); the suite skips GPU tests when CuPy is absent.
18
+
19
+ ## The bar for a change
20
+
21
+ pyturb's credibility rests on three habits — please keep them:
22
+
23
+ 1. **Every physics feature lands with an ensemble-statistics test against
24
+ theory.** New turbulence behaviour must be shown to match a closed form
25
+ (structure function, Noll variances, a PSD slope, …), not just "look right".
26
+ See `tests/` for the pattern and `validation/validate.py` for the gallery.
27
+ 2. **Every performance claim lands with a benchmark.** If you speed something
28
+ up, add or update a script under `benchmarks/`.
29
+ 3. **Every user-facing feature lands with docs.** A docstring at minimum; a
30
+ `docs/` page or example if it's a new capability.
31
+
32
+ ## Scope
33
+
34
+ pyturb is *the atmosphere*, not a full AO system. Please keep out of scope:
35
+ WFS/DM/controller simulation, tomographic reconstructors and slope
36
+ covariance, and Fresnel/scintillation propagation. We output
37
+ phase/OPD and hand those effects to the tools that own them — see
38
+ `docs/comparison.md`.
39
+
40
+ ## Style
41
+
42
+ - `ruff check` must pass (`pip install ruff`) — CI enforces it. Match the
43
+ surrounding style; the repo is hand-formatted, so `ruff format` is not imposed.
44
+ - NumPy-style docstrings; type hints on public signatures.
45
+ - Write backend-agnostic array code (works on NumPy and CuPy); avoid
46
+ host↔device syncs inside hot loops.
47
+ - Prefer `float32` defaults for GPU throughput; keep a `float64` path for
48
+ accuracy-sensitive setup.
49
+
50
+ ## Pull requests
51
+
52
+ Small, focused PRs with tests are easiest to review. Note in the description
53
+ which of the three habits above your change satisfies. Update `CHANGELOG.md`
54
+ under *unreleased*.
pyturb-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jacob Taylor
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.