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.
- pyturb-0.2.0/.github/workflows/ci.yml +45 -0
- pyturb-0.2.0/.github/workflows/docs.yml +41 -0
- pyturb-0.2.0/.github/workflows/gpu.yml +33 -0
- pyturb-0.2.0/.github/workflows/release.yml +37 -0
- pyturb-0.2.0/.gitignore +223 -0
- pyturb-0.2.0/CHANGELOG.md +116 -0
- pyturb-0.2.0/CLAUDE.md +73 -0
- pyturb-0.2.0/CONTRIBUTING.md +54 -0
- pyturb-0.2.0/LICENSE +21 -0
- pyturb-0.2.0/PKG-INFO +146 -0
- pyturb-0.2.0/README.md +105 -0
- pyturb-0.2.0/ROADMAP.md +44 -0
- pyturb-0.2.0/benchmarks/RESULTS.md +179 -0
- pyturb-0.2.0/benchmarks/bench_compare.py +363 -0
- pyturb-0.2.0/benchmarks/bench_frames.py +85 -0
- pyturb-0.2.0/benchmarks/bench_suite.py +204 -0
- pyturb-0.2.0/docs/api.md +73 -0
- pyturb-0.2.0/docs/comparison.md +55 -0
- pyturb-0.2.0/docs/concepts.md +63 -0
- pyturb-0.2.0/docs/images/validation.png +0 -0
- pyturb-0.2.0/docs/index.md +47 -0
- pyturb-0.2.0/docs/interop.md +92 -0
- pyturb-0.2.0/docs/quickstart.md +72 -0
- pyturb-0.2.0/docs/validation.md +50 -0
- pyturb-0.2.0/examples/01_screens.py +36 -0
- pyturb-0.2.0/examples/02_closed_loop.py +41 -0
- pyturb-0.2.0/examples/03_layered_atmosphere.py +38 -0
- pyturb-0.2.0/examples/04_off_axis.py +42 -0
- pyturb-0.2.0/examples/05_gpu_benchmark.py +22 -0
- pyturb-0.2.0/mkdocs.yml +55 -0
- pyturb-0.2.0/pyproject.toml +76 -0
- pyturb-0.2.0/src/pyturb/__init__.py +97 -0
- pyturb-0.2.0/src/pyturb/_accel.py +175 -0
- pyturb-0.2.0/src/pyturb/analysis.py +246 -0
- pyturb-0.2.0/src/pyturb/atmosphere.py +1103 -0
- pyturb-0.2.0/src/pyturb/backend.py +121 -0
- pyturb-0.2.0/src/pyturb/benchmark.py +77 -0
- pyturb-0.2.0/src/pyturb/extrude.py +725 -0
- pyturb-0.2.0/src/pyturb/flow.py +139 -0
- pyturb-0.2.0/src/pyturb/fourier.py +296 -0
- pyturb-0.2.0/src/pyturb/infinite.py +387 -0
- pyturb-0.2.0/src/pyturb/io.py +145 -0
- pyturb-0.2.0/src/pyturb/profiles.py +553 -0
- pyturb-0.2.0/src/pyturb/py.typed +0 -0
- pyturb-0.2.0/src/pyturb/utils.py +183 -0
- pyturb-0.2.0/tests/conftest.py +58 -0
- pyturb-0.2.0/tests/test_accel.py +71 -0
- pyturb-0.2.0/tests/test_analysis.py +108 -0
- pyturb-0.2.0/tests/test_atmosphere.py +558 -0
- pyturb-0.2.0/tests/test_backend_utils.py +84 -0
- pyturb-0.2.0/tests/test_benchmark.py +26 -0
- pyturb-0.2.0/tests/test_extrude.py +307 -0
- pyturb-0.2.0/tests/test_fourier.py +138 -0
- pyturb-0.2.0/tests/test_infinite.py +196 -0
- pyturb-0.2.0/tests/test_io.py +219 -0
- pyturb-0.2.0/tests/test_profiles.py +86 -0
- 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
|
pyturb-0.2.0/.gitignore
ADDED
|
@@ -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.
|