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/base.py
CHANGED
|
@@ -1,25 +1,332 @@
|
|
|
1
1
|
"""Base mixin for Palace simulation classes.
|
|
2
2
|
|
|
3
|
-
Provides common
|
|
3
|
+
Provides common methods shared across all simulation types:
|
|
4
|
+
DrivenSim, EigenmodeSim, ElectrostaticSim.
|
|
4
5
|
"""
|
|
5
6
|
|
|
6
7
|
from __future__ import annotations
|
|
7
8
|
|
|
9
|
+
import warnings
|
|
8
10
|
from pathlib import Path
|
|
9
|
-
from typing import TYPE_CHECKING
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
10
12
|
|
|
11
13
|
if TYPE_CHECKING:
|
|
12
|
-
|
|
14
|
+
from gdsfactory.component import Component
|
|
15
|
+
|
|
16
|
+
from gsim.common import Geometry, LayerStack
|
|
17
|
+
from gsim.palace.models import MaterialConfig, MeshConfig, NumericalConfig
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
class PalaceSimMixin:
|
|
16
21
|
"""Mixin providing common methods for all Palace simulation classes.
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
-
|
|
23
|
+
Subclasses must define these attributes (typically via Pydantic fields):
|
|
24
|
+
- geometry: Geometry | None
|
|
25
|
+
- stack: LayerStack | None
|
|
26
|
+
- materials: dict[str, MaterialConfig]
|
|
27
|
+
- numerical: NumericalConfig
|
|
28
|
+
- _output_dir: Path | None (private)
|
|
29
|
+
- _stack_kwargs: dict[str, Any] (private)
|
|
20
30
|
"""
|
|
21
31
|
|
|
32
|
+
# Type hints for required attributes (implemented by subclasses)
|
|
33
|
+
geometry: Geometry | None
|
|
34
|
+
stack: LayerStack | None
|
|
35
|
+
materials: dict[str, MaterialConfig]
|
|
36
|
+
numerical: NumericalConfig
|
|
22
37
|
_output_dir: Path | None
|
|
38
|
+
_stack_kwargs: dict[str, Any]
|
|
39
|
+
|
|
40
|
+
# -------------------------------------------------------------------------
|
|
41
|
+
# Output directory
|
|
42
|
+
# -------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
def set_output_dir(self, path: str | Path) -> None:
|
|
45
|
+
"""Set the output directory for mesh and config files.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
path: Directory path for output files
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> sim.set_output_dir("./palace-sim")
|
|
52
|
+
"""
|
|
53
|
+
self._output_dir = Path(path)
|
|
54
|
+
self._output_dir.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def output_dir(self) -> Path | None:
|
|
58
|
+
"""Get the current output directory."""
|
|
59
|
+
return self._output_dir
|
|
60
|
+
|
|
61
|
+
# -------------------------------------------------------------------------
|
|
62
|
+
# Geometry methods
|
|
63
|
+
# -------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
def set_geometry(self, component: Component) -> None:
|
|
66
|
+
"""Set the gdsfactory component for simulation.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
component: gdsfactory Component to simulate
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
>>> sim.set_geometry(my_component)
|
|
73
|
+
"""
|
|
74
|
+
from gsim.common import Geometry
|
|
75
|
+
|
|
76
|
+
self.geometry = Geometry(component=component)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def component(self) -> Component | None:
|
|
80
|
+
"""Get the current component (for backward compatibility)."""
|
|
81
|
+
return self.geometry.component if self.geometry else None
|
|
82
|
+
|
|
83
|
+
# Backward compatibility alias
|
|
84
|
+
@property
|
|
85
|
+
def _component(self) -> Component | None:
|
|
86
|
+
"""Internal component access (backward compatibility)."""
|
|
87
|
+
return self.component
|
|
88
|
+
|
|
89
|
+
# -------------------------------------------------------------------------
|
|
90
|
+
# Stack methods
|
|
91
|
+
# -------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
def set_stack(
|
|
94
|
+
self,
|
|
95
|
+
*,
|
|
96
|
+
yaml_path: str | Path | None = None,
|
|
97
|
+
air_above: float = 200.0,
|
|
98
|
+
substrate_thickness: float = 2.0,
|
|
99
|
+
include_substrate: bool = False,
|
|
100
|
+
**kwargs,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Configure the layer stack.
|
|
103
|
+
|
|
104
|
+
If yaml_path is provided, loads stack from YAML file.
|
|
105
|
+
Otherwise, extracts from active PDK with given parameters.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
yaml_path: Path to custom YAML stack file
|
|
109
|
+
air_above: Air box height above top metal in um
|
|
110
|
+
substrate_thickness: Thickness below z=0 in um
|
|
111
|
+
include_substrate: Include lossy silicon substrate
|
|
112
|
+
**kwargs: Additional args passed to extract_layer_stack
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
>>> sim.set_stack(air_above=300.0, substrate_thickness=2.0)
|
|
116
|
+
"""
|
|
117
|
+
self._stack_kwargs = {
|
|
118
|
+
"yaml_path": yaml_path,
|
|
119
|
+
"air_above": air_above,
|
|
120
|
+
"substrate_thickness": substrate_thickness,
|
|
121
|
+
"include_substrate": include_substrate,
|
|
122
|
+
**kwargs,
|
|
123
|
+
}
|
|
124
|
+
# Stack will be resolved lazily during mesh() or simulate()
|
|
125
|
+
self.stack = None
|
|
126
|
+
|
|
127
|
+
# -------------------------------------------------------------------------
|
|
128
|
+
# Material methods
|
|
129
|
+
# -------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
def set_material(
|
|
132
|
+
self,
|
|
133
|
+
name: str,
|
|
134
|
+
*,
|
|
135
|
+
material_type: Literal["conductor", "dielectric", "semiconductor"]
|
|
136
|
+
| None = None,
|
|
137
|
+
conductivity: float | None = None,
|
|
138
|
+
permittivity: float | None = None,
|
|
139
|
+
loss_tangent: float | None = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Override or add material properties.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
name: Material name
|
|
145
|
+
material_type: Material type (conductor, dielectric, semiconductor)
|
|
146
|
+
conductivity: Conductivity in S/m (for conductors)
|
|
147
|
+
permittivity: Relative permittivity (for dielectrics)
|
|
148
|
+
loss_tangent: Dielectric loss tangent
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
>>> sim.set_material(
|
|
152
|
+
... "aluminum", material_type="conductor", conductivity=3.8e7
|
|
153
|
+
... )
|
|
154
|
+
>>> sim.set_material("sio2", material_type="dielectric", permittivity=3.9)
|
|
155
|
+
"""
|
|
156
|
+
from gsim.palace.models import MaterialConfig
|
|
157
|
+
|
|
158
|
+
# Determine type if not provided
|
|
159
|
+
resolved_type = material_type
|
|
160
|
+
if resolved_type is None:
|
|
161
|
+
if conductivity is not None and conductivity > 1e4:
|
|
162
|
+
resolved_type = "conductor"
|
|
163
|
+
elif permittivity is not None:
|
|
164
|
+
resolved_type = "dielectric"
|
|
165
|
+
else:
|
|
166
|
+
resolved_type = "dielectric"
|
|
167
|
+
|
|
168
|
+
self.materials[name] = MaterialConfig(
|
|
169
|
+
type=resolved_type,
|
|
170
|
+
conductivity=conductivity,
|
|
171
|
+
permittivity=permittivity,
|
|
172
|
+
loss_tangent=loss_tangent,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def set_numerical(
|
|
176
|
+
self,
|
|
177
|
+
*,
|
|
178
|
+
order: int = 2,
|
|
179
|
+
tolerance: float = 1e-6,
|
|
180
|
+
max_iterations: int = 400,
|
|
181
|
+
solver_type: Literal["Default", "SuperLU", "STRUMPACK", "MUMPS"] = "Default",
|
|
182
|
+
preconditioner: Literal["Default", "AMS", "BoomerAMG"] = "Default",
|
|
183
|
+
device: Literal["CPU", "GPU"] = "CPU",
|
|
184
|
+
num_processors: int | None = None,
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Configure numerical solver parameters.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
order: Finite element order (1-4)
|
|
190
|
+
tolerance: Linear solver tolerance
|
|
191
|
+
max_iterations: Maximum solver iterations
|
|
192
|
+
solver_type: Linear solver type
|
|
193
|
+
preconditioner: Preconditioner type
|
|
194
|
+
device: Compute device (CPU or GPU)
|
|
195
|
+
num_processors: Number of processors (None = auto)
|
|
196
|
+
|
|
197
|
+
Example:
|
|
198
|
+
>>> sim.set_numerical(order=3, tolerance=1e-8)
|
|
199
|
+
"""
|
|
200
|
+
from gsim.palace.models import NumericalConfig
|
|
201
|
+
|
|
202
|
+
self.numerical = NumericalConfig(
|
|
203
|
+
order=order,
|
|
204
|
+
tolerance=tolerance,
|
|
205
|
+
max_iterations=max_iterations,
|
|
206
|
+
solver_type=solver_type,
|
|
207
|
+
preconditioner=preconditioner,
|
|
208
|
+
device=device,
|
|
209
|
+
num_processors=num_processors,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# -------------------------------------------------------------------------
|
|
213
|
+
# Internal helpers
|
|
214
|
+
# -------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
def _resolve_stack(self) -> LayerStack:
|
|
217
|
+
"""Resolve the layer stack from PDK or YAML.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Legacy LayerStack object for mesh generation
|
|
221
|
+
"""
|
|
222
|
+
from gsim.common.stack import get_stack
|
|
223
|
+
|
|
224
|
+
yaml_path = self._stack_kwargs.pop("yaml_path", None)
|
|
225
|
+
legacy_stack = get_stack(yaml_path=yaml_path, **self._stack_kwargs)
|
|
226
|
+
|
|
227
|
+
# Restore yaml_path for potential re-resolution
|
|
228
|
+
self._stack_kwargs["yaml_path"] = yaml_path
|
|
229
|
+
|
|
230
|
+
# Apply material overrides
|
|
231
|
+
for name, props in self.materials.items():
|
|
232
|
+
legacy_stack.materials[name] = props.to_dict()
|
|
233
|
+
|
|
234
|
+
# Store the LayerStack
|
|
235
|
+
self.stack = legacy_stack
|
|
236
|
+
|
|
237
|
+
return legacy_stack
|
|
238
|
+
|
|
239
|
+
def _build_mesh_config(
|
|
240
|
+
self,
|
|
241
|
+
preset: Literal["coarse", "default", "fine"] | None,
|
|
242
|
+
refined_mesh_size: float | None,
|
|
243
|
+
max_mesh_size: float | None,
|
|
244
|
+
margin: float | None,
|
|
245
|
+
air_above: float | None,
|
|
246
|
+
fmax: float | None,
|
|
247
|
+
show_gui: bool,
|
|
248
|
+
) -> MeshConfig:
|
|
249
|
+
"""Build mesh config from preset with optional overrides."""
|
|
250
|
+
from gsim.palace.models import MeshConfig
|
|
251
|
+
|
|
252
|
+
# Build mesh config from preset
|
|
253
|
+
if preset == "coarse":
|
|
254
|
+
mesh_config = MeshConfig.coarse()
|
|
255
|
+
elif preset == "fine":
|
|
256
|
+
mesh_config = MeshConfig.fine()
|
|
257
|
+
else:
|
|
258
|
+
mesh_config = MeshConfig.default()
|
|
259
|
+
|
|
260
|
+
# Track overrides for warning
|
|
261
|
+
overrides = []
|
|
262
|
+
if preset is not None:
|
|
263
|
+
if refined_mesh_size is not None:
|
|
264
|
+
overrides.append(f"refined_mesh_size={refined_mesh_size}")
|
|
265
|
+
if max_mesh_size is not None:
|
|
266
|
+
overrides.append(f"max_mesh_size={max_mesh_size}")
|
|
267
|
+
if margin is not None:
|
|
268
|
+
overrides.append(f"margin={margin}")
|
|
269
|
+
if air_above is not None:
|
|
270
|
+
overrides.append(f"air_above={air_above}")
|
|
271
|
+
if fmax is not None:
|
|
272
|
+
overrides.append(f"fmax={fmax}")
|
|
273
|
+
|
|
274
|
+
if overrides:
|
|
275
|
+
warnings.warn(
|
|
276
|
+
f"Preset '{preset}' values overridden by: {', '.join(overrides)}",
|
|
277
|
+
stacklevel=4,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Apply overrides
|
|
281
|
+
if refined_mesh_size is not None:
|
|
282
|
+
mesh_config.refined_mesh_size = refined_mesh_size
|
|
283
|
+
if max_mesh_size is not None:
|
|
284
|
+
mesh_config.max_mesh_size = max_mesh_size
|
|
285
|
+
if margin is not None:
|
|
286
|
+
mesh_config.margin = margin
|
|
287
|
+
if air_above is not None:
|
|
288
|
+
mesh_config.air_above = air_above
|
|
289
|
+
if fmax is not None:
|
|
290
|
+
mesh_config.fmax = fmax
|
|
291
|
+
mesh_config.show_gui = show_gui
|
|
292
|
+
|
|
293
|
+
return mesh_config
|
|
294
|
+
|
|
295
|
+
# -------------------------------------------------------------------------
|
|
296
|
+
# Convenience methods
|
|
297
|
+
# -------------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
def show_stack(self) -> None:
|
|
300
|
+
"""Print the layer stack table.
|
|
301
|
+
|
|
302
|
+
Example:
|
|
303
|
+
>>> sim.show_stack()
|
|
304
|
+
"""
|
|
305
|
+
from gsim.common.stack import print_stack_table
|
|
306
|
+
|
|
307
|
+
if self.stack is None:
|
|
308
|
+
self._resolve_stack()
|
|
309
|
+
|
|
310
|
+
if self.stack is not None:
|
|
311
|
+
print_stack_table(self.stack)
|
|
312
|
+
|
|
313
|
+
def plot_stack(self) -> None:
|
|
314
|
+
"""Plot the layer stack visualization.
|
|
315
|
+
|
|
316
|
+
Example:
|
|
317
|
+
>>> sim.plot_stack()
|
|
318
|
+
"""
|
|
319
|
+
from gsim.common.stack import plot_stack
|
|
320
|
+
|
|
321
|
+
if self.stack is None:
|
|
322
|
+
self._resolve_stack()
|
|
323
|
+
|
|
324
|
+
if self.stack is not None:
|
|
325
|
+
plot_stack(self.stack)
|
|
326
|
+
|
|
327
|
+
# -------------------------------------------------------------------------
|
|
328
|
+
# Visualization
|
|
329
|
+
# -------------------------------------------------------------------------
|
|
23
330
|
|
|
24
331
|
def plot_mesh(
|
|
25
332
|
self,
|
|
@@ -52,9 +359,7 @@ class PalaceSimMixin:
|
|
|
52
359
|
|
|
53
360
|
mesh_path = self._output_dir / "palace.msh"
|
|
54
361
|
if not mesh_path.exists():
|
|
55
|
-
raise ValueError(
|
|
56
|
-
f"Mesh file not found: {mesh_path}. Call mesh() first."
|
|
57
|
-
)
|
|
362
|
+
raise ValueError(f"Mesh file not found: {mesh_path}. Call mesh() first.")
|
|
58
363
|
|
|
59
364
|
# Default output path if not interactive
|
|
60
365
|
if output is None and not interactive:
|