lsurf 1.0.0__py3-none-any.whl

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 (180) hide show
  1. lsurf/__init__.py +471 -0
  2. lsurf/analysis/__init__.py +107 -0
  3. lsurf/analysis/healpix_utils.py +418 -0
  4. lsurf/analysis/sphere_viz.py +1280 -0
  5. lsurf/cli/__init__.py +48 -0
  6. lsurf/cli/build.py +398 -0
  7. lsurf/cli/config_schema.py +318 -0
  8. lsurf/cli/gui_cmd.py +76 -0
  9. lsurf/cli/interactive.py +850 -0
  10. lsurf/cli/main.py +81 -0
  11. lsurf/cli/run.py +806 -0
  12. lsurf/detectors/__init__.py +266 -0
  13. lsurf/detectors/analysis.py +289 -0
  14. lsurf/detectors/base.py +284 -0
  15. lsurf/detectors/constant_size_rings.py +485 -0
  16. lsurf/detectors/directional.py +45 -0
  17. lsurf/detectors/extended/__init__.py +73 -0
  18. lsurf/detectors/extended/local_sphere.py +353 -0
  19. lsurf/detectors/extended/recording_sphere.py +368 -0
  20. lsurf/detectors/planar.py +45 -0
  21. lsurf/detectors/protocol.py +187 -0
  22. lsurf/detectors/recording_spheres.py +63 -0
  23. lsurf/detectors/results.py +1140 -0
  24. lsurf/detectors/small/__init__.py +79 -0
  25. lsurf/detectors/small/directional.py +330 -0
  26. lsurf/detectors/small/planar.py +401 -0
  27. lsurf/detectors/small/spherical.py +450 -0
  28. lsurf/detectors/spherical.py +45 -0
  29. lsurf/geometry/__init__.py +199 -0
  30. lsurf/geometry/builder.py +478 -0
  31. lsurf/geometry/cell.py +228 -0
  32. lsurf/geometry/cell_geometry.py +247 -0
  33. lsurf/geometry/detector_arrays.py +1785 -0
  34. lsurf/geometry/geometry.py +222 -0
  35. lsurf/geometry/surface_analysis.py +375 -0
  36. lsurf/geometry/validation.py +91 -0
  37. lsurf/gui/__init__.py +51 -0
  38. lsurf/gui/app.py +903 -0
  39. lsurf/gui/core/__init__.py +39 -0
  40. lsurf/gui/core/scene.py +343 -0
  41. lsurf/gui/core/simulation.py +264 -0
  42. lsurf/gui/renderers/__init__.py +40 -0
  43. lsurf/gui/renderers/ray_renderer.py +353 -0
  44. lsurf/gui/renderers/source_renderer.py +505 -0
  45. lsurf/gui/renderers/surface_renderer.py +477 -0
  46. lsurf/gui/views/__init__.py +48 -0
  47. lsurf/gui/views/config_editor.py +3199 -0
  48. lsurf/gui/views/properties.py +257 -0
  49. lsurf/gui/views/results.py +291 -0
  50. lsurf/gui/views/scene_tree.py +180 -0
  51. lsurf/gui/views/viewport_3d.py +555 -0
  52. lsurf/gui/views/visualizations.py +712 -0
  53. lsurf/materials/__init__.py +169 -0
  54. lsurf/materials/base/__init__.py +64 -0
  55. lsurf/materials/base/full_inhomogeneous.py +208 -0
  56. lsurf/materials/base/grid_inhomogeneous.py +319 -0
  57. lsurf/materials/base/homogeneous.py +342 -0
  58. lsurf/materials/base/material_field.py +527 -0
  59. lsurf/materials/base/simple_inhomogeneous.py +418 -0
  60. lsurf/materials/base/spectral_inhomogeneous.py +497 -0
  61. lsurf/materials/implementations/__init__.py +120 -0
  62. lsurf/materials/implementations/data/alpha_values_typical_atmosphere_updated.txt +24 -0
  63. lsurf/materials/implementations/duct_atmosphere.py +390 -0
  64. lsurf/materials/implementations/exponential_atmosphere.py +435 -0
  65. lsurf/materials/implementations/gaussian_lens.py +120 -0
  66. lsurf/materials/implementations/interpolated_data.py +123 -0
  67. lsurf/materials/implementations/layered_atmosphere.py +134 -0
  68. lsurf/materials/implementations/linear_gradient.py +109 -0
  69. lsurf/materials/implementations/linsley_atmosphere.py +764 -0
  70. lsurf/materials/implementations/standard_materials.py +126 -0
  71. lsurf/materials/implementations/turbulent_atmosphere.py +135 -0
  72. lsurf/materials/implementations/us_standard_atmosphere.py +149 -0
  73. lsurf/materials/utils/__init__.py +77 -0
  74. lsurf/materials/utils/constants.py +45 -0
  75. lsurf/materials/utils/device_functions.py +117 -0
  76. lsurf/materials/utils/dispersion.py +160 -0
  77. lsurf/materials/utils/factories.py +142 -0
  78. lsurf/propagation/__init__.py +91 -0
  79. lsurf/propagation/detector_gpu.py +67 -0
  80. lsurf/propagation/gpu_device_rays.py +294 -0
  81. lsurf/propagation/kernels/__init__.py +175 -0
  82. lsurf/propagation/kernels/absorption/__init__.py +61 -0
  83. lsurf/propagation/kernels/absorption/grid.py +240 -0
  84. lsurf/propagation/kernels/absorption/simple.py +232 -0
  85. lsurf/propagation/kernels/absorption/spectral.py +410 -0
  86. lsurf/propagation/kernels/detection/__init__.py +64 -0
  87. lsurf/propagation/kernels/detection/protocol.py +102 -0
  88. lsurf/propagation/kernels/detection/spherical.py +255 -0
  89. lsurf/propagation/kernels/device_functions.py +790 -0
  90. lsurf/propagation/kernels/fresnel/__init__.py +64 -0
  91. lsurf/propagation/kernels/fresnel/protocol.py +97 -0
  92. lsurf/propagation/kernels/fresnel/standard.py +258 -0
  93. lsurf/propagation/kernels/intersection/__init__.py +79 -0
  94. lsurf/propagation/kernels/intersection/annular_plane.py +207 -0
  95. lsurf/propagation/kernels/intersection/bounded_plane.py +205 -0
  96. lsurf/propagation/kernels/intersection/plane.py +166 -0
  97. lsurf/propagation/kernels/intersection/protocol.py +95 -0
  98. lsurf/propagation/kernels/intersection/signed_distance.py +742 -0
  99. lsurf/propagation/kernels/intersection/sphere.py +190 -0
  100. lsurf/propagation/kernels/propagation/__init__.py +85 -0
  101. lsurf/propagation/kernels/propagation/grid.py +527 -0
  102. lsurf/propagation/kernels/propagation/protocol.py +105 -0
  103. lsurf/propagation/kernels/propagation/simple.py +460 -0
  104. lsurf/propagation/kernels/propagation/spectral.py +875 -0
  105. lsurf/propagation/kernels/registry.py +331 -0
  106. lsurf/propagation/kernels/surface/__init__.py +72 -0
  107. lsurf/propagation/kernels/surface/bisection.py +232 -0
  108. lsurf/propagation/kernels/surface/detection.py +402 -0
  109. lsurf/propagation/kernels/surface/reduction.py +166 -0
  110. lsurf/propagation/propagator_protocol.py +222 -0
  111. lsurf/propagation/propagators/__init__.py +101 -0
  112. lsurf/propagation/propagators/detector_handler.py +354 -0
  113. lsurf/propagation/propagators/factory.py +200 -0
  114. lsurf/propagation/propagators/fresnel_handler.py +305 -0
  115. lsurf/propagation/propagators/gpu_gradient.py +566 -0
  116. lsurf/propagation/propagators/gpu_surface_propagator.py +707 -0
  117. lsurf/propagation/propagators/gradient.py +429 -0
  118. lsurf/propagation/propagators/intersection_handler.py +327 -0
  119. lsurf/propagation/propagators/material_propagator.py +398 -0
  120. lsurf/propagation/propagators/signed_distance_handler.py +522 -0
  121. lsurf/propagation/propagators/spectral_gpu_gradient.py +553 -0
  122. lsurf/propagation/propagators/surface_interaction.py +616 -0
  123. lsurf/propagation/propagators/surface_propagator.py +719 -0
  124. lsurf/py.typed +1 -0
  125. lsurf/simulation/__init__.py +70 -0
  126. lsurf/simulation/config.py +164 -0
  127. lsurf/simulation/orchestrator.py +462 -0
  128. lsurf/simulation/result.py +299 -0
  129. lsurf/simulation/simulation.py +262 -0
  130. lsurf/sources/__init__.py +128 -0
  131. lsurf/sources/base.py +264 -0
  132. lsurf/sources/collimated.py +252 -0
  133. lsurf/sources/custom.py +409 -0
  134. lsurf/sources/diverging.py +228 -0
  135. lsurf/sources/gaussian.py +272 -0
  136. lsurf/sources/parallel_from_positions.py +197 -0
  137. lsurf/sources/point.py +172 -0
  138. lsurf/sources/uniform_diverging.py +258 -0
  139. lsurf/surfaces/__init__.py +184 -0
  140. lsurf/surfaces/cpu/__init__.py +50 -0
  141. lsurf/surfaces/cpu/curved_wave.py +463 -0
  142. lsurf/surfaces/cpu/gerstner_wave.py +381 -0
  143. lsurf/surfaces/cpu/wave_params.py +118 -0
  144. lsurf/surfaces/gpu/__init__.py +72 -0
  145. lsurf/surfaces/gpu/annular_plane.py +453 -0
  146. lsurf/surfaces/gpu/bounded_plane.py +390 -0
  147. lsurf/surfaces/gpu/curved_wave.py +483 -0
  148. lsurf/surfaces/gpu/gerstner_wave.py +377 -0
  149. lsurf/surfaces/gpu/multi_curved_wave.py +520 -0
  150. lsurf/surfaces/gpu/plane.py +299 -0
  151. lsurf/surfaces/gpu/recording_sphere.py +587 -0
  152. lsurf/surfaces/gpu/sphere.py +311 -0
  153. lsurf/surfaces/protocol.py +336 -0
  154. lsurf/surfaces/registry.py +373 -0
  155. lsurf/utilities/__init__.py +175 -0
  156. lsurf/utilities/detector_analysis.py +814 -0
  157. lsurf/utilities/fresnel.py +628 -0
  158. lsurf/utilities/interactions.py +1215 -0
  159. lsurf/utilities/propagation.py +602 -0
  160. lsurf/utilities/ray_data.py +532 -0
  161. lsurf/utilities/recording_sphere.py +745 -0
  162. lsurf/utilities/time_spread.py +463 -0
  163. lsurf/visualization/__init__.py +329 -0
  164. lsurf/visualization/absorption_plots.py +334 -0
  165. lsurf/visualization/atmospheric_plots.py +754 -0
  166. lsurf/visualization/common.py +348 -0
  167. lsurf/visualization/detector_plots.py +1350 -0
  168. lsurf/visualization/detector_sphere_plots.py +1173 -0
  169. lsurf/visualization/fresnel_plots.py +1061 -0
  170. lsurf/visualization/ocean_simulation_plots.py +999 -0
  171. lsurf/visualization/polarization_plots.py +916 -0
  172. lsurf/visualization/raytracing_plots.py +1521 -0
  173. lsurf/visualization/ring_detector_plots.py +1867 -0
  174. lsurf/visualization/time_spread_plots.py +531 -0
  175. lsurf-1.0.0.dist-info/METADATA +381 -0
  176. lsurf-1.0.0.dist-info/RECORD +180 -0
  177. lsurf-1.0.0.dist-info/WHEEL +5 -0
  178. lsurf-1.0.0.dist-info/entry_points.txt +2 -0
  179. lsurf-1.0.0.dist-info/licenses/LICENSE +32 -0
  180. lsurf-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,284 @@
