wavesimpro 1.5.0__tar.gz → 1.6.0__tar.gz
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.
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/PKG-INFO +1 -1
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/pyproject.toml +1 -1
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/__init__.py +1 -1
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/execute.py +80 -13
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/json_helper.py +24 -2
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/simulate.py +10 -2
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro.egg-info/PKG-INFO +1 -1
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/setup.cfg +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/__main__.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/binary_utils.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/client.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/config.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/exceptions.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/py.typed +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/setup.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro/validate.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro.egg-info/SOURCES.txt +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro.egg-info/dependency_links.txt +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro.egg-info/requires.txt +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.0}/src/wavesimpro.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wavesimpro"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.6.0"
|
|
8
8
|
description = "WavesimPro Python API — cloud execution client for the Rayfos simulation platform"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -15,7 +15,7 @@ import shutil
|
|
|
15
15
|
import logging
|
|
16
16
|
import tempfile
|
|
17
17
|
from pathlib import Path
|
|
18
|
-
from typing import Tuple, Optional, Callable, Dict, Any, List, Union
|
|
18
|
+
from typing import Tuple, Optional, Callable, Dict, Any, List, Union, Sequence
|
|
19
19
|
|
|
20
20
|
import numpy as np
|
|
21
21
|
|
|
@@ -61,6 +61,37 @@ def _monitor_output_filename(monitor_name: str) -> str:
|
|
|
61
61
|
return f"monitor_{core}.bin"
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def _normalize_boundary_widths(boundary_widths) -> Optional[List[Tuple[float, float]]]:
|
|
65
|
+
"""Normalize a per-axis boundary spec into three (left, right) pairs in µm.
|
|
66
|
+
|
|
67
|
+
Accepts a sequence of exactly 3 entries (x, y, z); each entry is either a
|
|
68
|
+
single number (same width on both sides of that axis) or a (left, right)
|
|
69
|
+
pair. Returns None when boundary_widths is None.
|
|
70
|
+
"""
|
|
71
|
+
if boundary_widths is None:
|
|
72
|
+
return None
|
|
73
|
+
if len(boundary_widths) != 3:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
"boundary_widths must have exactly 3 entries (x, y, z); each entry is "
|
|
76
|
+
"a width in MICROMETERS or a (left, right) pair, e.g. "
|
|
77
|
+
"boundary_widths=[6.0, (6.0, 0.0), 6.0]"
|
|
78
|
+
)
|
|
79
|
+
out: List[Tuple[float, float]] = []
|
|
80
|
+
for axis, w in zip("xyz", boundary_widths):
|
|
81
|
+
if isinstance(w, (int, float)):
|
|
82
|
+
left = right = float(w)
|
|
83
|
+
else:
|
|
84
|
+
if len(w) != 2:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"boundary_widths[{axis}] must be a number or a (left, right) pair, got {w!r}"
|
|
87
|
+
)
|
|
88
|
+
left, right = float(w[0]), float(w[1])
|
|
89
|
+
if left < 0 or right < 0:
|
|
90
|
+
raise ValueError(f"boundary_widths[{axis}] sides must be >= 0, got ({left}, {right})")
|
|
91
|
+
out.append((left, right))
|
|
92
|
+
return out
|
|
93
|
+
|
|
94
|
+
|
|
64
95
|
def execute_via_api(
|
|
65
96
|
refractive_index: np.ndarray,
|
|
66
97
|
source,
|
|
@@ -68,7 +99,9 @@ def execute_via_api(
|
|
|
68
99
|
pixel_size: float,
|
|
69
100
|
threshold: float = 1e-2,
|
|
70
101
|
max_iterations: int = 1000,
|
|
71
|
-
boundary_width: Optional[float] = None, # in MICROMETERS; REQUIRED
|
|
102
|
+
boundary_width: Optional[float] = None, # in MICROMETERS; REQUIRED unless boundary_widths is given
|
|
103
|
+
boundary_widths: Optional[Sequence[Any]] = None, # per-axis [x, y, z] µm; each a number or (left, right)
|
|
104
|
+
boundary_strength: Optional[float] = None, # None = engine default 1.0 (C++ engine only)
|
|
72
105
|
is_vectorial: bool = True,
|
|
73
106
|
data_type: str = "RefractiveIndex",
|
|
74
107
|
api_url: Optional[str] = None,
|
|
@@ -113,6 +146,18 @@ def execute_via_api(
|
|
|
113
146
|
BREAKING CHANGE: in earlier versions this parameter was in PIXELS
|
|
114
147
|
and defaulted to 0. If you are migrating, pass your old pixel
|
|
115
148
|
count × pixel_size instead.
|
|
149
|
+
boundary_widths: OPTIONAL asymmetric boundaries (C++ engine, 2026-09
|
|
150
|
+
machine service+). Sequence of exactly 3 entries (x, y, z) in
|
|
151
|
+
MICROMETERS; each entry is a single number (same width on both
|
|
152
|
+
sides of that axis) or a (left, right) pair, e.g.
|
|
153
|
+
``boundary_widths=[6.0, (6.0, 0.0), 6.0]``. When given,
|
|
154
|
+
boundary_width becomes optional (it is then only the symmetric
|
|
155
|
+
fallback stored as BoundarySize for older servers, defaulting to
|
|
156
|
+
the X-left value). Jobs that resolve to the Python engine collapse
|
|
157
|
+
to a single scalar (the X-left value) with a machine-side warning.
|
|
158
|
+
boundary_strength: OPTIONAL largest imaginary permittivity the
|
|
159
|
+
absorbing ramp lerps towards (C++ engine only). None (default)
|
|
160
|
+
= engine default 1.0. Older servers ignore it.
|
|
116
161
|
is_vectorial: True for Maxwell, False for Helmholtz (default: True)
|
|
117
162
|
data_type: 'RefractiveIndex' or 'Permittivity' (default: 'RefractiveIndex')
|
|
118
163
|
api_url: API base URL (default: from RAYFOS_API_URL env var)
|
|
@@ -149,16 +194,26 @@ def execute_via_api(
|
|
|
149
194
|
each shaped per its own sub-cube.
|
|
150
195
|
- converged: True if simulation completed successfully
|
|
151
196
|
"""
|
|
197
|
+
boundary_sides_um = _normalize_boundary_widths(boundary_widths)
|
|
152
198
|
if boundary_width is None:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
199
|
+
if boundary_sides_um is not None:
|
|
200
|
+
# Symmetric fallback stored as BoundarySize: used by pre-2026-09
|
|
201
|
+
# servers (which ignore the per-side fields) and by the Python
|
|
202
|
+
# engine (single scalar). X-axis left, per the degradation rule.
|
|
203
|
+
boundary_width = boundary_sides_um[0][0]
|
|
204
|
+
else:
|
|
205
|
+
raise ValueError(
|
|
206
|
+
"boundary_width is required and has no default: the right value depends "
|
|
207
|
+
"on the wavelength. It is the absorbing boundary width in MICROMETERS "
|
|
208
|
+
"(μm) — typically 4–5 × the vacuum wavelength. Thinner boundaries "
|
|
209
|
+
"reflect grazing/guided waves back into the domain; thicker ones "
|
|
210
|
+
"increase memory (the boundary pads both sides of every non-periodic "
|
|
211
|
+
"axis). NOTE: in earlier versions this parameter was in PIXELS and "
|
|
212
|
+
"defaulted to 0 — if migrating, pass old_pixel_count * pixel_size. "
|
|
213
|
+
"For asymmetric boundaries pass boundary_widths instead."
|
|
214
|
+
)
|
|
215
|
+
if boundary_strength is not None and boundary_strength <= 0:
|
|
216
|
+
raise ValueError(f"boundary_strength must be > 0 (None = engine default 1.0), got {boundary_strength}")
|
|
162
217
|
|
|
163
218
|
# 1. Load credentials
|
|
164
219
|
api_url = api_url or os.environ.get("RAYFOS_API_URL", "http://localhost:5159")
|
|
@@ -198,7 +253,13 @@ def execute_via_api(
|
|
|
198
253
|
logger.info(f" Max iterations: {max_iterations}")
|
|
199
254
|
if pseudo_propagation_distance > 0:
|
|
200
255
|
logger.info(f" Pseudo propagation distance: {pseudo_propagation_distance} um (overrides max iterations)")
|
|
201
|
-
|
|
256
|
+
if boundary_sides_um is None:
|
|
257
|
+
logger.info(f" Boundary width: {boundary_width} um ({int(round(boundary_width / pixel_size))} pixels)")
|
|
258
|
+
else:
|
|
259
|
+
logger.info(" Boundary widths (um): " + " ".join(
|
|
260
|
+
f"{ax}:[{left:g}, {right:g}]" for ax, (left, right) in zip("XYZ", boundary_sides_um)))
|
|
261
|
+
if boundary_strength is not None:
|
|
262
|
+
logger.info(f" Boundary strength: {boundary_strength}")
|
|
202
263
|
|
|
203
264
|
# 3. Prepare electromagnetic data
|
|
204
265
|
if data_type == "RefractiveIndex":
|
|
@@ -261,10 +322,14 @@ def execute_via_api(
|
|
|
261
322
|
domain_y_um = float(ny) * float(pixel_size)
|
|
262
323
|
domain_z_um = float(nz) * float(pixel_size)
|
|
263
324
|
try:
|
|
325
|
+
# The preflight endpoint takes one scalar width; with asymmetric
|
|
326
|
+
# boundaries use the largest side so the estimate stays conservative.
|
|
327
|
+
preflight_boundary_um = (float(boundary_width) if boundary_sides_um is None
|
|
328
|
+
else max(side for pair in boundary_sides_um for side in pair))
|
|
264
329
|
pre = client.preflight_check(
|
|
265
330
|
domain_um=(domain_x_um, domain_y_um, domain_z_um),
|
|
266
331
|
voxel_um=float(pixel_size),
|
|
267
|
-
boundary_um=
|
|
332
|
+
boundary_um=preflight_boundary_um,
|
|
268
333
|
periodic=(bool(periodic[0]), bool(periodic[1]), bool(periodic[2])),
|
|
269
334
|
engine_preference=engine_preference,
|
|
270
335
|
use_gpu=use_gpu,
|
|
@@ -435,6 +500,8 @@ def execute_via_api(
|
|
|
435
500
|
wavelength=float(wavelength),
|
|
436
501
|
voxel_size=float(pixel_size),
|
|
437
502
|
boundary_size_um=boundary_size_um,
|
|
503
|
+
boundary_sides_um=boundary_sides_um,
|
|
504
|
+
boundary_strength=boundary_strength,
|
|
438
505
|
residual_norm_threshold=float(threshold),
|
|
439
506
|
residual_norm_max_iterations=int(max_iterations),
|
|
440
507
|
periodic_x=bool(periodic[0]),
|
|
@@ -559,6 +559,8 @@ def create_wavesim_properties(
|
|
|
559
559
|
wavelength_unit: str = "Micrometers",
|
|
560
560
|
voxel_unit: str = "Micrometers",
|
|
561
561
|
boundary_size_um: float = 0.0,
|
|
562
|
+
boundary_sides_um: Optional[List[Tuple[float, float]]] = None,
|
|
563
|
+
boundary_strength: Optional[float] = None,
|
|
562
564
|
residual_norm_threshold: float = 1.0e-6,
|
|
563
565
|
residual_norm_max_iterations: int = 10000,
|
|
564
566
|
periodic_x: bool = False,
|
|
@@ -585,7 +587,13 @@ def create_wavesim_properties(
|
|
|
585
587
|
voxel_size: Voxel size value
|
|
586
588
|
wavelength_unit: Wavelength unit (default: 'Micrometers')
|
|
587
589
|
voxel_unit: Voxel size unit (default: 'Micrometers')
|
|
588
|
-
boundary_size_um: Boundary size in micrometers
|
|
590
|
+
boundary_size_um: Boundary size in micrometers (symmetric fallback)
|
|
591
|
+
boundary_sides_um: Optional three (left, right) pairs in micrometers
|
|
592
|
+
(x, y, z) → emitted as BoundarySizesX/Y/Z for asymmetric
|
|
593
|
+
boundaries. None = symmetric (boundary_size_um everywhere).
|
|
594
|
+
boundary_strength: Optional largest imaginary permittivity of the
|
|
595
|
+
absorbing ramp → emitted as BoundaryStrength. None = engine
|
|
596
|
+
default 1.0. C++ engine only.
|
|
589
597
|
residual_norm_threshold: Convergence threshold
|
|
590
598
|
residual_norm_max_iterations: Max iterations
|
|
591
599
|
periodic_x, periodic_y, periodic_z: Periodic boundary conditions
|
|
@@ -594,7 +602,7 @@ def create_wavesim_properties(
|
|
|
594
602
|
residual_norm_max_iterations (threshold still stops the run early).
|
|
595
603
|
C++ engine only.
|
|
596
604
|
"""
|
|
597
|
-
|
|
605
|
+
props = {
|
|
598
606
|
"SimulationDomain": simulation_domain,
|
|
599
607
|
"BackgroundMediumId": background_medium_id,
|
|
600
608
|
"Primitives": primitives,
|
|
@@ -629,6 +637,20 @@ def create_wavesim_properties(
|
|
|
629
637
|
}
|
|
630
638
|
}
|
|
631
639
|
|
|
640
|
+
# Asymmetric per-side boundary widths (BoundarySides on the C# model).
|
|
641
|
+
# Omitted entirely when symmetric so old servers see the same JSON as before.
|
|
642
|
+
if boundary_sides_um is not None:
|
|
643
|
+
for key, (left, right) in zip(("BoundarySizesX", "BoundarySizesY", "BoundarySizesZ"),
|
|
644
|
+
boundary_sides_um):
|
|
645
|
+
props[key] = {
|
|
646
|
+
"Left": {"Value": float(left), "Unit": "Micrometers"},
|
|
647
|
+
"Right": {"Value": float(right), "Unit": "Micrometers"},
|
|
648
|
+
}
|
|
649
|
+
if boundary_strength is not None:
|
|
650
|
+
props["BoundaryStrength"] = float(boundary_strength)
|
|
651
|
+
|
|
652
|
+
return props
|
|
653
|
+
|
|
632
654
|
|
|
633
655
|
def parse_progress_data(progress_obj: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
634
656
|
"""
|
|
@@ -97,7 +97,9 @@ def simulate(
|
|
|
97
97
|
sources: Sequence[tuple],
|
|
98
98
|
wavelength: float,
|
|
99
99
|
pixel_size: float,
|
|
100
|
-
boundary_width: float, # μm — REQUIRED
|
|
100
|
+
boundary_width: Optional[float] = None, # μm — REQUIRED unless boundary_widths is given
|
|
101
|
+
boundary_widths=None, # per-axis [x, y, z] µm; each a number or (left, right) — asymmetric boundaries
|
|
102
|
+
boundary_strength=None, # None = engine default 1.0 (C++ engine only)
|
|
101
103
|
periodic: tuple = (False, False, False), # forwarded to PeriodicX/Y/Z in JSON
|
|
102
104
|
use_gpu=None, # forwarded to API (None = auto)
|
|
103
105
|
n_domains=None, # local only — accepted and ignored
|
|
@@ -212,6 +214,8 @@ def simulate(
|
|
|
212
214
|
wavelength=wavelength,
|
|
213
215
|
pixel_size=pixel_size,
|
|
214
216
|
boundary_width=boundary_width, # μm end-to-end; execute_via_api takes μm
|
|
217
|
+
boundary_widths=boundary_widths,
|
|
218
|
+
boundary_strength=boundary_strength,
|
|
215
219
|
is_vectorial=is_vectorial,
|
|
216
220
|
data_type="Permittivity",
|
|
217
221
|
max_iterations=max_iterations,
|
|
@@ -246,7 +250,9 @@ def simulate_pro(
|
|
|
246
250
|
sources: Sequence[tuple],
|
|
247
251
|
wavelength: float,
|
|
248
252
|
pixel_size: float,
|
|
249
|
-
boundary_width: float, # μm — REQUIRED
|
|
253
|
+
boundary_width: Optional[float] = None, # μm — REQUIRED unless boundary_widths is given
|
|
254
|
+
boundary_widths=None, # per-axis [x, y, z] µm; each a number or (left, right) — asymmetric boundaries
|
|
255
|
+
boundary_strength=None, # None = engine default 1.0 (C++ engine only)
|
|
250
256
|
periodic: tuple = (False, False, False),
|
|
251
257
|
max_iterations: int = 100000,
|
|
252
258
|
threshold: float = 1.0e-6,
|
|
@@ -355,6 +361,8 @@ def simulate_pro(
|
|
|
355
361
|
wavelength=wavelength,
|
|
356
362
|
pixel_size=pixel_size,
|
|
357
363
|
boundary_width=boundary_width, # μm end-to-end; execute_via_api takes μm
|
|
364
|
+
boundary_widths=boundary_widths,
|
|
365
|
+
boundary_strength=boundary_strength,
|
|
358
366
|
is_vectorial=is_vectorial,
|
|
359
367
|
data_type="Permittivity",
|
|
360
368
|
max_iterations=max_iterations,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|