optixstuff 3.0.0__tar.gz → 4.0.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.
Files changed (28) hide show
  1. optixstuff-4.0.1/.codespellrc +4 -0
  2. optixstuff-4.0.1/.editorconfig +15 -0
  3. optixstuff-4.0.1/.gitattributes +10 -0
  4. {optixstuff-3.0.0 → optixstuff-4.0.1}/.gitignore +5 -0
  5. {optixstuff-3.0.0 → optixstuff-4.0.1}/.pre-commit-config.yaml +6 -2
  6. {optixstuff-3.0.0 → optixstuff-4.0.1}/.zenodo.json +1 -1
  7. {optixstuff-3.0.0 → optixstuff-4.0.1}/CHANGELOG.md +28 -0
  8. {optixstuff-3.0.0 → optixstuff-4.0.1}/CITATION.cff +1 -1
  9. {optixstuff-3.0.0 → optixstuff-4.0.1}/PKG-INFO +36 -22
  10. {optixstuff-3.0.0 → optixstuff-4.0.1}/README.md +9 -0
  11. optixstuff-4.0.1/SECURITY.md +9 -0
  12. optixstuff-4.0.1/pyproject.toml +110 -0
  13. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/_version.py +2 -2
  14. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/coronagraph.py +38 -16
  15. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/detector.py +2 -2
  16. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/optical_elements.py +4 -6
  17. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/optical_path.py +2 -1
  18. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/primary.py +20 -20
  19. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/speckle.py +66 -5
  20. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/yippy_coronagraph.py +113 -5
  21. optixstuff-3.0.0/pyproject.toml +0 -64
  22. {optixstuff-3.0.0 → optixstuff-4.0.1}/.readthedocs.yaml +0 -0
  23. {optixstuff-3.0.0 → optixstuff-4.0.1}/LICENSE +0 -0
  24. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/__init__.py +0 -0
  25. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/_repr.py +0 -0
  26. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/disperser.py +0 -0
  27. {optixstuff-3.0.0 → optixstuff-4.0.1}/src/optixstuff/exposure.py +0 -0
  28. {optixstuff-3.0.0 → optixstuff-4.0.1}/tools/check_internal_refs.py +0 -0
