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,329 @@
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
+ Visualization Module
36
+
37
+ Provides plotting functions for ray tracing and detector analysis.
38
+ Functions are organized into single-axis functions (for composability)
39
+ and composite figure builders.
40
+
41
+ Modules
42
+ -------
43
+ common : Shared utilities and styling
44
+ raytracing_plots : Ray path and surface visualization
45
+ detector_plots : Detector and beam analysis visualization
46
+
47
+ Examples
48
+ --------
49
+ Single-axis functions for custom layouts:
50
+
51
+ >>> import matplotlib.pyplot as plt
52
+ >>> from surface_roughness.visualization import (
53
+ ... plot_ray_paths_projection,
54
+ ... plot_ray_endpoints_scatter,
55
+ ... plot_detection_counts,
56
+ ... )
57
+ >>> fig, axes = plt.subplots(1, 2)
58
+ >>> plot_ray_paths_projection(axes[0], ray_history, projection="xz")
59
+ >>> plot_ray_endpoints_scatter(axes[1], rays, projection="xy")
60
+
61
+ Composite figures for quick visualization:
62
+
63
+ >>> from surface_roughness.visualization import (
64
+ ... create_ray_overview_figure,
65
+ ... create_detector_scan_figure,
66
+ ... )
67
+ >>> fig = create_ray_overview_figure(rays, surface)
68
+ >>> fig = create_detector_scan_figure(angles, counts, intensities, total)
69
+ """
70
+
71
+ # Common utilities
72
+ from .common import (
73
+ # Style constants
74
+ DEFAULT_FIGSIZE,
75
+ DEFAULT_LINEWIDTH,
76
+ DEFAULT_MARKERSIZE,
77
+ INTENSITY_CMAP,
78
+ LINE_ALPHA,
79
+ SCATTER_ALPHA,
80
+ WAVELENGTH_CMAP,
81
+ add_colorbar,
82
+ create_figure,
83
+ # Utility functions
84
+ get_color_mapping,
85
+ get_projection_config,
86
+ save_figure,
87
+ setup_axis_grid,
88
+ wavelength_to_color,
89
+ )
90
+
91
+ # Detector visualization
92
+ from .detector_plots import (
93
+ create_beam_profile_figure,
94
+ create_detector_scan_figure,
95
+ # Composite builders
96
+ create_wavelength_figure,
97
+ plot_angular_histogram,
98
+ plot_arrival_angle_distribution,
99
+ plot_beam_profile,
100
+ # Single-axis functions
101
+ plot_beam_slice,
102
+ plot_detection_counts,
103
+ plot_detection_efficiency,
104
+ # Production figures
105
+ plot_detector_scan_results,
106
+ plot_mean_arrival_time,
107
+ # Legacy convenience functions
108
+ plot_statistics_evolution,
109
+ plot_timing_distribution,
110
+ plot_wavelength_distribution,
111
+ plot_wavelength_histogram,
112
+ plot_wavelength_intensity_histogram,
113
+ )
114
+
115
+ # Ocean simulation visualization
116
+ from .ocean_simulation_plots import (
117
+ create_ocean_simulation_figures,
118
+ )
119
+
120
+ # Detector sphere visualization
121
+ from .detector_sphere_plots import (
122
+ plot_3d_intersection_scatter,
123
+ plot_arrival_time_distributions,
124
+ plot_energy_density_map,
125
+ plot_geometry_schematic,
126
+ plot_mollweide_detector_projection,
127
+ plot_ocean_intersections_top_view,
128
+ plot_pareto_front,
129
+ plot_time_spread_comparison,
130
+ )
131
+
132
+ # Ray tracing visualization
133
+ from .raytracing_plots import (
134
+ # Composite builders
135
+ create_ray_overview_figure,
136
+ plot_bounce_points,
137
+ plot_incoming_rays,
138
+ plot_multi_bounce_paths,
139
+ # Production figures
140
+ plot_production_ray_overview,
141
+ plot_ray_endpoints,
142
+ plot_ray_endpoints_histogram,
143
+ plot_ray_endpoints_scatter,
144
+ # Legacy convenience functions
145
+ plot_ray_paths_2d,
146
+ # Single-axis functions
147
+ plot_ray_paths_projection,
148
+ plot_ray_paths_with_surface,
149
+ plot_reflected_rays,
150
+ plot_surface_profile,
151
+ plot_wave_surface_detail,
152
+ )
153
+
154
+ # Fresnel reflection visualization
155
+ from .fresnel_plots import (
156
+ plot_brewster_validation,
157
+ plot_fresnel_reflection,
158
+ )
159
+
160
+ # Polarization visualization
161
+ from .polarization_plots import (
162
+ get_ray_coordinates,
163
+ plot_fresnel_reflectance_curves,
164
+ plot_measured_polarization_reflectance,
165
+ plot_polarization_ellipse,
166
+ plot_polarization_vector_components,
167
+ plot_polarization_vs_elevation,
168
+ )
169
+
170
+ # Time spread visualization
171
+ from .time_spread_plots import (
172
+ create_arrival_time_figure,
173
+ # Composite builders
174
+ create_time_spread_schematic,
175
+ plot_arrival_time_histogram,
176
+ plot_beam_footprint_top,
177
+ plot_footprint_arrival_times,
178
+ # Single-axis functions
179
+ plot_time_spread_geometry_side,
180
+ plot_time_spread_vs_divergence,
181
+ )
182
+
183
+ # Atmospheric refraction visualization
184
+ from .atmospheric_plots import (
185
+ plot_ray_trajectories,
186
+ plot_refractive_index_profile,
187
+ plot_trajectory_offset,
188
+ plot_horizontal_ray_deviation,
189
+ create_atmospheric_refraction_figure,
190
+ create_atmospheric_refraction_figure_with_offset,
191
+ plot_trajectory_comparison,
192
+ save_atmospheric_figure,
193
+ )
194
+
195
+ # Absorption visualization
196
+ from .absorption_plots import (
197
+ plot_homogeneous_absorption,
198
+ plot_exponential_atmosphere_absorption,
199
+ plot_spectral_atmosphere_absorption,
200
+ )
201
+
202
+ # Ring detector visualization
203
+ from .ring_detector_plots import (
204
+ compute_constant_size_bin_stats,
205
+ compute_ring_azimuth_maps,
206
+ compute_ring_spread_stats,
207
+ plot_constant_size_timing_analysis,
208
+ plot_constant_size_timing_distributions,
209
+ plot_geometry_analysis,
210
+ plot_per_ring_timing_distributions,
211
+ plot_polar_irradiance,
212
+ plot_ring_azimuth_heatmap,
213
+ plot_ring_overview,
214
+ plot_ring_side_view,
215
+ plot_spread_analysis,
216
+ plot_time_spread_comparison as plot_ring_time_spread_comparison,
217
+ plot_timing_heatmap,
218
+ )
219
+
220
+ __all__ = [
221
+ # Common
222
+ "DEFAULT_FIGSIZE",
223
+ "LINE_ALPHA",
224
+ "SCATTER_ALPHA",
225
+ "DEFAULT_LINEWIDTH",
226
+ "DEFAULT_MARKERSIZE",
227
+ "WAVELENGTH_CMAP",
228
+ "INTENSITY_CMAP",
229
+ "get_color_mapping",
230
+ "get_projection_config",
231
+ "wavelength_to_color",
232
+ "setup_axis_grid",
233
+ "add_colorbar",
234
+ "create_figure",
235
+ "save_figure",
236
+ # Ray Tracing - Single Axis
237
+ "plot_ray_paths_projection",
238
+ "plot_ray_endpoints_scatter",
239
+ "plot_ray_endpoints_histogram",
240
+ "plot_surface_profile",
241
+ "plot_bounce_points",
242
+ "plot_incoming_rays",
243
+ "plot_reflected_rays",
244
+ "plot_multi_bounce_paths",
245
+ # Ray Tracing - Composite
246
+ "create_ray_overview_figure",
247
+ "plot_production_ray_overview",
248
+ "plot_wave_surface_detail",
249
+ "plot_ray_paths_with_surface",
250
+ "plot_fresnel_reflection",
251
+ "plot_brewster_validation",
252
+ # Ray Tracing - Polarization
253
+ "plot_polarization_vector_components",
254
+ "plot_polarization_ellipse",
255
+ "plot_polarization_vs_elevation",
256
+ "plot_fresnel_reflectance_curves",
257
+ "plot_measured_polarization_reflectance",
258
+ "get_ray_coordinates",
259
+ # Ray Tracing - Legacy
260
+ "plot_ray_paths_2d",
261
+ "plot_ray_endpoints",
262
+ # Detector - Single Axis
263
+ "plot_beam_slice",
264
+ "plot_wavelength_histogram",
265
+ "plot_wavelength_intensity_histogram",
266
+ "plot_detection_counts",
267
+ "plot_detection_efficiency",
268
+ "plot_mean_arrival_time",
269
+ "plot_timing_distribution",
270
+ "plot_arrival_angle_distribution",
271
+ "plot_angular_histogram",
272
+ # Detector - Composite
273
+ "create_wavelength_figure",
274
+ "create_beam_profile_figure",
275
+ "create_detector_scan_figure",
276
+ "plot_detector_scan_results",
277
+ # Detector - Legacy
278
+ "plot_statistics_evolution",
279
+ "plot_beam_profile",
280
+ "plot_wavelength_distribution",
281
+ # Ocean Simulation - Complete Suite
282
+ "create_ocean_simulation_figures",
283
+ # Detector Sphere Visualization
284
+ "plot_geometry_schematic",
285
+ "plot_ocean_intersections_top_view",
286
+ "plot_3d_intersection_scatter",
287
+ "plot_pareto_front",
288
+ "plot_energy_density_map",
289
+ "plot_mollweide_detector_projection",
290
+ "plot_time_spread_comparison",
291
+ "plot_arrival_time_distributions",
292
+ # Time Spread - Single Axis
293
+ "plot_time_spread_geometry_side",
294
+ "plot_beam_footprint_top",
295
+ "plot_arrival_time_histogram",
296
+ "plot_footprint_arrival_times",
297
+ "plot_time_spread_vs_divergence",
298
+ # Time Spread - Composite
299
+ "create_time_spread_schematic",
300
+ "create_arrival_time_figure",
301
+ # Atmospheric Refraction
302
+ "plot_ray_trajectories",
303
+ "plot_refractive_index_profile",
304
+ "plot_trajectory_offset",
305
+ "plot_horizontal_ray_deviation",
306
+ "create_atmospheric_refraction_figure",
307
+ "create_atmospheric_refraction_figure_with_offset",
308
+ "plot_trajectory_comparison",
309
+ "save_atmospheric_figure",
310
+ # Absorption
311
+ "plot_homogeneous_absorption",
312
+ "plot_exponential_atmosphere_absorption",
313
+ "plot_spectral_atmosphere_absorption",
314
+ # Ring Detector
315
+ "plot_geometry_analysis",
316
+ "plot_ring_side_view",
317
+ "plot_ring_overview",
318
+ "plot_ring_azimuth_heatmap",
319
+ "plot_timing_heatmap",
320
+ "plot_per_ring_timing_distributions",
321
+ "plot_spread_analysis",
322
+ "plot_polar_irradiance",
323
+ "plot_ring_time_spread_comparison",
324
+ "plot_constant_size_timing_analysis",
325
+ "plot_constant_size_timing_distributions",
326
+ "compute_ring_azimuth_maps",
327
+ "compute_ring_spread_stats",
328
+ "compute_constant_size_bin_stats",
329
+ ]
@@ -0,0 +1,334 @@
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
+ Absorption Visualization
36
+
37
+ Plotting utilities for Beer-Lambert absorption analysis.
38
+ """
39
+
40
+ from pathlib import Path
41
+ from typing import Callable
42
+
43
+ import numpy as np
44
+ import matplotlib.pyplot as plt
45
+
46
+
47
+ def plot_homogeneous_absorption(
48
+ alpha: float,
49
+ distance: float,
50
+ simulated_intensity: float,
51
+ simulated_optical_depth: float,
52
+ initial_intensity: float = 1.0,
53
+ save_path: str | Path | None = None,
54
+ show: bool = True,
55
+ ) -> plt.Figure:
56
+ """
57
+ Plot Beer-Lambert decay for homogeneous absorbing medium.
58
+
59
+ Parameters
60
+ ----------
61
+ alpha : float
62
+ Absorption coefficient in m^-1
63
+ distance : float
64
+ Total propagation distance in m
65
+ simulated_intensity : float
66
+ Simulated final intensity
67
+ simulated_optical_depth : float
68
+ Simulated accumulated optical depth
69
+ initial_intensity : float
70
+ Initial intensity (default 1.0)
71
+ save_path : str or Path, optional
72
+ Path to save figure
73
+ show : bool
74
+ Whether to display the figure
75
+
76
+ Returns
77
+ -------
78
+ plt.Figure
79
+ The matplotlib figure
80
+ """
81
+ distances = np.linspace(0, distance, 100)
82
+ analytical_intensity = initial_intensity * np.exp(-alpha * distances)
83
+ analytical_tau = alpha * distances
84
+
85
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
86
+
87
+ # Intensity decay
88
+ ax1.plot(distances / 1000, analytical_intensity, "b-", label="Analytical")
89
+ ax1.scatter(
90
+ [distance / 1000],
91
+ [simulated_intensity],
92
+ c="r",
93
+ s=100,
94
+ label=f"Simulated ({simulated_intensity:.4f})",
95
+ zorder=5,
96
+ )
97
+ ax1.set_xlabel("Distance (km)")
98
+ ax1.set_ylabel("Intensity")
99
+ ax1.set_title("Beer-Lambert Decay")
100
+ ax1.legend()
101
+ ax1.grid(True, alpha=0.3)
102
+
103
+ # Optical depth
104
+ ax2.plot(distances / 1000, analytical_tau, "b-", label="Analytical")
105
+ ax2.scatter(
106
+ [distance / 1000],
107
+ [simulated_optical_depth],
108
+ c="r",
109
+ s=100,
110
+ label=f"Simulated ({simulated_optical_depth:.4f})",
111
+ zorder=5,
112
+ )
113
+ ax2.set_xlabel("Distance (km)")
114
+ ax2.set_ylabel("Optical Depth τ")
115
+ ax2.set_title("Optical Depth Accumulation")
116
+ ax2.legend()
117
+ ax2.grid(True, alpha=0.3)
118
+
119
+ plt.tight_layout()
120
+
121
+ if save_path:
122
+ fig.savefig(save_path, dpi=150)
123
+
124
+ if show:
125
+ plt.show()
126
+
127
+ return fig
128
+
129
+
130
+ def plot_exponential_atmosphere_absorption(
131
+ alpha_sea_level: float,
132
+ scale_height: float,
133
+ final_altitude: float,
134
+ simulated_intensity: float,
135
+ simulated_optical_depth: float,
136
+ max_altitude: float = 100_000.0,
137
+ save_path: str | Path | None = None,
138
+ show: bool = True,
139
+ ) -> plt.Figure:
140
+ """
141
+ Plot absorption profiles for exponential atmosphere.
142
+
143
+ Parameters
144
+ ----------
145
+ alpha_sea_level : float
146
+ Absorption coefficient at sea level in m^-1
147
+ scale_height : float
148
+ Atmospheric scale height in m
149
+ final_altitude : float
150
+ Final ray altitude in m
151
+ simulated_intensity : float
152
+ Simulated final intensity
153
+ simulated_optical_depth : float
154
+ Simulated accumulated optical depth
155
+ max_altitude : float
156
+ Maximum altitude for plots in m
157
+ save_path : str or Path, optional
158
+ Path to save figure
159
+ show : bool
160
+ Whether to display the figure
161
+
162
+ Returns
163
+ -------
164
+ plt.Figure
165
+ The matplotlib figure
166
+ """
167
+ altitudes = np.linspace(0, max_altitude, 500)
168
+ alpha_profile = alpha_sea_level * np.exp(-altitudes / scale_height)
169
+ tau_profile = (
170
+ alpha_sea_level * scale_height * (1 - np.exp(-altitudes / scale_height))
171
+ )
172
+ intensity_profile = np.exp(-tau_profile)
173
+
174
+ fig, axes = plt.subplots(1, 3, figsize=(15, 5))
175
+
176
+ # Absorption coefficient profile
177
+ axes[0].semilogy(alpha_profile * 1e6, altitudes / 1000, "b-")
178
+ axes[0].set_xlabel("α (×10⁻⁶ m⁻¹)")
179
+ axes[0].set_ylabel("Altitude (km)")
180
+ axes[0].set_title("Absorption Coefficient Profile")
181
+ axes[0].grid(True, alpha=0.3)
182
+
183
+ # Optical depth
184
+ axes[1].plot(tau_profile, altitudes / 1000, "b-", label="Analytical")
185
+ axes[1].scatter(
186
+ [simulated_optical_depth],
187
+ [final_altitude / 1000],
188
+ c="r",
189
+ s=100,
190
+ label="Simulated",
191
+ zorder=5,
192
+ )
193
+ axes[1].set_xlabel("Optical Depth τ")
194
+ axes[1].set_ylabel("Altitude (km)")
195
+ axes[1].set_title("Accumulated Optical Depth")
196
+ axes[1].legend()
197
+ axes[1].grid(True, alpha=0.3)
198
+
199
+ # Intensity
200
+ axes[2].plot(intensity_profile, altitudes / 1000, "b-", label="Analytical")
201
+ axes[2].scatter(
202
+ [simulated_intensity],
203
+ [final_altitude / 1000],
204
+ c="r",
205
+ s=100,
206
+ label="Simulated",
207
+ zorder=5,
208
+ )
209
+ axes[2].set_xlabel("Intensity (I/I₀)")
210
+ axes[2].set_ylabel("Altitude (km)")
211
+ axes[2].set_title("Transmitted Intensity")
212
+ axes[2].legend()
213
+ axes[2].grid(True, alpha=0.3)
214
+
215
+ plt.tight_layout()
216
+
217
+ if save_path:
218
+ fig.savefig(save_path, dpi=150)
219
+
220
+ if show:
221
+ plt.show()
222
+
223
+ return fig
224
+
225
+
226
+ def plot_spectral_atmosphere_absorption(
227
+ get_extinction: Callable[[float, float], float],
228
+ get_optical_depth: Callable[[float, float, float], float],
229
+ earth_radius: float,
230
+ ray_wavelengths: np.ndarray,
231
+ ray_positions: np.ndarray,
232
+ ray_optical_depths: np.ndarray,
233
+ wavelength_range: tuple[float, float] = (300e-9, 1500e-9),
234
+ altitude_range: tuple[float, float] = (0, 50_000),
235
+ test_altitudes: list[float] | None = None,
236
+ test_wavelengths: list[float] | None = None,
237
+ save_path: str | Path | None = None,
238
+ show: bool = True,
239
+ ) -> plt.Figure:
240
+ """
241
+ Plot spectral absorption characteristics.
242
+
243
+ Parameters
244
+ ----------
245
+ get_extinction : callable
246
+ Function(altitude, wavelength) -> extinction coefficient
247
+ get_optical_depth : callable
248
+ Function(z1, z2, wavelength) -> optical depth
249
+ earth_radius : float
250
+ Earth radius in m
251
+ ray_wavelengths : ndarray
252
+ Array of ray wavelengths in m
253
+ ray_positions : ndarray
254
+ Array of ray positions (N, 3)
255
+ ray_optical_depths : ndarray
256
+ Array of simulated optical depths
257
+ wavelength_range : tuple
258
+ (min, max) wavelength in m for plots
259
+ altitude_range : tuple
260
+ (min, max) altitude in m for plots
261
+ test_altitudes : list, optional
262
+ Altitudes for extinction vs wavelength plot
263
+ test_wavelengths : list, optional
264
+ Wavelengths for optical depth vs altitude plot
265
+ save_path : str or Path, optional
266
+ Path to save figure
267
+ show : bool
268
+ Whether to display the figure
269
+
270
+ Returns
271
+ -------
272
+ plt.Figure
273
+ The matplotlib figure
274
+ """
275
+ if test_altitudes is None:
276
+ test_altitudes = [5000, 10000, 20000, 30000]
277
+ if test_wavelengths is None:
278
+ test_wavelengths = [400e-9, 550e-9, 700e-9, 1000e-9]
279
+
280
+ wavelengths_plot = np.linspace(wavelength_range[0], wavelength_range[1], 100)
281
+ altitudes_range = np.linspace(altitude_range[0], altitude_range[1], 100)
282
+
283
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
284
+
285
+ # Extinction coefficient vs wavelength at different altitudes
286
+ for h in test_altitudes:
287
+ alpha_vals = [get_extinction(h, wl) for wl in wavelengths_plot]
288
+ ax1.semilogy(wavelengths_plot * 1e9, alpha_vals, label=f"{h/1000:.0f} km")
289
+
290
+ ax1.set_xlabel("Wavelength (nm)")
291
+ ax1.set_ylabel("Extinction coefficient (m⁻¹)")
292
+ ax1.set_title("Extinction vs Wavelength")
293
+ ax1.legend()
294
+ ax1.grid(True, alpha=0.3)
295
+
296
+ # Optical depth vs altitude for different wavelengths
297
+ for wl in test_wavelengths:
298
+ tau_vals = [get_optical_depth(0, h, wl) for h in altitudes_range]
299
+ ax2.plot(tau_vals, altitudes_range / 1000, label=f"{wl*1e9:.0f} nm")
300
+
301
+ # Add simulated points
302
+ for i in range(len(ray_wavelengths)):
303
+ final_h = (
304
+ np.sqrt(
305
+ ray_positions[i, 0] ** 2
306
+ + ray_positions[i, 1] ** 2
307
+ + ray_positions[i, 2] ** 2
308
+ )
309
+ - earth_radius
310
+ )
311
+ ax2.scatter(ray_optical_depths[i], final_h / 1000, c="r", s=50, zorder=5)
312
+
313
+ ax2.set_xlabel("Optical Depth τ")
314
+ ax2.set_ylabel("Altitude (km)")
315
+ ax2.set_title("Optical Depth vs Altitude")
316
+ ax2.legend()
317
+ ax2.grid(True, alpha=0.3)
318
+
319
+ plt.tight_layout()
320
+
321
+ if save_path:
322
+ fig.savefig(save_path, dpi=150)
323
+
324
+ if show:
325
+ plt.show()
326
+
327
+ return fig
328
+
329
+
330
+ __all__ = [
331
+ "plot_homogeneous_absorption",
332
+ "plot_exponential_atmosphere_absorption",
333
+ "plot_spectral_atmosphere_absorption",
334
+ ]