iactrace 0.10.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 (64) hide show
  1. iactrace-0.10.1/CHANGELOG.md +116 -0
  2. iactrace-0.10.1/LICENSE +29 -0
  3. iactrace-0.10.1/MANIFEST.in +10 -0
  4. iactrace-0.10.1/PKG-INFO +141 -0
  5. iactrace-0.10.1/README.md +88 -0
  6. iactrace-0.10.1/iactrace/__init__.py +59 -0
  7. iactrace-0.10.1/iactrace/analysis/__init__.py +13 -0
  8. iactrace-0.10.1/iactrace/analysis/focal_surface.py +216 -0
  9. iactrace-0.10.1/iactrace/camera/__init__.py +22 -0
  10. iactrace-0.10.1/iactrace/camera/_hexgeom.py +81 -0
  11. iactrace-0.10.1/iactrace/camera/camera.py +454 -0
  12. iactrace-0.10.1/iactrace/camera/detection_chain.py +104 -0
  13. iactrace-0.10.1/iactrace/camera/detector/__init__.py +11 -0
  14. iactrace-0.10.1/iactrace/camera/detector/photodetector.py +119 -0
  15. iactrace-0.10.1/iactrace/camera/detector/pmt.py +154 -0
  16. iactrace-0.10.1/iactrace/camera/detector/surface.py +138 -0
  17. iactrace-0.10.1/iactrace/camera/optics/__init__.py +13 -0
  18. iactrace-0.10.1/iactrace/camera/optics/concentrator.py +74 -0
  19. iactrace-0.10.1/iactrace/camera/optics/okumura.py +320 -0
  20. iactrace-0.10.1/iactrace/camera/optics/polygonal.py +279 -0
  21. iactrace-0.10.1/iactrace/camera/optics/winston.py +220 -0
  22. iactrace-0.10.1/iactrace/camera/sensor_group.py +473 -0
  23. iactrace-0.10.1/iactrace/core/__init__.py +87 -0
  24. iactrace-0.10.1/iactrace/core/_tolerances.py +18 -0
  25. iactrace-0.10.1/iactrace/core/apertures.py +173 -0
  26. iactrace-0.10.1/iactrace/core/bsdf.py +162 -0
  27. iactrace-0.10.1/iactrace/core/coatings.py +136 -0
  28. iactrace-0.10.1/iactrace/core/interactions.py +385 -0
  29. iactrace-0.10.1/iactrace/core/intersections.py +449 -0
  30. iactrace-0.10.1/iactrace/core/obstructions.py +170 -0
  31. iactrace-0.10.1/iactrace/core/optics.py +190 -0
  32. iactrace-0.10.1/iactrace/core/ray_bundle.py +204 -0
  33. iactrace-0.10.1/iactrace/core/render.py +502 -0
  34. iactrace-0.10.1/iactrace/core/sampling.py +82 -0
  35. iactrace-0.10.1/iactrace/core/surfaces.py +572 -0
  36. iactrace-0.10.1/iactrace/core/trajectory.py +53 -0
  37. iactrace-0.10.1/iactrace/core/transforms.py +111 -0
  38. iactrace-0.10.1/iactrace/io/__init__.py +28 -0
  39. iactrace-0.10.1/iactrace/io/adapters.py +1817 -0
  40. iactrace-0.10.1/iactrace/io/schemas.py +660 -0
  41. iactrace-0.10.1/iactrace/io/yaml_io.py +243 -0
  42. iactrace-0.10.1/iactrace/py.typed +0 -0
  43. iactrace-0.10.1/iactrace/telescope/__init__.py +9 -0
  44. iactrace-0.10.1/iactrace/telescope/_common.py +21 -0
  45. iactrace-0.10.1/iactrace/telescope/lenses.py +319 -0
  46. iactrace-0.10.1/iactrace/telescope/mirrors.py +432 -0
  47. iactrace-0.10.1/iactrace/telescope/obstructions.py +113 -0
  48. iactrace-0.10.1/iactrace/telescope/operations.py +578 -0
  49. iactrace-0.10.1/iactrace/telescope/telescope.py +400 -0
  50. iactrace-0.10.1/iactrace/viz/__init__.py +14 -0
  51. iactrace-0.10.1/iactrace/viz/_meshes.py +445 -0
  52. iactrace-0.10.1/iactrace/viz/_optics.py +194 -0
  53. iactrace-0.10.1/iactrace/viz/_sensors.py +282 -0
  54. iactrace-0.10.1/iactrace/viz/_utils.py +16 -0
  55. iactrace-0.10.1/iactrace/viz/overlays.py +170 -0
  56. iactrace-0.10.1/iactrace/viz/plotting.py +155 -0
  57. iactrace-0.10.1/iactrace/viz/scenes.py +273 -0
  58. iactrace-0.10.1/iactrace.egg-info/PKG-INFO +141 -0
  59. iactrace-0.10.1/iactrace.egg-info/SOURCES.txt +62 -0
  60. iactrace-0.10.1/iactrace.egg-info/dependency_links.txt +1 -0
  61. iactrace-0.10.1/iactrace.egg-info/requires.txt +31 -0
  62. iactrace-0.10.1/iactrace.egg-info/top_level.txt +1 -0
  63. iactrace-0.10.1/pyproject.toml +133 -0
  64. iactrace-0.10.1/setup.cfg +4 -0
