wavesimpro 1.9.0__tar.gz → 1.10.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.
Files changed (21) hide show
  1. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/PKG-INFO +10 -3
  2. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/README.md +9 -2
  3. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/pyproject.toml +1 -1
  4. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/__init__.py +17 -1
  5. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/json_helper.py +486 -9
  6. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro.egg-info/PKG-INFO +10 -3
  7. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/setup.cfg +0 -0
  8. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/__main__.py +0 -0
  9. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/binary_utils.py +0 -0
  10. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/client.py +0 -0
  11. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/config.py +0 -0
  12. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/exceptions.py +0 -0
  13. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/execute.py +0 -0
  14. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/py.typed +0 -0
  15. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/setup.py +0 -0
  16. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/simulate.py +0 -0
  17. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro/validate.py +0 -0
  18. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro.egg-info/SOURCES.txt +0 -0
  19. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro.egg-info/dependency_links.txt +0 -0
  20. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro.egg-info/requires.txt +0 -0
  21. {wavesimpro-1.9.0 → wavesimpro-1.10.0}/src/wavesimpro.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wavesimpro
3
- Version: 1.9.0
3
+ Version: 1.10.0
4
4
  Summary: WavesimPro Python API — cloud execution client for the Rayfos simulation platform
5
5
  Author: Rayfos
6
6
  Project-URL: Repository, https://github.com/rayfos/wavesimpro
@@ -59,8 +59,15 @@ field = wp.read_binary_field(run.download("output.bin"))
59
59
 
60
60
  ## What the API covers
61
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.
62
+ - Setup helpers for domains, media, primitives (box, sphere, ellipsoid, cylinder, cone,
63
+ N-sided prism, STL, GDSII layouts, voxel arrays), sources (point, plane wave, Gaussian beam,
64
+ custom field, custom-from-file, engine-solved waveguide mode) and monitors:
65
+ `create_box_primitive`, `create_sphere_primitive`, `create_ellipsoid_primitive`,
66
+ `create_cylinder_primitive`, `create_cone_primitive`, `create_prism_primitive`,
67
+ `create_stl_primitive`, `create_gds_primitive` (with `create_gds_layer_config` /
68
+ `create_gds_structure_override`), `create_voxels_model_primitive`.
69
+ - Per-primitive sub-pixel flag (`subpixel=True/False` on every shape, STL and GDSII helper)
70
+ to choose which shapes get smoothed interfaces.
64
71
  - Per-axis pixel sizes (`pixel_size=[dx, dy, dz]`) and sub-pixel permittivity averaging
65
72
  (`subpixel_factor=`), when the compute node supports them.
66
73
  - Preflight memory check and cost estimate before a run.
@@ -36,8 +36,15 @@ field = wp.read_binary_field(run.download("output.bin"))
36
36
 
37
37
  ## What the API covers
38
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.
39
+ - Setup helpers for domains, media, primitives (box, sphere, ellipsoid, cylinder, cone,
40
+ N-sided prism, STL, GDSII layouts, voxel arrays), sources (point, plane wave, Gaussian beam,
41
+ custom field, custom-from-file, engine-solved waveguide mode) and monitors:
42
+ `create_box_primitive`, `create_sphere_primitive`, `create_ellipsoid_primitive`,
43
+ `create_cylinder_primitive`, `create_cone_primitive`, `create_prism_primitive`,
44
+ `create_stl_primitive`, `create_gds_primitive` (with `create_gds_layer_config` /
45
+ `create_gds_structure_override`), `create_voxels_model_primitive`.
46
+ - Per-primitive sub-pixel flag (`subpixel=True/False` on every shape, STL and GDSII helper)
47
+ to choose which shapes get smoothed interfaces.
41
48
  - Per-axis pixel sizes (`pixel_size=[dx, dy, dz]`) and sub-pixel permittivity averaging
42
49
  (`subpixel_factor=`), when the compute node supports them.
43
50
  - Preflight memory check and cost estimate before a run.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wavesimpro"
