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
|
@@ -0,0 +1,318 @@
|
|
|
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
|
+
"""Pydantic models for L-SURF configuration file validation.
|
|
35
|
+
|
|
36
|
+
This module defines the schema for YAML/TOML configuration files used
|
|
37
|
+
to specify ray tracing simulations.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from typing import Any, Literal
|
|
41
|
+
|
|
42
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
43
|
+
|
|
44
|
+
# Import SimulationConfig from the library - used directly in CLI config
|
|
45
|
+
from ..simulation import SimulationConfig
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# =============================================================================
|
|
49
|
+
# Media/Material Configuration
|
|
50
|
+
# =============================================================================
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MediaConfig(BaseModel):
|
|
54
|
+
"""Configuration for a material/medium."""
|
|
55
|
+
|
|
56
|
+
type: Literal[
|
|
57
|
+
"vacuum",
|
|
58
|
+
"air",
|
|
59
|
+
"water",
|
|
60
|
+
"glass",
|
|
61
|
+
"homogeneous",
|
|
62
|
+
"exponential_atmosphere",
|
|
63
|
+
"duct_atmosphere",
|
|
64
|
+
"sellmeier",
|
|
65
|
+
"cauchy",
|
|
66
|
+
]
|
|
67
|
+
params: dict[str, Any] = Field(default_factory=dict)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# =============================================================================
|
|
71
|
+
# Source Configuration
|
|
72
|
+
# =============================================================================
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SourceConfig(BaseModel):
|
|
76
|
+
"""Configuration for a ray source."""
|
|
77
|
+
|
|
78
|
+
type: Literal[
|
|
79
|
+
"point",
|
|
80
|
+
"collimated_beam",
|
|
81
|
+
"diverging_beam",
|
|
82
|
+
"gaussian_beam",
|
|
83
|
+
"parallel_from_positions",
|
|
84
|
+
"custom",
|
|
85
|
+
]
|
|
86
|
+
params: dict[str, Any] = Field(default_factory=dict)
|
|
87
|
+
|
|
88
|
+
@field_validator("params")
|
|
89
|
+
@classmethod
|
|
90
|
+
def validate_source_params(cls, v: dict[str, Any], info) -> dict[str, Any]:
|
|
91
|
+
"""Validate source parameters based on type."""
|
|
92
|
+
source_type = info.data.get("type")
|
|
93
|
+
required_params: dict[str, list[str]] = {
|
|
94
|
+
"point": ["position", "num_rays", "wavelength"],
|
|
95
|
+
"collimated_beam": [
|
|
96
|
+
"center",
|
|
97
|
+
"direction",
|
|
98
|
+
"beam_radius",
|
|
99
|
+
"num_rays",
|
|
100
|
+
"wavelength",
|
|
101
|
+
],
|
|
102
|
+
"diverging_beam": [
|
|
103
|
+
"origin",
|
|
104
|
+
"mean_direction",
|
|
105
|
+
"divergence_angle",
|
|
106
|
+
"num_rays",
|
|
107
|
+
"wavelength",
|
|
108
|
+
],
|
|
109
|
+
"gaussian_beam": [
|
|
110
|
+
"waist_position",
|
|
111
|
+
"direction",
|
|
112
|
+
"waist_radius",
|
|
113
|
+
"num_rays",
|
|
114
|
+
"wavelength",
|
|
115
|
+
],
|
|
116
|
+
"parallel_from_positions": ["positions", "direction", "wavelength"],
|
|
117
|
+
"custom": ["positions", "directions", "wavelengths", "intensities"],
|
|
118
|
+
}
|
|
119
|
+
if source_type and source_type in required_params:
|
|
120
|
+
missing = [p for p in required_params[source_type] if p not in v]
|
|
121
|
+
if missing:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"Missing required parameters for {source_type}: {missing}"
|
|
124
|
+
)
|
|
125
|
+
return v
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# =============================================================================
|
|
129
|
+
# Surface Configuration
|
|
130
|
+
# =============================================================================
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class SurfaceConfig(BaseModel):
|
|
134
|
+
"""Configuration for a surface."""
|
|
135
|
+
|
|
136
|
+
name: str
|
|
137
|
+
type: Literal[
|
|
138
|
+
"plane",
|
|
139
|
+
"bounded_plane",
|
|
140
|
+
"annular_plane",
|
|
141
|
+
"sphere",
|
|
142
|
+
"gerstner_wave",
|
|
143
|
+
"curved_wave",
|
|
144
|
+
"multi_curved_wave",
|
|
145
|
+
]
|
|
146
|
+
role: Literal["optical", "detector", "absorber"]
|
|
147
|
+
front_medium: str | None = None
|
|
148
|
+
back_medium: str | None = None
|
|
149
|
+
params: dict[str, Any] = Field(default_factory=dict)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# =============================================================================
|
|
153
|
+
# Detector Configuration
|
|
154
|
+
# =============================================================================
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class DetectorConfig(BaseModel):
|
|
158
|
+
"""Configuration for a detector."""
|
|
159
|
+
|
|
160
|
+
name: str
|
|
161
|
+
type: Literal[
|
|
162
|
+
"spherical",
|
|
163
|
+
"planar",
|
|
164
|
+
"directional",
|
|
165
|
+
"recording_sphere",
|
|
166
|
+
"local_recording_sphere",
|
|
167
|
+
]
|
|
168
|
+
params: dict[str, Any] = Field(default_factory=dict)
|
|
169
|
+
|
|
170
|
+
@field_validator("params")
|
|
171
|
+
@classmethod
|
|
172
|
+
def validate_detector_params(cls, v: dict[str, Any], info) -> dict[str, Any]:
|
|
173
|
+
"""Validate detector parameters based on type."""
|
|
174
|
+
detector_type = info.data.get("type")
|
|
175
|
+
|
|
176
|
+
# recording_sphere has flexible parameters:
|
|
177
|
+
# - Either 'altitude' alone (uses defaults for earth_center/earth_radius)
|
|
178
|
+
# - Or 'center' + 'radius' (radius can be altitude or total sphere radius)
|
|
179
|
+
# - Or 'altitude' + 'earth_center' + optional 'earth_radius'
|
|
180
|
+
if detector_type == "recording_sphere":
|
|
181
|
+
has_altitude = "altitude" in v
|
|
182
|
+
has_center_radius = "center" in v and "radius" in v
|
|
183
|
+
has_radius_only = "radius" in v and "center" not in v
|
|
184
|
+
if not (has_altitude or has_center_radius or has_radius_only):
|
|
185
|
+
raise ValueError(
|
|
186
|
+
"recording_sphere requires either 'altitude', 'radius', "
|
|
187
|
+
"or 'center' + 'radius'"
|
|
188
|
+
)
|
|
189
|
+
return v
|
|
190
|
+
|
|
191
|
+
required_params: dict[str, list[str]] = {
|
|
192
|
+
"spherical": ["center", "radius"],
|
|
193
|
+
"planar": ["center", "normal", "width", "height"],
|
|
194
|
+
"directional": ["position", "direction", "acceptance_angle", "radius"],
|
|
195
|
+
"local_recording_sphere": ["radius"],
|
|
196
|
+
}
|
|
197
|
+
if detector_type and detector_type in required_params:
|
|
198
|
+
missing = [p for p in required_params[detector_type] if p not in v]
|
|
199
|
+
if missing:
|
|
200
|
+
raise ValueError(
|
|
201
|
+
f"Missing required parameters for {detector_type}: {missing}"
|
|
202
|
+
)
|
|
203
|
+
return v
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# =============================================================================
|
|
207
|
+
# Output Configuration
|
|
208
|
+
# =============================================================================
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class OutputConfig(BaseModel):
|
|
212
|
+
"""Output configuration."""
|
|
213
|
+
|
|
214
|
+
directory: str = "./results"
|
|
215
|
+
format: Literal["hdf5", "numpy", "csv"] = "hdf5"
|
|
216
|
+
save_ray_paths: bool = False
|
|
217
|
+
save_statistics: bool = True
|
|
218
|
+
prefix: str = "simulation"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# =============================================================================
|
|
222
|
+
# Root Configuration
|
|
223
|
+
# =============================================================================
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class LSURFConfig(BaseModel):
|
|
227
|
+
"""Root configuration model for L-SURF simulations.
|
|
228
|
+
|
|
229
|
+
Example YAML:
|
|
230
|
+
version: "1.0"
|
|
231
|
+
media:
|
|
232
|
+
atmosphere:
|
|
233
|
+
type: exponential_atmosphere
|
|
234
|
+
params:
|
|
235
|
+
scale_height: 8500
|
|
236
|
+
ocean:
|
|
237
|
+
type: water
|
|
238
|
+
background: atmosphere
|
|
239
|
+
source:
|
|
240
|
+
type: collimated_beam
|
|
241
|
+
params:
|
|
242
|
+
center: [0, 0, 100000]
|
|
243
|
+
direction: [0, 0, -1]
|
|
244
|
+
beam_radius: 0.005
|
|
245
|
+
num_rays: 100000
|
|
246
|
+
wavelength: 532e-9
|
|
247
|
+
surfaces:
|
|
248
|
+
- name: ocean_surface
|
|
249
|
+
type: curved_wave
|
|
250
|
+
role: optical
|
|
251
|
+
front_medium: atmosphere
|
|
252
|
+
back_medium: ocean
|
|
253
|
+
params:
|
|
254
|
+
center: [0, 0, -6.371e6]
|
|
255
|
+
radius: 6.371e6
|
|
256
|
+
detectors:
|
|
257
|
+
- name: sky_detector
|
|
258
|
+
type: recording_sphere
|
|
259
|
+
params:
|
|
260
|
+
center: [0, 0, -6.371e6]
|
|
261
|
+
radius: 6.404e6
|
|
262
|
+
simulation:
|
|
263
|
+
step_size: 100.0
|
|
264
|
+
max_bounces: 1
|
|
265
|
+
output:
|
|
266
|
+
directory: ./results
|
|
267
|
+
format: hdf5
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
version: str = "1.0"
|
|
271
|
+
media: dict[str, MediaConfig] = Field(default_factory=dict)
|
|
272
|
+
background: str | None = None
|
|
273
|
+
source: SourceConfig
|
|
274
|
+
surfaces: list[SurfaceConfig] = Field(default_factory=list)
|
|
275
|
+
detectors: list[DetectorConfig] = Field(default_factory=list)
|
|
276
|
+
simulation: SimulationConfig = Field(default_factory=SimulationConfig)
|
|
277
|
+
output: OutputConfig = Field(default_factory=OutputConfig)
|
|
278
|
+
|
|
279
|
+
@field_validator("simulation", mode="before")
|
|
280
|
+
@classmethod
|
|
281
|
+
def parse_simulation_config(cls, v):
|
|
282
|
+
"""Convert dict to SimulationConfig if needed."""
|
|
283
|
+
if isinstance(v, dict):
|
|
284
|
+
return SimulationConfig(**v)
|
|
285
|
+
return v
|
|
286
|
+
|
|
287
|
+
@model_validator(mode="after")
|
|
288
|
+
def validate_media_references(self) -> "LSURFConfig":
|
|
289
|
+
"""Validate that all media references exist."""
|
|
290
|
+
defined_media = set(self.media.keys())
|
|
291
|
+
|
|
292
|
+
# Check background reference
|
|
293
|
+
if self.background and self.background not in defined_media:
|
|
294
|
+
raise ValueError(
|
|
295
|
+
f"Background medium '{self.background}' not defined in media section"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Check surface media references
|
|
299
|
+
for surface in self.surfaces:
|
|
300
|
+
if surface.front_medium and surface.front_medium not in defined_media:
|
|
301
|
+
raise ValueError(
|
|
302
|
+
f"Surface '{surface.name}' references undefined medium "
|
|
303
|
+
f"'{surface.front_medium}'"
|
|
304
|
+
)
|
|
305
|
+
if surface.back_medium and surface.back_medium not in defined_media:
|
|
306
|
+
raise ValueError(
|
|
307
|
+
f"Surface '{surface.name}' references undefined medium "
|
|
308
|
+
f"'{surface.back_medium}'"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
return self
|
|
312
|
+
|
|
313
|
+
@model_validator(mode="after")
|
|
314
|
+
def validate_has_geometry(self) -> "LSURFConfig":
|
|
315
|
+
"""Validate that at least one surface or detector is defined."""
|
|
316
|
+
if not self.surfaces and not self.detectors:
|
|
317
|
+
raise ValueError("At least one surface or detector must be defined")
|
|
318
|
+
return self
|
lsurf/cli/gui_cmd.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
"""GUI command for L-SURF CLI.
|
|
35
|
+
|
|
36
|
+
Launches the interactive 3D visualization GUI.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
|
|
41
|
+
import click
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@click.command()
|
|
45
|
+
@click.argument("config_file", required=False, type=click.Path(exists=True))
|
|
46
|
+
@click.pass_context
|
|
47
|
+
def gui(ctx: click.Context, config_file: str | None) -> None:
|
|
48
|
+
"""Launch the interactive 3D visualization GUI.
|
|
49
|
+
|
|
50
|
+
Optionally load a configuration file (YAML or TOML) on startup.
|
|
51
|
+
|
|
52
|
+
\b
|
|
53
|
+
Examples:
|
|
54
|
+
# Launch empty GUI
|
|
55
|
+
lsurf gui
|
|
56
|
+
|
|
57
|
+
# Launch with a configuration file
|
|
58
|
+
lsurf gui simulation.yaml
|
|
59
|
+
lsurf gui ocean_config.toml
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
from lsurf.gui import launch_gui
|
|
63
|
+
except ImportError as e:
|
|
64
|
+
raise click.ClickException(
|
|
65
|
+
f"GUI dependencies not installed. Install with: pip install 'lsurf[gui]'\n"
|
|
66
|
+
f"Error: {e}"
|
|
67
|
+
) from e
|
|
68
|
+
|
|
69
|
+
config_path = Path(config_file) if config_file else None
|
|
70
|
+
|
|
71
|
+
if ctx.obj.get("verbose"):
|
|
72
|
+
click.echo("Launching L-SURF GUI...")
|
|
73
|
+
if config_path:
|
|
74
|
+
click.echo(f"Loading configuration: {config_path}")
|
|
75
|
+
|
|
76
|
+
launch_gui(config_path)
|