@@ -0,0 +1,4 @@
1
+ [codespell]
2
+ skip = .git,*.ipynb,uv.lock,*/_build,*.fits
3
+ # add domain words only if codespell flags them (clean on the libraries sampled)
4
+ # ignore-words-list =
@@ -0,0 +1,15 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.{yml,yaml,json,toml}]
12
+ indent_size = 2
13
+
14
+ [Makefile]
15
+ indent_style = tab
@@ -0,0 +1,10 @@
1
+ * text=auto eol=lf
2
+ *.ipynb text eol=lf linguist-documentation
3
+ docs/** linguist-documentation
4
+ *.fits binary
5
+ *.npy binary
6
+ *.npz binary
7
+ *.png binary
8
+ *.pdf binary
9
+ uv.lock linguist-generated -diff
10
+ CHANGELOG.md linguist-generated
@@ -163,4 +163,9 @@ cython_debug/
163
163
  input/
164
164
  output/
165
165
  scripts/
166
+ !.github/scripts/
166
167
  *.png
168
+
169
+ .claude/
170
+ .jax_cache/
171
+ .ruff_cache/
@@ -15,13 +15,17 @@ repos:
15
15
  args: [--pytest-test-first]
16
16
  - id: end-of-file-fixer
17
17
  - repo: https://github.com/astral-sh/ruff-pre-commit
18
- rev: v0.15.2
18
+ rev: v0.17.3
19
19
  hooks:
20
20
  - id: ruff-check
21
21
  args: [--fix]
22
22
  - id: ruff-format
23
+ - repo: https://github.com/codespell-project/codespell
24
+ rev: v2.4.3
25
+ hooks:
26
+ - id: codespell
23
27
  - repo: https://github.com/compilerla/conventional-pre-commit
24
- rev: v4.3.0
28
+ rev: v4.4.0
25
29
  hooks:
26
30
  - id: conventional-pre-commit
27
31
  stages: [commit-msg]
@@ -3,7 +3,7 @@
3
3
  "creators": [
4
4
  {
5
5
  "orcid": "0000-0002-3138-0240",
6
- "affiliation": "NASA Goddard Space Flight Center, Greenbelt, MD",
6
+ "affiliation": "University of Maryland, Baltimore County; NASA Goddard Space Flight Center; CRESST II",
7
7
  "name": "Spohn, Corey"
8
8
  }
9
9
  ],
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.0.1](https://github.com/CoreySpohn/optixstuff/compare/v4.0.0...v4.0.1) (2026-11-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * route yippy stellar and sky products through their own frame ([b9a029b](https://github.com/CoreySpohn/optixstuff/commit/b9a029b2b03a96564b443ad14aacfe3fdbb9b62b))
9
+
10
+ ## [4.0.0](https://github.com/CoreySpohn/optixstuff/compare/v3.0.0...v4.0.0) (2026-11-05)
11
+
12
+
13
+ ### ⚠ BREAKING CHANGES
14
+
15
+ * **coronagraph:** AbstractCoronagraph.core_mean_intensity implementations must accept the keyword-only stellar_diam_lod argument.
16
+ * **coronagraph:** YippyCoronagraph.core_mean_intensity and noise_floor_ayo now return values per (lambda/D)^2, the AbstractCoronagraph contract, instead of yippy's per-pixel fraction of the star's light; both grow by 1/pixel_scale_lod**2 (16x at 0.25 lambda/D pixels). Multiply either by core_area for the fraction in the aperture, and stop dividing by pixel_scale_lod**2 downstream.
17
+ * American spelling throughout, rename segment_centres_m to segment_centers_m
18
+
19
+ ### Features
20
+
21
+ * **coronagraph:** add stellar_diam_lod to core_mean_intensity and noise floor ([8cd9749](https://github.com/CoreySpohn/optixstuff/commit/8cd97496c3434ea7bd4effaf2b6f2f1fb095a294))
22
+ * **speckle:** exposure-averaged realization on the speckle-field contract ([d163bf6](https://github.com/CoreySpohn/optixstuff/commit/d163bf6a0a821b1d31bed46925ef63a4c19145de))
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * American spelling throughout, rename segment_centres_m to segment_centers_m ([ccf79e1](https://github.com/CoreySpohn/optixstuff/commit/ccf79e14d8aee487d1dcabd2cf08b4a3893b2a36))
28
+ * **coronagraph:** report the yippy AYO noise floor per (lambda/D)^2 ([3add7c7](https://github.com/CoreySpohn/optixstuff/commit/3add7c7c79f8153cfafa0a7409b0ed497c001ee4))
29
+ * **coronagraph:** report yippy core mean intensity per (lambda/D)^2 ([dc55aaa](https://github.com/CoreySpohn/optixstuff/commit/dc55aaaca375bb5e45b34d93df0ff9ad5c16fbd5))
30
+
3
31
  ## [3.0.0](https://github.com/CoreySpohn/optixstuff/compare/v2.0.0...v3.0.0) (2026-07-20)
4
32
 
5
33
 
@@ -7,7 +7,7 @@ authors:
7
7
  - family-names: Spohn
8
8
  given-names: Corey
9
9
  orcid: "https://orcid.org/0000-0002-3138-0240"
10
- affiliation: "NASA Goddard Space Flight Center"
10
+ affiliation: "University of Maryland, Baltimore County; NASA Goddard Space Flight Center; CRESST II"
11
11
  repository-code: "https://github.com/CoreySpohn/optixstuff"
12
12
  license: MIT
13
13
  doi: "10.5281/zenodo.20819379"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: optixstuff
3
- Version: 3.0.0
3
+ Version: 4.0.1
4
4
  Summary: Hardware abstractions for the HWO direct imaging simulation suite
5
5
  Project-URL: Homepage, https://github.com/CoreySpohn/optixstuff
6
6
  Project-URL: Issues, https://github.com/CoreySpohn/optixstuff/issues
@@ -32,33 +32,47 @@ Classifier: Intended Audience :: Science/Research
32
32
  Classifier: License :: OSI Approved :: MIT License
33
33
  Classifier: Programming Language :: Python :: 3
34
34
  Classifier: Topic :: Scientific/Engineering :: Astronomy
35
- Requires-Python: >=3.11
36
- Requires-Dist: equinox>=0.12.0
37
- Requires-Dist: hwoutils
38
- Requires-Dist: interpax
39
- Requires-Dist: jax>=0.4.1
40
- Requires-Dist: jaxlib>=0.4.1
41
- Requires-Dist: yippy
35
+ Requires-Python: >=3.12
36
+ Requires-Dist: equinox>=0.13.2
37
+ Requires-Dist: hwoutils>=1.10.0
38
+ Requires-Dist: interpax>=0.3.12
39
+ Requires-Dist: jax>=0.8.1
40
+ Requires-Dist: jaxlib>=0.8.1
41
+ Requires-Dist: jaxtyping>=0.3.3
42
+ Requires-Dist: yippy>=2.10.6
42
43
  Provides-Extra: dev
43
- Requires-Dist: pre-commit; extra == 'dev'
44
+ Requires-Dist: pre-commit>=3.5; extra == 'dev'
44
45
  Provides-Extra: docs
45
- Requires-Dist: ipython; extra == 'docs'
46
- Requires-Dist: matplotlib; extra == 'docs'
47
- Requires-Dist: myst-nb; extra == 'docs'
48
- Requires-Dist: sphinx; extra == 'docs'
49
- Requires-Dist: sphinx-autoapi; extra == 'docs'
50
- Requires-Dist: sphinx-autodoc-typehints; extra == 'docs'
51
- Requires-Dist: sphinx-book-theme; extra == 'docs'
52
- Requires-Dist: sphinxcontrib-mermaid; extra == 'docs'
46
+ Requires-Dist: ipython>=8.18; extra == 'docs'
47
+ Requires-Dist: matplotlib>=3.10.7; extra == 'docs'
48
+ Requires-Dist: myst-nb>=1.0; extra == 'docs'
49
+ Requires-Dist: sphinx-autoapi>=3.0; extra == 'docs'
50
+ Requires-Dist: sphinx-book-theme>=1.1; extra == 'docs'
51
+ Requires-Dist: sphinx>=7.2; extra == 'docs'
52
+ Requires-Dist: sphinxcontrib-mermaid>=0.9; extra == 'docs'
53
53
  Provides-Extra: test
54
- Requires-Dist: hypothesis; extra == 'test'
55
- Requires-Dist: nox; extra == 'test'
56
- Requires-Dist: pytest; extra == 'test'
57
- Requires-Dist: pytest-cov; extra == 'test'
54
+ Requires-Dist: hypothesis>=6.79.1; extra == 'test'
55
+ Requires-Dist: numpy>=2.0; extra == 'test'
56
+ Requires-Dist: psutil>=5.9.4; extra == 'test'
57
+ Requires-Dist: pytest-fail-slow>=0.1.0; extra == 'test'
58
+ Requires-Dist: pytest-markdown-docs>=0.5.0; extra == 'test'
59
+ Requires-Dist: pytest-testmon>=2.0.7; extra == 'test'
60
+ Requires-Dist: pytest-timeout>=1.3.1; extra == 'test'
61
+ Requires-Dist: pytest-xdist>=2.5; extra == 'test'
62
+ Requires-Dist: pytest>=7.3.2; extra == 'test'
58
63
  Description-Content-Type: text/markdown
59
64
 
60
65
  # optixstuff
61
66
 
67
+ <p align="center">
68
+ <a href="https://pypi.org/project/optixstuff/"><img src="https://img.shields.io/pypi/v/optixstuff.svg?style=flat-square&logo=pypi" alt="PyPI"/></a>
69
+ <a href="https://optixstuff.readthedocs.io"><img src="https://readthedocs.org/projects/optixstuff/badge/?version=latest&style=flat-square" alt="Documentation Status"/></a>
70
+ <a href="https://github.com/CoreySpohn/optixstuff/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/CoreySpohn/optixstuff/ci.yml?branch=main&style=flat-square&logo=github&label=tests" alt="Tests"/></a>
71
+ <a href="https://github.com/CoreySpohn/optixstuff/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" alt="License"/></a>
72
+ <a href="https://github.com/CoreySpohn/optixstuff/blob/main/pyproject.toml"><img src="https://img.shields.io/badge/python-3.12%2B-blue.svg?style=flat-square&logo=python" alt="Python 3.12+"/></a>
73
+ <a href="https://doi.org/10.5281/zenodo.20819379"><img src="https://img.shields.io/badge/DOI-10.5281%2Fzenodo.20819379-blue?style=flat-square" alt="DOI"/></a>
74
+ </p>
75
+
62
76
  Shared hardware objects — with standard values — for the HWO direct-imaging
63
77
  simulation suite.
64
78
 
@@ -1,5 +1,14 @@
1
1
  # optixstuff
2
2
 
3
+ <p align="center">
4
+ <a href="https://pypi.org/project/optixstuff/"><img src="https://img.shields.io/pypi/v/optixstuff.svg?style=flat-square&logo=pypi" alt="PyPI"/></a>
5
+ <a href="https://optixstuff.readthedocs.io"><img src="https://readthedocs.org/projects/optixstuff/badge/?version=latest&style=flat-square" alt="Documentation Status"/></a>
6
+ <a href="https://github.com/CoreySpohn/optixstuff/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/CoreySpohn/optixstuff/ci.yml?branch=main&style=flat-square&logo=github&label=tests" alt="Tests"/></a>
7
+ <a href="https://github.com/CoreySpohn/optixstuff/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" alt="License"/></a>
8
+ <a href="https://github.com/CoreySpohn/optixstuff/blob/main/pyproject.toml"><img src="https://img.shields.io/badge/python-3.12%2B-blue.svg?style=flat-square&logo=python" alt="Python 3.12+"/></a>
9
+ <a href="https://doi.org/10.5281/zenodo.20819379"><img src="https://img.shields.io/badge/DOI-10.5281%2Fzenodo.20819379-blue?style=flat-square" alt="DOI"/></a>
10
+ </p>
11
+
3
12
  Shared hardware objects — with standard values — for the HWO direct-imaging
4
13
  simulation suite.
5
14
 
@@ -0,0 +1,9 @@
1
+ # Security policy
2
+
3
+ This is research software maintained by one person. Please report a vulnerability, for
4
+ example a leaked credential, a malicious dependency, or a workflow that could be abused, through
5
+ GitHub private vulnerability reporting: use "Report a vulnerability" on the Security tab of this
6
+ repository. If that is unavailable, email corey.a.spohn@nasa.gov with "SECURITY" in the subject.
7
+
8
+ Expect an acknowledgement within a week. Only the latest release receives fixes. Incorrect
9
+ numerical results are not security issues; please open an ordinary issue for those.
@@ -0,0 +1,110 @@
1
+ [build-system]
2
+ requires = ['hatchling>=1.26.3', "hatch-fancy-pypi-readme>=24.1.0", "hatch-vcs>=0.4"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "optixstuff"
7
+ description = "Hardware abstractions for the HWO direct imaging simulation suite"
8
+ authors = [{ name = "Corey Spohn", email = "corey.a.spohn@nasa.gov" }]
9
+ dynamic = ['readme', 'version']
10
+ requires-python = ">=3.12"
11
+ license = { file = "LICENSE" }
12
+ classifiers = [
13
+ "Development Status :: 2 - Pre-Alpha",
14
+ "Intended Audience :: Science/Research",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3",
17
+ "Topic :: Scientific/Engineering :: Astronomy",
18
+ ]
19
+ dependencies = [
20
+ "equinox>=0.13.2",
21
+ "hwoutils>=1.10.0",
22
+ "interpax>=0.3.12",
23
+ "jax>=0.8.1",
24
+ "jaxlib>=0.8.1",
25
+ "jaxtyping>=0.3.3",
26
+ "yippy>=2.10.6",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ dev = ["pre-commit>=3.5"]
31
+ docs = [
32
+ "sphinx>=7.2",
33
+ "myst-nb>=1.0",
34
+ "sphinx-book-theme>=1.1",
35
+ "sphinx-autoapi>=3.0",
36
+ "sphinxcontrib-mermaid>=0.9",
37
+ "ipython>=8.18",
38
+ "matplotlib>=3.10.7",
39
+ ]
40
+ test = [
41
+ "pytest>=7.3.2",
42
+ "hypothesis>=6.79.1",
43
+ "pytest-xdist>=2.5",
44
+ "pytest-testmon>=2.0.7",
45
+ "pytest-markdown-docs>=0.5.0",
46
+ "pytest-timeout>=1.3.1",
47
+ "pytest-fail-slow>=0.1.0",
48
+ # The optional hwoutils budget plugin uses psutil for portable RSS sampling.
49
+ "psutil>=5.9.4",
50
+ "numpy>=2.0",
51
+ ]
52
+
53
+ [project.urls]
54
+ Homepage = "https://github.com/CoreySpohn/optixstuff"
55
+ Issues = "https://github.com/CoreySpohn/optixstuff/issues"
56
+
57
+ [tool.hatch.version]
58
+ source = "vcs"
59
+
60
+ [tool.hatch.build.hooks.vcs]
61
+ version-file = "src/optixstuff/_version.py"
62
+
63
+ [tool.hatch.metadata.hooks.fancy-pypi-readme]
64
+ content-type = "text/markdown"
65
+
66
+ [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
67
+ path = "README.md"
68
+
69
+ [tool.ruff.lint]
70
+ select = ["B", "D", "E", "F", "I", "UP", "RUF"]
71
+
72
+ [tool.ruff.lint.pydocstyle]
73
+ convention = "google"
74
+
75
+ [tool.ruff.lint.per-file-ignores]
76
+ "tests/**" = ["D"]
77
+
78
+ [tool.hatch.build.targets.wheel]
79
+ packages = ["src/optixstuff"]
80
+
81
+ [tool.hatch.build.targets.sdist]
82
+ exclude = ["/scripts", "/docs", "/tests", "/.github"]
83
+
84
+ [tool.pytest.ini_options]
85
+ filterwarnings = ["error"]
86
+ markers = [
87
+ "medium: tests with a 30-second default time budget",
88
+ "slow: tests with a 300-second default time budget, run nightly",
89
+ ]
90
+
91
+ [tool.testbudget]
92
+ library = "optixstuff"
93
+ mode = "report"
94
+ unit_s = 5
95
+ medium_s = 30
96
+ slow_s = 300
97
+
98
+ [tool.deptry]
99
+ known_first_party = ["optixstuff"]
100
+ optional_dependencies_dev_groups = ["dev", "docs", "test"]
101
+
102
+ [tool.deptry.per_rule_ignores]
103
+ # JAX loads its compiled backend; keep the explicit minimum version constraint.
104
+ DEP002 = ["jaxlib"]
105
+
106
+ [tool.ty.environment]
107
+ python-version = "3.11"
108
+
109
+ [tool.ty.src]
110
+ include = ["src/optixstuff"]
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '3.0.0'
22
- __version_tuple__ = version_tuple = (3, 0, 0)
21
+ __version__ = version = '4.0.1'
22
+ __version_tuple__ = version_tuple = (4, 0, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -67,7 +67,7 @@ class AbstractCoronagraph(eqx.Module):
67
67
  which serves the image contract from native-grid tables), physical
68
68
  wavefront propagation, or analytical models.
69
69
 
70
- All wavelength arguments are in nanometres throughout.
70
+ All wavelength arguments are in nanometers throughout.
71
71
  All separations and pixel scales are in lambda/D units -- the
72
72
  contract is dimensionless optics; angle conversions are the
73
73
  telescope primary's business.
@@ -98,7 +98,7 @@ class AbstractCoronagraph(eqx.Module):
98
98
 
99
99
  Args:
100
100
  separation_lod: Angular separation in lambda/D.
101
- wavelength_nm: Wavelength in nanometres.
101
+ wavelength_nm: Wavelength in nanometers.
102
102
  time_s: Time since mission start in seconds.
103
103
 
104
104
  Returns:
@@ -118,7 +118,7 @@ class AbstractCoronagraph(eqx.Module):
118
118
 
119
119
  Args:
120
120
  separation_lod: Angular separation in lambda/D.
121
- wavelength_nm: Wavelength in nanometres.
121
+ wavelength_nm: Wavelength in nanometers.
122
122
  time_s: Time since mission start in seconds.
123
123
 
124
124
  Returns:
@@ -133,13 +133,24 @@ class AbstractCoronagraph(eqx.Module):
133
133
  wavelength_nm: ArrayLike,
134
134
  *,
135
135
  time_s: ArrayLike = 0.0,
136
+ stellar_diam_lod: ArrayLike = 0.0,
136
137
  ) -> ArrayLike:
137
138
  """Mean stellar intensity within the photometric aperture.
138
139
 
140
+ The fraction of the star's incident light per unit solid angle, in
141
+ (lambda/D)^-2, averaged over the aperture (azimuthally, for a
142
+ tabulated curve). It does not depend on any pixel grid: multiplied by
143
+ :meth:`core_area` it is the fraction of the star's light that leaks
144
+ into the photometric aperture. A backend that tabulates the fraction
145
+ per pixel divides by the pixel's solid angle in (lambda/D)^2.
146
+
139
147
  Args:
140
148
  separation_lod: Angular separation in lambda/D.
141
- wavelength_nm: Wavelength in nanometres.
149
+ wavelength_nm: Wavelength in nanometers.
142
150
  time_s: Time since mission start in seconds.
151
+ stellar_diam_lod: Stellar angular diameter in lambda/D at the
152
+ queried wavelength; 0 means a point star. Implementations
153
+ without a diameter model accept and ignore it.
143
154
 
144
155
  Returns:
145
156
  Mean stellar leakage intensity in (lambda/D)^-2.
@@ -158,7 +169,7 @@ class AbstractCoronagraph(eqx.Module):
158
169
 
159
170
  Args:
160
171
  separation_lod: Angular separation in lambda/D.
161
- wavelength_nm: Wavelength in nanometres.
172
+ wavelength_nm: Wavelength in nanometers.
162
173
  time_s: Time since mission start in seconds.
163
174
 
164
175
  Returns:
@@ -189,7 +200,7 @@ class AbstractCoronagraph(eqx.Module):
189
200
  the diameter axis this signature lacks.
190
201
 
191
202
  Args:
192
- wavelength_nm: Wavelength in nanometres.
203
+ wavelength_nm: Wavelength in nanometers.
193
204
  pixel_scale_rad: Output pixel scale in radians/pixel.
194
205
  npixels: Output array side length in pixels. Must be a
195
206
  Python int (not a JAX array) as it determines the
@@ -211,7 +222,7 @@ class AbstractCoronagraph(eqx.Module):
211
222
  """Off-axis PSF at a given angular separation.
212
223
 
213
224
  Args:
214
- wavelength_nm: Wavelength in nanometres.
225
+ wavelength_nm: Wavelength in nanometers.
215
226
  separation_lod: Source separation in lambda/D.
216
227
  pixel_scale_rad: Output pixel scale in radians/pixel.
217
228
  npixels: Output array side length in pixels. Must be a
@@ -242,7 +253,7 @@ class AbstractCoronagraph(eqx.Module):
242
253
  """On-axis stellar leakage map at the requested sampling.
243
254
 
244
255
  Args:
245
- wavelength_nm: Wavelength in nanometres.
256
+ wavelength_nm: Wavelength in nanometers.
246
257
  stellar_diam_lod: Stellar angular diameter in lambda/D.
247
258
  pixel_scale_lod: Target pixel scale in lambda/D per pixel.
248
259
  shape: Target array shape (ny, nx).
@@ -266,7 +277,7 @@ class AbstractCoronagraph(eqx.Module):
266
277
  """Off-axis point-source PSFs at the requested sampling.
267
278
 
268
279
  Args:
269
- wavelength_nm: Wavelength in nanometres.
280
+ wavelength_nm: Wavelength in nanometers.
270
281
  x_lod: Source x-coordinates in lambda/D, shape (K,).
271
282
  y_lod: Source y-coordinates in lambda/D, shape (K,).
272
283
  pixel_scale_lod: Target pixel scale in lambda/D per pixel.
@@ -289,7 +300,7 @@ class AbstractCoronagraph(eqx.Module):
289
300
  """Per-pixel transmission map for a spatially uniform background.
290
301
 
291
302
  Args:
292
- wavelength_nm: Wavelength in nanometres.
303
+ wavelength_nm: Wavelength in nanometers.
293
304
  pixel_scale_lod: Target pixel scale in lambda/D per pixel.
294
305
  shape: Target array shape (ny, nx).
295
306
 
@@ -317,7 +328,7 @@ class AbstractCoronagraph(eqx.Module):
317
328
  output carries the same units per target pixel.
318
329
  map_pixel_scale_lod: Pixel scale of ``scene_map`` in
319
330
  lambda/D per pixel.
320
- wavelength_nm: Wavelength in nanometres.
331
+ wavelength_nm: Wavelength in nanometers.
321
332
  pixel_scale_lod: Target pixel scale in lambda/D per pixel.
322
333
  shape: Target array shape (ny, nx).
323
334
  rotation_deg: CCW rotation applied when mapping the scene
@@ -338,7 +349,10 @@ class AbstractTableCoronagraph(AbstractCoronagraph):
338
349
  ``psf_datacube``) -- and serves the sampling-explicit image contract
339
350
  from it with flux-conserving resamples to the requested target grid.
340
351
  Scalar performance curves remain the subclass's business (a table
341
- backend interpolates its own curve tables).
352
+ backend interpolates its own curve tables). Subclasses implementing
353
+ ``core_mean_intensity`` use ``stellar_diam_lod`` when their curve
354
+ tables provide a diameter axis; otherwise they accept and ignore it.
355
+ The image-map SPI does not prescribe a scalar curve table format.
342
356
 
343
357
  The tables are monochromatic by construction (a YIP is a per-band
344
358
  file), so the served maps ignore ``wavelength_nm`` beyond the
@@ -524,6 +538,9 @@ class AbstractScalarCoronagraph(AbstractCoronagraph):
524
538
  model that carries native-grid tables -- subclass
525
539
  :class:`AbstractTableCoronagraph` instead, or the image pipeline
526
540
  will silently render zeros.
541
+
542
+ Subclasses implementing ``core_mean_intensity`` accept and ignore
543
+ ``stellar_diam_lod`` unless they provide a stellar diameter model.
527
544
  """
528
545
 
529
546
  def on_axis_psf(
@@ -626,7 +643,7 @@ class MultiBandCoronagraph(AbstractCoronagraph):
626
643
  """Build a multi-band stack.
627
644
 
628
645
  Args:
629
- band_centers_nm: Band center wavelengths in nanometres, one
646
+ band_centers_nm: Band center wavelengths in nanometers, one
630
647
  per band.
631
648
  bands: The per-band ``AbstractCoronagraph`` models, aligned
632
649
  with ``band_centers_nm``.
@@ -687,10 +704,15 @@ class MultiBandCoronagraph(AbstractCoronagraph):
687
704
  separation_lod, wavelength_nm, time_s=time_s
688
705
  )
689
706
 
690
- def core_mean_intensity(self, separation_lod, wavelength_nm, *, time_s=0.0):
691
- """Mean stellar leakage from the nearest band."""
707
+ def core_mean_intensity(
708
+ self, separation_lod, wavelength_nm, *, time_s=0.0, stellar_diam_lod=0.0
709
+ ):
710
+ """Mean stellar leakage from the nearest band at the given diameter."""
692
711
  return self.band(wavelength_nm).core_mean_intensity(
693
- separation_lod, wavelength_nm, time_s=time_s
712
+ separation_lod,
713
+ wavelength_nm,
714
+ time_s=time_s,
715
+ stellar_diam_lod=stellar_diam_lod,
694
716
  )
695
717
 
696
718
  def occulter_transmission(self, separation_lod, wavelength_nm, *, time_s=0.0):
@@ -52,7 +52,7 @@ class AbstractDetector(eqx.Module):
52
52
  """Quantum efficiency at a given wavelength.
53
53
 
54
54
  Args:
55
- wavelength_nm: Wavelength in nanometres.
55
+ wavelength_nm: Wavelength in nanometers.
56
56
 
57
57
  Returns:
58
58
  QE as a fraction in [0, 1].
@@ -140,7 +140,7 @@ class AbstractDetector(eqx.Module):
140
140
 
141
141
  The marginal distribution of returned electrons is identical to
142
142
  :meth:`readout_source_electrons`. The two methods produce
143
- different specific realisations even with the same key.
143
+ different specific realizations even with the same key.
144
144
 
145
145
  Args:
146
146
  image_rate: Incident photon rate array in ph/s/pixel.
@@ -1,6 +1,5 @@
1
1
  """Optical element abstractions (throughput, filters, field stops)."""
2
2
 
3
-
4
3
  import abc
5
4
  from typing import final
6
5
 
@@ -27,7 +26,7 @@ class AbstractOpticalElement(eqx.Module):
27
26
  """Fractional throughput at a given wavelength.
28
27
 
29
28
  Args:
30
- wavelength_nm: Wavelength in nanometres.
29
+ wavelength_nm: Wavelength in nanometers.
31
30
 
32
31
  Returns:
33
32
  Scalar throughput in [0, 1].
@@ -40,7 +39,7 @@ class AbstractOpticalElement(eqx.Module):
40
39
 
41
40
  Args:
42
41
  arr: Input photon rate array [ph/s/pixel].
43
- wavelength_nm: Wavelength in nanometres.
42
+ wavelength_nm: Wavelength in nanometers.
44
43
 
45
44
  Returns:
46
45
  Attenuated photon rate array, same shape as arr.
@@ -79,14 +78,13 @@ class ConstantThroughput(AbstractUniformElement):
79
78
  def __repr__(self) -> str:
80
79
  """One-line summary of throughput value."""
81
80
  return (
82
- f"ConstantThroughput(name={self.name!r}, "
83
- f"throughput={self.throughput:.3g})"
81
+ f"ConstantThroughput(name={self.name!r}, throughput={self.throughput:.3g})"
84
82
  )
85
83
 
86
84
 
87
85
  @final
88
86
  class SpectralThroughput(AbstractUniformElement):
89
- """Wavelength-dependent throughput defined by sampled (wavelength, throughput) pairs.
87
+ """Wavelength-dependent throughput from sampled (wavelength, throughput) pairs.
90
88
 
91
89
  Linear interpolation between samples; throughput is zero outside
92
90
  the defined wavelength range.
@@ -77,6 +77,7 @@ class OpticalPath(eqx.Module):
77
77
 
78
78
  Args:
79
79
  coronagraph: One of:
80
+
80
81
  - an :class:`AbstractCoronagraph` instance (used as-is),
81
82
  - a YIP path (str or :class:`pathlib.Path`, wrapped with
82
83
  :class:`YippyCoronagraph`),
@@ -139,7 +140,7 @@ class OpticalPath(eqx.Module):
139
140
  """Total throughput of all attenuating elements.
140
141
 
141
142
  Args:
142
- wavelength_nm: Wavelength in nanometres.
143
+ wavelength_nm: Wavelength in nanometers.
143
144
 
144
145
  Returns:
145
146
  Combined fractional throughput in [0, 1].
@@ -14,17 +14,17 @@ class AbstractPrimary(eqx.Module):
14
14
  """
15
15
 
16
16
  diameter_m: AbstractVar[float]
17
- """Primary mirror diameter in metres."""
17
+ """Primary mirror diameter in meters."""
18
18
 
19
19
  area_m2: AbstractVar[float]
20
- """Effective collecting area in square metres."""
20
+ """Effective collecting area in square meters."""
21
21
 
22
22
 
23
23
  class SimplePrimary(AbstractPrimary):
24
24
  """A simple circular primary mirror with a central obscuration.
25
25
 
26
26
  Args:
27
- diameter_m: Primary mirror diameter in metres.
27
+ diameter_m: Primary mirror diameter in meters.
28
28
  obscuration: Linear obscuration fraction (0 = no obscuration).
29
29
  shape_factor: Fraction of unobscured area that is collecting
30
30
  (accounts for struts, segment gaps, etc.). Default 1.0.
@@ -47,12 +47,12 @@ class SimplePrimary(AbstractPrimary):
47
47
 
48
48
  @property
49
49
  def diameter_m(self) -> float:
50
- """Primary mirror diameter in metres."""
50
+ """Primary mirror diameter in meters."""
51
51
  return self._diameter_m
52
52
 
53
53
  @property
54
54
  def area_m2(self) -> float:
55
- """Effective collecting area in square metres."""
55
+ """Effective collecting area in square meters."""
56
56
  r = self._diameter_m / 2.0
57
57
  gross_area = jnp.pi * r**2
58
58
  return gross_area * (1.0 - self.obscuration**2) * self.shape_factor
@@ -68,9 +68,9 @@ class SimplePrimary(AbstractPrimary):
68
68
 
69
69
 
70
70
  def _hex_axial_coords(n_rings: int) -> list[tuple[int, int]]:
71
- """Axial (q, r) centres of a hex-packed n-ring layout.
71
+ """Axial (q, r) centers of a hex-packed n-ring layout.
72
72
 
73
- Yields ``1 + 3 n (n + 1)`` coordinates, centre first.
73
+ Yields ``1 + 3 n (n + 1)`` coordinates, center first.
74
74
  """
75
75
  coords = [(0, 0)]
76
76
  for q in range(-n_rings, n_rings + 1):
@@ -89,11 +89,11 @@ class SegmentedPrimary(AbstractPrimary):
89
89
  DESCRIBES the geometry; it does not propagate wavefronts.
90
90
 
91
91
  Args:
92
- diameter_m: Circumscribing diameter in metres.
93
- area_m2: Effective collecting area in square metres (gap/fill corrected).
94
- n_rings: Number of segment rings around the centre segment.
92
+ diameter_m: Circumscribing diameter in meters.
93
+ area_m2: Effective collecting area in square meters (gap/fill corrected).
94
+ n_rings: Number of segment rings around the center segment.
95
95
  n_segments: Total segment count (``1 + 3 n (n + 1)`` for a full layout).
96
- segment_gap_m: Inter-segment optical gap in metres.
96
+ segment_gap_m: Inter-segment optical gap in meters.
97
97
  segment_shape: Segment shape; only ``"hexagon"`` is supported today.
98
98
  """
99
99
 
@@ -129,17 +129,17 @@ class SegmentedPrimary(AbstractPrimary):
129
129
 
130
130
  @property
131
131
  def diameter_m(self) -> float:
132
- """Circumscribing diameter in metres."""
132
+ """Circumscribing diameter in meters."""
133
133
  return self._diameter_m
134
134
 
135
135
  @property
136
136
  def area_m2(self) -> float:
137
- """Effective collecting area in square metres."""
137
+ """Effective collecting area in square meters."""
138
138
  return self._area_m2
139
139
 
140
140
  @property
141
141
  def segment_flat_to_flat_m(self) -> float:
142
- """Segment flat-to-flat size in metres.
142
+ """Segment flat-to-flat size in meters.
143
143
 
144
144
  Exact (``point_to_point * sqrt(3) / 2``) when the segment size is
145
145
  given; otherwise the legacy circumscribing approximation
@@ -151,19 +151,19 @@ class SegmentedPrimary(AbstractPrimary):
151
151
 
152
152
  @property
153
153
  def segment_pitch_m(self) -> float:
154
- """Centre-to-centre distance of adjacent segments (flat-to-flat + gap)."""
154
+ """Center-to-center distance of adjacent segments (flat-to-flat + gap)."""
155
155
  return self.segment_flat_to_flat_m + self.segment_gap_m
156
156
 
157
157
  @property
158
- def segment_centres_m(self):
159
- """``(n_segments, 2)`` segment centre (x, y) positions in metres.
158
+ def segment_centers_m(self):
159
+ """``(n_segments, 2)`` segment center (x, y) positions in meters.
160
160
 
161
161
  A flat-top hexagonal lattice (flats up, points along x), the
162
- convention of the HWO EAC baseline pupils: the centre segment is
163
- first, and adjacent centres are one ``segment_pitch_m`` apart.
162
+ convention of the HWO EAC baseline pupils: the center segment is
163
+ first, and adjacent centers are one ``segment_pitch_m`` apart.
164
164
  """
165
165
  if self.segment_shape != "hexagon":
166
- msg = f"segment_centres_m undefined for shape {self.segment_shape!r}"
166
+ msg = f"segment_centers_m undefined for shape {self.segment_shape!r}"
167
167
  raise NotImplementedError(msg)
168
168
  axial = _hex_axial_coords(self.n_rings)
169
169
  pitch = self.segment_pitch_m
@@ -20,6 +20,8 @@ intensity cubes, fitted reduced-order, or learned); they share the
20
20
  import abc
21
21
 
22
22
  import equinox as eqx
23
+ import jax
24
+ import jax.numpy as jnp
23
25
  from equinox import AbstractVar
24
26
  from jax.typing import ArrayLike
25
27
  from jaxtyping import Array
@@ -33,7 +35,7 @@ class AbstractSpeckleField(eqx.Module):
33
35
  or a learned generator. All produce a coronagraph-plane contrast map
34
36
  through :meth:`realize`.
35
37
 
36
- All wavelength arguments are in nanometres; ``time_s`` is seconds since
38
+ All wavelength arguments are in nanometers; ``time_s`` is seconds since
37
39
  mission start. The returned map is on the field's native coronagraph
38
40
  plane at :attr:`pixel_scale_lod`; the caller resamples it to the
39
41
  detector grid (as for the coronagraph ``stellar_intens`` map).
@@ -57,12 +59,14 @@ class AbstractSpeckleField(eqx.Module):
57
59
  wavelength_nm: ArrayLike,
58
60
  time_s: ArrayLike = 0.0,
59
61
  ) -> Array:
60
- """Speckle contrast *delta* at a given time and wavelength.
62
+ """Speckle flux-fraction *delta* at a given time and wavelength.
61
63
 
62
64
  Returns the wavefront-error-induced excess over the deterministic
63
65
  coronagraphic floor -- i.e. ``I(t) - |E_nom|^2``, expanded as
64
- ``2 Re(E_nom* . G eps(t)) + |G eps(t)|^2``, in contrast units
65
- (fraction of the host-star flux per pixel). It must not include the
66
+ ``2 Re(E_nom* . G eps(t)) + |G eps(t)|^2``, as per-pixel flux
67
+ fractions (the fraction of pre-coronagraph host-star flux landing in
68
+ each pixel, the same units a YIP ``stellar_intens`` map carries --
69
+ NOT peak-referenced contrast). It must not include the
66
70
  ``|E_nom|^2`` floor itself: ``star_rate`` already applies that via
67
71
  the YIP ``stellar_intens`` map, so re-emitting it here would double
68
72
  count. An incoherent-halo implementation returns only the strictly
@@ -76,10 +80,67 @@ class AbstractSpeckleField(eqx.Module):
76
80
  / roll sequence.
77
81
 
78
82
  Args:
79
- wavelength_nm: Wavelength in nanometres.
83
+ wavelength_nm: Wavelength in nanometers.
80
84
  time_s: Time since mission start in seconds.
81
85
 
82
86
  Returns:
83
87
  2D float contrast-delta array on the native coronagraph plane.
84
88
  """
85
89
  ...
90
+
91
+ def realize_average(
92
+ self,
93
+ *,
94
+ wavelength_nm: ArrayLike,
95
+ exposure_s: ArrayLike,
96
+ start_time_s: ArrayLike = 0.0,
97
+ n_sub: int = 8,
98
+ ) -> Array:
99
+ """Speckle contrast delta averaged over an exposure.
100
+
101
+ :meth:`realize` is instantaneous, but a detector integrates. Averaging
102
+ matters because the map is QUADRATIC in the drifting coefficients:
103
+ the mean of the realized maps is not the map of the mean coefficients,
104
+ so an instantaneous sample is a biased stand-in for an exposure
105
+ whenever the field decorrelates on the exposure timescale. This
106
+ evaluates :meth:`realize` at ``n_sub`` sub-exposure midpoints and
107
+ averages, which is exact in the limit of many sub-steps and
108
+ second-order accurate at finite ``n_sub``.
109
+
110
+ How many sub-steps are enough, and how much the average suppresses
111
+ the speckle variance relative to a snapshot, are both set by how many
112
+ independent realizations the exposure spans. A generator that knows
113
+ its own temporal statistics can say so in closed form (see
114
+ ``physicaloptix.SpeckleProcess.exposure_neff``); when the exposure is
115
+ short against the decorrelation time, one sub-step is already right
116
+ and this reduces to :meth:`realize` at the exposure midpoint.
117
+
118
+ Args:
119
+ wavelength_nm: Wavelength in nanometers.
120
+ exposure_s: Exposure length in seconds.
121
+ start_time_s: Exposure start, in seconds since ``epoch_jd``.
122
+ n_sub: Number of sub-exposure samples. Must be at least 1.
123
+
124
+ Returns:
125
+ 2D float contrast-delta array, averaged over the exposure.
126
+
127
+ Raises:
128
+ ValueError: If ``n_sub`` is not a positive integer.
129
+ """
130
+ if int(n_sub) < 1 or int(n_sub) != n_sub:
131
+ raise ValueError(f"n_sub must be a positive integer, got {n_sub}")
132
+ n_sub = int(n_sub)
133
+ start = jnp.asarray(start_time_s, dtype=float)
134
+ step = jnp.asarray(exposure_s, dtype=float) / n_sub
135
+
136
+ def sample(index):
137
+ return self.realize(
138
+ wavelength_nm=wavelength_nm, time_s=start + (index + 0.5) * step
139
+ )
140
+
141
+ # Accumulate rather than stacking: one map is held at a time, so a
142
+ # large native grid does not cost n_sub copies.
143
+ def accumulate(index, total):
144
+ return total + sample(index)
145
+
146
+ return jax.lax.fori_loop(1, n_sub, accumulate, sample(0)) / n_sub
@@ -98,9 +98,31 @@ class YippyCoronagraph(AbstractTableCoronagraph):
98
98
  wavelength_nm: ArrayLike,
99
99
  *,
100
100
  time_s: ArrayLike = 0.0,
101
+ stellar_diam_lod: ArrayLike = 0.0,
101
102
  ) -> ArrayLike:
102
- """Mean stellar leakage from the YIP interpolation table."""
103
- return self._backend.core_mean_intensity(separation_lod)
103
+ """Mean stellar leakage per (lambda/D)^2 from the YIP interpolation table.
104
+
105
+ yippy tabulates the fraction of the star's light per pixel, so the
106
+ value is divided by the stellar pixel's solid angle,
107
+ to meet the contract of :meth:`AbstractCoronagraph.core_mean_intensity`.
108
+ The backend interpolates over the stellar diameter axis when present;
109
+ otherwise it accepts and ignores the diameter.
110
+
111
+ Args:
112
+ separation_lod: Angular separation in lambda/D.
113
+ wavelength_nm: Wavelength in nanometers (accepted and ignored).
114
+ time_s: Time since mission start in seconds (accepted and ignored).
115
+ stellar_diam_lod: Stellar angular diameter in lambda/D at the
116
+ queried wavelength; 0 means a point star.
117
+ """
118
+ per_pixel = self._backend.core_mean_intensity(separation_lod, stellar_diam_lod)
119
+ return (
120
+ per_pixel
121
+ / getattr(
122
+ self._backend, "stellar_pixel_scale_lod", self._backend.pixel_scale_lod
123
+ )
124
+ ** 2
125
+ )
104
126
 
105
127
  def occulter_transmission(
106
128
  self,
@@ -123,6 +145,68 @@ class YippyCoronagraph(AbstractTableCoronagraph):
123
145
  """Stellar leakage PSF from the YIP stellar intensity model."""
124
146
  return self._backend.stellar_intens(0.0)
125
147
 
148
+ def stellar_map(
149
+ self,
150
+ wavelength_nm: ArrayLike,
151
+ stellar_diam_lod: ArrayLike,
152
+ *,
153
+ pixel_scale_lod: ArrayLike,
154
+ shape: tuple[int, int],
155
+ ) -> Array:
156
+ """Resample stellar flux in the backend's stellar reference frame."""
157
+ method = getattr(self._backend, "stellar_map", None)
158
+ if method is None:
159
+ return super().stellar_map(
160
+ wavelength_nm,
161
+ stellar_diam_lod,
162
+ pixel_scale_lod=pixel_scale_lod,
163
+ shape=shape,
164
+ )
165
+ return method(
166
+ wavelength_nm,
167
+ stellar_diam_lod,
168
+ pixel_scale_lod=pixel_scale_lod,
169
+ shape=shape,
170
+ )
171
+
172
+ def source_psfs(
173
+ self,
174
+ wavelength_nm: ArrayLike,
175
+ x_lod: ArrayLike,
176
+ y_lod: ArrayLike,
177
+ *,
178
+ pixel_scale_lod: ArrayLike,
179
+ shape: tuple[int, int],
180
+ ) -> Array:
181
+ """Resample source flux in the backend's off-axis reference frame."""
182
+ method = getattr(self._backend, "source_psfs", None)
183
+ if method is None:
184
+ return super().source_psfs(
185
+ wavelength_nm,
186
+ x_lod,
187
+ y_lod,
188
+ pixel_scale_lod=pixel_scale_lod,
189
+ shape=shape,
190
+ )
191
+ return method(
192
+ wavelength_nm, x_lod, y_lod, pixel_scale_lod=pixel_scale_lod, shape=shape
193
+ )
194
+
195
+ def background_transmission(
196
+ self,
197
+ wavelength_nm: ArrayLike,
198
+ *,
199
+ pixel_scale_lod: ArrayLike,
200
+ shape: tuple[int, int],
201
+ ) -> Array:
202
+ """Resample sky values in the backend's sky reference frame."""
203
+ method = getattr(self._backend, "background_transmission", None)
204
+ if method is None:
205
+ return super().background_transmission(
206
+ wavelength_nm, pixel_scale_lod=pixel_scale_lod, shape=shape
207
+ )
208
+ return method(wavelength_nm, pixel_scale_lod=pixel_scale_lod, shape=shape)
209
+
126
210
  def off_axis_psf(
127
211
  self,
128
212
  wavelength_nm: ArrayLike,
@@ -142,14 +226,38 @@ class YippyCoronagraph(AbstractTableCoronagraph):
142
226
  self,
143
227
  separation_lod: ArrayLike,
144
228
  ppf: float = 30.0,
229
+ *,
230
+ stellar_diam_lod: ArrayLike = 0.0,
145
231
  ) -> ArrayLike:
146
- """AYO noise floor: core_mean_intensity / ppf.
232
+ """AYO noise floor per (lambda/D)^2: ``core_mean_intensity / ppf``.
147
233
 
148
- This is a convenience passthrough to the backend. Not part of the
234
+ Same units as :meth:`core_mean_intensity`, so multiplying by
235
+ :meth:`core_area` gives the floor as a fraction of the star's light.
236
+ yippy's own ``noise_floor_ayo`` is per pixel and is divided by the
237
+ stellar pixel's solid angle. Not part of the
149
238
  AbstractCoronagraph contract -- downstream ETCs should compute
150
239
  noise floors as pure functions.
240
+
241
+ Division by ``ppf`` precedes the pixel-area conversion to preserve
242
+ point-source rounding exactly.
243
+
244
+ Args:
245
+ separation_lod: Angular separation in lambda/D.
246
+ ppf: Post-processing noise suppression factor.
247
+ stellar_diam_lod: Stellar angular diameter in lambda/D at the
248
+ queried wavelength; 0 means a point star. Accepted and ignored
249
+ when the backend has no diameter model.
151
250
  """
152
- return self._backend.noise_floor_ayo(separation_lod, ppf)
251
+ per_pixel = (
252
+ self._backend.core_mean_intensity(separation_lod, stellar_diam_lod) / ppf
253
+ )
254
+ return (
255
+ per_pixel
256
+ / getattr(
257
+ self._backend, "stellar_pixel_scale_lod", self._backend.pixel_scale_lod
258
+ )
259
+ ** 2
260
+ )
153
261
 
154
262
  def raw_contrast(self, separation_lod: ArrayLike) -> ArrayLike:
155
263
  """Raw contrast from the YIP interpolation table."""
@@ -1,64 +0,0 @@
1
- [build-system]
2
- requires = ['hatchling', "hatch-fancy-pypi-readme", "hatch-vcs"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "optixstuff"
7
- description = "Hardware abstractions for the HWO direct imaging simulation suite"
8
- authors = [{ name = "Corey Spohn", email = "corey.a.spohn@nasa.gov" }]
9
- dynamic = ['readme', 'version']
10
- requires-python = ">=3.11"
11
- license = { file = "LICENSE" }
12
- classifiers = [
13
- "Development Status :: 2 - Pre-Alpha",
14
- "Intended Audience :: Science/Research",
15
- "License :: OSI Approved :: MIT License",
16
- "Programming Language :: Python :: 3",
17
- "Topic :: Scientific/Engineering :: Astronomy",
18
- ]
19
- dependencies = ["equinox>=0.12.0", "hwoutils", "interpax", "jax>=0.4.1", "jaxlib>=0.4.1", "yippy"]
20
-
21
- [project.optional-dependencies]
22
- dev = ["pre-commit"]
23
- docs = [
24
- "sphinx",
25
- "myst-nb",
26
- "sphinx-book-theme",
27
- "sphinx-autoapi",
28
- "sphinx_autodoc_typehints",
29
- "sphinxcontrib-mermaid",
30
- "ipython",
31
- "matplotlib",
32
- ]
33
- test = ["nox", "pytest", "hypothesis", "pytest-cov"]
34
-
35
- [project.urls]
36
- Homepage = "https://github.com/CoreySpohn/optixstuff"
37
- Issues = "https://github.com/CoreySpohn/optixstuff/issues"
38
-
39
- [tool.hatch.version]
40
- source = "vcs"
41
-
42
- [tool.hatch.build.hooks.vcs]
43
- version-file = "src/optixstuff/_version.py"
44
-
45
- [tool.hatch.metadata.hooks.fancy-pypi-readme]
46
- content-type = "text/markdown"
47
-
48
- [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
49
- path = "README.md"
50
-
51
- [tool.ruff.lint]
52
- select = ["B", "D", "E", "F", "I", "UP", "RUF"]
53
-
54
- [tool.ruff.lint.pydocstyle]
55
- convention = "google"
56
-
57
- [tool.ruff.lint.per-file-ignores]
58
- "tests/**" = ["D"]
59
-
60
- [tool.hatch.build.targets.wheel]
61
- packages = ["src/optixstuff"]
62
-
63
- [tool.hatch.build.targets.sdist]
64
- exclude = ["/scripts", "/docs", "/tests", "/.github"]
File without changes
File without changes