gsim 0.0.2__py3-none-any.whl → 0.0.4__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,472 @@
1
+ """Geometry extraction and creation for Palace mesh generation.
2
+
3
+ This module handles extracting polygons from gdsfactory components
4
+ and creating 3D geometry in gmsh.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ from dataclasses import dataclass
11
+ from typing import TYPE_CHECKING
12
+
13
+ from . import gmsh_utils
14
+
15
+ if TYPE_CHECKING:
16
+ from gsim.common.stack import LayerStack
17
+ from gsim.palace.ports.config import PalacePort
18
+
19
+
20
+ @dataclass
21
+ class GeometryData:
22
+ """Container for geometry data extracted from component."""
23
+
24
+ polygons: list # List of (layer_num, pts_x, pts_y) tuples
25
+ bbox: tuple[float, float, float, float] # (xmin, ymin, xmax, ymax)
26
+ layer_bboxes: dict # layer_num -> (xmin, ymin, xmax, ymax)
27
+
28
+
29
+ def extract_geometry(component, stack: LayerStack) -> GeometryData:
30
+ """Extract polygon geometry from a gdsfactory component.
31
+
32
+ Args:
33
+ component: gdsfactory Component
34
+ stack: LayerStack for layer mapping
35
+
36
+ Returns:
37
+ GeometryData with polygons and bounding boxes
38
+ """
39
+ polygons = []
40
+ global_bbox = [math.inf, math.inf, -math.inf, -math.inf]
41
+ layer_bboxes = {}
42
+
43
+ # Get polygons from component
44
+ polygons_by_index = component.get_polygons()
45
+
46
+ # Build layer_index -> GDS tuple mapping
47
+ layout = component.kcl.layout
48
+ index_to_gds = {}
49
+ for layer_index in range(layout.layers()):
50
+ if layout.is_valid_layer(layer_index):
51
+ info = layout.get_info(layer_index)
52
+ index_to_gds[layer_index] = (info.layer, info.datatype)
53
+
54
+ # Build GDS tuple -> layer number mapping
55
+ gds_to_layernum = {}
56
+ for layer_data in stack.layers.values():
57
+ gds_tuple = tuple(layer_data.gds_layer)
58
+ gds_to_layernum[gds_tuple] = gds_tuple[0]
59
+
60
+ # Convert polygons
61
+ for layer_index, polys in polygons_by_index.items():
62
+ gds_tuple = index_to_gds.get(layer_index)
63
+ if gds_tuple is None:
64
+ continue
65
+
66
+ layernum = gds_to_layernum.get(gds_tuple)
67
+ if layernum is None:
68
+ continue
69
+
70
+ for poly in polys:
71
+ # Convert klayout polygon to lists (nm -> um)
72
+ points = list(poly.each_point_hull())
73
+ if len(points) < 3:
74
+ continue
75
+
76
+ pts_x = [pt.x / 1000.0 for pt in points]
77
+ pts_y = [pt.y / 1000.0 for pt in points]
78
+
79
+ polygons.append((layernum, pts_x, pts_y))
80
+
81
+ # Update bounding boxes
82
+ xmin, xmax = min(pts_x), max(pts_x)
83
+ ymin, ymax = min(pts_y), max(pts_y)
84
+
85
+ global_bbox[0] = min(global_bbox[0], xmin)
86
+ global_bbox[1] = min(global_bbox[1], ymin)
87
+ global_bbox[2] = max(global_bbox[2], xmax)
88
+ global_bbox[3] = max(global_bbox[3], ymax)
89
+
90
+ if layernum not in layer_bboxes:
91
+ layer_bboxes[layernum] = [xmin, ymin, xmax, ymax]
92
+ else:
93
+ bbox = layer_bboxes[layernum]
94
+ bbox[0] = min(bbox[0], xmin)
95
+ bbox[1] = min(bbox[1], ymin)
96
+ bbox[2] = max(bbox[2], xmax)
97
+ bbox[3] = max(bbox[3], ymax)
98
+
99
+ return GeometryData(
100
+ polygons=polygons,
101
+ bbox=(global_bbox[0], global_bbox[1], global_bbox[2], global_bbox[3]),
102
+ layer_bboxes=layer_bboxes,
103
+ )
104
+
105
+
106
+ def get_layer_info(stack: LayerStack, gds_layer: int) -> dict | None:
107
+ """Get layer info from stack by GDS layer number.
108
+
109
+ Args:
110
+ stack: LayerStack with layer definitions
111
+ gds_layer: GDS layer number
112
+
113
+ Returns:
114
+ Dict with layer info or None if not found
115
+ """
116
+ for name, layer in stack.layers.items():
117
+ if layer.gds_layer[0] == gds_layer:
118
+ return {
119
+ "name": name,
120
+ "zmin": layer.zmin,
121
+ "zmax": layer.zmax,
122
+ "thickness": layer.zmax - layer.zmin,
123
+ "material": layer.material,
124
+ "type": layer.layer_type,
125
+ }
126
+ return None
127
+
128
+
129
+ def add_metals(
130
+ kernel,
131
+ geometry: GeometryData,
132
+ stack: LayerStack,
133
+ ) -> dict:
134
+ """Add metal and via geometries to gmsh.
135
+
136
+ Creates extruded volumes for vias and shells (surfaces) for conductors.
137
+
138
+ Args:
139
+ kernel: gmsh OCC kernel
140
+ geometry: Extracted geometry data
141
+ stack: LayerStack with layer definitions
142
+
143
+ Returns:
144
+ Dict with layer_name -> list of (surface_tags_xy, surface_tags_z) for
145
+ conductors, or volume_tags for vias.
146
+ """
147
+ # layer_name -> {"volumes": [], "surfaces_xy": [], "surfaces_z": []}
148
+ metal_tags = {}
149
+
150
+ # Group polygons by layer
151
+ polygons_by_layer = {}
152
+ for layernum, pts_x, pts_y in geometry.polygons:
153
+ if layernum not in polygons_by_layer:
154
+ polygons_by_layer[layernum] = []
155
+ polygons_by_layer[layernum].append((pts_x, pts_y))
156
+
157
+ # Process each layer
158
+ for layernum, polys in polygons_by_layer.items():
159
+ layer_info = get_layer_info(stack, layernum)
160
+ if layer_info is None:
161
+ continue
162
+
163
+ layer_name = layer_info["name"]
164
+ layer_type = layer_info["type"]
165
+ zmin = layer_info["zmin"]
166
+ thickness = layer_info["thickness"]
167
+
168
+ if layer_type not in ("conductor", "via"):
169
+ continue
170
+
171
+ if layer_name not in metal_tags:
172
+ metal_tags[layer_name] = {
173
+ "volumes": [],
174
+ "surfaces_xy": [],
175
+ "surfaces_z": [],
176
+ }
177
+
178
+ for pts_x, pts_y in polys:
179
+ # Create extruded polygon
180
+ surfacetag = gmsh_utils.create_polygon_surface(kernel, pts_x, pts_y, zmin)
181
+ if surfacetag is None:
182
+ continue
183
+
184
+ if thickness > 0:
185
+ result = kernel.extrude([(2, surfacetag)], 0, 0, thickness)
186
+ volumetag = result[1][1]
187
+
188
+ if layer_type == "via":
189
+ # Keep vias as volumes
190
+ metal_tags[layer_name]["volumes"].append(volumetag)
191
+ else:
192
+ # For conductors, get shell surfaces and remove volume
193
+ _, surfaceloops = kernel.getSurfaceLoops(volumetag)
194
+ if surfaceloops:
195
+ metal_tags[layer_name]["volumes"].append(
196
+ (volumetag, surfaceloops[0])
197
+ )
198
+ kernel.remove([(3, volumetag)])
199
+
200
+ kernel.removeAllDuplicates()
201
+ kernel.synchronize()
202
+
203
+ return metal_tags
204
+
205
+
206
+ def add_dielectrics(
207
+ kernel,
208
+ geometry: GeometryData,
209
+ stack: LayerStack,
210
+ margin: float,
211
+ air_margin: float,
212
+ ) -> dict:
213
+ """Add dielectric boxes and airbox to gmsh.
214
+
215
+ Args:
216
+ kernel: gmsh OCC kernel
217
+ geometry: Extracted geometry data
218
+ stack: LayerStack with dielectric definitions
219
+ margin: XY margin around design (um)
220
+ air_margin: Air box margin (um)
221
+
222
+ Returns:
223
+ Dict with material_name -> list of volume_tags
224
+ """
225
+ dielectric_tags = {}
226
+
227
+ # Get overall geometry bounds
228
+ xmin, ymin, xmax, ymax = geometry.bbox
229
+ xmin -= margin
230
+ ymin -= margin
231
+ xmax += margin
232
+ ymax += margin
233
+
234
+ # Track overall z range
235
+ z_min_all = math.inf
236
+ z_max_all = -math.inf
237
+
238
+ # Sort dielectrics by z (top to bottom for correct layering)
239
+ sorted_dielectrics = sorted(
240
+ stack.dielectrics, key=lambda d: d["zmax"], reverse=True
241
+ )
242
+
243
+ # Add dielectric boxes
244
+ offset = 0
245
+ offset_delta = margin / 20
246
+
247
+ for dielectric in sorted_dielectrics:
248
+ material = dielectric["material"]
249
+ d_zmin = dielectric["zmin"]
250
+ d_zmax = dielectric["zmax"]
251
+
252
+ z_min_all = min(z_min_all, d_zmin)
253
+ z_max_all = max(z_max_all, d_zmax)
254
+
255
+ if material not in dielectric_tags:
256
+ dielectric_tags[material] = []
257
+
258
+ # Create box with slight offset to avoid mesh issues
259
+ box_tag = gmsh_utils.create_box(
260
+ kernel,
261
+ xmin - offset,
262
+ ymin - offset,
263
+ d_zmin,
264
+ xmax + offset,
265
+ ymax + offset,
266
+ d_zmax,
267
+ )
268
+ dielectric_tags[material].append(box_tag)
269
+
270
+ # Alternate offset to avoid coincident faces
271
+ offset = offset_delta if offset == 0 else 0
272
+
273
+ # Add surrounding airbox
274
+ air_xmin = xmin - air_margin
275
+ air_ymin = ymin - air_margin
276
+ air_xmax = xmax + air_margin
277
+ air_ymax = ymax + air_margin
278
+ air_zmin = z_min_all - air_margin
279
+ air_zmax = z_max_all + air_margin
280
+
281
+ airbox_tag = kernel.addBox(
282
+ air_xmin,
283
+ air_ymin,
284
+ air_zmin,
285
+ air_xmax - air_xmin,
286
+ air_ymax - air_ymin,
287
+ air_zmax - air_zmin,
288
+ )
289
+ dielectric_tags["airbox"] = [airbox_tag]
290
+
291
+ kernel.synchronize()
292
+
293
+ return dielectric_tags
294
+
295
+
296
+ def add_ports(
297
+ kernel,
298
+ ports: list[PalacePort],
299
+ stack: LayerStack,
300
+ ) -> tuple[dict, list]:
301
+ """Add port surfaces to gmsh.
302
+
303
+ Args:
304
+ kernel: gmsh OCC kernel
305
+ ports: List of PalacePort objects (single or multi-element)
306
+ stack: Layer stack
307
+
308
+ Returns:
309
+ (port_tags dict, port_info list)
310
+
311
+ For single-element ports: port_tags["P{num}"] = [surface_tag]
312
+ For multi-element ports: port_tags["P{num}"] = [surface_tag, surface_tag, ...]
313
+ """
314
+ from gsim.palace.ports.config import PortGeometry
315
+
316
+ port_tags = {} # "P{num}" -> [surface_tag(s)]
317
+ port_info = []
318
+ port_num = 1
319
+
320
+ for port in ports:
321
+ if port.multi_element:
322
+ # Multi-element port (CPW)
323
+ if port.layer is None or port.centers is None or port.directions is None:
324
+ continue
325
+ target_layer = stack.layers.get(port.layer)
326
+ if target_layer is None:
327
+ continue
328
+
329
+ z = target_layer.zmin
330
+ hw = port.width / 2
331
+ hl = (port.length or port.width) / 2
332
+
333
+ # Determine axis from orientation
334
+ angle = port.orientation % 360
335
+ is_y_axis = 45 <= angle < 135 or 225 <= angle < 315
336
+
337
+ surfaces = []
338
+ for cx, cy in port.centers:
339
+ if is_y_axis:
340
+ surf = gmsh_utils.create_port_rectangle(
341
+ kernel, cx - hw, cy - hl, z, cx + hw, cy + hl, z
342
+ )
343
+ else:
344
+ surf = gmsh_utils.create_port_rectangle(
345
+ kernel, cx - hl, cy - hw, z, cx + hl, cy + hw, z
346
+ )
347
+ surfaces.append(surf)
348
+
349
+ port_tags[f"P{port_num}"] = surfaces
350
+
351
+ port_info.append(
352
+ {
353
+ "portnumber": port_num,
354
+ "Z0": port.impedance,
355
+ "type": "cpw",
356
+ "elements": [
357
+ {"surface_idx": i, "direction": port.directions[i]}
358
+ for i in range(len(port.centers))
359
+ ],
360
+ "width": port.width,
361
+ "length": port.length or port.width,
362
+ "zmin": z,
363
+ "zmax": z,
364
+ }
365
+ )
366
+
367
+ elif port.geometry == PortGeometry.VIA:
368
+ # Via port: vertical between two layers
369
+ if port.from_layer is None or port.to_layer is None:
370
+ continue
371
+ from_layer = stack.layers.get(port.from_layer)
372
+ to_layer = stack.layers.get(port.to_layer)
373
+ if from_layer is None or to_layer is None:
374
+ continue
375
+
376
+ x, y = port.center
377
+ hw = port.width / 2
378
+
379
+ if from_layer.zmin < to_layer.zmin:
380
+ zmin = from_layer.zmax
381
+ zmax = to_layer.zmin
382
+ else:
383
+ zmin = to_layer.zmax
384
+ zmax = from_layer.zmin
385
+
386
+ # Create vertical port surface
387
+ if port.direction in ("x", "-x"):
388
+ surfacetag = gmsh_utils.create_port_rectangle(
389
+ kernel, x, y - hw, zmin, x, y + hw, zmax
390
+ )
391
+ else:
392
+ surfacetag = gmsh_utils.create_port_rectangle(
393
+ kernel, x - hw, y, zmin, x + hw, y, zmax
394
+ )
395
+
396
+ port_tags[f"P{port_num}"] = [surfacetag]
397
+ port_info.append(
398
+ {
399
+ "portnumber": port_num,
400
+ "Z0": port.impedance,
401
+ "type": "via",
402
+ "direction": "Z",
403
+ "length": zmax - zmin,
404
+ "width": port.width,
405
+ "xmin": x - hw if port.direction in ("y", "-y") else x,
406
+ "xmax": x + hw if port.direction in ("y", "-y") else x,
407
+ "ymin": y - hw if port.direction in ("x", "-x") else y,
408
+ "ymax": y + hw if port.direction in ("x", "-x") else y,
409
+ "zmin": zmin,
410
+ "zmax": zmax,
411
+ }
412
+ )
413
+
414
+ else:
415
+ # Inplane port: horizontal on single layer
416
+ if port.layer is None:
417
+ continue
418
+ target_layer = stack.layers.get(port.layer)
419
+ if target_layer is None:
420
+ continue
421
+
422
+ x, y = port.center
423
+ hw = port.width / 2
424
+ z = target_layer.zmin
425
+
426
+ hl = (port.length or port.width) / 2
427
+ if port.direction in ("x", "-x"):
428
+ surfacetag = gmsh_utils.create_port_rectangle(
429
+ kernel, x - hl, y - hw, z, x + hl, y + hw, z
430
+ )
431
+ length = 2 * hl
432
+ width = port.width
433
+ else:
434
+ surfacetag = gmsh_utils.create_port_rectangle(
435
+ kernel, x - hw, y - hl, z, x + hw, y + hl, z
436
+ )
437
+ length = port.width
438
+ width = 2 * hl
439
+
440
+ port_tags[f"P{port_num}"] = [surfacetag]
441
+ port_info.append(
442
+ {
443
+ "portnumber": port_num,
444
+ "Z0": port.impedance,
445
+ "type": "lumped",
446
+ "direction": port.direction.upper(),
447
+ "length": length,
448
+ "width": width,
449
+ "xmin": x - hl if port.direction in ("x", "-x") else x - hw,
450
+ "xmax": x + hl if port.direction in ("x", "-x") else x + hw,
451
+ "ymin": y - hw if port.direction in ("x", "-x") else y - hl,
452
+ "ymax": y + hw if port.direction in ("x", "-x") else y + hl,
453
+ "zmin": z,
454
+ "zmax": z,
455
+ }
456
+ )
457
+
458
+ port_num += 1
459
+
460
+ kernel.synchronize()
461
+
462
+ return port_tags, port_info
463
+
464
+
465
+ __all__ = [
466
+ "GeometryData",
467
+ "add_dielectrics",
468
+ "add_metals",
469
+ "add_ports",
470
+ "extract_geometry",
471
+ "get_layer_info",
472
+ ]
@@ -0,0 +1,170 @@
1
+ """Physical group assignment for Palace mesh generation.
2
+
3
+ This module handles assigning gmsh physical groups after geometry fragmentation.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ from . import gmsh_utils
11
+
12
+ if TYPE_CHECKING:
13
+ from gsim.common.stack import LayerStack
14
+
15
+
16
+ def assign_physical_groups(
17
+ kernel,
18
+ metal_tags: dict,
19
+ dielectric_tags: dict,
20
+ port_tags: dict,
21
+ port_info: list,
22
+ geom_dimtags: list,
23
+ geom_map: list,
24
+ _stack: LayerStack,
25
+ ) -> dict:
26
+ """Assign physical groups after fragmenting.
27
+
28
+ Args:
29
+ kernel: gmsh OCC kernel
30
+ metal_tags: Metal layer tags from add_metals()
31
+ dielectric_tags: Dielectric material tags from add_dielectrics()
32
+ port_tags: Port surface tags (may have multiple surfaces for CPW)
33
+ port_info: Port metadata including type info
34
+ geom_dimtags: Dimension tags from fragmentation
35
+ geom_map: Geometry map from fragmentation
36
+ _stack: Layer stack (unused; reserved for future material metadata)
37
+
38
+ Returns:
39
+ Dict with group info for config file generation:
40
+ {
41
+ "volumes": {material_name: {"phys_group": int, "tags": [int]}},
42
+ "conductor_surfaces": {layer_name: {"phys_group": int, "tags": [int]}},
43
+ "port_surfaces": {port_name: {"phys_group": int, "tags": [int]} or
44
+ {"type": "cpw", "elements": [...]}},
45
+ "boundary_surfaces": {"absorbing": {"phys_group": int, "tags": [int]}}
46
+ }
47
+ """
48
+ groups = {
49
+ "volumes": {},
50
+ "conductor_surfaces": {},
51
+ "port_surfaces": {},
52
+ "boundary_surfaces": {},
53
+ }
54
+
55
+ # Assign volume groups for dielectrics
56
+ for material_name, tags in dielectric_tags.items():
57
+ new_tags = gmsh_utils.get_tags_after_fragment(
58
+ tags, geom_dimtags, geom_map, dimension=3
59
+ )
60
+ if new_tags:
61
+ # Only take first N tags (same as original count)
62
+ new_tags = new_tags[: len(tags)]
63
+ phys_group = gmsh_utils.assign_physical_group(3, new_tags, material_name)
64
+ groups["volumes"][material_name] = {
65
+ "phys_group": phys_group,
66
+ "tags": new_tags,
67
+ }
68
+
69
+ # Assign surface groups for conductors
70
+ for layer_name, tag_info in metal_tags.items():
71
+ if tag_info["volumes"]:
72
+ all_xy_tags = []
73
+ all_z_tags = []
74
+
75
+ for item in tag_info["volumes"]:
76
+ if isinstance(item, tuple):
77
+ _volumetag, surface_tags = item
78
+ # Get updated surface tags after fragment
79
+ new_surface_tags = gmsh_utils.get_tags_after_fragment(
80
+ surface_tags, geom_dimtags, geom_map, dimension=2
81
+ )
82
+
83
+ # Separate xy and z surfaces
84
+ for tag in new_surface_tags:
85
+ if gmsh_utils.is_vertical_surface(tag):
86
+ all_z_tags.append(tag)
87
+ else:
88
+ all_xy_tags.append(tag)
89
+
90
+ if all_xy_tags:
91
+ phys_group = gmsh_utils.assign_physical_group(
92
+ 2, all_xy_tags, f"{layer_name}_xy"
93
+ )
94
+ groups["conductor_surfaces"][f"{layer_name}_xy"] = {
95
+ "phys_group": phys_group,
96
+ "tags": all_xy_tags,
97
+ }
98
+
99
+ if all_z_tags:
100
+ phys_group = gmsh_utils.assign_physical_group(
101
+ 2, all_z_tags, f"{layer_name}_z"
102
+ )
103
+ groups["conductor_surfaces"][f"{layer_name}_z"] = {
104
+ "phys_group": phys_group,
105
+ "tags": all_z_tags,
106
+ }
107
+
108
+ # Assign port surface groups
109
+ for port_name, tags in port_tags.items():
110
+ # Find corresponding port_info entry
111
+ port_num = int(port_name[1:]) # "P1" -> 1
112
+ info = next((p for p in port_info if p["portnumber"] == port_num), None)
113
+
114
+ if info and info.get("type") == "cpw":
115
+ # CPW port: create separate physical group for each element
116
+ element_phys_groups = []
117
+ for i, tag in enumerate(tags):
118
+ new_tag_list = gmsh_utils.get_tags_after_fragment(
119
+ [tag], geom_dimtags, geom_map, dimension=2
120
+ )
121
+ if new_tag_list:
122
+ elem_name = f"{port_name}_E{i}"
123
+ phys_group = gmsh_utils.assign_physical_group(
124
+ 2, new_tag_list, elem_name
125
+ )
126
+ element_phys_groups.append(
127
+ {
128
+ "phys_group": phys_group,
129
+ "tags": new_tag_list,
130
+ "direction": info["elements"][i]["direction"],
131
+ }
132
+ )
133
+
134
+ groups["port_surfaces"][port_name] = {
135
+ "type": "cpw",
136
+ "elements": element_phys_groups,
137
+ }
138
+ else:
139
+ # Regular single-element port
140
+ new_tags = gmsh_utils.get_tags_after_fragment(
141
+ tags, geom_dimtags, geom_map, dimension=2
142
+ )
143
+ if new_tags:
144
+ phys_group = gmsh_utils.assign_physical_group(2, new_tags, port_name)
145
+ groups["port_surfaces"][port_name] = {
146
+ "phys_group": phys_group,
147
+ "tags": new_tags,
148
+ }
149
+
150
+ # Assign boundary surfaces (from airbox)
151
+ if "airbox" in groups["volumes"]:
152
+ airbox_tags = groups["volumes"]["airbox"]["tags"]
153
+ if airbox_tags:
154
+ _, simulation_boundary = kernel.getSurfaceLoops(airbox_tags[0])
155
+ if simulation_boundary:
156
+ boundary_tags = list(next(iter(simulation_boundary)))
157
+ phys_group = gmsh_utils.assign_physical_group(
158
+ 2, boundary_tags, "Absorbing_boundary"
159
+ )
160
+ groups["boundary_surfaces"]["absorbing"] = {
161
+ "phys_group": phys_group,
162
+ "tags": boundary_tags,
163
+ }
164
+
165
+ kernel.synchronize()
166
+
167
+ return groups
168
+
169
+
170
+ __all__ = ["assign_physical_groups"]
@@ -35,26 +35,19 @@ from gsim.palace.models.results import SimulationResult, ValidationResult
35
35
  from gsim.palace.models.stack import MaterialConfig
36
36
 
37
37
  __all__ = [
38
- # Geometry
39
- "GeometryConfig",
40
- # Stack
41
- "MaterialConfig",
42
- # Ports
43
38
  "CPWPortConfig",
44
- "PortConfig",
45
- "TerminalConfig",
46
- "WavePortConfig",
47
- # Mesh
48
- "MeshConfig",
49
- # Numerical
50
- "NumericalConfig",
51
- # Problems
52
39
  "DrivenConfig",
53
40
  "EigenmodeConfig",
54
41
  "ElectrostaticConfig",
42
+ "GeometryConfig",
55
43
  "MagnetostaticConfig",
56
- "TransientConfig",
57
- # Results
44
+ "MaterialConfig",
45
+ "MeshConfig",
46
+ "NumericalConfig",
47
+ "PortConfig",
58
48
  "SimulationResult",
49
+ "TerminalConfig",
50
+ "TransientConfig",
59
51
  "ValidationResult",
52
+ "WavePortConfig",
60
53
  ]