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
lsurf/cli/__init__.py ADDED
@@ -0,0 +1,48 @@
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
+ """L-SURF Command Line Interface.
35
+
36
+ This module provides a Click-based CLI for building geometry configurations
37
+ and running ray tracing simulations.
38
+
39
+ Commands:
40
+ lsurf build - Interactive/CLI geometry builder that generates config files
41
+ lsurf run - Execute simulations from config files
42
+ """
43
+
44
+ __version__ = "1.0.0"
45
+
46
+ from .main import cli
47
+
48
+ __all__ = ["cli", "__version__"]
lsurf/cli/build.py ADDED
@@ -0,0 +1,398 @@
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
+ """L-SURF CLI build command.
35
+
36
+ This module implements the `lsurf build` command for creating geometry
37
+ configuration files interactively or from templates.
38
+ """
39
+
40
+ import sys
41
+ from pathlib import Path
42
+ from typing import Any
43
+
44
+ import click
45
+
46
+ try:
47
+ import tomli_w
48
+ except ImportError:
49
+ tomli_w = None
50
+
51
+ try:
52
+ import yaml
53
+ except ImportError:
54
+ yaml = None
55
+
56
+ from .config_schema import (
57
+ LSURFConfig,
58
+ )
59
+ from .interactive import (
60
+ get_template,
61
+ get_template_description,
62
+ get_template_names,
63
+ run_interactive_wizard,
64
+ )
65
+
66
+
67
+ def config_to_dict(config: LSURFConfig) -> dict[str, Any]:
68
+ """Convert a Pydantic config to a serializable dict."""
69
+ return config.model_dump(exclude_none=True, exclude_defaults=False)
70
+
71
+
72
+ def write_yaml(config: dict[str, Any], path: Path) -> None:
73
+ """Write configuration to a YAML file."""
74
+ if yaml is None:
75
+ raise click.ClickException(
76
+ "PyYAML is required for YAML output. Install with: pip install pyyaml"
77
+ )
78
+
79
+ with open(path, "w") as f:
80
+ yaml.dump(config, f, default_flow_style=False, sort_keys=False)
81
+
82
+
83
+ def write_toml(config: dict[str, Any], path: Path) -> None:
84
+ """Write configuration to a TOML file."""
85
+ if tomli_w is None:
86
+ raise click.ClickException(
87
+ "tomli-w is required for TOML output. Install with: pip install tomli-w"
88
+ )
89
+
90
+ with open(path, "wb") as f:
91
+ tomli_w.dump(config, f)
92
+
93
+
94
+ @click.command()
95
+ @click.option(
96
+ "-o",
97
+ "--output",
98
+ "output_path",
99
+ type=click.Path(dir_okay=False, writable=True, path_type=Path),
100
+ required=True,
101
+ help="Output config file path (.yaml or .toml)",
102
+ )
103
+ @click.option(
104
+ "--template",
105
+ "template_name",
106
+ type=click.Choice(get_template_names()),
107
+ help="Use a pre-built template configuration",
108
+ )
109
+ @click.option(
110
+ "--non-interactive",
111
+ is_flag=True,
112
+ help="Disable interactive prompts (requires --template)",
113
+ )
114
+ @click.option(
115
+ "--source",
116
+ "source_type",
117
+ type=click.Choice(["collimated", "point", "diverging", "gaussian"]),
118
+ help="Source type (non-interactive mode)",
119
+ )
120
+ @click.option(
121
+ "--num-rays",
122
+ type=int,
123
+ default=10000,
124
+ help="Number of rays (non-interactive mode)",
125
+ )
126
+ @click.option(
127
+ "--surface",
128
+ "surface_type",
129
+ type=click.Choice(["plane", "sphere", "ocean"]),
130
+ help="Surface type (non-interactive mode)",
131
+ )
132
+ @click.option(
133
+ "--detector",
134
+ "detector_type",
135
+ type=click.Choice(["sphere", "plane", "recording_sphere"]),
136
+ help="Detector type (non-interactive mode)",
137
+ )
138
+ @click.option(
139
+ "--list-templates",
140
+ is_flag=True,
141
+ help="List available templates and exit",
142
+ )
143
+ @click.pass_context
144
+ def build(
145
+ ctx: click.Context,
146
+ output_path: Path,
147
+ template_name: str | None,
148
+ non_interactive: bool,
149
+ source_type: str | None,
150
+ num_rays: int,
151
+ surface_type: str | None,
152
+ detector_type: str | None,
153
+ list_templates: bool,
154
+ ) -> None:
155
+ """Build a geometry configuration file.
156
+
157
+ \b
158
+ Creates a configuration file for L-SURF simulations. Can run
159
+ interactively (default), from a template, or with CLI flags.
160
+
161
+ \b
162
+ Examples:
163
+ # Interactive mode
164
+ lsurf build -o simulation.yaml
165
+
166
+ # From template
167
+ lsurf build --template ocean -o ocean_sim.yaml
168
+
169
+ # Non-interactive with template
170
+ lsurf build --non-interactive --template glass -o glass.toml
171
+
172
+ # List available templates
173
+ lsurf build --list-templates -o dummy.yaml
174
+ """
175
+ verbose = ctx.obj.get("verbose", False)
176
+
177
+ # Handle --list-templates
178
+ if list_templates:
179
+ click.echo("\nAvailable templates:\n")
180
+ for name in get_template_names():
181
+ desc = get_template_description(name)
182
+ click.echo(f" {name:15} - {desc}")
183
+ click.echo()
184
+ return
185
+
186
+ # Determine output format from extension
187
+ suffix = output_path.suffix.lower()
188
+ if suffix not in (".yaml", ".yml", ".toml"):
189
+ raise click.ClickException(
190
+ f"Output file must have .yaml, .yml, or .toml extension, got: {suffix}"
191
+ )
192
+
193
+ is_yaml = suffix in (".yaml", ".yml")
194
+
195
+ # Build configuration
196
+ config_dict: dict[str, Any]
197
+
198
+ if template_name:
199
+ # Use template
200
+ config_dict = get_template(template_name)
201
+ if config_dict is None:
202
+ raise click.ClickException(f"Unknown template: {template_name}")
203
+
204
+ # Apply any overrides from CLI flags
205
+ if num_rays != 10000:
206
+ config_dict["source"]["params"]["num_rays"] = num_rays
207
+
208
+ if verbose:
209
+ click.echo(f"Using template: {template_name}")
210
+ click.echo(f"Description: {get_template_description(template_name)}")
211
+
212
+ elif non_interactive:
213
+ # Non-interactive without template requires source/surface/detector
214
+ if not source_type:
215
+ raise click.ClickException(
216
+ "--non-interactive requires --template or explicit configuration flags"
217
+ )
218
+
219
+ config_dict = _build_from_flags(
220
+ source_type=source_type,
221
+ num_rays=num_rays,
222
+ surface_type=surface_type,
223
+ detector_type=detector_type,
224
+ )
225
+
226
+ else:
227
+ # Interactive mode
228
+ try:
229
+ config = run_interactive_wizard()
230
+ config_dict = config_to_dict(config)
231
+ except KeyboardInterrupt:
232
+ click.echo("\nConfiguration cancelled.")
233
+ sys.exit(1)
234
+
235
+ # Validate configuration
236
+ try:
237
+ LSURFConfig(**config_dict)
238
+ except Exception as e:
239
+ raise click.ClickException(f"Invalid configuration: {e}")
240
+
241
+ # Write output
242
+ output_path.parent.mkdir(parents=True, exist_ok=True)
243
+
244
+ if is_yaml:
245
+ write_yaml(config_dict, output_path)
246
+ else:
247
+ write_toml(config_dict, output_path)
248
+
249
+ click.echo(f"\nConfiguration written to: {output_path}")
250
+
251
+ if verbose:
252
+ click.echo("\nConfiguration summary:")
253
+ click.echo(f" Media: {list(config_dict.get('media', {}).keys())}")
254
+ click.echo(f" Source: {config_dict.get('source', {}).get('type')}")
255
+ click.echo(f" Surfaces: {len(config_dict.get('surfaces', []))}")
256
+ click.echo(f" Detectors: {len(config_dict.get('detectors', []))}")
257
+
258
+
259
+ def _build_from_flags(
260
+ source_type: str | None,
261
+ num_rays: int,
262
+ surface_type: str | None,
263
+ detector_type: str | None,
264
+ ) -> dict[str, Any]:
265
+ """Build a minimal configuration from CLI flags."""
266
+ config: dict[str, Any] = {
267
+ "version": "1.0",
268
+ "media": {
269
+ "air": {"type": "air", "params": {}},
270
+ },
271
+ "background": "air",
272
+ "surfaces": [],
273
+ "detectors": [],
274
+ "simulation": {"step_size": 100.0, "max_bounces": 5, "use_gpu": True},
275
+ "output": {"directory": "./results", "format": "hdf5"},
276
+ }
277
+
278
+ # Source configuration
279
+ source_configs = {
280
+ "collimated": {
281
+ "type": "collimated_beam",
282
+ "params": {
283
+ "center": [0.0, 0.0, 0.0],
284
+ "direction": [0.0, 0.0, 1.0],
285
+ "beam_radius": 0.01,
286
+ "num_rays": num_rays,
287
+ "wavelength": 532e-9,
288
+ },
289
+ },
290
+ "point": {
291
+ "type": "point",
292
+ "params": {
293
+ "position": [0.0, 0.0, 0.0],
294
+ "num_rays": num_rays,
295
+ "wavelength": 532e-9,
296
+ },
297
+ },
298
+ "diverging": {
299
+ "type": "diverging_beam",
300
+ "params": {
301
+ "origin": [0.0, 0.0, 0.0],
302
+ "mean_direction": [0.0, 0.0, 1.0],
303
+ "divergence_angle": 0.1,
304
+ "num_rays": num_rays,
305
+ "wavelength": 532e-9,
306
+ },
307
+ },
308
+ "gaussian": {
309
+ "type": "gaussian_beam",
310
+ "params": {
311
+ "waist_position": [0.0, 0.0, 0.0],
312
+ "direction": [0.0, 0.0, 1.0],
313
+ "waist_radius": 0.001,
314
+ "num_rays": num_rays,
315
+ "wavelength": 532e-9,
316
+ },
317
+ },
318
+ }
319
+
320
+ config["source"] = source_configs.get(source_type, source_configs["collimated"])
321
+
322
+ # Surface configuration
323
+ if surface_type:
324
+ surface_configs = {
325
+ "plane": {
326
+ "name": "plane_surface",
327
+ "type": "plane",
328
+ "role": "optical",
329
+ "front_medium": "air",
330
+ "back_medium": "air",
331
+ "params": {"point": [0.0, 0.0, 1.0], "normal": [0.0, 0.0, -1.0]},
332
+ },
333
+ "sphere": {
334
+ "name": "sphere_surface",
335
+ "type": "sphere",
336
+ "role": "optical",
337
+ "front_medium": "air",
338
+ "back_medium": "air",
339
+ "params": {"center": [0.0, 0.0, 1.0], "radius": 0.5},
340
+ },
341
+ "ocean": {
342
+ "name": "ocean_surface",
343
+ "type": "curved_wave",
344
+ "role": "optical",
345
+ "front_medium": "air",
346
+ "back_medium": "air",
347
+ "params": {
348
+ "amplitude": 0.5,
349
+ "wavelength": 100.0,
350
+ "direction": [1.0, 0.0],
351
+ "earth_center": [0.0, 0.0, -6.371e6],
352
+ "earth_radius": 6.371e6,
353
+ },
354
+ },
355
+ }
356
+ config["surfaces"].append(surface_configs[surface_type])
357
+
358
+ # Detector configuration
359
+ if detector_type:
360
+ detector_configs = {
361
+ "sphere": {
362
+ "name": "sphere_detector",
363
+ "type": "spherical",
364
+ "params": {"center": [0.0, 0.0, 2.0], "radius": 0.1},
365
+ },
366
+ "plane": {
367
+ "name": "plane_detector",
368
+ "type": "planar",
369
+ "params": {
370
+ "center": [0.0, 0.0, 2.0],
371
+ "normal": [0.0, 0.0, -1.0],
372
+ "width": 0.2,
373
+ "height": 0.2,
374
+ },
375
+ },
376
+ "recording_sphere": {
377
+ "name": "recording_sphere_detector",
378
+ "type": "recording_sphere",
379
+ "params": {"center": [0.0, 0.0, -6.371e6], "radius": 6.371e6 + 35000},
380
+ },
381
+ }
382
+ config["detectors"].append(detector_configs[detector_type])
383
+ else:
384
+ # Add a default detector if none specified
385
+ config["detectors"].append(
386
+ {
387
+ "name": "default_detector",
388
+ "type": "planar",
389
+ "params": {
390
+ "center": [0.0, 0.0, 2.0],
391
+ "normal": [0.0, 0.0, -1.0],
392
+ "width": 0.5,
393
+ "height": 0.5,
394
+ },
395
+ }
396
+ )
397
+
398
+ return config