@@ -0,0 +1,116 @@
1
+ # Changelog
2
+
3
+ ## v0.10.1 (2026-08-07)
4
+
5
+ ### Fix
6
+
7
+ - **core**: fixed shadowing bug for point sources, switched to 1/d^2 scaling for point source
8
+ - **core**: account for opl if slab is first element, salt downstream of first surface dependent on id now, clarify aspheric convention
9
+
10
+ ## v0.10.0 (2026-08-06)
11
+
12
+ ### Feat
13
+
14
+ - **trajectory**: unified treatment of trajectories between components, fixed bugs in viz
15
+ - **viz,-core**: added the ability to save trajectories for the optical trace, added methods to visualize traces for optics and detection_chain
16
+
17
+ ### Fix
18
+
19
+ - **docs,-core**: fixed documentation and removed bugs from formatting. fixed some problems with normalization and polygonal apertures
20
+ - **core**: fixed issue with surface relocalization
21
+ - **core,-viz**: fixed a bug in the intersection logic, leading to the wrong root being chosen for conics. also made float32 computation more stable by propagating to local object reference. improved visualization of traced rays. refactored telescope3d by breaking up into files
22
+
23
+ ### Refactor
24
+
25
+ - **ruff**: reformat and fixes with ruff
26
+ - **ruff,-tests**: reformated with ruff, changed default setting of laye_vs_eager test to not be on pixel boundary
27
+
28
+ ## v0.9.1 (2026-08-06)
29
+
30
+ ### Fix
31
+
32
+ - **core, docs**: fixed angle conventions, more info
33
+
34
+ ## v0.9.0 (2026-07-18)
35
+
36
+ ### BREAKING CHANGE
37
+
38
+ - Closes #11
39
+
40
+ ### Feat
41
+
42
+ - **core,tests,camera**: refactored tests to be more concentrated and better motivated, refactored to reduce code duplication and increase polymorphism, restructured camera module
43
+ - **camera**: created a combined tracing chain, including photosensor surface, allowing for peeking
44
+ - **core**: added an alive flag to ray bundles, to avoid confusing properties regarding value and hit mask
45
+ - **camera**: added okumura cone prototype
46
+ - **camera,configs,io,viz,tests**: implemented a detection chain that allows for concentrators and complicated photosensors
47
+ - **core**: added angle dependent coatings via tabulation
48
+
49
+ ### Fix
50
+
51
+ - **configs**: changed the ordering of the modules to reflective of the way it is done in sim_telarray
52
+ - **core,-surfaces**: added more surface types, fixed shadowing bug for tracing
53
+ - **configs**: improved telescope shadowing (no baffling instead of monolithic camera enclosure, to avoid incorrect self shadowing. also added a best guess okumura cone to LSTCam
54
+ - **configs**: Added winston cones to flashcam, nectarcam and HESS2cam
55
+ - **configs,viz**: added winston cone to HESS1U, fixed relative position of mirrors in the context of coordinate conventions, added visualization of the sensor grid
56
+ - **core**: fix that there is no shadowing applied in the last leg to the focal plane, improved focal plane analysis
57
+ - **camera**: fixed roundtripping issues with camera and coatings
58
+ - **camera,core,io**: fixed bugs regarding mirroring, io and camera rays
59
+ - **io**: fixed bsdf not bein correctly serialized
60
+
61
+ ### Refactor
62
+
63
+ - **refactor**: refactored camera module, slimmed tests, better io for surfaces
64
+ - **format,-doc**: ruff formatting and small doc changes regarding conventions
65
+
66
+ ## v0.8.0 (2026-06-11)
67
+
68
+ ### Feat
69
+
70
+ - **opl**: make opl behave as expected, weighted with index of refraction, add distance to source or initial mirror
71
+
72
+ ### Fix
73
+
74
+ - **viz**: fixed 3D visualization to correctly handle transparency in jupyter notebooks
75
+
76
+ ## v0.7.0 (2026-05-12)
77
+
78
+ ### Feat
79
+
80
+ - **configs**: added configs for the SST telescope with CHECS camera
81
+ - **io,viz**: enabled both apertures types for slabs and lenses for plotting and io
82
+ - **analysis,camera,viz**: improved plotting and combined plotting path for sensors, added analysis tools such as focal plane and fixed multi-sensor groups
83
+
84
+ ### Fix
85
+
86
+ - **benchmarks**: fixed benchmark to mvoe from MCIntegrator to direct sampling
87
+ - **surfaces**: bug in aspheric surface formula squared unnecessarily
88
+
89
+ ### Refactor
90
+
91
+ - **docs,configs**: adjusted docs to reflect new structure, split configs into telescope/camera, ran ruff
92
+ - **iactrace**: major refactor of architecture, better support for lens optics
93
+ - **utils**: removed utils path, unneccessary for new sampling method
94
+
95
+ ## v0.6.1 (2026-05-06)
96
+
97
+ ### Fix
98
+
99
+ - **viz**: changed hexshow colorscaling to follow the same logic as squareshow via plt.Normalize
100
+ - **operations**: changed mirror displacement to go along the local mirror z axis, similar to simtel_array
101
+ - **core**: fixed bug in conic intersection
102
+
103
+ ### Perf
104
+
105
+ - **core**: bypass NR for pure conics
106
+
107
+ ## v0.6.0 (2026-02-09)
108
+
109
+ ### BREAKING CHANGE
110
+
111
+ - initial project setup
112
+
113
+ ### Feat
114
+
115
+ - add changelog
116
+ - initial release
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Gerrit Roellinghoff
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,10 @@
1
+ prune configs
2
+ prune licenses
3
+ prune benchmarks
4
+ prune docs
5
+ prune tests
6
+ exclude asv.conf.json
7
+
8
+ include LICENSE
9
+ include README.md
10
+ include CHANGELOG.md
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: iactrace
3
+ Version: 0.10.1
4
+ Summary: JAX-based optical ray tracing for Imaging Atmospheric Cherenkov Telescopes
5
+ Author-email: Gerrit Roellinghoff <gerrit.roellinghoff@fau.de>
6
+ Maintainer-email: Gerrit Roellinghoff <gerrit.roellinghoff@fau.de>
7
+ License: BSD-3-Clause
8
+ Project-URL: Homepage, https://github.com/GerritRo/iactrace
9
+ Project-URL: Documentation, https://gerritro.github.io/iactrace/
10
+ Project-URL: Repository, https://github.com/GerritRo/iactrace.git
11
+ Project-URL: Issues, https://github.com/GerritRo/iactrace/issues
12
+ Keywords: ray-tracing,optics,cherenkov,telescope,jax,differentiable,iact,astronomy,astrophysics
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Topic :: Scientific/Engineering :: Physics
16
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
17
+ Classifier: License :: OSI Approved :: BSD License
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.12
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: jax>=0.4.20
25
+ Requires-Dist: jaxlib>=0.4.20
26
+ Requires-Dist: numpy>=1.22.0
27
+ Requires-Dist: matplotlib>=3.5.0
28
+ Requires-Dist: trimesh>=3.15.0
29
+ Requires-Dist: equinox>=0.11.0
30
+ Requires-Dist: pyyaml>=6.0
31
+ Requires-Dist: pydantic>=2.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=7.0; extra == "dev"
34
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
35
+ Requires-Dist: jupyter>=1.0; extra == "dev"
36
+ Requires-Dist: mypy>=1.0; extra == "dev"
37
+ Requires-Dist: nbclient>=0.7.0; extra == "dev"
38
+ Requires-Dist: types-PyYAML>=6.0; extra == "dev"
39
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
40
+ Requires-Dist: optax>=0.2.6; extra == "dev"
41
+ Requires-Dist: asv>=0.6.0; extra == "dev"
42
+ Requires-Dist: virtualenv>=20.0; extra == "dev"
43
+ Requires-Dist: commitizen>=3.12.0; extra == "dev"
44
+ Provides-Extra: gpu
45
+ Requires-Dist: jax[cuda12]; extra == "gpu"
46
+ Provides-Extra: docs
47
+ Requires-Dist: sphinx; extra == "docs"
48
+ Requires-Dist: furo; extra == "docs"
49
+ Requires-Dist: sphinx-automodapi; extra == "docs"
50
+ Requires-Dist: sphinx-design; extra == "docs"
51
+ Requires-Dist: myst-nb; extra == "docs"
52
+ Dynamic: license-file
53
+
54
+ # IACTrace
55
+
56
+ ![Python](https://img.shields.io/badge/python-3.12%2B-blue)
57
+ [![License](https://img.shields.io/badge/license-BSD--3--Clause-green)](LICENSE)
58
+ [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
59
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
60
+ [![codecov](https://codecov.io/gh/GerritRo/iactrace/branch/main/graph/badge.svg)](https://codecov.io/gh/GerritRo/iactrace)
61
+
62
+ **JAX-based optical ray tracing for Imaging Atmospheric Cherenkov Telescopes**
63
+
64
+ IACTrace is a differentiable ray tracing library for simulating the optical properties of IACT (Imaging Atmospheric Cherenkov Telescope) systems. Built on JAX and Equinox, it supports automatic differentiation for optimization and inverse problems.
65
+
66
+ ## Features
67
+
68
+ - Differentiable ray tracing with JAX
69
+ - Multi-stage optical systems: segmented primaries, secondary mirrors, lenses and windows
70
+ - Square and hexagonal sensor arrays with per-pixel light concentrators (Winston / Okumura cones)
71
+ - Physical photodetectors: PMTs, SiPMs can be connected in-line with concentrators
72
+ - Aspheric, Zernike and freeform surfaces, plus error models (roughness, misalignment, figure errors)
73
+ - Obstruction modeling (cylinders, boxes, spheres, oriented boxes, triangles)
74
+ - YAML-based telescope and camera configuration
75
+ - Response matrix calculation
76
+
77
+ ## Installation
78
+ ```bash
79
+ pip install iactrace
80
+ ```
81
+ Or straight from the repository:
82
+
83
+ ```bash
84
+ pip install git+https://github.com/GerritRo/iactrace/
85
+ ```
86
+
87
+ For development:
88
+ ```bash
89
+ git clone https://github.com/GerritRo/iactrace.git
90
+ cd iactrace
91
+ pip install -e ".[dev]"
92
+ ```
93
+
94
+ ## Quick Start
95
+
96
+ Telescope and camera are configured with separate YAML files and loaded
97
+ independently.
98
+
99
+ ```python
100
+ import jax
101
+ import jax.numpy as jnp
102
+ from iactrace import Telescope, Camera
103
+
104
+ # Random key for Monte Carlo ray sampling.
105
+ key = jax.random.key(0)
106
+
107
+ # Load the telescope (optics + camera frame) and the camera (sensor layout).
108
+ telescope = Telescope.from_yaml("configs/HESS/CT3.yaml", n_samples=256, key=key)
109
+ camera = Camera.from_yaml("configs/HESS/HESS1U.yaml")
110
+
111
+ # Simulate an on-axis astronomical source (parallel light).
112
+ directions = jnp.array([[0.0, 0.0, -1.0]])
113
+ values = jnp.array([1.0])
114
+
115
+ # render() traces rays through the optics and returns a lazy ray bundle;
116
+ # camera.image() folds it into the pixel image.
117
+ ray_bundle = telescope.render(directions, values, source_type="parallel")
118
+ image = camera.image(ray_bundle)
119
+ ```
120
+
121
+ See the [Quick Start guide](https://gerritro.github.io/iactrace/getting_started/quickstart.html)
122
+ for point sources, off-axis geometry, and visualization.
123
+
124
+ ## Documentation
125
+
126
+ Full documentation and examples are available at: **https://gerritro.github.io/iactrace/**
127
+
128
+ ### Building Documentation Locally
129
+
130
+ ```bash
131
+ pip install -e ".[docs]"
132
+ cd docs && make html
133
+ ```
134
+
135
+ ## License
136
+
137
+ BSD-3-Clause License - see [LICENSE](LICENSE) for details.
138
+
139
+ ## Citation
140
+
141
+ If you use IACTrace in your research, please cite this repository.
@@ -0,0 +1,88 @@
1
+ # IACTrace
2
+
3
+ ![Python](https://img.shields.io/badge/python-3.12%2B-blue)
4
+ [![License](https://img.shields.io/badge/license-BSD--3--Clause-green)](LICENSE)
5
+ [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
6
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
7
+ [![codecov](https://codecov.io/gh/GerritRo/iactrace/branch/main/graph/badge.svg)](https://codecov.io/gh/GerritRo/iactrace)
8
+
9
+ **JAX-based optical ray tracing for Imaging Atmospheric Cherenkov Telescopes**
10
+
11
+ IACTrace is a differentiable ray tracing library for simulating the optical properties of IACT (Imaging Atmospheric Cherenkov Telescope) systems. Built on JAX and Equinox, it supports automatic differentiation for optimization and inverse problems.
12
+
13
+ ## Features
14
+
15
+ - Differentiable ray tracing with JAX
16
+ - Multi-stage optical systems: segmented primaries, secondary mirrors, lenses and windows
17
+ - Square and hexagonal sensor arrays with per-pixel light concentrators (Winston / Okumura cones)
18
+ - Physical photodetectors: PMTs, SiPMs can be connected in-line with concentrators
19
+ - Aspheric, Zernike and freeform surfaces, plus error models (roughness, misalignment, figure errors)
20
+ - Obstruction modeling (cylinders, boxes, spheres, oriented boxes, triangles)
21
+ - YAML-based telescope and camera configuration
22
+ - Response matrix calculation
23
+
24
+ ## Installation
25
+ ```bash
26
+ pip install iactrace
27
+ ```
28
+ Or straight from the repository:
29
+
30
+ ```bash
31
+ pip install git+https://github.com/GerritRo/iactrace/
32
+ ```
33
+
34
+ For development:
35
+ ```bash
36
+ git clone https://github.com/GerritRo/iactrace.git
37
+ cd iactrace
38
+ pip install -e ".[dev]"
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ Telescope and camera are configured with separate YAML files and loaded
44
+ independently.
45
+
46
+ ```python
47
+ import jax
48
+ import jax.numpy as jnp
49
+ from iactrace import Telescope, Camera
50
+
51
+ # Random key for Monte Carlo ray sampling.
52
+ key = jax.random.key(0)
53
+
54
+ # Load the telescope (optics + camera frame) and the camera (sensor layout).
55
+ telescope = Telescope.from_yaml("configs/HESS/CT3.yaml", n_samples=256, key=key)
56
+ camera = Camera.from_yaml("configs/HESS/HESS1U.yaml")
57
+
58
+ # Simulate an on-axis astronomical source (parallel light).
59
+ directions = jnp.array([[0.0, 0.0, -1.0]])
60
+ values = jnp.array([1.0])
61
+
62
+ # render() traces rays through the optics and returns a lazy ray bundle;
63
+ # camera.image() folds it into the pixel image.
64
+ ray_bundle = telescope.render(directions, values, source_type="parallel")
65
+ image = camera.image(ray_bundle)
66
+ ```
67
+
68
+ See the [Quick Start guide](https://gerritro.github.io/iactrace/getting_started/quickstart.html)
69
+ for point sources, off-axis geometry, and visualization.
70
+
71
+ ## Documentation
72
+
73
+ Full documentation and examples are available at: **https://gerritro.github.io/iactrace/**
74
+
75
+ ### Building Documentation Locally
76
+
77
+ ```bash
78
+ pip install -e ".[docs]"
79
+ cd docs && make html
80
+ ```
81
+
82
+ ## License
83
+
84
+ BSD-3-Clause License - see [LICENSE](LICENSE) for details.
85
+
86
+ ## Citation
87
+
88
+ If you use IACTrace in your research, please cite this repository.
@@ -0,0 +1,59 @@
1
+ import jax
2
+
3
+ from .camera import (
4
+ PMT,
5
+ Camera,
6
+ Concentrator,
7
+ ConstantQE,
8
+ DetectionChain,
9
+ HexagonalSensorGroup,
10
+ OkumuraCone,
11
+ PhotoDetector,
12
+ SensorGroup,
13
+ SquareSensorGroup,
14
+ WinstonCone,
15
+ )
16
+ from .core import LazyRayBundle, RayBundle, TraceResult, Trajectory
17
+ from .io import save_camera, save_telescope
18
+ from .telescope import Telescope
19
+ from .viz import (
20
+ add_trajectories,
21
+ show_camera,
22
+ show_image,
23
+ show_sensor_chain,
24
+ show_telescope,
25
+ )
26
+
27
+ __version__ = "0.10.1"
28
+
29
+ jax.config.update("jax_default_matmul_precision", "highest")
30
+
31
+ __all__ = [
32
+ "Telescope",
33
+ "Camera",
34
+ "RayBundle",
35
+ "LazyRayBundle",
36
+ "Trajectory",
37
+ "TraceResult",
38
+ "Concentrator",
39
+ "WinstonCone",
40
+ "OkumuraCone",
41
+ "DetectionChain",
42
+ # Sensors
43
+ "SensorGroup",
44
+ "SquareSensorGroup",
45
+ "HexagonalSensorGroup",
46
+ # PhotoDetector
47
+ "PhotoDetector",
48
+ "ConstantQE",
49
+ "PMT",
50
+ # Visualization
51
+ "show_image",
52
+ "show_telescope",
53
+ "show_camera",
54
+ "show_sensor_chain",
55
+ "add_trajectories",
56
+ # I/O
57
+ "save_telescope",
58
+ "save_camera",
59
+ ]
@@ -0,0 +1,13 @@
1
+ from .focal_surface import (
2
+ AsphericFocalSurface,
3
+ FlatFocalPlane,
4
+ FocalSurface,
5
+ FocalSurfaceHits,
6
+ )
7
+
8
+ __all__ = [
9
+ "FocalSurface",
10
+ "FlatFocalPlane",
11
+ "AsphericFocalSurface",
12
+ "FocalSurfaceHits",
13
+ ]
@@ -0,0 +1,216 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import abstractmethod
4
+
5
+ import equinox as eqx
6
+ import jax
7
+ import jax.numpy as jnp
8
+ from jax import Array
9
+
10
+ from ..core.intersections import (
11
+ intersect_conic,
12
+ intersect_plane,
13
+ newton_raphson_intersect,
14
+ )
15
+ from ..core.ray_bundle import LazyRayBundle, RayBundle
16
+ from ..core.surfaces import sag_raw
17
+ from ..core.transforms import euler_to_matrix
18
+
19
+
20
+ class FocalSurfaceHits(eqx.Module):
21
+ """Result of intersecting a :class:`RayBundle` with a :class:`FocalSurface`.
22
+
23
+ All arrays have leading dimension ``n_rays`` and are aligned with the input
24
+ bundle. Dead rays appear with ``hit_mask = False`` and meaningless values
25
+ in the position/direction arrays: always filter with ``hit_mask`` (or
26
+ :attr:`alive`) before using ``xy_local`` for plotting or statistics.
27
+
28
+ Attributes:
29
+ xy_local: Tangent-plane coordinates at the hit, in the surface-local
30
+ frame (n_rays, 2).
31
+ z_local: Sag of the surface at the hit (n_rays,). Always 0 for a flat
32
+ focal plane.
33
+ t: Ray parameter at the hit; equals the world-frame distance travelled
34
+ from ``ray_bundle.origins`` to the surface (n_rays,).
35
+ hit_mask: Liveness at the surface (n_rays,): ``True`` for a ray that
36
+ was still alive on arrival **and** crossed this surface. This is
37
+ the input bundle's ``alive`` flag ANDed with a real intersection,
38
+ so a ray lost upstream (shadowed, off-aperture) is ``False`` here
39
+ even if its stale geometry would formally cross the surface.
40
+ directions_local: Ray directions in the surface-local frame (n_rays, 3).
41
+ Useful for chief-ray / angle-of-incidence analysis.
42
+ opl: Per-ray optical path length from the source wavefront to the
43
+ focal surface, in metres (n_rays,). Equals
44
+ ``ray_bundle.path_length + t * ray_bundle.n``.
45
+ """
46
+
47
+ xy_local: Array
48
+ z_local: Array
49
+ t: Array
50
+ hit_mask: Array
51
+ directions_local: Array
52
+ opl: Array
53
+ values: Array
54
+
55
+ @property
56
+ def alive(self) -> Array:
57
+ """Rays that reached the surface carrying light (``hit_mask & values > 0``).
58
+
59
+ The one-stop filter for photometry / spot diagrams: it excludes both
60
+ geometry loss (``hit_mask``) and rays attenuated to zero throughput
61
+ such as absorbed or totally-internally-reflected rays.
62
+ """
63
+ return self.hit_mask & (self.values > 0)
64
+
65
+
66
+ class FocalSurface(eqx.Module):
67
+ """Abstract focal surface attached to the camera frame.
68
+
69
+ Subclasses implement :meth:`_intersect_local`, which receives a single ray
70
+ already transformed into the surface-local frame (vertex at origin, +Z
71
+ along the surface normal at the vertex). The base :meth:`intersect`
72
+ handles materialisation of lazy bundles, the world->local transform, and
73
+ vectorisation across rays.
74
+ """
75
+
76
+ position: Array # (3,) vertex of the surface in the camera/telescope frame
77
+ rotation: Array # (3,) Euler XYZ degrees; local +Z is the normal at the vertex
78
+
79
+ @abstractmethod
80
+ def _intersect_local(
81
+ self,
82
+ o_local: Array,
83
+ d_local: Array,
84
+ ) -> tuple[Array, Array, Array, Array]:
85
+ """Intersect a single local-frame ray with the surface.
86
+
87
+ Args:
88
+ o_local: Ray origin in the surface-local frame (3,).
89
+ d_local: Ray direction in the surface-local frame (3,), normalized.
90
+
91
+ Returns:
92
+ Tuple ``(t, xy_local, z_local, valid)`` where:
93
+ - ``t`` is the ray parameter (scalar),
94
+ - ``xy_local`` is the in-plane hit coordinate (2,),
95
+ - ``z_local`` is the surface sag at the hit (scalar),
96
+ - ``valid`` is a bool flag indicating a real intersection.
97
+ """
98
+ raise NotImplementedError
99
+
100
+ def intersect(
101
+ self,
102
+ ray_bundle: RayBundle | LazyRayBundle,
103
+ ) -> FocalSurfaceHits:
104
+ """Intersect every ray in ``ray_bundle`` with this focal surface.
105
+
106
+ Lazy bundles are materialised first; per-ray analysis cannot fold.
107
+ """
108
+ if isinstance(ray_bundle, LazyRayBundle):
109
+ ray_bundle = ray_bundle.materialise()
110
+
111
+ rot = euler_to_matrix(self.rotation)
112
+
113
+ o_local = (ray_bundle.origins - self.position) @ rot
114
+ d_local = ray_bundle.directions @ rot
115
+
116
+ t, xy_local, z_local, valid = jax.vmap(self._intersect_local)(
117
+ o_local,
118
+ d_local,
119
+ )
120
+ # Liveness at the surface: only rays that were still alive coming in
121
+ # and land on a real intersection.
122
+ hit_mask = ray_bundle.alive & valid & jnp.isfinite(t)
123
+
124
+ opl = ray_bundle.path_length + jnp.where(
125
+ hit_mask,
126
+ t * ray_bundle.n,
127
+ 0.0,
128
+ )
129
+ return FocalSurfaceHits(
130
+ xy_local=xy_local,
131
+ z_local=z_local,
132
+ t=t,
133
+ hit_mask=hit_mask,
134
+ directions_local=d_local,
135
+ opl=opl,
136
+ values=ray_bundle.values,
137
+ )
138
+
139
+
140
+ class FlatFocalPlane(FocalSurface):
141
+ """A flat focal plane.
142
+
143
+ The plane passes through ``position`` with its normal aligned with the
144
+ local +Z axis defined by ``rotation``. This is the same model
145
+ :func:`intersect_sensor` uses for sensors, just without pixel binning.
146
+ """
147
+
148
+ def __init__(
149
+ self,
150
+ position: Array | None = None,
151
+ rotation: Array | None = None,
152
+ ) -> None:
153
+ self.position = jnp.zeros(3) if position is None else jnp.asarray(position, dtype=float)
154
+ self.rotation = jnp.zeros(3) if rotation is None else jnp.asarray(rotation, dtype=float)
155
+
156
+ def _intersect_local(self, o_local, d_local):
157
+ center = jnp.zeros(3)
158
+ rot = jnp.eye(3)
159
+ xy, t = intersect_plane(o_local, d_local, center, rot)
160
+ valid = t < 1e10
161
+ z = jnp.zeros((), dtype=xy.dtype)
162
+ return t, xy, z, valid
163
+
164
+
165
+ class AsphericFocalSurface(FocalSurface):
166
+ """A rotationally-symmetric aspheric focal surface.
167
+
168
+ Parameterisation matches :class:`AsphericSurfaceGroup`; the surface sag is
169
+
170
+ z(r) = c r^2 / (1 + sqrt(1 - (1 + k) c^2 r^2))
171
+ + sum_i a_i r^(2i+4)
172
+
173
+ with ``r^2 = x^2 + y^2`` in the local frame, i.e. ``aspherics[i]``
174
+ multiplies ``r^(2i + 4)`` -- the polynomial starts at ``r^4``, since an
175
+ ``r^2`` term would be degenerate with ``curvature``. The vertex sits at
176
+ ``position`` and the optical axis points along the local +Z (set by
177
+ ``rotation``). Pure conics are recovered with ``aspherics=None``;
178
+ ``curvature = 0`` reduces to a flat plane (Newton converges in one step
179
+ from the closed-form conic init).
180
+ """
181
+
182
+ curvature: Array
183
+ conic: Array
184
+ aspherics: Array
185
+
186
+ def __init__(
187
+ self,
188
+ position: Array | None = None,
189
+ rotation: Array | None = None,
190
+ *,
191
+ curvature: float | Array = 0.0,
192
+ conic: float | Array = 0.0,
193
+ aspherics: Array | None = None,
194
+ ) -> None:
195
+ self.position = jnp.zeros(3) if position is None else jnp.asarray(position, dtype=float)
196
+ self.rotation = jnp.zeros(3) if rotation is None else jnp.asarray(rotation, dtype=float)
197
+ self.curvature = jnp.asarray(curvature, dtype=float)
198
+ self.conic = jnp.asarray(conic, dtype=float)
199
+ self.aspherics = (
200
+ jnp.zeros((0,)) if aspherics is None else jnp.asarray(aspherics, dtype=float)
201
+ )
202
+
203
+ def _intersect_local(self, o_local, d_local):
204
+ c = self.curvature
205
+ k = self.conic
206
+ a = self.aspherics
207
+
208
+ t_init = intersect_conic(o_local, d_local, c, k)
209
+ t, hit_xy, valid = newton_raphson_intersect(
210
+ lambda x, y: sag_raw(x, y, c, k, a),
211
+ o_local,
212
+ d_local,
213
+ t_init,
214
+ )
215
+ z = sag_raw(hit_xy[0], hit_xy[1], c, k, a)
216
+ return t, hit_xy, z, valid