wavesimpro 1.6.5__tar.gz → 1.9.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.9.0/PKG-INFO +69 -0
- wavesimpro-1.9.0/README.md +46 -0
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/pyproject.toml +1 -1
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/__init__.py +3 -1
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/client.py +12 -2
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/execute.py +51 -18
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/json_helper.py +127 -6
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/simulate.py +11 -7
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/validate.py +46 -21
- wavesimpro-1.9.0/src/wavesimpro.egg-info/PKG-INFO +69 -0
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro.egg-info/SOURCES.txt +1 -0
- wavesimpro-1.6.5/PKG-INFO +0 -22
- wavesimpro-1.6.5/src/wavesimpro.egg-info/PKG-INFO +0 -22
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/setup.cfg +0 -0
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/__main__.py +0 -0
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/binary_utils.py +0 -0
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/config.py +0 -0
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/exceptions.py +0 -0
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/py.typed +0 -0
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro/setup.py +0 -0
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro.egg-info/dependency_links.txt +0 -0
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro.egg-info/requires.txt +0 -0
- {wavesimpro-1.6.5 → wavesimpro-1.9.0}/src/wavesimpro.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wavesimpro
|
|
3
|
+
Version: 1.9.0
|
|
4
|
+
Summary: WavesimPro Python API — cloud execution client for the Rayfos simulation platform
|
|
5
|
+
Author: Rayfos
|
|
6
|
+
Project-URL: Repository, https://github.com/rayfos/wavesimpro
|
|
7
|
+
Keywords: wavesim,simulation,electromagnetic,rayfos,api
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: numpy>=1.20.0
|
|
16
|
+
Requires-Dist: requests>=2.28.0
|
|
17
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
18
|
+
Requires-Dist: tqdm>=4.60.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
21
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
22
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
23
|
+
|
|
24
|
+
# wavesimpro
|
|
25
|
+
|
|
26
|
+
Python client for the Rayfos Wavesim cloud: build a simulation setup, run it on a Rayfos
|
|
27
|
+
compute node, and read the results back as NumPy arrays.
|
|
28
|
+
|
|
29
|
+
Wavesim is a single-wavelength frequency-domain solver (vectorial Maxwell when a polarized
|
|
30
|
+
source is present, scalar Helmholtz otherwise). This package talks to the Rayfos JobServer
|
|
31
|
+
REST API; it does not contain the solver.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install wavesimpro
|
|
37
|
+
python -m wavesimpro setup # one-time: API key + server URL wizard
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The wizard stores the credentials; `wavesimpro.configure(api_key=..., api_url=...)` sets
|
|
41
|
+
them for one process instead.
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import wavesimpro as wp
|
|
47
|
+
|
|
48
|
+
props = wp.create_wavesim_properties(
|
|
49
|
+
wavelength=1.0, pixel_size=0.1, boundary_width=5.0,
|
|
50
|
+
domain=wp.create_simulation_domain(20, 20, 20),
|
|
51
|
+
mediums=[wp.create_medium("Silicon", 3.48, 0.0)],
|
|
52
|
+
primitives=[wp.create_box_primitive("core", 0, 0, 0, 10, 0.5, 20, medium="Silicon")],
|
|
53
|
+
sources=[wp.create_mode_source("in", 0, 0, -8.0, polarization="X")],
|
|
54
|
+
monitors=[wp.create_monitor("out", 0, 0, 8.0, 10, 10, 0)],
|
|
55
|
+
)
|
|
56
|
+
run = wp.simulate(props, name="strip waveguide")
|
|
57
|
+
field = wp.read_binary_field(run.download("output.bin"))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## What the API covers
|
|
61
|
+
|
|
62
|
+
- Setup helpers for domains, media, primitives (box, STL, voxel arrays), sources
|
|
63
|
+
(point, plane wave, Gaussian beam, custom field, engine-solved waveguide mode) and monitors.
|
|
64
|
+
- Per-axis pixel sizes (`pixel_size=[dx, dy, dz]`) and sub-pixel permittivity averaging
|
|
65
|
+
(`subpixel_factor=`), when the compute node supports them.
|
|
66
|
+
- Preflight memory check and cost estimate before a run.
|
|
67
|
+
- Binary upload/download helpers for permittivity volumes, source fields and monitors.
|
|
68
|
+
|
|
69
|
+
See `CHANGELOG.md` for what each version added. Issues and feature requests: support@rayfos.com.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# wavesimpro
|
|
2
|
+
|
|
3
|
+
Python client for the Rayfos Wavesim cloud: build a simulation setup, run it on a Rayfos
|
|
4
|
+
compute node, and read the results back as NumPy arrays.
|
|
5
|
+
|
|
6
|
+
Wavesim is a single-wavelength frequency-domain solver (vectorial Maxwell when a polarized
|
|
7
|
+
source is present, scalar Helmholtz otherwise). This package talks to the Rayfos JobServer
|
|
8
|
+
REST API; it does not contain the solver.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install wavesimpro
|
|
14
|
+
python -m wavesimpro setup # one-time: API key + server URL wizard
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The wizard stores the credentials; `wavesimpro.configure(api_key=..., api_url=...)` sets
|
|
18
|
+
them for one process instead.
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import wavesimpro as wp
|
|
24
|
+
|
|
25
|
+
props = wp.create_wavesim_properties(
|
|
26
|
+
wavelength=1.0, pixel_size=0.1, boundary_width=5.0,
|
|
27
|
+
domain=wp.create_simulation_domain(20, 20, 20),
|
|
28
|
+
mediums=[wp.create_medium("Silicon", 3.48, 0.0)],
|
|
29
|
+
primitives=[wp.create_box_primitive("core", 0, 0, 0, 10, 0.5, 20, medium="Silicon")],
|
|
30
|
+
sources=[wp.create_mode_source("in", 0, 0, -8.0, polarization="X")],
|
|
31
|
+
monitors=[wp.create_monitor("out", 0, 0, 8.0, 10, 10, 0)],
|
|
32
|
+
)
|
|
33
|
+
run = wp.simulate(props, name="strip waveguide")
|
|
34
|
+
field = wp.read_binary_field(run.download("output.bin"))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## What the API covers
|
|
38
|
+
|
|
39
|
+
- Setup helpers for domains, media, primitives (box, STL, voxel arrays), sources
|
|
40
|
+
(point, plane wave, Gaussian beam, custom field, engine-solved waveguide mode) and monitors.
|
|
41
|
+
- Per-axis pixel sizes (`pixel_size=[dx, dy, dz]`) and sub-pixel permittivity averaging
|
|
42
|
+
(`subpixel_factor=`), when the compute node supports them.
|
|
43
|
+
- Preflight memory check and cost estimate before a run.
|
|
44
|
+
- Binary upload/download helpers for permittivity volumes, source fields and monitors.
|
|
45
|
+
|
|
46
|
+
See `CHANGELOG.md` for what each version added. Issues and feature requests: support@rayfos.com.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wavesimpro"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.9.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"
|
|
@@ -37,6 +37,7 @@ from .json_helper import (
|
|
|
37
37
|
create_point_source,
|
|
38
38
|
create_plane_wave_source,
|
|
39
39
|
create_gaussian_beam_source,
|
|
40
|
+
create_mode_source,
|
|
40
41
|
create_voxels_model_primitive,
|
|
41
42
|
create_custom_field_source,
|
|
42
43
|
create_custom_from_file_source,
|
|
@@ -45,7 +46,7 @@ from .json_helper import (
|
|
|
45
46
|
parse_progress_data,
|
|
46
47
|
)
|
|
47
48
|
|
|
48
|
-
__version__ = "1.
|
|
49
|
+
__version__ = "1.9.0"
|
|
49
50
|
__all__ = [
|
|
50
51
|
# Client
|
|
51
52
|
"RayfosClient",
|
|
@@ -81,6 +82,7 @@ __all__ = [
|
|
|
81
82
|
"create_point_source",
|
|
82
83
|
"create_plane_wave_source",
|
|
83
84
|
"create_gaussian_beam_source",
|
|
85
|
+
"create_mode_source",
|
|
84
86
|
"create_voxels_model_primitive",
|
|
85
87
|
"create_custom_field_source",
|
|
86
88
|
"create_custom_from_file_source",
|
|
@@ -280,7 +280,7 @@ class RayfosClient:
|
|
|
280
280
|
def preflight_check(
|
|
281
281
|
self,
|
|
282
282
|
domain_um: tuple,
|
|
283
|
-
voxel_um
|
|
283
|
+
voxel_um, # float, or (dx, dy, dz) µm for a per-axis grid
|
|
284
284
|
boundary_sides_um: Sequence[Tuple[float, float]],
|
|
285
285
|
periodic: tuple = (False, False, False),
|
|
286
286
|
engine_preference: str = "Any",
|
|
@@ -329,6 +329,12 @@ class RayfosClient:
|
|
|
329
329
|
def _um(v: float) -> Dict[str, Any]:
|
|
330
330
|
return {"value": float(v), "unit": "Micrometers"}
|
|
331
331
|
|
|
332
|
+
voxels = (tuple(float(v) for v in voxel_um)
|
|
333
|
+
if isinstance(voxel_um, (list, tuple)) else (float(voxel_um),) * 3)
|
|
334
|
+
if len(voxels) != 3:
|
|
335
|
+
raise ValueError("voxel_um must be one number or (dx, dy, dz)")
|
|
336
|
+
anisotropic = any(abs(v - voxels[0]) > 1e-6 * max(voxels) for v in voxels)
|
|
337
|
+
|
|
332
338
|
body = {
|
|
333
339
|
"wavesimProperties": {
|
|
334
340
|
"simulationDomain": {
|
|
@@ -336,7 +342,11 @@ class RayfosClient:
|
|
|
336
342
|
"position": {"x": _um(0.0), "y": _um(0.0), "z": _um(0.0)},
|
|
337
343
|
"size": {"width": _um(size_x), "height": _um(size_y), "depth": _um(size_z)},
|
|
338
344
|
},
|
|
339
|
-
|
|
345
|
+
# voxelSize = the smallest axis (what scalar readers use); voxelSizes carries
|
|
346
|
+
# the per-axis pitch so the estimator sizes the grid the engine will pad.
|
|
347
|
+
"voxelSize": _um(min(voxels)),
|
|
348
|
+
**({"voxelSizes": {"x": _um(voxels[0]), "y": _um(voxels[1]), "z": _um(voxels[2])}}
|
|
349
|
+
if anisotropic else {}),
|
|
340
350
|
# boundarySize is the symmetric fallback older servers read; the
|
|
341
351
|
# per-side widths below are what the current estimator uses.
|
|
342
352
|
"boundarySize": _um(sides[0][0]),
|
|
@@ -92,11 +92,37 @@ def _normalize_boundary_widths(boundary_widths) -> Optional[List[Tuple[float, fl
|
|
|
92
92
|
return out
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
def pixel_sizes_um(pixel_size) -> Tuple[float, float, float]:
|
|
96
|
+
"""
|
|
97
|
+
Normalise a pixel size to a per-axis (dx, dy, dz) tuple in micrometres.
|
|
98
|
+
|
|
99
|
+
``pixel_size`` may be one number (isotropic, the usual case) or three
|
|
100
|
+
``[dx, dy, dz]`` (anisotropic: coarsen axes the field is band-limited along,
|
|
101
|
+
e.g. the transverse axes of a paraxial beam or a weakly guiding waveguide;
|
|
102
|
+
Python engine only). Every axis must be positive.
|
|
103
|
+
"""
|
|
104
|
+
if isinstance(pixel_size, (int, float, np.integer, np.floating)):
|
|
105
|
+
p = float(pixel_size)
|
|
106
|
+
return (p, p, p)
|
|
107
|
+
vals = tuple(float(v) for v in pixel_size)
|
|
108
|
+
if len(vals) != 3:
|
|
109
|
+
raise ValueError(f"pixel_size must be one number or [dx, dy, dz], got {pixel_size!r}")
|
|
110
|
+
if any(v <= 0 for v in vals):
|
|
111
|
+
raise ValueError(f"pixel_size components must be positive, got {vals}")
|
|
112
|
+
return vals # type: ignore[return-value]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def is_anisotropic(pixel_sizes: Sequence[float]) -> bool:
|
|
116
|
+
"""True when the three pitches differ beyond float round-off."""
|
|
117
|
+
m = max(pixel_sizes)
|
|
118
|
+
return any(abs(v - pixel_sizes[0]) > 1e-6 * m for v in pixel_sizes)
|
|
119
|
+
|
|
120
|
+
|
|
95
121
|
def execute_via_api(
|
|
96
122
|
refractive_index: np.ndarray,
|
|
97
123
|
source,
|
|
98
124
|
wavelength: float,
|
|
99
|
-
pixel_size
|
|
125
|
+
pixel_size, # float, or [dx, dy, dz] µm (see pixel_sizes_um)
|
|
100
126
|
threshold: float = 1e-2,
|
|
101
127
|
max_iterations: int = 1000,
|
|
102
128
|
boundary_width: Optional[float] = None, # in MICROMETERS; REQUIRED unless boundary_widths is given
|
|
@@ -134,7 +160,9 @@ def execute_via_api(
|
|
|
134
160
|
- dict with 'Es' (complex field array) and 'position' ([x,y,z] or [x,y,z,pol])
|
|
135
161
|
- list of such dicts for multiple sources
|
|
136
162
|
wavelength: Wavelength in micrometers
|
|
137
|
-
pixel_size: Pixel size in micrometers
|
|
163
|
+
pixel_size: Pixel size in micrometers — one number, or [dx, dy, dz] for a
|
|
164
|
+
per-axis grid (coarsen axes the field is band-limited along; Python
|
|
165
|
+
engine only, see pixel_sizes_um)
|
|
138
166
|
threshold: Convergence threshold (default: 1e-2)
|
|
139
167
|
max_iterations: Max iterations (default: 1000)
|
|
140
168
|
boundary_width: Absorbing boundary width in MICROMETERS (μm). Required —
|
|
@@ -248,13 +276,17 @@ def execute_via_api(
|
|
|
248
276
|
|
|
249
277
|
logger.info(f" Domain: [{nx}, {ny}, {nz}]")
|
|
250
278
|
logger.info(f" Wavelength: {wavelength:.3f} um")
|
|
251
|
-
|
|
279
|
+
ps = pixel_sizes_um(pixel_size)
|
|
280
|
+
anisotropic = is_anisotropic(ps)
|
|
281
|
+
ps_min = min(ps)
|
|
282
|
+
ps_label = f"({ps[0]:.4g}, {ps[1]:.4g}, {ps[2]:.4g})" if anisotropic else f"{ps[0]:.3f}"
|
|
283
|
+
logger.info(f" Pixel size: {ps_label} um")
|
|
252
284
|
logger.info(f" Threshold: {threshold:.2e}")
|
|
253
285
|
logger.info(f" Max iterations: {max_iterations}")
|
|
254
286
|
if pseudo_propagation_distance > 0:
|
|
255
287
|
logger.info(f" Pseudo propagation distance: {pseudo_propagation_distance} um (overrides max iterations)")
|
|
256
288
|
if boundary_sides_um is None:
|
|
257
|
-
logger.info(f" Boundary width: {boundary_width} um ({int(round(boundary_width /
|
|
289
|
+
logger.info(f" Boundary width: {boundary_width} um ({int(round(boundary_width / ps_min))} pixels on the finest axis)")
|
|
258
290
|
else:
|
|
259
291
|
logger.info(" Boundary widths (um): " + " ".join(
|
|
260
292
|
f"{ax}:[{left:g}, {right:g}]" for ax, (left, right) in zip("XYZ", boundary_sides_um)))
|
|
@@ -318,9 +350,9 @@ def execute_via_api(
|
|
|
318
350
|
|
|
319
351
|
# 5b. Preflight check — abort BEFORE creating a project / uploading anything
|
|
320
352
|
# if the simulation won't fit on the available compute targets.
|
|
321
|
-
domain_x_um = float(nx) *
|
|
322
|
-
domain_y_um = float(ny) *
|
|
323
|
-
domain_z_um = float(nz) *
|
|
353
|
+
domain_x_um = float(nx) * ps[0]
|
|
354
|
+
domain_y_um = float(ny) * ps[1]
|
|
355
|
+
domain_z_um = float(nz) * ps[2]
|
|
324
356
|
# The preflight describes exactly the grid the engine will pad: the same
|
|
325
357
|
# per-side boundary widths that go into the submitted properties.
|
|
326
358
|
preflight_sides_um = (boundary_sides_um if boundary_sides_um is not None
|
|
@@ -328,7 +360,7 @@ def execute_via_api(
|
|
|
328
360
|
try:
|
|
329
361
|
pre = client.preflight_check(
|
|
330
362
|
domain_um=(domain_x_um, domain_y_um, domain_z_um),
|
|
331
|
-
voxel_um=
|
|
363
|
+
voxel_um=ps,
|
|
332
364
|
boundary_sides_um=preflight_sides_um,
|
|
333
365
|
periodic=(bool(periodic[0]), bool(periodic[1]), bool(periodic[2])),
|
|
334
366
|
engine_preference=engine_preference,
|
|
@@ -379,7 +411,7 @@ def execute_via_api(
|
|
|
379
411
|
|
|
380
412
|
# 7. Validate parameters
|
|
381
413
|
validate_parameters(
|
|
382
|
-
em_data, sources_info, wavelength,
|
|
414
|
+
em_data, sources_info, wavelength, ps,
|
|
383
415
|
threshold, max_iterations, boundary_width, is_vectorial,
|
|
384
416
|
strict_validation=strict_validation,
|
|
385
417
|
gpu_memory_max_gb=gpu_memory_max_gb,
|
|
@@ -424,9 +456,9 @@ def execute_via_api(
|
|
|
424
456
|
|
|
425
457
|
# 10. Build WavesimProperties payload
|
|
426
458
|
logger.info("Building command payload...")
|
|
427
|
-
domain_x_um = float(nx) *
|
|
428
|
-
domain_y_um = float(ny) *
|
|
429
|
-
domain_z_um = float(nz) *
|
|
459
|
+
domain_x_um = float(nx) * ps[0]
|
|
460
|
+
domain_y_um = float(ny) * ps[1]
|
|
461
|
+
domain_z_um = float(nz) * ps[2]
|
|
430
462
|
|
|
431
463
|
simulation_domain = create_simulation_domain(domain_x_um, domain_y_um, domain_z_um)
|
|
432
464
|
background_medium = create_medium("background", "Background", background_ri)
|
|
@@ -435,7 +467,7 @@ def execute_via_api(
|
|
|
435
467
|
voxel_primitive = create_voxels_model_primitive(
|
|
436
468
|
primitive_name, em_data_id, "background",
|
|
437
469
|
0.0, 0.0, 0.0,
|
|
438
|
-
|
|
470
|
+
ps[0], ps[1], ps[2],
|
|
439
471
|
int(nx), int(ny), int(nz),
|
|
440
472
|
"Complex64", data_type,
|
|
441
473
|
)
|
|
@@ -459,9 +491,9 @@ def execute_via_api(
|
|
|
459
491
|
# centre, world 0,0,0). Keeping every client emitting the same shape
|
|
460
492
|
# of WavesimProperties means the server has one coordinate path, not
|
|
461
493
|
# one per client.
|
|
462
|
-
src_extent_x = float(src_nx) *
|
|
463
|
-
src_extent_y = float(src_ny) *
|
|
464
|
-
src_extent_z = float(src_nz) *
|
|
494
|
+
src_extent_x = float(src_nx) * ps[0]
|
|
495
|
+
src_extent_y = float(src_ny) * ps[1]
|
|
496
|
+
src_extent_z = float(src_nz) * ps[2]
|
|
465
497
|
position_x_centered = float(src["position"][0]) + src_extent_x / 2.0 - domain_x_um / 2.0
|
|
466
498
|
position_y_centered = float(src["position"][1]) + src_extent_y / 2.0 - domain_y_um / 2.0
|
|
467
499
|
position_z_centered = float(src["position"][2]) + src_extent_z / 2.0 - domain_z_um / 2.0
|
|
@@ -493,7 +525,8 @@ def execute_via_api(
|
|
|
493
525
|
monitors=monitors or [],
|
|
494
526
|
background_medium_id="background",
|
|
495
527
|
wavelength=float(wavelength),
|
|
496
|
-
voxel_size=
|
|
528
|
+
voxel_size=ps_min, # smallest axis: scalar readers over-resolve
|
|
529
|
+
voxel_sizes=list(ps) if anisotropic else None, # per-axis pitch, Python engine only
|
|
497
530
|
boundary_size_um=boundary_size_um,
|
|
498
531
|
boundary_sides_um=boundary_sides_um,
|
|
499
532
|
boundary_strength=boundary_strength,
|
|
@@ -534,7 +567,7 @@ def execute_via_api(
|
|
|
534
567
|
print(f" Version : {version_id}")
|
|
535
568
|
print(f" Command : {command_id}")
|
|
536
569
|
print(f" Mode : {_mode}")
|
|
537
|
-
print(f" Domain : {_domain_str} lambda={wavelength} um dx={
|
|
570
|
+
print(f" Domain : {_domain_str} lambda={wavelength} um dx={ps_label} um")
|
|
538
571
|
print(f" Engine : {_engine} | GPU: {_gpu}")
|
|
539
572
|
if preferred_machine_id:
|
|
540
573
|
print(f" Machine : {preferred_machine_id}")
|
|
@@ -118,10 +118,11 @@ def create_box_primitive(
|
|
|
118
118
|
"Y": {"Value": center_y, "Unit": unit},
|
|
119
119
|
"Z": {"Value": center_z, "Unit": unit}
|
|
120
120
|
},
|
|
121
|
+
# Size3D on the server is Width/Height/Depth (X/Y/Z keys were silently ignored → 4 µm default).
|
|
121
122
|
"Size": {
|
|
122
|
-
"
|
|
123
|
-
"
|
|
124
|
-
"
|
|
123
|
+
"Width": {"Value": size_x, "Unit": unit},
|
|
124
|
+
"Height": {"Value": size_y, "Unit": unit},
|
|
125
|
+
"Depth": {"Value": size_z, "Unit": unit}
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
|
|
@@ -321,10 +322,11 @@ def create_gaussian_beam_source(
|
|
|
321
322
|
"Imaginary": amplitude_imag
|
|
322
323
|
},
|
|
323
324
|
"SourcePlane": source_plane,
|
|
325
|
+
# Size3D on the server is Width/Height/Depth (X/Y/Z keys were silently ignored → 10 µm default).
|
|
324
326
|
"Size": {
|
|
325
|
-
"
|
|
326
|
-
"
|
|
327
|
-
"
|
|
327
|
+
"Width": {"Value": size_x, "Unit": unit},
|
|
328
|
+
"Height": {"Value": size_y, "Unit": unit},
|
|
329
|
+
"Depth": {"Value": size_z, "Unit": unit}
|
|
328
330
|
},
|
|
329
331
|
"Alpha": alpha,
|
|
330
332
|
"Origin": origin
|
|
@@ -397,6 +399,97 @@ def create_voxels_model_primitive(
|
|
|
397
399
|
}
|
|
398
400
|
|
|
399
401
|
|
|
402
|
+
def create_mode_source(
|
|
403
|
+
name: str = "Mode1",
|
|
404
|
+
position_x: float = 0.0,
|
|
405
|
+
position_y: float = 0.0,
|
|
406
|
+
position_z: float = 0.0,
|
|
407
|
+
polarization: str = "None",
|
|
408
|
+
amplitude_real: float = 1.0,
|
|
409
|
+
amplitude_imag: float = 0.0,
|
|
410
|
+
neff_guess: Optional[float] = None,
|
|
411
|
+
solver: str = "Engine",
|
|
412
|
+
file_id: Optional[str] = None,
|
|
413
|
+
size_x: float = 0.0,
|
|
414
|
+
size_y: float = 0.0,
|
|
415
|
+
unit: str = "Micrometers"
|
|
416
|
+
) -> Dict[str, Any]:
|
|
417
|
+
"""
|
|
418
|
+
Create a waveguide Mode source.
|
|
419
|
+
|
|
420
|
+
solver="Engine" (default): the compute node solves the eigenmode of the voxelised
|
|
421
|
+
permittivity cross-section at the source's z when the run starts — the real geometry,
|
|
422
|
+
every primitive and material included — and launches it along +z on the XY plane. Nothing
|
|
423
|
+
is uploaded. The eigenmode closest to ``neff_guess`` is injected; leave it None for the
|
|
424
|
+
fundamental mode (the guess defaults to just below the highest index in the cross-section).
|
|
425
|
+
Runs need a compute node advertising the ``ModeSource:Cpp`` feature (C++ engine); the
|
|
426
|
+
solved n_eff is written to the run's ``modeSources.json`` output.
|
|
427
|
+
|
|
428
|
+
solver="Client": a precomputed mode profile uploaded as a field file (``file_id``), run
|
|
429
|
+
like a CustomField source — the Studio designer's flow.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
name: Source name
|
|
433
|
+
position_x, position_y, position_z: Source position (centre-based). The engine solves on
|
|
434
|
+
the XY plane at z, inside the size_x × size_y window centred on (x, y).
|
|
435
|
+
polarization: "None" (scalar Helmholtz mode), "X" (Ex) or "Y" (Ey). "Z" is not a mode
|
|
436
|
+
field in the XY plane and is rejected for the engine solver.
|
|
437
|
+
amplitude_real, amplitude_imag: Complex amplitude applied to the normalised profile
|
|
438
|
+
neff_guess: Engine solver: effective-index guess; the closest eigenmode is injected
|
|
439
|
+
solver: "Engine" or "Client"
|
|
440
|
+
file_id: Client solver: id of the uploaded mode-profile field file (required)
|
|
441
|
+
size_x, size_y: Engine solver: the solve window around the position (clamped to the
|
|
442
|
+
domain); 0 = the whole cross-section. Client solver: extent of the uploaded profile.
|
|
443
|
+
unit: Unit of measurement
|
|
444
|
+
"""
|
|
445
|
+
solver_norm = str(solver).strip().capitalize()
|
|
446
|
+
if solver_norm not in ("Engine", "Client"):
|
|
447
|
+
raise ValueError(f"solver must be 'Engine' or 'Client', got {solver!r}")
|
|
448
|
+
if solver_norm == "Client" and not file_id:
|
|
449
|
+
raise ValueError("solver='Client' needs file_id (the uploaded mode-profile field)")
|
|
450
|
+
if solver_norm == "Engine" and str(polarization) == "Z":
|
|
451
|
+
raise ValueError("an engine-solved mode is 'None' (scalar), 'X' (Ex) or 'Y' (Ey); 'Z' has no mode field in the XY plane")
|
|
452
|
+
if neff_guess is not None and not neff_guess > 0:
|
|
453
|
+
raise ValueError("neff_guess must be positive (or None for automatic)")
|
|
454
|
+
|
|
455
|
+
source: Dict[str, Any] = {
|
|
456
|
+
"Id": str(uuid.uuid4()),
|
|
457
|
+
"SourceType": "Mode",
|
|
458
|
+
"Name": name,
|
|
459
|
+
"IsEnabled": True,
|
|
460
|
+
"Position": {
|
|
461
|
+
"X": {"Value": position_x, "Unit": unit},
|
|
462
|
+
"Y": {"Value": position_y, "Unit": unit},
|
|
463
|
+
"Z": {"Value": position_z, "Unit": unit}
|
|
464
|
+
},
|
|
465
|
+
"Polarization": polarization,
|
|
466
|
+
"Origin": "Center",
|
|
467
|
+
"Amplitude": {
|
|
468
|
+
"Real": amplitude_real,
|
|
469
|
+
"Imaginary": amplitude_imag
|
|
470
|
+
},
|
|
471
|
+
"Solver": solver_norm,
|
|
472
|
+
# The engine works on the XY cross-section and launches along +z; these describe the
|
|
473
|
+
# same plane for the client/designer path and for Studio's drawing.
|
|
474
|
+
"SourcePlane": "xy",
|
|
475
|
+
"Size": {
|
|
476
|
+
"Width": {"Value": size_x, "Unit": unit},
|
|
477
|
+
"Height": {"Value": size_y, "Unit": unit},
|
|
478
|
+
"Depth": {"Value": 0.0, "Unit": unit}
|
|
479
|
+
},
|
|
480
|
+
# Designer bookkeeping Studio expects on a Mode source (not used by the engine solver).
|
|
481
|
+
"WaveguideGeometry": "Rectangular",
|
|
482
|
+
"ModeFamily": "TE",
|
|
483
|
+
"ModeIndex_n": 0,
|
|
484
|
+
"ModeIndex_m": 1,
|
|
485
|
+
}
|
|
486
|
+
if neff_guess is not None:
|
|
487
|
+
source["NeffGuess"] = float(neff_guess)
|
|
488
|
+
if file_id:
|
|
489
|
+
source["FileId"] = file_id
|
|
490
|
+
return source
|
|
491
|
+
|
|
492
|
+
|
|
400
493
|
def create_custom_field_source(
|
|
401
494
|
name: str,
|
|
402
495
|
field_data_id: str,
|
|
@@ -564,6 +657,7 @@ def create_wavesim_properties(
|
|
|
564
657
|
wavelength_unit: str = "Micrometers",
|
|
565
658
|
voxel_unit: str = "Micrometers",
|
|
566
659
|
boundary_size_um: float = 0.0,
|
|
660
|
+
voxel_sizes: Optional[List[float]] = None,
|
|
567
661
|
boundary_sides_um: Optional[List[Tuple[float, float]]] = None,
|
|
568
662
|
boundary_strength: Optional[float] = None,
|
|
569
663
|
residual_norm_threshold: float = 1.0e-6,
|
|
@@ -574,6 +668,7 @@ def create_wavesim_properties(
|
|
|
574
668
|
alpha: float = 0.75,
|
|
575
669
|
keep_residuals_list: bool = False,
|
|
576
670
|
pseudo_propagation_distance: float = 0.0,
|
|
671
|
+
subpixel_factor: Optional[int] = None,
|
|
577
672
|
) -> Dict[str, Any]:
|
|
578
673
|
"""
|
|
579
674
|
Create complete WavesimProperties structure
|
|
@@ -654,6 +749,32 @@ def create_wavesim_properties(
|
|
|
654
749
|
if boundary_strength is not None:
|
|
655
750
|
props["BoundaryStrength"] = float(boundary_strength)
|
|
656
751
|
|
|
752
|
+
# Per-axis voxel sizes (VoxelSizes3D on the C# model). Omitted when isotropic so
|
|
753
|
+
# old servers see the same JSON as before; when present, VoxelSize above must be
|
|
754
|
+
# the smallest axis (callers pass min(voxel_sizes)) so scalar readers over-resolve.
|
|
755
|
+
if voxel_sizes is not None:
|
|
756
|
+
if len(voxel_sizes) != 3:
|
|
757
|
+
raise ValueError("voxel_sizes must be [dx, dy, dz]")
|
|
758
|
+
props["VoxelSizes"] = {
|
|
759
|
+
axis: {"Value": float(v), "Unit": voxel_unit}
|
|
760
|
+
for axis, v in zip(("X", "Y", "Z"), voxel_sizes)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
# Sub-pixel permittivity averaging (PermittivitySampling on the C# model). Omitted =
|
|
764
|
+
# Staircase. The service probes each voxel of primitives / STL / GDSII at
|
|
765
|
+
# subpixel_factor points per axis and stores the mean permittivity; it does NOT
|
|
766
|
+
# re-sample a VoxelsModel array (average that yourself before upload).
|
|
767
|
+
if subpixel_factor is not None:
|
|
768
|
+
s = int(subpixel_factor)
|
|
769
|
+
if s < 1 or s > 64:
|
|
770
|
+
raise ValueError("subpixel_factor must be an integer between 1 and 64 (16 is the reference value)")
|
|
771
|
+
props["PermittivitySampling"] = {
|
|
772
|
+
"Mode": "SubPixel" if s > 1 else "Staircase",
|
|
773
|
+
"Factor": s,
|
|
774
|
+
"Rule": "ArithmeticMean",
|
|
775
|
+
"InterfaceOnly": True,
|
|
776
|
+
}
|
|
777
|
+
|
|
657
778
|
return props
|
|
658
779
|
|
|
659
780
|
|
|
@@ -17,7 +17,7 @@ from typing import Callable, Optional, Sequence
|
|
|
17
17
|
|
|
18
18
|
import numpy as np
|
|
19
19
|
|
|
20
|
-
from .execute import execute_via_api
|
|
20
|
+
from .execute import execute_via_api, pixel_sizes_um
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def _caller_script_name() -> str:
|
|
@@ -100,7 +100,7 @@ def simulate(
|
|
|
100
100
|
permittivity: np.ndarray,
|
|
101
101
|
sources: Sequence[tuple],
|
|
102
102
|
wavelength: float,
|
|
103
|
-
pixel_size
|
|
103
|
+
pixel_size, # float µm, or [dx, dy, dz] µm per axis (Python engine only; see pixel_sizes_um)
|
|
104
104
|
boundary_width: Optional[float] = None, # μm — REQUIRED unless boundary_widths is given
|
|
105
105
|
boundary_widths=None, # per-axis [x, y, z] µm; each a number or (left, right) — asymmetric boundaries
|
|
106
106
|
boundary_strength=None, # None = engine default 1.0 (C++ engine only)
|
|
@@ -183,15 +183,17 @@ def simulate(
|
|
|
183
183
|
|
|
184
184
|
is_vectorial = any(len(s[1]) == 4 for s in sources)
|
|
185
185
|
|
|
186
|
+
# Source positions are voxel indices; convert per axis (pixel_size may be [dx, dy, dz]).
|
|
187
|
+
dx, dy, dz = pixel_sizes_um(pixel_size)
|
|
186
188
|
api_sources = []
|
|
187
189
|
for src_array, src_pos in sources:
|
|
188
190
|
pos = list(src_pos)
|
|
189
191
|
if is_vectorial:
|
|
190
192
|
pol_1based = int(pos[0]) + 1
|
|
191
|
-
api_pos = [pos[1] *
|
|
193
|
+
api_pos = [pos[1] * dx, pos[2] * dy, pos[3] * dz, pol_1based]
|
|
192
194
|
src_field = (src_array[0] if src_array.ndim == 4 else src_array).astype(np.complex64)
|
|
193
195
|
else:
|
|
194
|
-
api_pos = [pos[0] *
|
|
196
|
+
api_pos = [pos[0] * dx, pos[1] * dy, pos[2] * dz]
|
|
195
197
|
src_field = src_array.astype(np.complex64)
|
|
196
198
|
api_sources.append({"Es": src_field, "position": api_pos})
|
|
197
199
|
|
|
@@ -253,7 +255,7 @@ def simulate_pro(
|
|
|
253
255
|
permittivity: np.ndarray,
|
|
254
256
|
sources: Sequence[tuple],
|
|
255
257
|
wavelength: float,
|
|
256
|
-
pixel_size
|
|
258
|
+
pixel_size, # float µm, or [dx, dy, dz] µm per axis (Python engine only; see pixel_sizes_um)
|
|
257
259
|
boundary_width: Optional[float] = None, # μm — REQUIRED unless boundary_widths is given
|
|
258
260
|
boundary_widths=None, # per-axis [x, y, z] µm; each a number or (left, right) — asymmetric boundaries
|
|
259
261
|
boundary_strength=None, # None = engine default 1.0 (C++ engine only)
|
|
@@ -330,15 +332,17 @@ def simulate_pro(
|
|
|
330
332
|
|
|
331
333
|
is_vectorial = any(len(s[1]) == 4 for s in sources)
|
|
332
334
|
|
|
335
|
+
# Source positions are voxel indices; convert per axis (pixel_size may be [dx, dy, dz]).
|
|
336
|
+
dx, dy, dz = pixel_sizes_um(pixel_size)
|
|
333
337
|
api_sources = []
|
|
334
338
|
for src_array, src_pos in sources:
|
|
335
339
|
pos = list(src_pos)
|
|
336
340
|
if is_vectorial:
|
|
337
341
|
pol_1based = int(pos[0]) + 1
|
|
338
|
-
api_pos = [pos[1] *
|
|
342
|
+
api_pos = [pos[1] * dx, pos[2] * dy, pos[3] * dz, pol_1based]
|
|
339
343
|
src_field = (src_array[0] if src_array.ndim == 4 else src_array).astype(np.complex64)
|
|
340
344
|
else:
|
|
341
|
-
api_pos = [pos[0] *
|
|
345
|
+
api_pos = [pos[0] * dx, pos[1] * dy, pos[2] * dz]
|
|
342
346
|
src_field = src_array.astype(np.complex64)
|
|
343
347
|
api_sources.append({"Es": src_field, "position": api_pos})
|
|
344
348
|
|
|
@@ -43,7 +43,7 @@ def validate_parameters(
|
|
|
43
43
|
refractive_index: np.ndarray,
|
|
44
44
|
sources: List[Dict[str, Any]],
|
|
45
45
|
wavelength: float,
|
|
46
|
-
pixel_size
|
|
46
|
+
pixel_size, # float, or [dx, dy, dz] µm per axis
|
|
47
47
|
threshold: float = 1e-2,
|
|
48
48
|
max_iterations: int = 1000,
|
|
49
49
|
boundary_width: Optional[float] = None,
|
|
@@ -179,24 +179,49 @@ def validate_parameters(
|
|
|
179
179
|
# 3. Validate pixel size against the wavelength IN THE HIGHEST-INDEX MEDIUM
|
|
180
180
|
# (lambda_medium = lambda_vacuum / n_max). n_max is the refractive index
|
|
181
181
|
# — derived as sqrt(eps) when permittivity was passed.
|
|
182
|
+
# pixel_size may be one number or [dx, dy, dz]: every axis must resolve the field.
|
|
183
|
+
# Scalar checks further down (boundary pixels, domain extents) use the finest axis.
|
|
184
|
+
pixel_sizes = None
|
|
182
185
|
if pixel_size is None:
|
|
183
186
|
errors.append("Pixel size is required")
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
187
|
+
else:
|
|
188
|
+
try:
|
|
189
|
+
pixel_sizes = ((float(pixel_size),) * 3 if not isinstance(pixel_size, (list, tuple, np.ndarray))
|
|
190
|
+
else tuple(float(v) for v in pixel_size))
|
|
191
|
+
except (TypeError, ValueError):
|
|
192
|
+
errors.append(f"Pixel size must be a number or [dx, dy, dz] (got {pixel_size!r})")
|
|
193
|
+
if pixel_sizes is not None and len(pixel_sizes) != 3:
|
|
194
|
+
errors.append(f"Pixel size must be one number or [dx, dy, dz] (got {pixel_size!r})")
|
|
195
|
+
pixel_sizes = None
|
|
196
|
+
if pixel_sizes is not None and any(v <= 0 for v in pixel_sizes):
|
|
197
|
+
errors.append(f"Pixel size must be positive (got {pixel_sizes})")
|
|
198
|
+
pixel_sizes = None
|
|
199
|
+
if pixel_sizes is not None:
|
|
200
|
+
pixel_size = min(pixel_sizes)
|
|
201
|
+
anisotropic = any(abs(v - pixel_sizes[0]) > 1e-6 * max(pixel_sizes) for v in pixel_sizes)
|
|
202
|
+
if anisotropic:
|
|
203
|
+
logger.info(f" Per-axis pixel size: {pixel_sizes} um (Python engine only)")
|
|
204
|
+
if n_max is not None and wavelength and wavelength > 0:
|
|
205
|
+
nyquist_limit = wavelength / (2 * n_max)
|
|
206
|
+
for axis, p in zip("xyz", pixel_sizes):
|
|
207
|
+
label = f"Pixel size {axis} ({p:.4f} um)" if anisotropic else f"Pixel size ({p:.4f} um)"
|
|
208
|
+
if p > nyquist_limit:
|
|
209
|
+
warnings.append(
|
|
210
|
+
f"{label} exceeds Nyquist limit "
|
|
211
|
+
f"({nyquist_limit:.4f} um for n_max={n_max:.2f}). Results may be inaccurate."
|
|
212
|
+
)
|
|
213
|
+
if not anisotropic:
|
|
214
|
+
break
|
|
215
|
+
pixels_per_wavelength = wavelength / (n_max * max(pixel_sizes))
|
|
216
|
+
if pixels_per_wavelength < 10:
|
|
217
|
+
warnings.append(
|
|
218
|
+
f"Only {pixels_per_wavelength:.1f} pixels per wavelength in the "
|
|
219
|
+
f"highest-index medium (n_max={n_max:.2f}) on the coarsest axis. Recommend >= 10 "
|
|
220
|
+
f"unless that axis is deliberately coarse (band-limited field)."
|
|
221
|
+
)
|
|
222
|
+
logger.info(f" Pixels per wavelength in n_max={n_max:.2f} (coarsest axis): {pixels_per_wavelength:.1f}")
|
|
223
|
+
else:
|
|
224
|
+
pixel_size = None
|
|
200
225
|
|
|
201
226
|
# 4. Validate threshold
|
|
202
227
|
if threshold is None:
|
|
@@ -319,10 +344,10 @@ def validate_parameters(
|
|
|
319
344
|
pos = src.get("position")
|
|
320
345
|
if pos is None or len(pos) < 3:
|
|
321
346
|
errors.append(f"Source {i + 1}: position must have at least 3 elements [x, y, z]")
|
|
322
|
-
elif
|
|
323
|
-
domain_x = nx *
|
|
324
|
-
domain_y = ny *
|
|
325
|
-
domain_z = nz *
|
|
347
|
+
elif pixel_sizes is not None:
|
|
348
|
+
domain_x = nx * pixel_sizes[0]
|
|
349
|
+
domain_y = ny * pixel_sizes[1]
|
|
350
|
+
domain_z = nz * pixel_sizes[2]
|
|
326
351
|
if pos[0] < 0 or pos[0] > domain_x:
|
|
327
352
|
warnings.append(f"Source {i + 1}: x position ({pos[0]:.2f} um) outside domain [0, {domain_x:.2f}]")
|
|
328
353
|
if pos[1] < 0 or pos[1] > domain_y:
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wavesimpro
|
|
3
|
+
Version: 1.9.0
|
|
4
|
+
Summary: WavesimPro Python API — cloud execution client for the Rayfos simulation platform
|
|
5
|
+
Author: Rayfos
|
|
6
|
+
Project-URL: Repository, https://github.com/rayfos/wavesimpro
|
|
7
|
+
Keywords: wavesim,simulation,electromagnetic,rayfos,api
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: numpy>=1.20.0
|
|
16
|
+
Requires-Dist: requests>=2.28.0
|
|
17
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
18
|
+
Requires-Dist: tqdm>=4.60.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
21
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
22
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
23
|
+
|
|
24
|
+
# wavesimpro
|
|
25
|
+
|
|
26
|
+
Python client for the Rayfos Wavesim cloud: build a simulation setup, run it on a Rayfos
|
|
27
|
+
compute node, and read the results back as NumPy arrays.
|
|
28
|
+
|
|
29
|
+
Wavesim is a single-wavelength frequency-domain solver (vectorial Maxwell when a polarized
|
|
30
|
+
source is present, scalar Helmholtz otherwise). This package talks to the Rayfos JobServer
|
|
31
|
+
REST API; it does not contain the solver.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install wavesimpro
|
|
37
|
+
python -m wavesimpro setup # one-time: API key + server URL wizard
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The wizard stores the credentials; `wavesimpro.configure(api_key=..., api_url=...)` sets
|
|
41
|
+
them for one process instead.
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import wavesimpro as wp
|
|
47
|
+
|
|
48
|
+
props = wp.create_wavesim_properties(
|
|
49
|
+
wavelength=1.0, pixel_size=0.1, boundary_width=5.0,
|
|
50
|
+
domain=wp.create_simulation_domain(20, 20, 20),
|
|
51
|
+
mediums=[wp.create_medium("Silicon", 3.48, 0.0)],
|
|
52
|
+
primitives=[wp.create_box_primitive("core", 0, 0, 0, 10, 0.5, 20, medium="Silicon")],
|
|
53
|
+
sources=[wp.create_mode_source("in", 0, 0, -8.0, polarization="X")],
|
|
54
|
+
monitors=[wp.create_monitor("out", 0, 0, 8.0, 10, 10, 0)],
|
|
55
|
+
)
|
|
56
|
+
run = wp.simulate(props, name="strip waveguide")
|
|
57
|
+
field = wp.read_binary_field(run.download("output.bin"))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## What the API covers
|
|
61
|
+
|
|
62
|
+
- Setup helpers for domains, media, primitives (box, STL, voxel arrays), sources
|
|
63
|
+
(point, plane wave, Gaussian beam, custom field, engine-solved waveguide mode) and monitors.
|
|
64
|
+
- Per-axis pixel sizes (`pixel_size=[dx, dy, dz]`) and sub-pixel permittivity averaging
|
|
65
|
+
(`subpixel_factor=`), when the compute node supports them.
|
|
66
|
+
- Preflight memory check and cost estimate before a run.
|
|
67
|
+
- Binary upload/download helpers for permittivity volumes, source fields and monitors.
|
|
68
|
+
|
|
69
|
+
See `CHANGELOG.md` for what each version added. Issues and feature requests: support@rayfos.com.
|
wavesimpro-1.6.5/PKG-INFO
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: wavesimpro
|
|
3
|
-
Version: 1.6.5
|
|
4
|
-
Summary: WavesimPro Python API — cloud execution client for the Rayfos simulation platform
|
|
5
|
-
Author: Rayfos
|
|
6
|
-
Project-URL: Repository, https://github.com/rayfos/wavesimpro
|
|
7
|
-
Keywords: wavesim,simulation,electromagnetic,rayfos,api
|
|
8
|
-
Classifier: Development Status :: 4 - Beta
|
|
9
|
-
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: Intended Audience :: Science/Research
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Topic :: Scientific/Engineering
|
|
13
|
-
Requires-Python: >=3.8
|
|
14
|
-
Description-Content-Type: text/markdown
|
|
15
|
-
Requires-Dist: numpy>=1.20.0
|
|
16
|
-
Requires-Dist: requests>=2.28.0
|
|
17
|
-
Requires-Dist: python-dotenv>=1.0.0
|
|
18
|
-
Requires-Dist: tqdm>=4.60.0
|
|
19
|
-
Provides-Extra: dev
|
|
20
|
-
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
21
|
-
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
22
|
-
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: wavesimpro
|
|
3
|
-
Version: 1.6.5
|
|
4
|
-
Summary: WavesimPro Python API — cloud execution client for the Rayfos simulation platform
|
|
5
|
-
Author: Rayfos
|
|
6
|
-
Project-URL: Repository, https://github.com/rayfos/wavesimpro
|
|
7
|
-
Keywords: wavesim,simulation,electromagnetic,rayfos,api
|
|
8
|
-
Classifier: Development Status :: 4 - Beta
|
|
9
|
-
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: Intended Audience :: Science/Research
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Topic :: Scientific/Engineering
|
|
13
|
-
Requires-Python: >=3.8
|
|
14
|
-
Description-Content-Type: text/markdown
|
|
15
|
-
Requires-Dist: numpy>=1.20.0
|
|
16
|
-
Requires-Dist: requests>=2.28.0
|
|
17
|
-
Requires-Dist: python-dotenv>=1.0.0
|
|
18
|
-
Requires-Dist: tqdm>=4.60.0
|
|
19
|
-
Provides-Extra: dev
|
|
20
|
-
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
21
|
-
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
22
|
-
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
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
|