gsim 0.0.0__py3-none-any.whl → 0.0.3__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 +57 -0
- gsim/common/geometry.py +76 -0
- gsim/{palace → common}/stack/__init__.py +8 -5
- gsim/{palace → common}/stack/extractor.py +33 -107
- gsim/{palace → common}/stack/materials.py +27 -11
- gsim/{palace → common}/stack/visualization.py +3 -3
- gsim/gcloud.py +78 -25
- gsim/palace/__init__.py +85 -45
- gsim/palace/base.py +373 -0
- gsim/palace/driven.py +728 -0
- gsim/palace/eigenmode.py +557 -0
- gsim/palace/electrostatic.py +398 -0
- gsim/palace/mesh/__init__.py +13 -1
- gsim/palace/mesh/config_generator.py +367 -0
- gsim/palace/mesh/generator.py +66 -744
- gsim/palace/mesh/geometry.py +472 -0
- gsim/palace/mesh/groups.py +170 -0
- gsim/palace/mesh/pipeline.py +22 -1
- gsim/palace/models/__init__.py +53 -0
- gsim/palace/models/geometry.py +34 -0
- gsim/palace/models/mesh.py +95 -0
- gsim/palace/models/numerical.py +66 -0
- gsim/palace/models/ports.py +137 -0
- gsim/palace/models/problems.py +195 -0
- gsim/palace/models/results.py +160 -0
- gsim/palace/models/stack.py +59 -0
- gsim/palace/ports/config.py +1 -1
- gsim/viz.py +9 -6
- {gsim-0.0.0.dist-info → gsim-0.0.3.dist-info}/METADATA +9 -6
- gsim-0.0.3.dist-info/RECORD +35 -0
- {gsim-0.0.0.dist-info → gsim-0.0.3.dist-info}/WHEEL +1 -1
- gsim-0.0.0.dist-info/RECORD +0 -18
- {gsim-0.0.0.dist-info → gsim-0.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"""Electrostatic simulation class for capacitance extraction.
|
|
2
|
+
|
|
3
|
+
This module provides the ElectrostaticSim class for extracting
|
|
4
|
+
capacitance matrices between terminals.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import tempfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Literal
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
|
|
15
|
+
|
|
16
|
+
from gsim.common import Geometry, LayerStack
|
|
17
|
+
from gsim.palace.base import PalaceSimMixin
|
|
18
|
+
from gsim.palace.models import (
|
|
19
|
+
ElectrostaticConfig,
|
|
20
|
+
MaterialConfig,
|
|
21
|
+
MeshConfig,
|
|
22
|
+
NumericalConfig,
|
|
23
|
+
SimulationResult,
|
|
24
|
+
TerminalConfig,
|
|
25
|
+
ValidationResult,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
32
|
+
"""Electrostatic simulation for capacitance matrix extraction.
|
|
33
|
+
|
|
34
|
+
This class configures and runs electrostatic simulations to extract
|
|
35
|
+
the capacitance matrix between conductor terminals. Unlike driven
|
|
36
|
+
and eigenmode simulations, this does not use ports.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
>>> from gsim.palace import ElectrostaticSim
|
|
40
|
+
>>>
|
|
41
|
+
>>> sim = ElectrostaticSim()
|
|
42
|
+
>>> sim.set_geometry(component)
|
|
43
|
+
>>> sim.set_stack(air_above=300.0)
|
|
44
|
+
>>> sim.add_terminal("T1", layer="topmetal2")
|
|
45
|
+
>>> sim.add_terminal("T2", layer="topmetal2")
|
|
46
|
+
>>> sim.set_electrostatic()
|
|
47
|
+
>>> sim.set_output_dir("./sim")
|
|
48
|
+
>>> sim.mesh(preset="default")
|
|
49
|
+
>>> results = sim.simulate()
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
geometry: Wrapped gdsfactory Component (from common)
|
|
53
|
+
stack: Layer stack configuration (from common)
|
|
54
|
+
terminals: List of terminal configurations
|
|
55
|
+
electrostatic: Electrostatic simulation configuration
|
|
56
|
+
materials: Material property overrides
|
|
57
|
+
numerical: Numerical solver configuration
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
model_config = ConfigDict(
|
|
61
|
+
validate_assignment=True,
|
|
62
|
+
arbitrary_types_allowed=True,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Composed objects (from common)
|
|
66
|
+
geometry: Geometry | None = None
|
|
67
|
+
stack: LayerStack | None = None
|
|
68
|
+
|
|
69
|
+
# Terminal configurations (no ports in electrostatic)
|
|
70
|
+
terminals: list[TerminalConfig] = Field(default_factory=list)
|
|
71
|
+
|
|
72
|
+
# Electrostatic simulation config
|
|
73
|
+
electrostatic: ElectrostaticConfig = Field(default_factory=ElectrostaticConfig)
|
|
74
|
+
|
|
75
|
+
# Material overrides and numerical config
|
|
76
|
+
materials: dict[str, MaterialConfig] = Field(default_factory=dict)
|
|
77
|
+
numerical: NumericalConfig = Field(default_factory=NumericalConfig)
|
|
78
|
+
|
|
79
|
+
# Stack configuration (stored as kwargs until resolved)
|
|
80
|
+
_stack_kwargs: dict[str, Any] = PrivateAttr(default_factory=dict)
|
|
81
|
+
|
|
82
|
+
# Internal state
|
|
83
|
+
_output_dir: Path | None = PrivateAttr(default=None)
|
|
84
|
+
_configured_terminals: bool = PrivateAttr(default=False)
|
|
85
|
+
|
|
86
|
+
# -------------------------------------------------------------------------
|
|
87
|
+
# Terminal methods
|
|
88
|
+
# -------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def add_terminal(
|
|
91
|
+
self,
|
|
92
|
+
name: str,
|
|
93
|
+
*,
|
|
94
|
+
layer: str,
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Add a terminal for capacitance extraction.
|
|
97
|
+
|
|
98
|
+
Terminals define conductor surfaces for capacitance matrix extraction.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
name: Terminal name
|
|
102
|
+
layer: Target conductor layer
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
>>> sim.add_terminal("T1", layer="topmetal2")
|
|
106
|
+
>>> sim.add_terminal("T2", layer="topmetal2")
|
|
107
|
+
"""
|
|
108
|
+
# Remove existing terminal with same name
|
|
109
|
+
self.terminals = [t for t in self.terminals if t.name != name]
|
|
110
|
+
self.terminals.append(
|
|
111
|
+
TerminalConfig(
|
|
112
|
+
name=name,
|
|
113
|
+
layer=layer,
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# -------------------------------------------------------------------------
|
|
118
|
+
# Electrostatic configuration
|
|
119
|
+
# -------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
def set_electrostatic(
|
|
122
|
+
self,
|
|
123
|
+
*,
|
|
124
|
+
save_fields: int = 0,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Configure electrostatic simulation.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
save_fields: Number of field solutions to save
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
>>> sim.set_electrostatic(save_fields=1)
|
|
133
|
+
"""
|
|
134
|
+
self.electrostatic = ElectrostaticConfig(
|
|
135
|
+
save_fields=save_fields,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# -------------------------------------------------------------------------
|
|
139
|
+
# Validation
|
|
140
|
+
# -------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
def validate_config(self) -> ValidationResult:
|
|
143
|
+
"""Validate the simulation configuration.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
ValidationResult with validation status and messages
|
|
147
|
+
"""
|
|
148
|
+
errors = []
|
|
149
|
+
warnings_list = []
|
|
150
|
+
|
|
151
|
+
# Check geometry
|
|
152
|
+
if self.geometry is None:
|
|
153
|
+
errors.append("No component set. Call set_geometry(component) first.")
|
|
154
|
+
|
|
155
|
+
# Check stack
|
|
156
|
+
if self.stack is None and not self._stack_kwargs:
|
|
157
|
+
warnings_list.append(
|
|
158
|
+
"No stack configured. Will use active PDK with defaults."
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Electrostatic requires at least 2 terminals
|
|
162
|
+
if len(self.terminals) < 2:
|
|
163
|
+
errors.append(
|
|
164
|
+
"Electrostatic simulation requires at least 2 terminals. "
|
|
165
|
+
"Call add_terminal() to add terminals."
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Validate terminal configurations
|
|
169
|
+
errors.extend(
|
|
170
|
+
f"Terminal '{terminal.name}': 'layer' is required"
|
|
171
|
+
for terminal in self.terminals
|
|
172
|
+
if not terminal.layer
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
valid = len(errors) == 0
|
|
176
|
+
return ValidationResult(valid=valid, errors=errors, warnings=warnings_list)
|
|
177
|
+
|
|
178
|
+
# -------------------------------------------------------------------------
|
|
179
|
+
# Internal helpers
|
|
180
|
+
# -------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
def _generate_mesh_internal(
|
|
183
|
+
self,
|
|
184
|
+
output_dir: Path,
|
|
185
|
+
mesh_config: MeshConfig,
|
|
186
|
+
model_name: str,
|
|
187
|
+
verbose: bool,
|
|
188
|
+
) -> SimulationResult:
|
|
189
|
+
"""Internal mesh generation."""
|
|
190
|
+
from gsim.palace.mesh import MeshConfig as LegacyMeshConfig
|
|
191
|
+
from gsim.palace.mesh import generate_mesh
|
|
192
|
+
|
|
193
|
+
component = self.geometry.component if self.geometry else None
|
|
194
|
+
|
|
195
|
+
legacy_mesh_config = LegacyMeshConfig(
|
|
196
|
+
refined_mesh_size=mesh_config.refined_mesh_size,
|
|
197
|
+
max_mesh_size=mesh_config.max_mesh_size,
|
|
198
|
+
cells_per_wavelength=mesh_config.cells_per_wavelength,
|
|
199
|
+
margin=mesh_config.margin,
|
|
200
|
+
air_above=mesh_config.air_above,
|
|
201
|
+
fmax=mesh_config.fmax,
|
|
202
|
+
show_gui=mesh_config.show_gui,
|
|
203
|
+
preview_only=mesh_config.preview_only,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
stack = self._resolve_stack()
|
|
207
|
+
|
|
208
|
+
if verbose:
|
|
209
|
+
logger.info("Generating mesh in %s", output_dir)
|
|
210
|
+
|
|
211
|
+
mesh_result = generate_mesh(
|
|
212
|
+
component=component,
|
|
213
|
+
stack=stack,
|
|
214
|
+
ports=[], # No ports for electrostatic
|
|
215
|
+
output_dir=output_dir,
|
|
216
|
+
config=legacy_mesh_config,
|
|
217
|
+
model_name=model_name,
|
|
218
|
+
driven_config=None, # No driven config for electrostatic
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return SimulationResult(
|
|
222
|
+
mesh_path=mesh_result.mesh_path,
|
|
223
|
+
output_dir=output_dir,
|
|
224
|
+
config_path=mesh_result.config_path,
|
|
225
|
+
port_info=mesh_result.port_info,
|
|
226
|
+
mesh_stats=mesh_result.mesh_stats,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# -------------------------------------------------------------------------
|
|
230
|
+
# Preview
|
|
231
|
+
# -------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
def preview(
|
|
234
|
+
self,
|
|
235
|
+
*,
|
|
236
|
+
preset: Literal["coarse", "default", "fine"] | None = None,
|
|
237
|
+
refined_mesh_size: float | None = None,
|
|
238
|
+
max_mesh_size: float | None = None,
|
|
239
|
+
margin: float | None = None,
|
|
240
|
+
air_above: float | None = None,
|
|
241
|
+
fmax: float | None = None,
|
|
242
|
+
show_gui: bool = True,
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Preview the mesh without running simulation.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
preset: Mesh quality preset ("coarse", "default", "fine")
|
|
248
|
+
refined_mesh_size: Mesh size near conductors (um)
|
|
249
|
+
max_mesh_size: Max mesh size in air/dielectric (um)
|
|
250
|
+
margin: XY margin around design (um)
|
|
251
|
+
air_above: Air above top metal (um)
|
|
252
|
+
fmax: Max frequency for mesh sizing (Hz)
|
|
253
|
+
show_gui: Show gmsh GUI for interactive preview
|
|
254
|
+
|
|
255
|
+
Example:
|
|
256
|
+
>>> sim.preview(preset="fine", show_gui=True)
|
|
257
|
+
"""
|
|
258
|
+
from gsim.palace.mesh import MeshConfig as LegacyMeshConfig
|
|
259
|
+
from gsim.palace.mesh import generate_mesh
|
|
260
|
+
|
|
261
|
+
component = self.geometry.component if self.geometry else None
|
|
262
|
+
|
|
263
|
+
validation = self.validate_config()
|
|
264
|
+
if not validation.valid:
|
|
265
|
+
raise ValueError("Invalid configuration:\n" + "\n".join(validation.errors))
|
|
266
|
+
|
|
267
|
+
mesh_config = self._build_mesh_config(
|
|
268
|
+
preset=preset,
|
|
269
|
+
refined_mesh_size=refined_mesh_size,
|
|
270
|
+
max_mesh_size=max_mesh_size,
|
|
271
|
+
margin=margin,
|
|
272
|
+
air_above=air_above,
|
|
273
|
+
fmax=fmax,
|
|
274
|
+
show_gui=show_gui,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
stack = self._resolve_stack()
|
|
278
|
+
|
|
279
|
+
legacy_mesh_config = LegacyMeshConfig(
|
|
280
|
+
refined_mesh_size=mesh_config.refined_mesh_size,
|
|
281
|
+
max_mesh_size=mesh_config.max_mesh_size,
|
|
282
|
+
cells_per_wavelength=mesh_config.cells_per_wavelength,
|
|
283
|
+
margin=mesh_config.margin,
|
|
284
|
+
air_above=mesh_config.air_above,
|
|
285
|
+
fmax=mesh_config.fmax,
|
|
286
|
+
show_gui=show_gui,
|
|
287
|
+
preview_only=True,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
291
|
+
generate_mesh(
|
|
292
|
+
component=component,
|
|
293
|
+
stack=stack,
|
|
294
|
+
ports=[],
|
|
295
|
+
output_dir=tmpdir,
|
|
296
|
+
config=legacy_mesh_config,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# -------------------------------------------------------------------------
|
|
300
|
+
# Mesh generation
|
|
301
|
+
# -------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
def mesh(
|
|
304
|
+
self,
|
|
305
|
+
*,
|
|
306
|
+
preset: Literal["coarse", "default", "fine"] | None = None,
|
|
307
|
+
refined_mesh_size: float | None = None,
|
|
308
|
+
max_mesh_size: float | None = None,
|
|
309
|
+
margin: float | None = None,
|
|
310
|
+
air_above: float | None = None,
|
|
311
|
+
fmax: float | None = None,
|
|
312
|
+
show_gui: bool = False,
|
|
313
|
+
model_name: str = "palace",
|
|
314
|
+
verbose: bool = True,
|
|
315
|
+
) -> SimulationResult:
|
|
316
|
+
"""Generate the mesh for Palace simulation.
|
|
317
|
+
|
|
318
|
+
Requires set_output_dir() to be called first.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
preset: Mesh quality preset ("coarse", "default", "fine")
|
|
322
|
+
refined_mesh_size: Mesh size near conductors (um), overrides preset
|
|
323
|
+
max_mesh_size: Max mesh size in air/dielectric (um), overrides preset
|
|
324
|
+
margin: XY margin around design (um), overrides preset
|
|
325
|
+
air_above: Air above top metal (um), overrides preset
|
|
326
|
+
fmax: Max frequency for mesh sizing (Hz) - less relevant for electrostatic
|
|
327
|
+
show_gui: Show gmsh GUI during meshing
|
|
328
|
+
model_name: Base name for output files
|
|
329
|
+
verbose: Print progress messages
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
SimulationResult with mesh path
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
ValueError: If output_dir not set or configuration is invalid
|
|
336
|
+
|
|
337
|
+
Example:
|
|
338
|
+
>>> sim.set_output_dir("./sim")
|
|
339
|
+
>>> result = sim.mesh(preset="fine")
|
|
340
|
+
>>> print(f"Mesh saved to: {result.mesh_path}")
|
|
341
|
+
"""
|
|
342
|
+
if self._output_dir is None:
|
|
343
|
+
raise ValueError("Output directory not set. Call set_output_dir() first.")
|
|
344
|
+
|
|
345
|
+
mesh_config = self._build_mesh_config(
|
|
346
|
+
preset=preset,
|
|
347
|
+
refined_mesh_size=refined_mesh_size,
|
|
348
|
+
max_mesh_size=max_mesh_size,
|
|
349
|
+
margin=margin,
|
|
350
|
+
air_above=air_above,
|
|
351
|
+
fmax=fmax,
|
|
352
|
+
show_gui=show_gui,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
validation = self.validate_config()
|
|
356
|
+
if not validation.valid:
|
|
357
|
+
raise ValueError("Invalid configuration:\n" + "\n".join(validation.errors))
|
|
358
|
+
|
|
359
|
+
output_dir = self._output_dir
|
|
360
|
+
|
|
361
|
+
self._resolve_stack()
|
|
362
|
+
|
|
363
|
+
return self._generate_mesh_internal(
|
|
364
|
+
output_dir=output_dir,
|
|
365
|
+
mesh_config=mesh_config,
|
|
366
|
+
model_name=model_name,
|
|
367
|
+
verbose=verbose,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# -------------------------------------------------------------------------
|
|
371
|
+
# Simulation
|
|
372
|
+
# -------------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
def simulate(
|
|
375
|
+
self,
|
|
376
|
+
output_dir: str | Path | None = None,
|
|
377
|
+
*,
|
|
378
|
+
verbose: bool = True,
|
|
379
|
+
) -> dict[str, Path]:
|
|
380
|
+
"""Run electrostatic simulation on GDSFactory+ cloud.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
output_dir: Directory containing mesh files
|
|
384
|
+
verbose: Print progress messages
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Dict mapping result filenames to local paths
|
|
388
|
+
|
|
389
|
+
Raises:
|
|
390
|
+
NotImplementedError: Electrostatic is not yet fully implemented
|
|
391
|
+
"""
|
|
392
|
+
raise NotImplementedError(
|
|
393
|
+
"Electrostatic simulation is not yet fully implemented on cloud. "
|
|
394
|
+
"Use DrivenSim for S-parameter extraction."
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
__all__ = ["ElectrostaticSim"]
|
gsim/palace/mesh/__init__.py
CHANGED
|
@@ -28,7 +28,16 @@ Usage:
|
|
|
28
28
|
|
|
29
29
|
from __future__ import annotations
|
|
30
30
|
|
|
31
|
-
from gsim.palace.mesh.generator import
|
|
31
|
+
from gsim.palace.mesh.generator import (
|
|
32
|
+
GeometryData,
|
|
33
|
+
write_config,
|
|
34
|
+
)
|
|
35
|
+
from gsim.palace.mesh.generator import (
|
|
36
|
+
MeshResult as MeshResultDirect,
|
|
37
|
+
)
|
|
38
|
+
from gsim.palace.mesh.generator import (
|
|
39
|
+
generate_mesh as generate_mesh_direct,
|
|
40
|
+
)
|
|
32
41
|
from gsim.palace.mesh.pipeline import (
|
|
33
42
|
GroundPlane,
|
|
34
43
|
MeshConfig,
|
|
@@ -40,11 +49,14 @@ from gsim.palace.mesh.pipeline import (
|
|
|
40
49
|
from . import gmsh_utils
|
|
41
50
|
|
|
42
51
|
__all__ = [
|
|
52
|
+
"GeometryData",
|
|
43
53
|
"GroundPlane",
|
|
44
54
|
"MeshConfig",
|
|
45
55
|
"MeshPreset",
|
|
46
56
|
"MeshResult",
|
|
57
|
+
"MeshResultDirect",
|
|
47
58
|
"generate_mesh",
|
|
48
59
|
"generate_mesh_direct",
|
|
49
60
|
"gmsh_utils",
|
|
61
|
+
"write_config",
|
|
50
62
|
]
|