wavesimpro 1.5.0__tar.gz → 1.6.1__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.1}/PKG-INFO +1 -1
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/pyproject.toml +1 -1
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/__init__.py +1 -1
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/client.py +6 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/execute.py +84 -14
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/json_helper.py +738 -711
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/simulate.py +21 -9
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/validate.py +102 -93
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro.egg-info/PKG-INFO +1 -1
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/setup.cfg +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/__main__.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/binary_utils.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/config.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/exceptions.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/py.typed +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro/setup.py +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro.egg-info/SOURCES.txt +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro.egg-info/dependency_links.txt +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/src/wavesimpro.egg-info/requires.txt +0 -0
- {wavesimpro-1.5.0 → wavesimpro-1.6.1}/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.1"
|
|
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"
|
|
@@ -285,6 +285,7 @@ class RayfosClient:
|
|
|
285
285
|
periodic: tuple = (False, False, False),
|
|
286
286
|
engine_preference: str = "Any",
|
|
287
287
|
use_gpu: Optional[bool] = None,
|
|
288
|
+
is_vectorial: Optional[bool] = None,
|
|
288
289
|
) -> Dict[str, Any]:
|
|
289
290
|
"""
|
|
290
291
|
Ask the server (cloud or machine-service) whether a simulation of the
|
|
@@ -301,6 +302,10 @@ class RayfosClient:
|
|
|
301
302
|
periodic: (periodic_x, periodic_y, periodic_z) — periodic axes don't get boundary padding
|
|
302
303
|
engine_preference: "Any" | "Python" | "Cpp"
|
|
303
304
|
use_gpu: None (auto), True (require GPU), False (force CPU)
|
|
305
|
+
is_vectorial: True = Maxwell (vectorial, 3 field components), False =
|
|
306
|
+
Helmholtz (scalar). Memory differs ~3x between the two, so ALWAYS
|
|
307
|
+
pass it: this payload carries no sources, and without the flag the
|
|
308
|
+
server falls back to "Maxwell unless engine_preference is Python".
|
|
304
309
|
|
|
305
310
|
Returns:
|
|
306
311
|
PreflightCheckResponse dict with keys:
|
|
@@ -339,6 +344,7 @@ class RayfosClient:
|
|
|
339
344
|
},
|
|
340
345
|
"enginePreference": engine_preference,
|
|
341
346
|
"useGpu": use_gpu,
|
|
347
|
+
"isVectorial": is_vectorial,
|
|
342
348
|
}
|
|
343
349
|
# Machine service URL takes precedence (local single-machine preflight); else cloud aggregator.
|
|
344
350
|
if self.config.machine_service_url:
|
|
@@ -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,13 +322,18 @@ 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,
|
|
336
|
+
is_vectorial=is_vectorial,
|
|
271
337
|
)
|
|
272
338
|
except Exception as exc:
|
|
273
339
|
logger.warning(f"Preflight check failed (server may not support /api/preflightCheck yet): {exc}. Proceeding without it.")
|
|
@@ -277,9 +343,10 @@ def execute_via_api(
|
|
|
277
343
|
status = (pre.get("status") or "").lower()
|
|
278
344
|
pre_description = pre.get("description") or ""
|
|
279
345
|
est = pre.get("estimate") or {}
|
|
346
|
+
solver = est.get("solver") or ("Maxwell" if est.get("isVectorial") else "Helmholtz")
|
|
280
347
|
logger.info(
|
|
281
348
|
f"Preflight: status={status} "
|
|
282
|
-
f"est_total={est.get('totalMemoryGB', 0):.2f} GB "
|
|
349
|
+
f"est_total={est.get('totalMemoryGB', 0):.2f} GB ({solver}) "
|
|
283
350
|
f"(E-field {est.get('efieldMemoryGB', 0):.2f} GB + workspace {est.get('workspaceMemoryGB', 0):.2f} GB) "
|
|
284
351
|
f"grid={est.get('nx')}x{est.get('ny')}x{est.get('nz')}"
|
|
285
352
|
)
|
|
@@ -326,6 +393,7 @@ def execute_via_api(
|
|
|
326
393
|
gpu_memory_max_gb=gpu_memory_max_gb,
|
|
327
394
|
periodic=periodic,
|
|
328
395
|
pseudo_propagation_distance=pseudo_propagation_distance,
|
|
396
|
+
data_type=data_type,
|
|
329
397
|
)
|
|
330
398
|
|
|
331
399
|
# 8. Upload RI/permittivity
|
|
@@ -435,6 +503,8 @@ def execute_via_api(
|
|
|
435
503
|
wavelength=float(wavelength),
|
|
436
504
|
voxel_size=float(pixel_size),
|
|
437
505
|
boundary_size_um=boundary_size_um,
|
|
506
|
+
boundary_sides_um=boundary_sides_um,
|
|
507
|
+
boundary_strength=boundary_strength,
|
|
438
508
|
residual_norm_threshold=float(threshold),
|
|
439
509
|
residual_norm_max_iterations=int(max_iterations),
|
|
440
510
|
periodic_x=bool(periodic[0]),
|