wavesimpro 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,755 @@
1
+ """
2
+ Helper functions to build WavesimProperties JSON structure
3
+
4
+ This module provides Python functions to build the correct JSON structure
5
+ that matches RayfosCommandModels.WavesimProperties C# class.
6
+
7
+ Ported from examples/wavesim_json_helper.py with fixes for MATLAB API parity.
8
+ """
9
+
10
+ import json
11
+ import uuid
12
+ from typing import List, Dict, Any, Optional
13
+
14
+
15
+ def create_simulation_domain(
16
+ size_x: float = 10.0,
17
+ size_y: float = 10.0,
18
+ size_z: float = 10.0,
19
+ center_x: float = 0.0,
20
+ center_y: float = 0.0,
21
+ center_z: float = 0.0,
22
+ unit: str = "Micrometers"
23
+ ) -> Dict[str, Any]:
24
+ """
25
+ Create SimulationDomain structure
26
+
27
+ Args:
28
+ size_x, size_y, size_z: Domain size (width, height, depth)
29
+ center_x, center_y, center_z: Domain center position
30
+ unit: Unit of measurement (Micrometers, Nanometers, etc.)
31
+ """
32
+ return {
33
+ "PositioningMode": "CenterAndSize",
34
+ "Position": {
35
+ "X": {"Value": center_x, "Unit": unit},
36
+ "Y": {"Value": center_y, "Unit": unit},
37
+ "Z": {"Value": center_z, "Unit": unit}
38
+ },
39
+ "Size": {
40
+ "Width": {"Value": size_x, "Unit": unit},
41
+ "Height": {"Value": size_y, "Unit": unit},
42
+ "Depth": {"Value": size_z, "Unit": unit}
43
+ },
44
+ }
45
+
46
+
47
+ def create_stl_primitive(
48
+ name: str,
49
+ file_id: str,
50
+ medium_id: str,
51
+ center_x: float = 0.0,
52
+ center_y: float = 0.0,
53
+ center_z: float = 0.0,
54
+ rotation_x: float = 0.0,
55
+ rotation_y: float = 0.0,
56
+ rotation_z: float = 0.0,
57
+ scale_x: float = 1.0,
58
+ scale_y: float = 1.0,
59
+ scale_z: float = 1.0,
60
+ unit: str = "Micrometers"
61
+ ) -> Dict[str, Any]:
62
+ """
63
+ Create STL primitive structure
64
+
65
+ Args:
66
+ name: Primitive name
67
+ file_id: ID or filename of uploaded STL file
68
+ medium_id: Medium/Material ID to use
69
+ center_x, center_y, center_z: Position
70
+ rotation_x, rotation_y, rotation_z: Rotation angles in degrees
71
+ scale_x, scale_y, scale_z: Scale factors
72
+ unit: Unit of measurement
73
+ """
74
+ return {
75
+ "Id": str(uuid.uuid4()),
76
+ "Name": name,
77
+ "ShapeType": "StlModel",
78
+ "MediumId": medium_id,
79
+ "PositioningMode": "CenterAndSize",
80
+ "Position": {
81
+ "X": {"Value": center_x, "Unit": unit},
82
+ "Y": {"Value": center_y, "Unit": unit},
83
+ "Z": {"Value": center_z, "Unit": unit}
84
+ },
85
+ "PositionAnchor": "Center",
86
+ "Rotation": {
87
+ "X": rotation_x,
88
+ "Y": rotation_y,
89
+ "Z": rotation_z
90
+ },
91
+ "StlDataId": file_id,
92
+ "ScaleFactors": {
93
+ "X": scale_x,
94
+ "Y": scale_y,
95
+ "Z": scale_z
96
+ },
97
+ "_tempStlData": {}
98
+ }
99
+
100
+
101
+ def create_box_primitive(
102
+ name: str,
103
+ medium_id: str,
104
+ center_x: float,
105
+ center_y: float,
106
+ center_z: float,
107
+ size_x: float,
108
+ size_y: float,
109
+ size_z: float,
110
+ unit: str = "Micrometers"
111
+ ) -> Dict[str, Any]:
112
+ """Create a box primitive"""
113
+ return {
114
+ "Type": "Box",
115
+ "Name": name,
116
+ "MediumId": medium_id,
117
+ "PositioningMode": "CenterAndSize",
118
+ "Position": {
119
+ "X": {"Value": center_x, "Unit": unit},
120
+ "Y": {"Value": center_y, "Unit": unit},
121
+ "Z": {"Value": center_z, "Unit": unit}
122
+ },
123
+ "Size": {
124
+ "X": {"Value": size_x, "Unit": unit},
125
+ "Y": {"Value": size_y, "Unit": unit},
126
+ "Z": {"Value": size_z, "Unit": unit}
127
+ }
128
+ }
129
+
130
+
131
+ def create_medium(
132
+ medium_id: str,
133
+ name: str,
134
+ refractive_index_real: float,
135
+ refractive_index_imag: float = 0.0,
136
+ category: str = "dielectric",
137
+ description: Optional[str] = None,
138
+ color: Optional[str] = None
139
+ ) -> Dict[str, Any]:
140
+ """
141
+ Create medium/material structure
142
+
143
+ Args:
144
+ medium_id: Unique ID for this medium (lowercase with hyphens)
145
+ name: Display name
146
+ refractive_index_real: Real part of refractive index
147
+ refractive_index_imag: Imaginary part (for absorption)
148
+ category: Material category ("gas", "dielectric", "semiconductor", "metal")
149
+ description: Optional description
150
+ color: Hex color code (e.g., "#FF0000")
151
+ """
152
+ medium = {
153
+ "id": medium_id,
154
+ "name": name,
155
+ "category": category,
156
+ "mediumType": "medium",
157
+ "inputMethod": "refractive_index",
158
+ "primaryReal": refractive_index_real,
159
+ "primaryImaginary": refractive_index_imag,
160
+ "permeabilityReal": 1,
161
+ "permeabilityImaginary": 0
162
+ }
163
+ if description:
164
+ medium["description"] = description
165
+ if color:
166
+ medium["Color"] = color
167
+ return medium
168
+
169
+
170
+ def create_point_source(
171
+ name: str = "PointSource1",
172
+ position_x: float = 0.0,
173
+ position_y: float = 0.0,
174
+ position_z: float = 0.0,
175
+ amplitude_real: float = 1.0,
176
+ amplitude_imag: float = 0.0,
177
+ polarization: str = "None",
178
+ unit: str = "Micrometers"
179
+ ) -> Dict[str, Any]:
180
+ """
181
+ Create Point source
182
+
183
+ Args:
184
+ name: Source name
185
+ position_x, position_y, position_z: Source position
186
+ amplitude_real, amplitude_imag: Complex amplitude
187
+ polarization: "None", "X", "Y", "Z"
188
+ unit: Unit of measurement
189
+ """
190
+ return {
191
+ "Id": str(uuid.uuid4()),
192
+ "Name": name,
193
+ "SourceType": "Point",
194
+ "isEnabled": True,
195
+ "UseInternalPythonSource": False,
196
+ "Position": {
197
+ "X": {"Value": position_x, "Unit": unit},
198
+ "Y": {"Value": position_y, "Unit": unit},
199
+ "Z": {"Value": position_z, "Unit": unit}
200
+ },
201
+ "Polarization": polarization,
202
+ "Amplitude": {
203
+ "Real": amplitude_real,
204
+ "Imaginary": amplitude_imag
205
+ }
206
+ }
207
+
208
+
209
+ def create_plane_wave_source(
210
+ name: str = "PlaneWave1",
211
+ position_x: float = 0.0,
212
+ position_y: float = 0.0,
213
+ position_z: float = 0.0,
214
+ source_plane: str = "XZ",
215
+ amplitude_real: float = 1.0,
216
+ amplitude_imag: float = 0.0,
217
+ size_x: float = 10.0,
218
+ size_y: float = 10.0,
219
+ size_z: float = 10.0,
220
+ polarization: str = "None",
221
+ theta: Optional[float] = None,
222
+ phi: Optional[float] = None,
223
+ origin: str = "center",
224
+ unit: str = "Micrometers"
225
+ ) -> Dict[str, Any]:
226
+ """
227
+ Create PlaneWave source
228
+
229
+ Args:
230
+ name: Source name
231
+ position_x, position_y, position_z: Source position
232
+ source_plane: "X", "Y", "Z", "XY", "YZ", "XZ" (UPPERCASE)
233
+ amplitude_real, amplitude_imag: Complex amplitude
234
+ size_x, size_y, size_z: Source dimensions
235
+ polarization: "None", "X", "Y", "Z" (UPPERCASE)
236
+ theta: Incident angle in radians (optional)
237
+ phi: Azimuthal angle in radians (optional)
238
+ origin: "center" or "topleft"
239
+ unit: Unit of measurement
240
+ """
241
+ source = {
242
+ "SourceType": "PlaneWave",
243
+ "Name": name,
244
+ "IsEnabled": True,
245
+ "Position": {
246
+ "X": {"Value": position_x, "Unit": unit},
247
+ "Y": {"Value": position_y, "Unit": unit},
248
+ "Z": {"Value": position_z, "Unit": unit}
249
+ },
250
+ "Polarization": polarization,
251
+ "Amplitude": {
252
+ "Real": amplitude_real,
253
+ "Imaginary": amplitude_imag
254
+ },
255
+ "SourcePlane": source_plane,
256
+ "Size": {
257
+ "Width": {"Value": size_x, "Unit": unit},
258
+ "Height": {"Value": size_y, "Unit": unit},
259
+ "Depth": {"Value": size_z, "Unit": unit}
260
+ },
261
+ "Origin": origin
262
+ }
263
+
264
+ if theta is not None:
265
+ source["Theta"] = theta
266
+ if phi is not None:
267
+ source["Phi"] = phi
268
+
269
+ return source
270
+
271
+
272
+ def create_gaussian_beam_source(
273
+ name: str = "GaussianBeam1",
274
+ position_x: float = 0.0,
275
+ position_y: float = 0.0,
276
+ position_z: float = 0.0,
277
+ source_plane: str = "XZ",
278
+ amplitude_real: float = 1.0,
279
+ amplitude_imag: float = 0.0,
280
+ size_x: float = 10.0,
281
+ size_y: float = 10.0,
282
+ size_z: float = 10.0,
283
+ polarization: str = "None",
284
+ alpha: float = 3.0,
285
+ sigma: Optional[float] = None,
286
+ theta: Optional[float] = None,
287
+ phi: Optional[float] = None,
288
+ origin: str = "center",
289
+ unit: str = "Micrometers"
290
+ ) -> Dict[str, Any]:
291
+ """
292
+ Create GaussianBeam source
293
+
294
+ Args:
295
+ name: Source name
296
+ position_x, position_y, position_z: Source position
297
+ source_plane: "X", "Y", "Z", "XY", "YZ", "XZ"
298
+ amplitude_real, amplitude_imag: Complex amplitude
299
+ size_x, size_y, size_z: Source dimensions
300
+ polarization: "None", "X", "Y", "Z"
301
+ alpha: Gaussian width factor (default: 3.0)
302
+ sigma: Standard deviation (optional, overrides alpha)
303
+ theta: Incident angle in radians (optional)
304
+ phi: Azimuthal angle in radians (optional)
305
+ origin: "center" or "topleft"
306
+ unit: Unit of measurement
307
+ """
308
+ source = {
309
+ "SourceType": "GaussianBeam",
310
+ "Name": name,
311
+ "IsEnabled": True,
312
+ "Position": {
313
+ "X": {"Value": position_x, "Unit": unit},
314
+ "Y": {"Value": position_y, "Unit": unit},
315
+ "Z": {"Value": position_z, "Unit": unit}
316
+ },
317
+ "Polarization": polarization,
318
+ "Amplitude": {
319
+ "Real": amplitude_real,
320
+ "Imaginary": amplitude_imag
321
+ },
322
+ "SourcePlane": source_plane,
323
+ "Size": {
324
+ "X": {"Value": size_x, "Unit": unit},
325
+ "Y": {"Value": size_y, "Unit": unit},
326
+ "Z": {"Value": size_z, "Unit": unit}
327
+ },
328
+ "Alpha": alpha,
329
+ "Origin": origin
330
+ }
331
+
332
+ if sigma is not None:
333
+ source["Sigma"] = sigma
334
+ if theta is not None:
335
+ source["Theta"] = theta
336
+ if phi is not None:
337
+ source["Phi"] = phi
338
+
339
+ return source
340
+
341
+
342
+ def create_voxels_model_primitive(
343
+ name: str,
344
+ voxels_data_id: str,
345
+ medium_id: str,
346
+ center_x: float = 0.0,
347
+ center_y: float = 0.0,
348
+ center_z: float = 0.0,
349
+ voxel_size_x: float = 0.1,
350
+ voxel_size_y: float = 0.1,
351
+ voxel_size_z: float = 0.1,
352
+ grid_nx: int = 32,
353
+ grid_ny: int = 32,
354
+ grid_nz: int = 32,
355
+ data_format: str = "Float32",
356
+ data_type: str = "RefractiveIndex",
357
+ unit: str = "Micrometers"
358
+ ) -> Dict[str, Any]:
359
+ """
360
+ Create VoxelsModel primitive for arbitrary refractive index distributions
361
+
362
+ Args:
363
+ name: Primitive name
364
+ voxels_data_id: ID of uploaded binary voxel data file
365
+ medium_id: Medium/Material ID for the primitive
366
+ center_x, center_y, center_z: Center position
367
+ voxel_size_x, voxel_size_y, voxel_size_z: Size of each voxel
368
+ grid_nx, grid_ny, grid_nz: Number of voxels in each dimension
369
+ data_format: 'Float32', 'Float64', 'Complex64', 'Complex128'
370
+ data_type: 'RefractiveIndex', 'ComplexRefractiveIndex', 'Permittivity', 'ComplexPermittivity'
371
+ unit: Unit of measurement
372
+ """
373
+ return {
374
+ "ShapeType": "VoxelsModel",
375
+ "Name": name,
376
+ "MediumId": medium_id,
377
+ "VoxelsDataId": voxels_data_id,
378
+ "Position": {
379
+ "X": {"Value": center_x, "Unit": unit},
380
+ "Y": {"Value": center_y, "Unit": unit},
381
+ "Z": {"Value": center_z, "Unit": unit}
382
+ },
383
+ "SourceVoxelSize": {
384
+ "X": voxel_size_x,
385
+ "Y": voxel_size_y,
386
+ "Z": voxel_size_z
387
+ },
388
+ "SourceGridDimensions": {
389
+ "X": grid_nx,
390
+ "Y": grid_ny,
391
+ "Z": grid_nz
392
+ },
393
+ "DataFormat": data_format,
394
+ "DataType": data_type,
395
+ "Rotation": {"X": 0, "Y": 0, "Z": 0}
396
+ }
397
+
398
+
399
+ def create_custom_field_source(
400
+ name: str,
401
+ field_data_id: str,
402
+ position_x: float = 0.0,
403
+ position_y: float = 0.0,
404
+ position_z: float = 0.0,
405
+ grid_nx: int = 1,
406
+ grid_ny: int = 1,
407
+ grid_nz: int = 1,
408
+ polarization: str = "X",
409
+ amplitude_real: float = 1.0,
410
+ amplitude_imag: float = 0.0,
411
+ data_format: str = "Complex64",
412
+ origin: str = "center",
413
+ unit: str = "Micrometers"
414
+ ) -> Dict[str, Any]:
415
+ """
416
+ Create CustomField source for pre-computed field distributions (e.g., eigenmodes)
417
+
418
+ Backend derives voxel size from the simulation domain settings.
419
+
420
+ Args:
421
+ name: Source name
422
+ field_data_id: ID of uploaded complex field data file
423
+ position_x, position_y, position_z: Source position
424
+ grid_nx, grid_ny, grid_nz: Grid dimensions of field data
425
+ polarization: 'X', 'Y', 'Z', 'None'
426
+ amplitude_real, amplitude_imag: Amplitude scaling
427
+ data_format: 'Complex64' or 'Complex128'
428
+ origin: 'center' or 'topleft'
429
+ unit: Unit of measurement
430
+ """
431
+ return {
432
+ "SourceType": "CustomField",
433
+ "Name": name,
434
+ "IsEnabled": True,
435
+ "FieldDataId": field_data_id,
436
+ "Position": {
437
+ "X": {"Value": position_x, "Unit": unit},
438
+ "Y": {"Value": position_y, "Unit": unit},
439
+ "Z": {"Value": position_z, "Unit": unit}
440
+ },
441
+ "Polarization": polarization,
442
+ "Origin": origin,
443
+ "Amplitude": {
444
+ "Real": amplitude_real,
445
+ "Imaginary": amplitude_imag
446
+ },
447
+ "GridDimensions": {
448
+ "X": grid_nx,
449
+ "Y": grid_ny,
450
+ "Z": grid_nz
451
+ },
452
+ "DataFormat": data_format,
453
+ }
454
+
455
+
456
+ def create_custom_from_file_source(
457
+ name: str,
458
+ file_id: str,
459
+ position_x: float = 0.0,
460
+ position_y: float = 0.0,
461
+ position_z: float = 0.0,
462
+ file_format: str = "JsonV2",
463
+ unit: str = "Micrometers"
464
+ ) -> Dict[str, Any]:
465
+ """
466
+ Create CustomFromFile source (external file)
467
+
468
+ Args:
469
+ name: Source name
470
+ file_id: ID or filename of uploaded source file
471
+ position_x, position_y, position_z: Source position
472
+ file_format: "JsonV2", "NumpyNpy", "MatlabMat", "RawBinary"
473
+ unit: Unit of measurement
474
+ """
475
+ return {
476
+ "SourceType": "CustomFromFile",
477
+ "Name": name,
478
+ "IsEnabled": True,
479
+ "Position": {
480
+ "X": {"Value": position_x, "Unit": unit},
481
+ "Y": {"Value": position_y, "Unit": unit},
482
+ "Z": {"Value": position_z, "Unit": unit}
483
+ },
484
+ "FileDataId": file_id,
485
+ "FileFormat": file_format
486
+ }
487
+
488
+
489
+ def create_field_monitor(
490
+ name: str,
491
+ center_x: float,
492
+ center_y: float,
493
+ center_z: float,
494
+ size_x: float,
495
+ size_y: float,
496
+ size_z: float,
497
+ plane: str = "xy",
498
+ record_ex: bool = True,
499
+ record_ey: bool = True,
500
+ record_ez: bool = True,
501
+ record_hx: bool = False,
502
+ record_hy: bool = False,
503
+ record_hz: bool = False,
504
+ unit: str = "Micrometers"
505
+ ) -> Dict[str, Any]:
506
+ """
507
+ Create field monitor
508
+
509
+ Args:
510
+ name: Monitor name
511
+ center_x, center_y, center_z: Monitor center position
512
+ size_x, size_y, size_z: Monitor size (use 0 for 2D plane)
513
+ plane: Monitor plane "xy", "yz", "xz" (lowercase)
514
+ record_ex, record_ey, record_ez: Record E-field components
515
+ record_hx, record_hy, record_hz: Record H-field components
516
+ unit: Unit of measurement
517
+ """
518
+ return {
519
+ "id": str(uuid.uuid4()),
520
+ "name": name,
521
+ "monitorType": "field",
522
+ "isEnabled": True,
523
+ "PositioningMode": "CenterAndSize",
524
+ "Position": {
525
+ "X": {"Value": center_x, "Unit": unit},
526
+ "Y": {"Value": center_y, "Unit": unit},
527
+ "Z": {"Value": center_z, "Unit": unit}
528
+ },
529
+ "Size": {
530
+ "Width": {"Value": size_x, "Unit": unit},
531
+ "Height": {"Value": size_y, "Unit": unit},
532
+ "Depth": {"Value": size_z, "Unit": unit}
533
+ },
534
+ "plane": plane,
535
+ "spatialSampling": {"x": 1, "y": 1, "z": 1},
536
+ "RecordEx": record_ex,
537
+ "RecordEy": record_ey,
538
+ "RecordEz": record_ez,
539
+ "RecordHx": record_hx,
540
+ "RecordHy": record_hy,
541
+ "RecordHz": record_hz
542
+ }
543
+
544
+
545
+ def create_flux_monitor(
546
+ name: str,
547
+ center_x: float,
548
+ center_y: float,
549
+ center_z: float,
550
+ size_x: float,
551
+ size_y: float,
552
+ size_z: float,
553
+ plane: str = "xy",
554
+ unit: str = "Micrometers"
555
+ ) -> Dict[str, Any]:
556
+ """
557
+ Create flux monitor
558
+
559
+ Args:
560
+ name: Monitor name
561
+ center_x, center_y, center_z: Monitor center position
562
+ size_x, size_y, size_z: Monitor size (use 0 for 2D plane)
563
+ plane: Monitor plane "xy", "yz", "xz" (lowercase)
564
+ unit: Unit of measurement
565
+ """
566
+ return {
567
+ "monitorType": "flux",
568
+ "Name": name,
569
+ "IsEnabled": True,
570
+ "PositioningMode": "CenterAndSize",
571
+ "Position": {
572
+ "X": {"Value": center_x, "Unit": unit},
573
+ "Y": {"Value": center_y, "Unit": unit},
574
+ "Z": {"Value": center_z, "Unit": unit}
575
+ },
576
+ "Size": {
577
+ "X": {"Value": size_x, "Unit": unit},
578
+ "Y": {"Value": size_y, "Unit": unit},
579
+ "Z": {"Value": size_z, "Unit": unit}
580
+ },
581
+ "Plane": plane
582
+ }
583
+
584
+
585
+ def create_permittivity_monitor(
586
+ name: str,
587
+ center_x: float,
588
+ center_y: float,
589
+ center_z: float,
590
+ size_x: float,
591
+ size_y: float,
592
+ size_z: float,
593
+ plane: str = "xy",
594
+ unit: str = "Micrometers"
595
+ ) -> Dict[str, Any]:
596
+ """
597
+ Create permittivity monitor
598
+
599
+ Args:
600
+ name: Monitor name
601
+ center_x, center_y, center_z: Monitor center position
602
+ size_x, size_y, size_z: Monitor size (use 0 for 2D plane)
603
+ plane: Monitor plane "xy", "yz", "xz" (lowercase)
604
+ unit: Unit of measurement
605
+ """
606
+ return {
607
+ "id": str(uuid.uuid4()),
608
+ "name": name,
609
+ "monitorType": "permittivity",
610
+ "isEnabled": True,
611
+ "PositioningMode": "CenterAndSize",
612
+ "Position": {
613
+ "X": {"Value": center_x, "Unit": unit},
614
+ "Y": {"Value": center_y, "Unit": unit},
615
+ "Z": {"Value": center_z, "Unit": unit}
616
+ },
617
+ "Size": {
618
+ "Width": {"Value": size_x, "Unit": unit},
619
+ "Height": {"Value": size_y, "Unit": unit},
620
+ "Depth": {"Value": size_z, "Unit": unit}
621
+ },
622
+ "plane": plane,
623
+ "spatialSampling": {"x": 1, "y": 1, "z": 1}
624
+ }
625
+
626
+
627
+ def create_wavesim_properties(
628
+ simulation_domain: Dict[str, Any],
629
+ primitives: List[Dict[str, Any]],
630
+ input_sources: List[Dict[str, Any]],
631
+ mediums: List[Dict[str, Any]],
632
+ monitors: List[Dict[str, Any]],
633
+ background_medium_id: str = "",
634
+ wavelength: float = 1.55,
635
+ voxel_size: float = 0.05,
636
+ wavelength_unit: str = "Micrometers",
637
+ voxel_unit: str = "Micrometers",
638
+ boundary_size_um: float = 0.0,
639
+ residual_norm_threshold: float = 1.0e-6,
640
+ residual_norm_max_iterations: int = 10000,
641
+ periodic_x: bool = False,
642
+ periodic_y: bool = False,
643
+ periodic_z: bool = False
644
+ ) -> Dict[str, Any]:
645
+ """
646
+ Create complete WavesimProperties structure
647
+
648
+ This is the top-level structure that should be serialized to JSON
649
+ and passed as argumentsJson to create_command().
650
+
651
+ Args:
652
+ simulation_domain: Domain from create_simulation_domain()
653
+ primitives: List of primitive structures
654
+ input_sources: List of source structures
655
+ mediums: List of medium structures
656
+ monitors: List of monitor structures
657
+ background_medium_id: ID of background medium
658
+ wavelength: Wavelength value
659
+ voxel_size: Voxel size value
660
+ wavelength_unit: Wavelength unit (default: 'Micrometers')
661
+ voxel_unit: Voxel size unit (default: 'Micrometers')
662
+ boundary_size_um: Boundary size in micrometers
663
+ residual_norm_threshold: Convergence threshold
664
+ residual_norm_max_iterations: Max iterations
665
+ periodic_x, periodic_y, periodic_z: Periodic boundary conditions
666
+ """
667
+ return {
668
+ "SimulationDomain": simulation_domain,
669
+ "BackgroundMediumId": background_medium_id,
670
+ "Primitives": primitives,
671
+ "InputSources": input_sources,
672
+ "Mediums": mediums,
673
+ "Monitors": monitors,
674
+ "Wavelength": {
675
+ "Value": wavelength,
676
+ "Unit": wavelength_unit
677
+ },
678
+ "VoxelSize": {
679
+ "Value": voxel_size,
680
+ "Unit": voxel_unit
681
+ },
682
+ "BoundarySize": {
683
+ "Value": boundary_size_um,
684
+ "Unit": "Micrometers"
685
+ },
686
+ "ResidualNormThreshold": residual_norm_threshold,
687
+ "ResidualNormMaxIterations": residual_norm_max_iterations,
688
+ "PeriodicX": periodic_x,
689
+ "PeriodicY": periodic_y,
690
+ "PeriodicZ": periodic_z,
691
+ "DefaultLengthUnit": "Micrometers",
692
+ "CoordinateSystemCenter": {
693
+ "X": {"Value": 0.0, "Unit": "Micrometers"},
694
+ "Y": {"Value": 0.0, "Unit": "Micrometers"},
695
+ "Z": {"Value": 0.0, "Unit": "Micrometers"}
696
+ }
697
+ }
698
+
699
+
700
+ def parse_progress_data(progress_obj: Optional[Dict[str, Any]]) -> Dict[str, Any]:
701
+ """
702
+ Parse CommandProgressDto with nested JSON strings.
703
+
704
+ Args:
705
+ progress_obj: CommandProgressDto dict from get_command_status()
706
+
707
+ Returns:
708
+ Dict with parsed progress fields: status, iteration, percentage,
709
+ max_iterations, residual_norm, threshold
710
+ """
711
+ info: Dict[str, Any] = {}
712
+
713
+ if not progress_obj:
714
+ return info
715
+
716
+ if "status" in progress_obj:
717
+ info["status"] = progress_obj["status"]
718
+
719
+ # Parse Progress JSON string
720
+ progress_str = progress_obj.get("progress")
721
+ if progress_str:
722
+ try:
723
+ if isinstance(progress_str, str):
724
+ import json as _json
725
+ progress_data = _json.loads(progress_str)
726
+ else:
727
+ progress_data = progress_str
728
+
729
+ if "iteration" in progress_data:
730
+ info["iteration"] = progress_data["iteration"]
731
+ if "percentage" in progress_data:
732
+ info["percentage"] = progress_data["percentage"]
733
+ if "max_iterations" in progress_data:
734
+ info["max_iterations"] = progress_data["max_iterations"]
735
+ except (json.JSONDecodeError, TypeError, ValueError):
736
+ pass
737
+
738
+ # Parse Data JSON string
739
+ data_str = progress_obj.get("data")
740
+ if data_str:
741
+ try:
742
+ if isinstance(data_str, str):
743
+ import json as _json
744
+ data_obj = _json.loads(data_str)
745
+ else:
746
+ data_obj = data_str
747
+
748
+ if "residual_norm" in data_obj:
749
+ info["residual_norm"] = data_obj["residual_norm"]
750
+ if "residual_norm_threshold" in data_obj:
751
+ info["threshold"] = data_obj["residual_norm_threshold"]
752
+ except (json.JSONDecodeError, TypeError, ValueError):
753
+ pass
754
+
755
+ return info