7
- version = "1.9.0"
7
+ version = "1.10.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"
@@ -33,6 +33,14 @@ from .json_helper import (
33
33
  create_simulation_domain,
34
34
  create_stl_primitive,
35
35
  create_box_primitive,
36
+ create_sphere_primitive,
37
+ create_ellipsoid_primitive,
38
+ create_cylinder_primitive,
39
+ create_cone_primitive,
40
+ create_prism_primitive,
41
+ create_gds_primitive,
42
+ create_gds_layer_config,
43
+ create_gds_structure_override,
36
44
  create_medium,
37
45
  create_point_source,
38
46
  create_plane_wave_source,
@@ -46,7 +54,7 @@ from .json_helper import (
46
54
  parse_progress_data,
47
55
  )
48
56
 
49
- __version__ = "1.9.0"
57
+ __version__ = "1.10.0"
50
58
  __all__ = [
51
59
  # Client
52
60
  "RayfosClient",
@@ -78,6 +86,14 @@ __all__ = [
78
86
  "create_simulation_domain",
79
87
  "create_stl_primitive",
80
88
  "create_box_primitive",
89
+ "create_sphere_primitive",
90
+ "create_ellipsoid_primitive",
91
+ "create_cylinder_primitive",
92
+ "create_cone_primitive",
93
+ "create_prism_primitive",
94
+ "create_gds_primitive",
95
+ "create_gds_layer_config",
96
+ "create_gds_structure_override",
81
97
  "create_medium",
82
98
  "create_point_source",
83
99
  "create_plane_wave_source",
@@ -42,6 +42,84 @@ def create_simulation_domain(
42
42
  }
43
43
 
44
44
 
45
+ # ---------------------------------------------------------------------------
46
+ # Primitive helpers
47
+ #
48
+ # Every primitive dict mirrors RayfosCommandModels.PrimitiveBase (Primitives.cs):
49
+ # Id (uuid), Name, ShapeType (enum serialised as its name, e.g. "Cylinder"),
50
+ # MediumId, Position {X,Y,Z: Length}, PositionAnchor (enum name), Rotation
51
+ # {X,Y,Z} in degrees, plus the per-shape properties. Lengths are {Value, Unit}.
52
+ # "PositioningMode": "CenterAndSize" is the Studio bookkeeping key the existing
53
+ # helpers already emit (ignored by the server).
54
+ #
55
+ # Sub-pixel flag ("SubPixel"): True = this primitive's interface voxels are
56
+ # probe-averaged (create_wavesim_properties' subpixel_factor probes per axis);
57
+ # False = centre-sampled staircase for this primitive; None (default) = key
58
+ # omitted, the primitive follows the simulation-level PermittivitySampling.Mode
59
+ # (the pre-1.10 behaviour).
60
+ # ---------------------------------------------------------------------------
61
+
62
+ _POSITION_ANCHORS = (
63
+ "Center", "BottomLeftFront", "BottomCenter", "BottomRightFront", "BottomLeftBack",
64
+ "BottomRightBack", "TopCenter", "TopLeftFront", "TopRightFront", "TopLeftBack", "TopRightBack",
65
+ )
66
+ # Anchors each shape's GetCanonicalCenterPosition() accepts (others throw on the server).
67
+ _BOX_ANCHORS = ("Center", "BottomLeftFront", "BottomCenter", "TopCenter")
68
+ _AXIAL_ANCHORS = ("Center", "BottomCenter", "TopCenter") # Cylinder, Cone, Prism
69
+ _AXES = ("X", "Y", "Z")
70
+
71
+
72
+ def _check_anchor(anchor: str, allowed: Tuple[str, ...], shape: str) -> str:
73
+ a = str(anchor)
74
+ if a not in allowed:
75
+ raise ValueError(f"position_anchor for {shape} must be one of {list(allowed)}, got {anchor!r}")
76
+ return a
77
+
78
+
79
+ def _check_axis(axis: str) -> str:
80
+ a = str(axis).upper()
81
+ if a not in _AXES:
82
+ raise ValueError(f"axis must be 'X', 'Y' or 'Z', got {axis!r}")
83
+ return a
84
+
85
+
86
+ def _length(value: float, unit: str) -> Dict[str, Any]:
87
+ return {"Value": float(value), "Unit": unit}
88
+
89
+
90
+ def _primitive_base(
91
+ shape_type: str,
92
+ name: str,
93
+ medium_id: str,
94
+ x: float,
95
+ y: float,
96
+ z: float,
97
+ rotation_x: float,
98
+ rotation_y: float,
99
+ rotation_z: float,
100
+ position_anchor: str,
101
+ unit: str,
102
+ subpixel: Optional[bool],
103
+ ) -> Dict[str, Any]:
104
+ prim: Dict[str, Any] = {
105
+ "Id": str(uuid.uuid4()),
106
+ "Name": name,
107
+ "ShapeType": shape_type,
108
+ "MediumId": medium_id,
109
+ "PositioningMode": "CenterAndSize",
110
+ "Position": {"X": _length(x, unit), "Y": _length(y, unit), "Z": _length(z, unit)},
111
+ "PositionAnchor": position_anchor,
112
+ "Rotation": {"X": float(rotation_x), "Y": float(rotation_y), "Z": float(rotation_z)},
113
+ }
114
+ _apply_subpixel(prim, subpixel)
115
+ return prim
116
+
117
+
118
+ def _apply_subpixel(prim: Dict[str, Any], subpixel: Optional[bool]) -> None:
119
+ if subpixel is not None:
120
+ prim["SubPixel"] = bool(subpixel)
121
+
122
+
45
123
  def create_stl_primitive(
46
124
  name: str,
47
125
  file_id: str,
@@ -55,7 +133,8 @@ def create_stl_primitive(
55
133
  scale_x: float = 1.0,
56
134
  scale_y: float = 1.0,
57
135
  scale_z: float = 1.0,
58
- unit: str = "Micrometers"
136
+ unit: str = "Micrometers",
137
+ subpixel: Optional[bool] = None,
59
138
  ) -> Dict[str, Any]:
60
139
  """
61
140
  Create STL primitive structure
@@ -68,8 +147,10 @@ def create_stl_primitive(
68
147
  rotation_x, rotation_y, rotation_z: Rotation angles in degrees
69
148
  scale_x, scale_y, scale_z: Scale factors
70
149
  unit: Unit of measurement
150
+ subpixel: True = sub-pixel average this mesh's interface voxels, False = staircase,
151
+ None (default) = key omitted, follow the simulation-level setting.
71
152
  """
72
- return {
153
+ prim = {
73
154
  "Id": str(uuid.uuid4()),
74
155
  "Name": name,
75
156
  "ShapeType": "StlModel",
@@ -94,6 +175,8 @@ def create_stl_primitive(
94
175
  },
95
176
  "_tempStlData": {}
96
177
  }
178
+ _apply_subpixel(prim, subpixel)
179
+ return prim
97
180
 
98
181
 
99
182
  def create_box_primitive(
@@ -105,19 +188,46 @@ def create_box_primitive(
105
188
  size_x: float,
106
189
  size_y: float,
107
190
  size_z: float,
108
- unit: str = "Micrometers"
191
+ unit: str = "Micrometers",
192
+ subpixel: Optional[bool] = None,
193
+ position_anchor: str = "Center",
194
+ rotation_x: float = 0.0,
195
+ rotation_y: float = 0.0,
196
+ rotation_z: float = 0.0,
109
197
  ) -> Dict[str, Any]:
110
- """Create a box primitive"""
111
- return {
198
+ """
199
+ Create a box primitive (BoxPrimitive, CenterAndSize representation).
200
+
201
+ Args:
202
+ name: Primitive name
203
+ medium_id: Medium/Material ID
204
+ center_x, center_y, center_z: Position (what it refers to is set by position_anchor)
205
+ size_x, size_y, size_z: Full extents along X/Y/Z (Size.Width/Height/Depth)
206
+ unit: Unit of measurement for position and size
207
+ subpixel: True = sub-pixel average this box's interface voxels, False = staircase,
208
+ None (default) = key omitted, follow the simulation-level setting.
209
+ position_anchor: "Center" (default), "BottomLeftFront" (minimum corner),
210
+ "BottomCenter" (centre of the -Y face) or "TopCenter" (centre of the +Y face).
211
+ The server rejects any other anchor for a box.
212
+ rotation_x, rotation_y, rotation_z: Rotation in degrees
213
+ """
214
+ anchor = _check_anchor(position_anchor, _BOX_ANCHORS, "Box")
215
+ prim: Dict[str, Any] = {
216
+ "Id": str(uuid.uuid4()),
217
+ # "Type" kept for older servers; "ShapeType" is the canonical discriminator (same value).
112
218
  "Type": "Box",
219
+ "ShapeType": "Box",
113
220
  "Name": name,
114
221
  "MediumId": medium_id,
115
222
  "PositioningMode": "CenterAndSize",
223
+ "Representation": "CenterAndSize",
116
224
  "Position": {
117
225
  "X": {"Value": center_x, "Unit": unit},
118
226
  "Y": {"Value": center_y, "Unit": unit},
119
227
  "Z": {"Value": center_z, "Unit": unit}
120
228
  },
229
+ "PositionAnchor": anchor,
230
+ "Rotation": {"X": float(rotation_x), "Y": float(rotation_y), "Z": float(rotation_z)},
121
231
  # Size3D on the server is Width/Height/Depth (X/Y/Z keys were silently ignored → 4 µm default).
122
232
  "Size": {
123
233
  "Width": {"Value": size_x, "Unit": unit},
@@ -125,6 +235,338 @@ def create_box_primitive(
125
235
  "Depth": {"Value": size_z, "Unit": unit}
126
236
  }
127
237
  }
238
+ _apply_subpixel(prim, subpixel)
239
+ return prim
240
+
241
+
242
+ def create_sphere_primitive(
243
+ name: str,
244
+ medium_id: str,
245
+ center_x: float,
246
+ center_y: float,
247
+ center_z: float,
248
+ radius: float,
249
+ rotation_x: float = 0.0,
250
+ rotation_y: float = 0.0,
251
+ rotation_z: float = 0.0,
252
+ position_anchor: str = "Center",
253
+ unit: str = "Micrometers",
254
+ subpixel: Optional[bool] = None,
255
+ ) -> Dict[str, Any]:
256
+ """
257
+ Create a sphere primitive (SpherePrimitive).
258
+
259
+ Args:
260
+ name: Primitive name
261
+ medium_id: Medium/Material ID
262
+ center_x, center_y, center_z: Sphere centre
263
+ radius: Radius (Length in ``unit``)
264
+ rotation_x, rotation_y, rotation_z: Rotation in degrees (no effect on a sphere)
265
+ position_anchor: Stored as given (default "Center"); the server always treats
266
+ Position as the centre of a sphere.
267
+ unit: Unit of measurement
268
+ subpixel: True / False / None (follow the simulation-level setting)
269
+ """
270
+ anchor = _check_anchor(position_anchor, _POSITION_ANCHORS, "Sphere")
271
+ prim = _primitive_base("Sphere", name, medium_id, center_x, center_y, center_z,
272
+ rotation_x, rotation_y, rotation_z, anchor, unit, subpixel)
273
+ prim["Radius"] = _length(radius, unit)
274
+ return prim
275
+
276
+
277
+ def create_ellipsoid_primitive(
278
+ name: str,
279
+ medium_id: str,
280
+ center_x: float,
281
+ center_y: float,
282
+ center_z: float,
283
+ radius_x: float,
284
+ radius_y: float,
285
+ radius_z: float,
286
+ rotation_x: float = 0.0,
287
+ rotation_y: float = 0.0,
288
+ rotation_z: float = 0.0,
289
+ position_anchor: str = "Center",
290
+ unit: str = "Micrometers",
291
+ subpixel: Optional[bool] = None,
292
+ ) -> Dict[str, Any]:
293
+ """
294
+ Create an ellipsoid primitive (EllipsoidPrimitive).
295
+
296
+ Args:
297
+ name: Primitive name
298
+ medium_id: Medium/Material ID
299
+ center_x, center_y, center_z: Ellipsoid centre
300
+ radius_x, radius_y, radius_z: Semi-axes along X/Y/Z (before rotation)
301
+ rotation_x, rotation_y, rotation_z: Rotation in degrees
302
+ position_anchor: Stored as given (default "Center"); the server always treats
303
+ Position as the centre of an ellipsoid.
304
+ unit: Unit of measurement
305
+ subpixel: True / False / None (follow the simulation-level setting)
306
+ """
307
+ anchor = _check_anchor(position_anchor, _POSITION_ANCHORS, "Ellipsoid")
308
+ prim = _primitive_base("Ellipsoid", name, medium_id, center_x, center_y, center_z,
309
+ rotation_x, rotation_y, rotation_z, anchor, unit, subpixel)
310
+ prim["RadiusX"] = _length(radius_x, unit)
311
+ prim["RadiusY"] = _length(radius_y, unit)
312
+ prim["RadiusZ"] = _length(radius_z, unit)
313
+ return prim
314
+
315
+
316
+ def create_cylinder_primitive(
317
+ name: str,
318
+ medium_id: str,
319
+ position_x: float,
320
+ position_y: float,
321
+ position_z: float,
322
+ radius: float,
323
+ height: float,
324
+ axis: str = "Z",
325
+ rotation_x: float = 0.0,
326
+ rotation_y: float = 0.0,
327
+ rotation_z: float = 0.0,
328
+ position_anchor: str = "BottomCenter",
329
+ unit: str = "Micrometers",
330
+ subpixel: Optional[bool] = None,
331
+ ) -> Dict[str, Any]:
332
+ """
333
+ Create a cylinder primitive (CylinderPrimitive).
334
+
335
+ Args:
336
+ name: Primitive name
337
+ medium_id: Medium/Material ID
338
+ position_x, position_y, position_z: Anchor point (see position_anchor)
339
+ radius: Radius
340
+ height: Extent along ``axis``
341
+ axis: "X", "Y" or "Z" (default "Z")
342
+ rotation_x, rotation_y, rotation_z: Rotation in degrees
343
+ position_anchor: "BottomCenter" (default, as the server and Studio): Position is the
344
+ centre of the base face and the cylinder extends +height along ``axis``;
345
+ "Center": Position is the geometric centre; "TopCenter": centre of the top face.
346
+ unit: Unit of measurement
347
+ subpixel: True / False / None (follow the simulation-level setting)
348
+ """
349
+ anchor = _check_anchor(position_anchor, _AXIAL_ANCHORS, "Cylinder")
350
+ prim = _primitive_base("Cylinder", name, medium_id, position_x, position_y, position_z,
351
+ rotation_x, rotation_y, rotation_z, anchor, unit, subpixel)
352
+ prim["Radius"] = _length(radius, unit)
353
+ prim["Height"] = _length(height, unit)
354
+ prim["Axis"] = _check_axis(axis)
355
+ return prim
356
+
357
+
358
+ def create_cone_primitive(
359
+ name: str,
360
+ medium_id: str,
361
+ position_x: float,
362
+ position_y: float,
363
+ position_z: float,
364
+ radius: float,
365
+ height: float,
366
+ axis: str = "Z",
367
+ rotation_x: float = 0.0,
368
+ rotation_y: float = 0.0,
369
+ rotation_z: float = 0.0,
370
+ position_anchor: str = "BottomCenter",
371
+ unit: str = "Micrometers",
372
+ subpixel: Optional[bool] = None,
373
+ ) -> Dict[str, Any]:
374
+ """
375
+ Create a cone primitive (ConePrimitive). The base is a disc of ``radius``; the apex
376
+ lies ``height`` further along +``axis``.
377
+
378
+ Args:
379
+ name: Primitive name
380
+ medium_id: Medium/Material ID
381
+ position_x, position_y, position_z: Anchor point (see position_anchor)
382
+ radius: Base radius
383
+ height: Base-to-apex distance along ``axis``
384
+ axis: "X", "Y" or "Z" (default "Z")
385
+ rotation_x, rotation_y, rotation_z: Rotation in degrees
386
+ position_anchor: "BottomCenter" (default, as the server and Studio): Position is the
387
+ centre of the base; "Center": Position is the CENTROID (height/4 above the base,
388
+ not height/2); "TopCenter": Position is the apex.
389
+ unit: Unit of measurement
390
+ subpixel: True / False / None (follow the simulation-level setting)
391
+ """
392
+ anchor = _check_anchor(position_anchor, _AXIAL_ANCHORS, "Cone")
393
+ prim = _primitive_base("Cone", name, medium_id, position_x, position_y, position_z,
394
+ rotation_x, rotation_y, rotation_z, anchor, unit, subpixel)
395
+ prim["Radius"] = _length(radius, unit)
396
+ prim["Height"] = _length(height, unit)
397
+ prim["Axis"] = _check_axis(axis)
398
+ return prim
399
+
400
+
401
+ def create_prism_primitive(
402
+ name: str,
403
+ medium_id: str,
404
+ position_x: float,
405
+ position_y: float,
406
+ position_z: float,
407
+ radius: float,
408
+ height: float,
409
+ number_of_sides: int = 6,
410
+ axis: str = "Z",
411
+ top_scale: float = 1.0,
412
+ twist_degrees: float = 0.0,
413
+ rotation_x: float = 0.0,
414
+ rotation_y: float = 0.0,
415
+ rotation_z: float = 0.0,
416
+ position_anchor: str = "BottomCenter",
417
+ unit: str = "Micrometers",
418
+ subpixel: Optional[bool] = None,
419
+ ) -> Dict[str, Any]:
420
+ """
421
+ Create a regular N-sided prism primitive (PrismPrimitive): a regular polygon with
422
+ ``number_of_sides`` corners on a circle of ``radius`` extruded ``height`` along ``axis``.
423
+
424
+ Args:
425
+ name: Primitive name
426
+ medium_id: Medium/Material ID
427
+ position_x, position_y, position_z: Anchor point (see position_anchor)
428
+ radius: Circumradius of the bottom polygon
429
+ height: Extent along ``axis``
430
+ number_of_sides: Polygon corners, integer >= 3 (3 = triangular prism, 4 = square,
431
+ large N approaches a cylinder). Default 6.
432
+ axis: "X", "Y" or "Z" (default "Z")
433
+ top_scale: Top-ring radius factor: 1 = straight prism (default), 0 = pyramid,
434
+ > 1 = flared
435
+ twist_degrees: Rotation of the top ring about ``axis`` in degrees (default 0)
436
+ rotation_x, rotation_y, rotation_z: Rotation in degrees
437
+ position_anchor: "BottomCenter" (default, as the server and Studio), "Center" or
438
+ "TopCenter" (±height/2 along ``axis``, as for a cylinder).
439
+ unit: Unit of measurement
440
+ subpixel: True / False / None (follow the simulation-level setting)
441
+ """
442
+ anchor = _check_anchor(position_anchor, _AXIAL_ANCHORS, "Prism")
443
+ n = int(number_of_sides)
444
+ if n != number_of_sides or n < 3:
445
+ raise ValueError(f"number_of_sides must be an integer >= 3, got {number_of_sides!r}")
446
+ if top_scale < 0:
447
+ raise ValueError(f"top_scale must be >= 0, got {top_scale!r}")
448
+ prim = _primitive_base("Prism", name, medium_id, position_x, position_y, position_z,
449
+ rotation_x, rotation_y, rotation_z, anchor, unit, subpixel)
450
+ prim["Radius"] = _length(radius, unit)
451
+ prim["Height"] = _length(height, unit)
452
+ prim["Axis"] = _check_axis(axis)
453
+ prim["NumberOfSides"] = n
454
+ prim["TopScale"] = float(top_scale)
455
+ prim["TwistDegrees"] = float(twist_degrees)
456
+ return prim
457
+
458
+
459
+ def create_gds_layer_config(
460
+ layer_number: int,
461
+ medium_id: Optional[str] = None,
462
+ height_um: Optional[float] = None,
463
+ z_offset_um: Optional[float] = None,
464
+ ) -> Dict[str, Any]:
465
+ """
466
+ Per-layer settings for create_gds_primitive(layer_configs=...) (GdsLayerConfig).
467
+
468
+ Args:
469
+ layer_number: GDS layer number
470
+ medium_id: Medium for this layer; None = the GDS primitive's medium_id
471
+ height_um: Extrusion thickness in micrometres; None = the primitive's layer_height_um
472
+ z_offset_um: Bottom of the layer in micrometres relative to the primitive;
473
+ None = auto-stacked
474
+ """
475
+ cfg: Dict[str, Any] = {"LayerNumber": int(layer_number)}
476
+ if medium_id is not None:
477
+ cfg["MediumId"] = medium_id
478
+ if height_um is not None:
479
+ cfg["Height"] = float(height_um)
480
+ if z_offset_um is not None:
481
+ cfg["ZOffset"] = float(z_offset_um)
482
+ return cfg
483
+
484
+
485
+ def create_gds_structure_override(
486
+ structure_name: str,
487
+ layer_overrides: Optional[List[Dict[str, Any]]] = None,
488
+ ) -> Dict[str, Any]:
489
+ """
490
+ Per-structure (GDS cell) overrides for create_gds_primitive(structure_overrides=...)
491
+ (GdsStructureConfig). ``layer_overrides`` is a list built with create_gds_layer_config()
492
+ (same keys as GdsStructureLayerOverride: LayerNumber, MediumId, Height, ZOffset); unset
493
+ fields inherit from the primitive's layer_configs.
494
+ """
495
+ cfg: Dict[str, Any] = {"StructureName": structure_name}
496
+ if layer_overrides is not None:
497
+ cfg["LayerOverrides"] = list(layer_overrides)
498
+ return cfg
499
+
500
+
501
+ def create_gds_primitive(
502
+ name: str,
503
+ file_id: str,
504
+ medium_id: str,
505
+ center_x: float = 0.0,
506
+ center_y: float = 0.0,
507
+ center_z: float = 0.0,
508
+ layer_height_um: float = 1.0,
509
+ selected_layers: Optional[List[int]] = None,
510
+ layer_configs: Optional[List[Dict[str, Any]]] = None,
511
+ structure_overrides: Optional[List[Dict[str, Any]]] = None,
512
+ metadata_file_id: Optional[str] = None,
513
+ voxelization_method: str = "ViaStl",
514
+ rotation_x: float = 0.0,
515
+ rotation_y: float = 0.0,
516
+ rotation_z: float = 0.0,
517
+ scale_x: float = 1.0,
518
+ scale_y: float = 1.0,
519
+ scale_z: float = 1.0,
520
+ position_anchor: str = "Center",
521
+ unit: str = "Micrometers",
522
+ subpixel: Optional[bool] = None,
523
+ ) -> Dict[str, Any]:
524
+ """
525
+ Create a GDSII layout primitive (GdsModelPrimitive) from an uploaded .gds file.
526
+
527
+ The layout's polygons are extruded per layer; Position is the centre of the top
528
+ structure's bounding box. Layer thickness and offsets are plain numbers in
529
+ MICROMETRES on the server model (not Length objects), whatever ``unit`` is.
530
+
531
+ Args:
532
+ name: Primitive name
533
+ file_id: File id of the uploaded GDS file (GdsDataId), as for create_stl_primitive
534
+ medium_id: Default medium for every layer
535
+ center_x, center_y, center_z: Position of the layout centre
536
+ layer_height_um: Default extrusion thickness of each layer in µm (LayerHeight)
537
+ selected_layers: Layer numbers to include; None = all layers
538
+ layer_configs: Per-layer settings from create_gds_layer_config(); None = every layer
539
+ uses medium_id / layer_height_um (legacy mode)
540
+ structure_overrides: Per-structure (cell) overrides from
541
+ create_gds_structure_override(); None = none
542
+ metadata_file_id: Optional uploaded layer-metadata YML file id (GdsMetadataId)
543
+ voxelization_method: "ViaStl" (default) or "Direct"
544
+ rotation_x, rotation_y, rotation_z: Rotation in degrees
545
+ scale_x, scale_y, scale_z: Scale factors
546
+ position_anchor: Stored as given (default "Center"); the server treats Position as
547
+ the layout centre whatever the anchor.
548
+ unit: Unit of Position
549
+ subpixel: True / False / None (follow the simulation-level setting)
550
+ """
551
+ anchor = _check_anchor(position_anchor, _POSITION_ANCHORS, "GdsModel")
552
+ method = str(voxelization_method)
553
+ if method not in ("ViaStl", "Direct"):
554
+ raise ValueError(f"voxelization_method must be 'ViaStl' or 'Direct', got {voxelization_method!r}")
555
+ prim = _primitive_base("GdsModel", name, medium_id, center_x, center_y, center_z,
556
+ rotation_x, rotation_y, rotation_z, anchor, unit, subpixel)
557
+ prim["GdsDataId"] = file_id
558
+ prim["ScaleFactors"] = {"X": float(scale_x), "Y": float(scale_y), "Z": float(scale_z)}
559
+ prim["LayerHeight"] = float(layer_height_um)
560
+ prim["VoxelizationMethod"] = method
561
+ if metadata_file_id is not None:
562
+ prim["GdsMetadataId"] = metadata_file_id
563
+ if selected_layers is not None:
564
+ prim["SelectedLayers"] = [int(n) for n in selected_layers]
565
+ if layer_configs is not None:
566
+ prim["LayerConfigs"] = list(layer_configs)
567
+ if structure_overrides is not None:
568
+ prim["StructureOverrides"] = list(structure_overrides)
569
+ return prim
128
570
 
129
571
 
130
572
  def create_medium(
@@ -189,9 +631,8 @@ def create_point_source(
189
631
  return {
190
632
  "Id": str(uuid.uuid4()),
191
633
  "Name": name,
192
- "Id": str(uuid.uuid4()),
193
634
  "SourceType": "Point",
194
- "isEnabled": True,
635
+ "IsEnabled": True,
195
636
  "UseInternalPythonSource": False,
196
637
  "Position": {
197
638
  "X": {"Value": position_x, "Unit": unit},
@@ -357,7 +798,9 @@ def create_voxels_model_primitive(
357
798
  grid_nz: int = 32,
358
799
  data_format: str = "Float32",
359
800
  data_type: str = "RefractiveIndex",
360
- unit: str = "Micrometers"
801
+ unit: str = "Micrometers",
802
+ subpixel: Optional[bool] = None,
803
+ antialias_width: Optional[float] = None,
361
804
  ) -> Dict[str, Any]:
362
805
  """
363
806
  Create VoxelsModel primitive for arbitrary refractive index distributions
@@ -372,6 +815,19 @@ def create_voxels_model_primitive(
372
815
  data_format: 'Float32', 'Float64', 'Complex64', 'Complex128'
373
816
  data_type: 'RefractiveIndex', 'ComplexRefractiveIndex', 'Permittivity', 'ComplexPermittivity'
374
817
  unit: Unit of measurement
818
+
819
+ subpixel: True anti-aliases the array on the service:
820
+ - array at the simulation voxel size: softened IN PLACE by a normalised Gaussian of
821
+ ``antialias_width`` voxels on the permittivity (same resolution, volume kept).
822
+ On reference shapes it halves the interface error vs true sub-pixel values.
823
+ - array finer by a whole number per axis (voxel size = simulation voxel / k): each
824
+ k_x x k_y x k_z block is averaged into one simulation voxel (the same rule as the
825
+ shapes' sub-pixel probes); cells the array only partly covers blend with what
826
+ lies underneath.
827
+ None/False: the array must be at the simulation voxel size and is copied voxel for
828
+ voxel. The simulation-level ``subpixel_factor`` does not apply to voxel arrays.
829
+ antialias_width: Gaussian width (standard deviation, voxels) for the in-place case;
830
+ None = 0.5 (best on average); thin waveguides prefer ~0.7. Range 0.1-3.
375
831
  """
376
832
  return {
377
833
  "ShapeType": "VoxelsModel",
@@ -395,7 +851,9 @@ def create_voxels_model_primitive(
395
851
  },
396
852
  "DataFormat": data_format,
397
853
  "DataType": data_type,
398
- "Rotation": {"X": 0, "Y": 0, "Z": 0}
854
+ "Rotation": {"X": 0, "Y": 0, "Z": 0},
855
+ **({"SubPixel": bool(subpixel)} if subpixel is not None else {}),
856
+ **({"AntiAliasWidth": float(antialias_width)} if antialias_width is not None else {}),
399
857
  }
400
858
 
401
859
 
@@ -701,6 +1159,16 @@ def create_wavesim_properties(
701
1159
  derives the iteration count from this distance and it OVERRIDES
702
1160
  residual_norm_max_iterations (threshold still stops the run early).
703
1161
  C++ engine only.
1162
+ subpixel_factor: Sub-pixel probe count per axis, s (1..64, 16 is the reference
1163
+ value). It is used by every primitive whose SubPixel is true; a primitive with
1164
+ no explicit flag follows the simulation-level mode, which this argument sets to
1165
+ SubPixel (s > 1) — so passing it alone smooths every primitive, STL and GDSII
1166
+ (the pre-1.10 behaviour). Per-primitive flags (``subpixel=True`` on the
1167
+ primitive helpers) are the preferred way to choose which shapes are smoothed.
1168
+ When this is None but some primitive carries an explicit flag, the factor is
1169
+ emitted with Mode "Staircase" and s = 16, so flagged primitives have a probe
1170
+ count and unflagged ones stay staircased. VoxelsModel arrays are never
1171
+ re-sampled.
704
1172
  """
705
1173
  props = {
706
1174
  "SimulationDomain": simulation_domain,
@@ -764,6 +1232,8 @@ def create_wavesim_properties(
764
1232
  # Staircase. The service probes each voxel of primitives / STL / GDSII at
765
1233
  # subpixel_factor points per axis and stores the mean permittivity; it does NOT
766
1234
  # re-sample a VoxelsModel array (average that yourself before upload).
1235
+ # Mode is the default for primitives without an explicit "SubPixel" flag; flagged
1236
+ # primitives decide for themselves and use Factor as their probe count.
767
1237
  if subpixel_factor is not None:
768
1238
  s = int(subpixel_factor)
769
1239
  if s < 1 or s > 64:
@@ -774,6 +1244,13 @@ def create_wavesim_properties(
774
1244
  "Rule": "ArithmeticMean",
775
1245
  "InterfaceOnly": True,
776
1246
  }
1247
+ elif any(isinstance(p, dict) and "SubPixel" in p for p in (primitives or [])):
1248
+ props["PermittivitySampling"] = {
1249
+ "Mode": "Staircase",
1250
+ "Factor": 16,
1251
+ "Rule": "ArithmeticMean",
1252
+ "InterfaceOnly": True,
1253
+ }
777
1254
 
778
1255
  return props
779
1256
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wavesimpro
3
- Version: 1.9.0
3
+ Version: 1.10.0
4
4
  Summary: WavesimPro Python API — cloud execution client for the Rayfos simulation platform
5
5
  Author: Rayfos
6
6
  Project-URL: Repository, https://github.com/rayfos/wavesimpro
@@ -59,8 +59,15 @@ field = wp.read_binary_field(run.download("output.bin"))
59
59
 
60
60
  ## What the API covers
61
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.
62
+ - Setup helpers for domains, media, primitives (box, sphere, ellipsoid, cylinder, cone,
63
+ N-sided prism, STL, GDSII layouts, voxel arrays), sources (point, plane wave, Gaussian beam,
64
+ custom field, custom-from-file, engine-solved waveguide mode) and monitors:
65
+ `create_box_primitive`, `create_sphere_primitive`, `create_ellipsoid_primitive`,
66
+ `create_cylinder_primitive`, `create_cone_primitive`, `create_prism_primitive`,
67
+ `create_stl_primitive`, `create_gds_primitive` (with `create_gds_layer_config` /
68
+ `create_gds_structure_override`), `create_voxels_model_primitive`.
69
+ - Per-primitive sub-pixel flag (`subpixel=True/False` on every shape, STL and GDSII helper)
70
+ to choose which shapes get smoothed interfaces.
64
71
  - Per-axis pixel sizes (`pixel_size=[dx, dy, dz]`) and sub-pixel permittivity averaging
65
72
  (`subpixel_factor=`), when the compute node supports them.
66
73
  - Preflight memory check and cost estimate before a run.
File without changes