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.
- gsim/__init__.py +1 -1
- gsim/common/__init__.py +9 -13
- gsim/common/stack/extractor.py +4 -4
- gsim/common/stack/materials.py +2 -2
- gsim/common/stack/visualization.py +3 -3
- gsim/gcloud.py +80 -25
- gsim/palace/__init__.py +53 -64
- gsim/palace/base.py +313 -8
- gsim/palace/driven.py +26 -302
- gsim/palace/eigenmode.py +44 -264
- gsim/palace/electrostatic.py +35 -259
- gsim/palace/mesh/__init__.py +13 -1
- gsim/palace/mesh/config_generator.py +367 -0
- gsim/palace/mesh/generator.py +40 -899
- gsim/palace/mesh/geometry.py +472 -0
- gsim/palace/mesh/groups.py +170 -0
- gsim/palace/models/__init__.py +8 -15
- gsim/palace/models/mesh.py +9 -9
- gsim/palace/models/numerical.py +9 -9
- gsim/palace/models/ports.py +4 -5
- gsim/palace/models/problems.py +1 -1
- gsim/palace/models/results.py +5 -4
- gsim/viz.py +9 -6
- {gsim-0.0.2.dist-info → gsim-0.0.4.dist-info}/METADATA +7 -6
- gsim-0.0.4.dist-info/RECORD +35 -0
- {gsim-0.0.2.dist-info → gsim-0.0.4.dist-info}/WHEEL +1 -1
- gsim-0.0.2.dist-info/RECORD +0 -32
- {gsim-0.0.2.dist-info → gsim-0.0.4.dist-info}/top_level.txt +0 -0
gsim/palace/driven.py
CHANGED
|
@@ -8,11 +8,8 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
import tempfile
|
|
11
|
-
import warnings
|
|
12
11
|
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
|
-
from typing import TYPE_CHECKING, Any, Literal
|
|
12
|
+
from typing import Any, Literal
|
|
16
13
|
|
|
17
14
|
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
|
|
18
15
|
|
|
@@ -29,8 +26,7 @@ from gsim.palace.models import (
|
|
|
29
26
|
ValidationResult,
|
|
30
27
|
)
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
from gdsfactory.component import Component
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
34
30
|
|
|
35
31
|
|
|
36
32
|
class DrivenSim(PalaceSimMixin, BaseModel):
|
|
@@ -95,91 +91,6 @@ class DrivenSim(PalaceSimMixin, BaseModel):
|
|
|
95
91
|
_last_mesh_result: Any = PrivateAttr(default=None)
|
|
96
92
|
_last_ports: list = PrivateAttr(default_factory=list)
|
|
97
93
|
|
|
98
|
-
# -------------------------------------------------------------------------
|
|
99
|
-
# Output directory
|
|
100
|
-
# -------------------------------------------------------------------------
|
|
101
|
-
|
|
102
|
-
def set_output_dir(self, path: str | Path) -> None:
|
|
103
|
-
"""Set the output directory for mesh and config files.
|
|
104
|
-
|
|
105
|
-
Args:
|
|
106
|
-
path: Directory path for output files
|
|
107
|
-
|
|
108
|
-
Example:
|
|
109
|
-
>>> sim.set_output_dir("./palace-sim")
|
|
110
|
-
"""
|
|
111
|
-
self._output_dir = Path(path)
|
|
112
|
-
self._output_dir.mkdir(parents=True, exist_ok=True)
|
|
113
|
-
|
|
114
|
-
@property
|
|
115
|
-
def output_dir(self) -> Path | None:
|
|
116
|
-
"""Get the current output directory."""
|
|
117
|
-
return self._output_dir
|
|
118
|
-
|
|
119
|
-
# -------------------------------------------------------------------------
|
|
120
|
-
# Geometry methods
|
|
121
|
-
# -------------------------------------------------------------------------
|
|
122
|
-
|
|
123
|
-
def set_geometry(self, component: Component) -> None:
|
|
124
|
-
"""Set the gdsfactory component for simulation.
|
|
125
|
-
|
|
126
|
-
Args:
|
|
127
|
-
component: gdsfactory Component to simulate
|
|
128
|
-
|
|
129
|
-
Example:
|
|
130
|
-
>>> sim.set_geometry(my_component)
|
|
131
|
-
"""
|
|
132
|
-
self.geometry = Geometry(component=component)
|
|
133
|
-
|
|
134
|
-
@property
|
|
135
|
-
def component(self) -> Component | None:
|
|
136
|
-
"""Get the current component (for backward compatibility)."""
|
|
137
|
-
return self.geometry.component if self.geometry else None
|
|
138
|
-
|
|
139
|
-
# Backward compatibility alias
|
|
140
|
-
@property
|
|
141
|
-
def _component(self) -> Component | None:
|
|
142
|
-
"""Internal component access (backward compatibility)."""
|
|
143
|
-
return self.component
|
|
144
|
-
|
|
145
|
-
# -------------------------------------------------------------------------
|
|
146
|
-
# Stack methods
|
|
147
|
-
# -------------------------------------------------------------------------
|
|
148
|
-
|
|
149
|
-
def set_stack(
|
|
150
|
-
self,
|
|
151
|
-
*,
|
|
152
|
-
yaml_path: str | Path | None = None,
|
|
153
|
-
air_above: float = 200.0,
|
|
154
|
-
substrate_thickness: float = 2.0,
|
|
155
|
-
include_substrate: bool = False,
|
|
156
|
-
**kwargs,
|
|
157
|
-
) -> None:
|
|
158
|
-
"""Configure the layer stack.
|
|
159
|
-
|
|
160
|
-
If yaml_path is provided, loads stack from YAML file.
|
|
161
|
-
Otherwise, extracts from active PDK with given parameters.
|
|
162
|
-
|
|
163
|
-
Args:
|
|
164
|
-
yaml_path: Path to custom YAML stack file
|
|
165
|
-
air_above: Air box height above top metal in um
|
|
166
|
-
substrate_thickness: Thickness below z=0 in um
|
|
167
|
-
include_substrate: Include lossy silicon substrate
|
|
168
|
-
**kwargs: Additional args passed to extract_layer_stack
|
|
169
|
-
|
|
170
|
-
Example:
|
|
171
|
-
>>> sim.set_stack(air_above=300.0, substrate_thickness=2.0)
|
|
172
|
-
"""
|
|
173
|
-
self._stack_kwargs = {
|
|
174
|
-
"yaml_path": yaml_path,
|
|
175
|
-
"air_above": air_above,
|
|
176
|
-
"substrate_thickness": substrate_thickness,
|
|
177
|
-
"include_substrate": include_substrate,
|
|
178
|
-
**kwargs,
|
|
179
|
-
}
|
|
180
|
-
# Stack will be resolved lazily during mesh() or simulate()
|
|
181
|
-
self.stack = None
|
|
182
|
-
|
|
183
94
|
# -------------------------------------------------------------------------
|
|
184
95
|
# Port methods
|
|
185
96
|
# -------------------------------------------------------------------------
|
|
@@ -210,7 +121,9 @@ class DrivenSim(PalaceSimMixin, BaseModel):
|
|
|
210
121
|
|
|
211
122
|
Example:
|
|
212
123
|
>>> sim.add_port("o1", layer="topmetal2", length=5.0)
|
|
213
|
-
>>> sim.add_port(
|
|
124
|
+
>>> sim.add_port(
|
|
125
|
+
... "feed", from_layer="metal1", to_layer="topmetal2", geometry="via"
|
|
126
|
+
... )
|
|
214
127
|
"""
|
|
215
128
|
# Remove existing config for this port if any
|
|
216
129
|
self.ports = [p for p in self.ports if p.name != name]
|
|
@@ -318,88 +231,11 @@ class DrivenSim(PalaceSimMixin, BaseModel):
|
|
|
318
231
|
excitation_port=excitation_port,
|
|
319
232
|
)
|
|
320
233
|
|
|
321
|
-
# -------------------------------------------------------------------------
|
|
322
|
-
# Material methods
|
|
323
|
-
# -------------------------------------------------------------------------
|
|
324
|
-
|
|
325
|
-
def set_material(
|
|
326
|
-
self,
|
|
327
|
-
name: str,
|
|
328
|
-
*,
|
|
329
|
-
type: Literal["conductor", "dielectric", "semiconductor"] | None = None,
|
|
330
|
-
conductivity: float | None = None,
|
|
331
|
-
permittivity: float | None = None,
|
|
332
|
-
loss_tangent: float | None = None,
|
|
333
|
-
) -> None:
|
|
334
|
-
"""Override or add material properties.
|
|
335
|
-
|
|
336
|
-
Args:
|
|
337
|
-
name: Material name
|
|
338
|
-
type: Material type (conductor, dielectric, semiconductor)
|
|
339
|
-
conductivity: Conductivity in S/m (for conductors)
|
|
340
|
-
permittivity: Relative permittivity (for dielectrics)
|
|
341
|
-
loss_tangent: Dielectric loss tangent
|
|
342
|
-
|
|
343
|
-
Example:
|
|
344
|
-
>>> sim.set_material("aluminum", type="conductor", conductivity=3.8e7)
|
|
345
|
-
>>> sim.set_material("sio2", type="dielectric", permittivity=3.9)
|
|
346
|
-
"""
|
|
347
|
-
# Determine type if not provided
|
|
348
|
-
if type is None:
|
|
349
|
-
if conductivity is not None and conductivity > 1e4:
|
|
350
|
-
type = "conductor"
|
|
351
|
-
elif permittivity is not None:
|
|
352
|
-
type = "dielectric"
|
|
353
|
-
else:
|
|
354
|
-
type = "dielectric"
|
|
355
|
-
|
|
356
|
-
self.materials[name] = MaterialConfig(
|
|
357
|
-
type=type,
|
|
358
|
-
conductivity=conductivity,
|
|
359
|
-
permittivity=permittivity,
|
|
360
|
-
loss_tangent=loss_tangent,
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
def set_numerical(
|
|
364
|
-
self,
|
|
365
|
-
*,
|
|
366
|
-
order: int = 2,
|
|
367
|
-
tolerance: float = 1e-6,
|
|
368
|
-
max_iterations: int = 400,
|
|
369
|
-
solver_type: Literal["Default", "SuperLU", "STRUMPACK", "MUMPS"] = "Default",
|
|
370
|
-
preconditioner: Literal["Default", "AMS", "BoomerAMG"] = "Default",
|
|
371
|
-
device: Literal["CPU", "GPU"] = "CPU",
|
|
372
|
-
num_processors: int | None = None,
|
|
373
|
-
) -> None:
|
|
374
|
-
"""Configure numerical solver parameters.
|
|
375
|
-
|
|
376
|
-
Args:
|
|
377
|
-
order: Finite element order (1-4)
|
|
378
|
-
tolerance: Linear solver tolerance
|
|
379
|
-
max_iterations: Maximum solver iterations
|
|
380
|
-
solver_type: Linear solver type
|
|
381
|
-
preconditioner: Preconditioner type
|
|
382
|
-
device: Compute device (CPU or GPU)
|
|
383
|
-
num_processors: Number of processors (None = auto)
|
|
384
|
-
|
|
385
|
-
Example:
|
|
386
|
-
>>> sim.set_numerical(order=3, tolerance=1e-8)
|
|
387
|
-
"""
|
|
388
|
-
self.numerical = NumericalConfig(
|
|
389
|
-
order=order,
|
|
390
|
-
tolerance=tolerance,
|
|
391
|
-
max_iterations=max_iterations,
|
|
392
|
-
solver_type=solver_type,
|
|
393
|
-
preconditioner=preconditioner,
|
|
394
|
-
device=device,
|
|
395
|
-
num_processors=num_processors,
|
|
396
|
-
)
|
|
397
|
-
|
|
398
234
|
# -------------------------------------------------------------------------
|
|
399
235
|
# Validation
|
|
400
236
|
# -------------------------------------------------------------------------
|
|
401
237
|
|
|
402
|
-
def
|
|
238
|
+
def validate_config(self) -> ValidationResult:
|
|
403
239
|
"""Validate the simulation configuration.
|
|
404
240
|
|
|
405
241
|
Returns:
|
|
@@ -428,22 +264,21 @@ class DrivenSim(PalaceSimMixin, BaseModel):
|
|
|
428
264
|
# Validate port configurations
|
|
429
265
|
for port in self.ports:
|
|
430
266
|
if port.geometry == "inplane" and port.layer is None:
|
|
267
|
+
errors.append(f"Port '{port.name}': inplane ports require 'layer'")
|
|
268
|
+
if port.geometry == "via" and (
|
|
269
|
+
port.from_layer is None or port.to_layer is None
|
|
270
|
+
):
|
|
431
271
|
errors.append(
|
|
432
|
-
f"Port '{port.name}':
|
|
272
|
+
f"Port '{port.name}': via ports require "
|
|
273
|
+
"'from_layer' and 'to_layer'"
|
|
433
274
|
)
|
|
434
|
-
if port.geometry == "via":
|
|
435
|
-
if port.from_layer is None or port.to_layer is None:
|
|
436
|
-
errors.append(
|
|
437
|
-
f"Port '{port.name}': via ports require "
|
|
438
|
-
"'from_layer' and 'to_layer'"
|
|
439
|
-
)
|
|
440
275
|
|
|
441
276
|
# Validate CPW ports
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
277
|
+
errors.extend(
|
|
278
|
+
f"CPW port ({cpw.upper}, {cpw.lower}): 'layer' is required"
|
|
279
|
+
for cpw in self.cpw_ports
|
|
280
|
+
if not cpw.layer
|
|
281
|
+
)
|
|
447
282
|
|
|
448
283
|
# Validate excitation port if specified
|
|
449
284
|
if self.driven.excitation_port is not None:
|
|
@@ -463,30 +298,7 @@ class DrivenSim(PalaceSimMixin, BaseModel):
|
|
|
463
298
|
# Internal helpers
|
|
464
299
|
# -------------------------------------------------------------------------
|
|
465
300
|
|
|
466
|
-
def
|
|
467
|
-
"""Resolve the layer stack from PDK or YAML.
|
|
468
|
-
|
|
469
|
-
Returns:
|
|
470
|
-
Legacy LayerStack object for mesh generation
|
|
471
|
-
"""
|
|
472
|
-
from gsim.common.stack import get_stack
|
|
473
|
-
|
|
474
|
-
yaml_path = self._stack_kwargs.pop("yaml_path", None)
|
|
475
|
-
legacy_stack = get_stack(yaml_path=yaml_path, **self._stack_kwargs)
|
|
476
|
-
|
|
477
|
-
# Restore yaml_path for potential re-resolution
|
|
478
|
-
self._stack_kwargs["yaml_path"] = yaml_path
|
|
479
|
-
|
|
480
|
-
# Apply material overrides
|
|
481
|
-
for name, props in self.materials.items():
|
|
482
|
-
legacy_stack.materials[name] = props.to_dict()
|
|
483
|
-
|
|
484
|
-
# Store the LayerStack
|
|
485
|
-
self.stack = legacy_stack
|
|
486
|
-
|
|
487
|
-
return legacy_stack
|
|
488
|
-
|
|
489
|
-
def _configure_ports_on_component(self, stack: LayerStack) -> None:
|
|
301
|
+
def _configure_ports_on_component(self, stack: LayerStack) -> None: # noqa: ARG002
|
|
490
302
|
"""Configure ports on the component using legacy functions."""
|
|
491
303
|
from gsim.palace.ports import (
|
|
492
304
|
configure_cpw_port,
|
|
@@ -516,7 +328,7 @@ class DrivenSim(PalaceSimMixin, BaseModel):
|
|
|
516
328
|
f"Available ports: {[p.name for p in component.ports]}"
|
|
517
329
|
)
|
|
518
330
|
|
|
519
|
-
if port_config.geometry == "inplane":
|
|
331
|
+
if port_config.geometry == "inplane" and port_config.layer is not None:
|
|
520
332
|
configure_inplane_port(
|
|
521
333
|
gf_port,
|
|
522
334
|
layer=port_config.layer,
|
|
@@ -524,7 +336,9 @@ class DrivenSim(PalaceSimMixin, BaseModel):
|
|
|
524
336
|
impedance=port_config.impedance,
|
|
525
337
|
excited=port_config.excited,
|
|
526
338
|
)
|
|
527
|
-
elif port_config.geometry == "via"
|
|
339
|
+
elif port_config.geometry == "via" and (
|
|
340
|
+
port_config.from_layer is not None and port_config.to_layer is not None
|
|
341
|
+
):
|
|
528
342
|
configure_via_port(
|
|
529
343
|
gf_port,
|
|
530
344
|
from_layer=port_config.from_layer,
|
|
@@ -571,60 +385,6 @@ class DrivenSim(PalaceSimMixin, BaseModel):
|
|
|
571
385
|
|
|
572
386
|
self._configured_ports = True
|
|
573
387
|
|
|
574
|
-
def _build_mesh_config(
|
|
575
|
-
self,
|
|
576
|
-
preset: Literal["coarse", "default", "fine"] | None,
|
|
577
|
-
refined_mesh_size: float | None,
|
|
578
|
-
max_mesh_size: float | None,
|
|
579
|
-
margin: float | None,
|
|
580
|
-
air_above: float | None,
|
|
581
|
-
fmax: float | None,
|
|
582
|
-
show_gui: bool,
|
|
583
|
-
) -> MeshConfig:
|
|
584
|
-
"""Build mesh config from preset with optional overrides."""
|
|
585
|
-
# Build mesh config from preset
|
|
586
|
-
if preset == "coarse":
|
|
587
|
-
mesh_config = MeshConfig.coarse()
|
|
588
|
-
elif preset == "fine":
|
|
589
|
-
mesh_config = MeshConfig.fine()
|
|
590
|
-
else:
|
|
591
|
-
mesh_config = MeshConfig.default()
|
|
592
|
-
|
|
593
|
-
# Track overrides for warning
|
|
594
|
-
overrides = []
|
|
595
|
-
if preset is not None:
|
|
596
|
-
if refined_mesh_size is not None:
|
|
597
|
-
overrides.append(f"refined_mesh_size={refined_mesh_size}")
|
|
598
|
-
if max_mesh_size is not None:
|
|
599
|
-
overrides.append(f"max_mesh_size={max_mesh_size}")
|
|
600
|
-
if margin is not None:
|
|
601
|
-
overrides.append(f"margin={margin}")
|
|
602
|
-
if air_above is not None:
|
|
603
|
-
overrides.append(f"air_above={air_above}")
|
|
604
|
-
if fmax is not None:
|
|
605
|
-
overrides.append(f"fmax={fmax}")
|
|
606
|
-
|
|
607
|
-
if overrides:
|
|
608
|
-
warnings.warn(
|
|
609
|
-
f"Preset '{preset}' values overridden by: {', '.join(overrides)}",
|
|
610
|
-
stacklevel=4,
|
|
611
|
-
)
|
|
612
|
-
|
|
613
|
-
# Apply overrides
|
|
614
|
-
if refined_mesh_size is not None:
|
|
615
|
-
mesh_config.refined_mesh_size = refined_mesh_size
|
|
616
|
-
if max_mesh_size is not None:
|
|
617
|
-
mesh_config.max_mesh_size = max_mesh_size
|
|
618
|
-
if margin is not None:
|
|
619
|
-
mesh_config.margin = margin
|
|
620
|
-
if air_above is not None:
|
|
621
|
-
mesh_config.air_above = air_above
|
|
622
|
-
if fmax is not None:
|
|
623
|
-
mesh_config.fmax = fmax
|
|
624
|
-
mesh_config.show_gui = show_gui
|
|
625
|
-
|
|
626
|
-
return mesh_config
|
|
627
|
-
|
|
628
388
|
def _generate_mesh_internal(
|
|
629
389
|
self,
|
|
630
390
|
output_dir: Path,
|
|
@@ -731,11 +491,9 @@ class DrivenSim(PalaceSimMixin, BaseModel):
|
|
|
731
491
|
component = self.geometry.component if self.geometry else None
|
|
732
492
|
|
|
733
493
|
# Validate configuration
|
|
734
|
-
validation = self.
|
|
494
|
+
validation = self.validate_config()
|
|
735
495
|
if not validation.valid:
|
|
736
|
-
raise ValueError(
|
|
737
|
-
f"Invalid configuration:\n" + "\n".join(validation.errors)
|
|
738
|
-
)
|
|
496
|
+
raise ValueError("Invalid configuration:\n" + "\n".join(validation.errors))
|
|
739
497
|
|
|
740
498
|
# Build mesh config
|
|
741
499
|
mesh_config = self._build_mesh_config(
|
|
@@ -776,38 +534,6 @@ class DrivenSim(PalaceSimMixin, BaseModel):
|
|
|
776
534
|
config=legacy_mesh_config,
|
|
777
535
|
)
|
|
778
536
|
|
|
779
|
-
# -------------------------------------------------------------------------
|
|
780
|
-
# Convenience methods
|
|
781
|
-
# -------------------------------------------------------------------------
|
|
782
|
-
|
|
783
|
-
def show_stack(self) -> None:
|
|
784
|
-
"""Print the layer stack table.
|
|
785
|
-
|
|
786
|
-
Example:
|
|
787
|
-
>>> sim.show_stack()
|
|
788
|
-
"""
|
|
789
|
-
from gsim.common.stack import print_stack_table
|
|
790
|
-
|
|
791
|
-
if self.stack is None:
|
|
792
|
-
self._resolve_stack()
|
|
793
|
-
|
|
794
|
-
if self.stack is not None:
|
|
795
|
-
print_stack_table(self.stack)
|
|
796
|
-
|
|
797
|
-
def plot_stack(self) -> None:
|
|
798
|
-
"""Plot the layer stack visualization.
|
|
799
|
-
|
|
800
|
-
Example:
|
|
801
|
-
>>> sim.plot_stack()
|
|
802
|
-
"""
|
|
803
|
-
from gsim.common.stack import plot_stack
|
|
804
|
-
|
|
805
|
-
if self.stack is None:
|
|
806
|
-
self._resolve_stack()
|
|
807
|
-
|
|
808
|
-
if self.stack is not None:
|
|
809
|
-
plot_stack(self.stack)
|
|
810
|
-
|
|
811
537
|
# -------------------------------------------------------------------------
|
|
812
538
|
# Mesh generation
|
|
813
539
|
# -------------------------------------------------------------------------
|
|
@@ -873,11 +599,9 @@ class DrivenSim(PalaceSimMixin, BaseModel):
|
|
|
873
599
|
)
|
|
874
600
|
|
|
875
601
|
# Validate configuration
|
|
876
|
-
validation = self.
|
|
602
|
+
validation = self.validate_config()
|
|
877
603
|
if not validation.valid:
|
|
878
|
-
raise ValueError(
|
|
879
|
-
f"Invalid configuration:\n" + "\n".join(validation.errors)
|
|
880
|
-
)
|
|
604
|
+
raise ValueError("Invalid configuration:\n" + "\n".join(validation.errors))
|
|
881
605
|
|
|
882
606
|
output_dir = self._output_dir
|
|
883
607
|
|