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.
- lsurf/__init__.py +471 -0
- lsurf/analysis/__init__.py +107 -0
- lsurf/analysis/healpix_utils.py +418 -0
- lsurf/analysis/sphere_viz.py +1280 -0
- lsurf/cli/__init__.py +48 -0
- lsurf/cli/build.py +398 -0
- lsurf/cli/config_schema.py +318 -0
- lsurf/cli/gui_cmd.py +76 -0
- lsurf/cli/interactive.py +850 -0
- lsurf/cli/main.py +81 -0
- lsurf/cli/run.py +806 -0
- lsurf/detectors/__init__.py +266 -0
- lsurf/detectors/analysis.py +289 -0
- lsurf/detectors/base.py +284 -0
- lsurf/detectors/constant_size_rings.py +485 -0
- lsurf/detectors/directional.py +45 -0
- lsurf/detectors/extended/__init__.py +73 -0
- lsurf/detectors/extended/local_sphere.py +353 -0
- lsurf/detectors/extended/recording_sphere.py +368 -0
- lsurf/detectors/planar.py +45 -0
- lsurf/detectors/protocol.py +187 -0
- lsurf/detectors/recording_spheres.py +63 -0
- lsurf/detectors/results.py +1140 -0
- lsurf/detectors/small/__init__.py +79 -0
- lsurf/detectors/small/directional.py +330 -0
- lsurf/detectors/small/planar.py +401 -0
- lsurf/detectors/small/spherical.py +450 -0
- lsurf/detectors/spherical.py +45 -0
- lsurf/geometry/__init__.py +199 -0
- lsurf/geometry/builder.py +478 -0
- lsurf/geometry/cell.py +228 -0
- lsurf/geometry/cell_geometry.py +247 -0
- lsurf/geometry/detector_arrays.py +1785 -0
- lsurf/geometry/geometry.py +222 -0
- lsurf/geometry/surface_analysis.py +375 -0
- lsurf/geometry/validation.py +91 -0
- lsurf/gui/__init__.py +51 -0
- lsurf/gui/app.py +903 -0
- lsurf/gui/core/__init__.py +39 -0
- lsurf/gui/core/scene.py +343 -0
- lsurf/gui/core/simulation.py +264 -0
- lsurf/gui/renderers/__init__.py +40 -0
- lsurf/gui/renderers/ray_renderer.py +353 -0
- lsurf/gui/renderers/source_renderer.py +505 -0
- lsurf/gui/renderers/surface_renderer.py +477 -0
- lsurf/gui/views/__init__.py +48 -0
- lsurf/gui/views/config_editor.py +3199 -0
- lsurf/gui/views/properties.py +257 -0
- lsurf/gui/views/results.py +291 -0
- lsurf/gui/views/scene_tree.py +180 -0
- lsurf/gui/views/viewport_3d.py +555 -0
- lsurf/gui/views/visualizations.py +712 -0
- lsurf/materials/__init__.py +169 -0
- lsurf/materials/base/__init__.py +64 -0
- lsurf/materials/base/full_inhomogeneous.py +208 -0
- lsurf/materials/base/grid_inhomogeneous.py +319 -0
- lsurf/materials/base/homogeneous.py +342 -0
- lsurf/materials/base/material_field.py +527 -0
- lsurf/materials/base/simple_inhomogeneous.py +418 -0
- lsurf/materials/base/spectral_inhomogeneous.py +497 -0
- lsurf/materials/implementations/__init__.py +120 -0
- lsurf/materials/implementations/data/alpha_values_typical_atmosphere_updated.txt +24 -0
- lsurf/materials/implementations/duct_atmosphere.py +390 -0
- lsurf/materials/implementations/exponential_atmosphere.py +435 -0
- lsurf/materials/implementations/gaussian_lens.py +120 -0
- lsurf/materials/implementations/interpolated_data.py +123 -0
- lsurf/materials/implementations/layered_atmosphere.py +134 -0
- lsurf/materials/implementations/linear_gradient.py +109 -0
- lsurf/materials/implementations/linsley_atmosphere.py +764 -0
- lsurf/materials/implementations/standard_materials.py +126 -0
- lsurf/materials/implementations/turbulent_atmosphere.py +135 -0
- lsurf/materials/implementations/us_standard_atmosphere.py +149 -0
- lsurf/materials/utils/__init__.py +77 -0
- lsurf/materials/utils/constants.py +45 -0
- lsurf/materials/utils/device_functions.py +117 -0
- lsurf/materials/utils/dispersion.py +160 -0
- lsurf/materials/utils/factories.py +142 -0
- lsurf/propagation/__init__.py +91 -0
- lsurf/propagation/detector_gpu.py +67 -0
- lsurf/propagation/gpu_device_rays.py +294 -0
- lsurf/propagation/kernels/__init__.py +175 -0
- lsurf/propagation/kernels/absorption/__init__.py +61 -0
- lsurf/propagation/kernels/absorption/grid.py +240 -0
- lsurf/propagation/kernels/absorption/simple.py +232 -0
- lsurf/propagation/kernels/absorption/spectral.py +410 -0
- lsurf/propagation/kernels/detection/__init__.py +64 -0
- lsurf/propagation/kernels/detection/protocol.py +102 -0
- lsurf/propagation/kernels/detection/spherical.py +255 -0
- lsurf/propagation/kernels/device_functions.py +790 -0
- lsurf/propagation/kernels/fresnel/__init__.py +64 -0
- lsurf/propagation/kernels/fresnel/protocol.py +97 -0
- lsurf/propagation/kernels/fresnel/standard.py +258 -0
- lsurf/propagation/kernels/intersection/__init__.py +79 -0
- lsurf/propagation/kernels/intersection/annular_plane.py +207 -0
- lsurf/propagation/kernels/intersection/bounded_plane.py +205 -0
- lsurf/propagation/kernels/intersection/plane.py +166 -0
- lsurf/propagation/kernels/intersection/protocol.py +95 -0
- lsurf/propagation/kernels/intersection/signed_distance.py +742 -0
- lsurf/propagation/kernels/intersection/sphere.py +190 -0
- lsurf/propagation/kernels/propagation/__init__.py +85 -0
- lsurf/propagation/kernels/propagation/grid.py +527 -0
- lsurf/propagation/kernels/propagation/protocol.py +105 -0
- lsurf/propagation/kernels/propagation/simple.py +460 -0
- lsurf/propagation/kernels/propagation/spectral.py +875 -0
- lsurf/propagation/kernels/registry.py +331 -0
- lsurf/propagation/kernels/surface/__init__.py +72 -0
- lsurf/propagation/kernels/surface/bisection.py +232 -0
- lsurf/propagation/kernels/surface/detection.py +402 -0
- lsurf/propagation/kernels/surface/reduction.py +166 -0
- lsurf/propagation/propagator_protocol.py +222 -0
- lsurf/propagation/propagators/__init__.py +101 -0
- lsurf/propagation/propagators/detector_handler.py +354 -0
- lsurf/propagation/propagators/factory.py +200 -0
- lsurf/propagation/propagators/fresnel_handler.py +305 -0
- lsurf/propagation/propagators/gpu_gradient.py +566 -0
- lsurf/propagation/propagators/gpu_surface_propagator.py +707 -0
- lsurf/propagation/propagators/gradient.py +429 -0
- lsurf/propagation/propagators/intersection_handler.py +327 -0
- lsurf/propagation/propagators/material_propagator.py +398 -0
- lsurf/propagation/propagators/signed_distance_handler.py +522 -0
- lsurf/propagation/propagators/spectral_gpu_gradient.py +553 -0
- lsurf/propagation/propagators/surface_interaction.py +616 -0
- lsurf/propagation/propagators/surface_propagator.py +719 -0
- lsurf/py.typed +1 -0
- lsurf/simulation/__init__.py +70 -0
- lsurf/simulation/config.py +164 -0
- lsurf/simulation/orchestrator.py +462 -0
- lsurf/simulation/result.py +299 -0
- lsurf/simulation/simulation.py +262 -0
- lsurf/sources/__init__.py +128 -0
- lsurf/sources/base.py +264 -0
- lsurf/sources/collimated.py +252 -0
- lsurf/sources/custom.py +409 -0
- lsurf/sources/diverging.py +228 -0
- lsurf/sources/gaussian.py +272 -0
- lsurf/sources/parallel_from_positions.py +197 -0
- lsurf/sources/point.py +172 -0
- lsurf/sources/uniform_diverging.py +258 -0
- lsurf/surfaces/__init__.py +184 -0
- lsurf/surfaces/cpu/__init__.py +50 -0
- lsurf/surfaces/cpu/curved_wave.py +463 -0
- lsurf/surfaces/cpu/gerstner_wave.py +381 -0
- lsurf/surfaces/cpu/wave_params.py +118 -0
- lsurf/surfaces/gpu/__init__.py +72 -0
- lsurf/surfaces/gpu/annular_plane.py +453 -0
- lsurf/surfaces/gpu/bounded_plane.py +390 -0
- lsurf/surfaces/gpu/curved_wave.py +483 -0
- lsurf/surfaces/gpu/gerstner_wave.py +377 -0
- lsurf/surfaces/gpu/multi_curved_wave.py +520 -0
- lsurf/surfaces/gpu/plane.py +299 -0
- lsurf/surfaces/gpu/recording_sphere.py +587 -0
- lsurf/surfaces/gpu/sphere.py +311 -0
- lsurf/surfaces/protocol.py +336 -0
- lsurf/surfaces/registry.py +373 -0
- lsurf/utilities/__init__.py +175 -0
- lsurf/utilities/detector_analysis.py +814 -0
- lsurf/utilities/fresnel.py +628 -0
- lsurf/utilities/interactions.py +1215 -0
- lsurf/utilities/propagation.py +602 -0
- lsurf/utilities/ray_data.py +532 -0
- lsurf/utilities/recording_sphere.py +745 -0
- lsurf/utilities/time_spread.py +463 -0
- lsurf/visualization/__init__.py +329 -0
- lsurf/visualization/absorption_plots.py +334 -0
- lsurf/visualization/atmospheric_plots.py +754 -0
- lsurf/visualization/common.py +348 -0
- lsurf/visualization/detector_plots.py +1350 -0
- lsurf/visualization/detector_sphere_plots.py +1173 -0
- lsurf/visualization/fresnel_plots.py +1061 -0
- lsurf/visualization/ocean_simulation_plots.py +999 -0
- lsurf/visualization/polarization_plots.py +916 -0
- lsurf/visualization/raytracing_plots.py +1521 -0
- lsurf/visualization/ring_detector_plots.py +1867 -0
- lsurf/visualization/time_spread_plots.py +531 -0
- lsurf-1.0.0.dist-info/METADATA +381 -0
- lsurf-1.0.0.dist-info/RECORD +180 -0
- lsurf-1.0.0.dist-info/WHEEL +5 -0
- lsurf-1.0.0.dist-info/entry_points.txt +2 -0
- lsurf-1.0.0.dist-info/licenses/LICENSE +32 -0
- 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
|