1
+ # The Clear BSD License
2
+ #
3
+ # Copyright (c) 2026 Tobias Heibges
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted (subject to the limitations in the disclaimer
8
+ # below) provided that the following conditions are met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ #
13
+ # * Redistributions in binary form must reproduce the above copyright
14
+ # notice, this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ #
17
+ # * Neither the name of the copyright holder nor the names of its
18
+ # contributors may be used to endorse or promote products derived from this
19
+ # software without specific prior written permission.
20
+ #
21
+ # NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
22
+ # THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
23
+ # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
26
+ # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
29
+ # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
30
+ # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+
34
+ """
35
+ Detector Base Class and Types
36
+
37
+ Defines the abstract base class for all detectors and the DetectionEvent
38
+ data class for recording ray detection events.
39
+
40
+ Design Notes
41
+ ------------
42
+ - Follows Interface Segregation Principle: focused on ray detection
43
+ - DetectionEvent is immutable for thread safety
44
+ - Base class provides common analysis methods
45
+ """
46
+
47
+ from abc import ABC, abstractmethod
48
+ from dataclasses import dataclass
49
+
50
+ import numpy as np
51
+ from numpy.typing import NDArray
52
+
53
+ from ..utilities.ray_data import RayBatch
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class DetectionEvent:
58
+ """
59
+ Represents a single ray detection event.
60
+
61
+ Immutable record of a ray being detected, including its position,
62
+ direction, timing, and intensity at the moment of detection.
63
+
64
+ Parameters
65
+ ----------
66
+ ray_index : int
67
+ Index of the ray in the original RayBatch.
68
+ position : ndarray, shape (3,)
69
+ 3D position where ray hit detector in meters.
70
+ direction : ndarray, shape (3,)
71
+ Ray direction at detection (unit vector).
72
+ time : float
73
+ Arrival time at detector in seconds.
74
+ wavelength : float
75
+ Ray wavelength in meters.
76
+ intensity : float
77
+ Ray intensity at detection.
78
+
79
+ Notes
80
+ -----
81
+ This class is frozen (immutable) for thread safety and to prevent
82
+ accidental modification of detection records.
83
+ """
84
+
85
+ ray_index: int
86
+ position: NDArray[np.float32]
87
+ direction: NDArray[np.float32]
88
+ time: float
89
+ wavelength: float
90
+ intensity: float
91
+
92
+
93
+ class Detector(ABC):
94
+ """
95
+ Abstract base class for all detectors.
96
+
97
+ A detector records rays that pass through its detection volume,
98
+ capturing their positions, directions, timing, and other properties.
99
+
100
+ Parameters
101
+ ----------
102
+ name : str, optional
103
+ Detector name for identification. Default is "Detector".
104
+
105
+ Attributes
106
+ ----------
107
+ name : str
108
+ Detector identifier.
109
+ events : list of DetectionEvent
110
+ Recorded detection events.
111
+
112
+ Notes
113
+ -----
114
+ Derived classes must implement the `detect()` method which tests
115
+ rays against the detector geometry and records detection events.
116
+
117
+ The base class provides common analysis methods for processing
118
+ the recorded events.
119
+
120
+ Examples
121
+ --------
122
+ Creating a custom detector:
123
+
124
+ >>> class MyDetector(Detector):
125
+ ... def __init__(self, position, radius, name="MyDetector"):
126
+ ... super().__init__(name)
127
+ ... self.position = np.array(position)
128
+ ... self.radius = radius
129
+ ...
130
+ ... def detect(self, rays, current_time=0.0):
131
+ ... events = []
132
+ ... # ... detection logic ...
133
+ ... return events
134
+ """
135
+
136
+ def __init__(self, name: str = "Detector"):
137
+ """
138
+ Initialize detector.
139
+
140
+ Parameters
141
+ ----------
142
+ name : str, optional
143
+ Detector name for identification. Default is "Detector".
144
+ """
145
+ self.name = name
146
+ self.events: list[DetectionEvent] = []
147
+
148
+ @abstractmethod
149
+ def detect(self, rays: RayBatch, current_time: float = 0.0) -> list[DetectionEvent]:
150
+ """
151
+ Check which rays intersect this detector and record events.
152
+
153
+ Parameters
154
+ ----------
155
+ rays : RayBatch
156
+ Ray batch to test for detection.
157
+ current_time : float, optional
158
+ Current simulation time for reference. Default is 0.0.
159
+
160
+ Returns
161
+ -------
162
+ list of DetectionEvent
163
+ List of newly detected events.
164
+
165
+ Notes
166
+ -----
167
+ Implementations should:
168
+ - Only test active rays (rays.active == True)
169
+ - Create DetectionEvent for each detected ray
170
+ - Append events to self.events
171
+ - Return the list of newly created events
172
+ """
173
+ pass
174
+
175
+ def clear(self) -> None:
176
+ """
177
+ Clear all recorded events.
178
+
179
+ Resets the detector to its initial state with no recorded events.
180
+ """
181
+ self.events = []
182
+
183
+ def get_arrival_times(self) -> NDArray[np.float64]:
184
+ """
185
+ Get array of all arrival times.
186
+
187
+ Returns
188
+ -------
189
+ times : ndarray, shape (N,)
190
+ Arrival times in seconds for all detected rays.
191
+
192
+ Examples
193
+ --------
194
+ >>> times = detector.get_arrival_times()
195
+ >>> print(f"Mean arrival time: {times.mean():.3e} s")
196
+ """
197
+ return np.array([e.time for e in self.events], dtype=np.float64)
198
+
199
+ def get_arrival_angles(
200
+ self, reference_direction: NDArray[np.float32]
201
+ ) -> NDArray[np.float64]:
202
+ """
203
+ Get angles between ray directions and reference direction.
204
+
205
+ Parameters
206
+ ----------
207
+ reference_direction : ndarray, shape (3,)
208
+ Reference vector for angle calculation.
209
+
210
+ Returns
211
+ -------
212
+ angles : ndarray, shape (N,)
213
+ Angles in radians between each detected ray's direction
214
+ and the reference direction.
215
+
216
+ Examples
217
+ --------
218
+ >>> angles = detector.get_arrival_angles(np.array([0, 0, 1]))
219
+ >>> print(f"Mean angle: {np.degrees(angles.mean()):.1f} degrees")
220
+ """
221
+ ref = reference_direction / np.linalg.norm(reference_direction)
222
+ angles = []
223
+ for event in self.events:
224
+ dir_norm = event.direction / np.linalg.norm(event.direction)
225
+ cos_angle = np.dot(dir_norm, ref)
226
+ cos_angle = np.clip(cos_angle, -1.0, 1.0)
227
+ angles.append(np.arccos(cos_angle))
228
+ return np.array(angles, dtype=np.float64)
229
+
230
+ def get_intensities(self) -> NDArray[np.float64]:
231
+ """
232
+ Get array of all detected intensities.
233
+
234
+ Returns
235
+ -------
236
+ intensities : ndarray, shape (N,)
237
+ Intensity values for all detected rays.
238
+ """
239
+ return np.array([e.intensity for e in self.events], dtype=np.float64)
240
+
241
+ def get_wavelengths(self) -> NDArray[np.float64]:
242
+ """
243
+ Get array of all detected wavelengths.
244
+
245
+ Returns
246
+ -------
247
+ wavelengths : ndarray, shape (N,)
248
+ Wavelengths in meters for all detected rays.
249
+ """
250
+ return np.array([e.wavelength for e in self.events], dtype=np.float64)
251
+
252
+ def get_positions(self) -> NDArray[np.float32]:
253
+ """
254
+ Get array of all detection positions.
255
+
256
+ Returns
257
+ -------
258
+ positions : ndarray, shape (N, 3)
259
+ 3D positions where rays were detected.
260
+ """
261
+ if len(self.events) == 0:
262
+ return np.zeros((0, 3), dtype=np.float32)
263
+ return np.stack([e.position for e in self.events])
264
+
265
+ def get_total_intensity(self) -> float:
266
+ """
267
+ Get sum of all detected intensities.
268
+
269
+ Returns
270
+ -------
271
+ float
272
+ Total detected intensity (power).
273
+ """
274
+ return float(np.sum(self.get_intensities()))
275
+
276
+ def __len__(self) -> int:
277
+ """Return number of detection events."""
278
+ return len(self.events)
279
+
280
+ def __repr__(self) -> str:
281
+ """Return string representation."""
282
+ return (
283
+ f"{self.__class__.__name__}(name='{self.name}', events={len(self.events)})"
284
+ )