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.
@@ -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
 
@@ -28,8 +25,7 @@ from gsim.palace.models import (
28
25
  ValidationResult,
29
26
  )
30
27
 
31
- if TYPE_CHECKING:
32
- from gdsfactory.component import Component
28
+ logger = logging.getLogger(__name__)
33
29
 
34
30
 
35
31
  class ElectrostaticSim(PalaceSimMixin, BaseModel):
@@ -37,8 +33,7 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
37
33
 
38
34
  This class configures and runs electrostatic simulations to extract
39
35
  the capacitance matrix between conductor terminals. Unlike driven
40
- and eigenmode simulations, this does not use ports. Uses composition
41
- (no inheritance) with shared Geometry and Stack components from gsim.common.
36
+ and eigenmode simulations, this does not use ports.
42
37
 
43
38
  Example:
44
39
  >>> from gsim.palace import ElectrostaticSim
@@ -49,7 +44,8 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
49
44
  >>> sim.add_terminal("T1", layer="topmetal2")
50
45
  >>> sim.add_terminal("T2", layer="topmetal2")
51
46
  >>> sim.set_electrostatic()
52
- >>> sim.mesh("./sim", preset="default")
47
+ >>> sim.set_output_dir("./sim")
48
+ >>> sim.mesh(preset="default")
53
49
  >>> results = sim.simulate()
54
50
 
55
51
  Attributes:
@@ -87,65 +83,6 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
87
83
  _output_dir: Path | None = PrivateAttr(default=None)
88
84
  _configured_terminals: bool = PrivateAttr(default=False)
89
85
 
90
- # -------------------------------------------------------------------------
91
- # Geometry methods
92
- # -------------------------------------------------------------------------
93
-
94
- def set_geometry(self, component: Component) -> None:
95
- """Set the gdsfactory component for simulation.
96
-
97
- Args:
98
- component: gdsfactory Component to simulate
99
-
100
- Example:
101
- >>> sim.set_geometry(my_component)
102
- """
103
- self.geometry = Geometry(component=component)
104
-
105
- @property
106
- def component(self) -> Component | None:
107
- """Get the current component (for backward compatibility)."""
108
- return self.geometry.component if self.geometry else None
109
-
110
- @property
111
- def _component(self) -> Component | None:
112
- """Internal component access (backward compatibility)."""
113
- return self.component
114
-
115
- # -------------------------------------------------------------------------
116
- # Stack methods
117
- # -------------------------------------------------------------------------
118
-
119
- def set_stack(
120
- self,
121
- *,
122
- yaml_path: str | Path | None = None,
123
- air_above: float = 200.0,
124
- substrate_thickness: float = 2.0,
125
- include_substrate: bool = False,
126
- **kwargs,
127
- ) -> None:
128
- """Configure the layer stack.
129
-
130
- Args:
131
- yaml_path: Path to custom YAML stack file
132
- air_above: Air box height above top metal in um
133
- substrate_thickness: Thickness below z=0 in um
134
- include_substrate: Include lossy silicon substrate
135
- **kwargs: Additional args passed to extract_layer_stack
136
-
137
- Example:
138
- >>> sim.set_stack(air_above=300.0, substrate_thickness=2.0)
139
- """
140
- self._stack_kwargs = {
141
- "yaml_path": yaml_path,
142
- "air_above": air_above,
143
- "substrate_thickness": substrate_thickness,
144
- "include_substrate": include_substrate,
145
- **kwargs,
146
- }
147
- self.stack = None
148
-
149
86
  # -------------------------------------------------------------------------
150
87
  # Terminal methods
151
88
  # -------------------------------------------------------------------------
@@ -198,86 +135,11 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
198
135
  save_fields=save_fields,
199
136
  )
200
137
 
201
- # -------------------------------------------------------------------------
202
- # Material methods
203
- # -------------------------------------------------------------------------
204
-
205
- def set_material(
206
- self,
207
- name: str,
208
- *,
209
- type: Literal["conductor", "dielectric", "semiconductor"] | None = None,
210
- conductivity: float | None = None,
211
- permittivity: float | None = None,
212
- loss_tangent: float | None = None,
213
- ) -> None:
214
- """Override or add material properties.
215
-
216
- Args:
217
- name: Material name
218
- type: Material type (conductor, dielectric, semiconductor)
219
- conductivity: Conductivity in S/m (for conductors)
220
- permittivity: Relative permittivity (for dielectrics)
221
- loss_tangent: Dielectric loss tangent
222
-
223
- Example:
224
- >>> sim.set_material("aluminum", type="conductor", conductivity=3.8e7)
225
- """
226
- if type is None:
227
- if conductivity is not None and conductivity > 1e4:
228
- type = "conductor"
229
- elif permittivity is not None:
230
- type = "dielectric"
231
- else:
232
- type = "dielectric"
233
-
234
- self.materials[name] = MaterialConfig(
235
- type=type,
236
- conductivity=conductivity,
237
- permittivity=permittivity,
238
- loss_tangent=loss_tangent,
239
- )
240
-
241
- def set_numerical(
242
- self,
243
- *,
244
- order: int = 2,
245
- tolerance: float = 1e-6,
246
- max_iterations: int = 400,
247
- solver_type: Literal["Default", "SuperLU", "STRUMPACK", "MUMPS"] = "Default",
248
- preconditioner: Literal["Default", "AMS", "BoomerAMG"] = "Default",
249
- device: Literal["CPU", "GPU"] = "CPU",
250
- num_processors: int | None = None,
251
- ) -> None:
252
- """Configure numerical solver parameters.
253
-
254
- Args:
255
- order: Finite element order (1-4)
256
- tolerance: Linear solver tolerance
257
- max_iterations: Maximum solver iterations
258
- solver_type: Linear solver type
259
- preconditioner: Preconditioner type
260
- device: Compute device (CPU or GPU)
261
- num_processors: Number of processors (None = auto)
262
-
263
- Example:
264
- >>> sim.set_numerical(order=3, tolerance=1e-8)
265
- """
266
- self.numerical = NumericalConfig(
267
- order=order,
268
- tolerance=tolerance,
269
- max_iterations=max_iterations,
270
- solver_type=solver_type,
271
- preconditioner=preconditioner,
272
- device=device,
273
- num_processors=num_processors,
274
- )
275
-
276
138
  # -------------------------------------------------------------------------
277
139
  # Validation
278
140
  # -------------------------------------------------------------------------
279
141
 
280
- def validate(self) -> ValidationResult:
142
+ def validate_config(self) -> ValidationResult:
281
143
  """Validate the simulation configuration.
282
144
 
283
145
  Returns:
@@ -304,9 +166,11 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
304
166
  )
305
167
 
306
168
  # Validate terminal configurations
307
- for terminal in self.terminals:
308
- if not terminal.layer:
309
- errors.append(f"Terminal '{terminal.name}': 'layer' is required")
169
+ errors.extend(
170
+ f"Terminal '{terminal.name}': 'layer' is required"
171
+ for terminal in self.terminals
172
+ if not terminal.layer
173
+ )
310
174
 
311
175
  valid = len(errors) == 0
312
176
  return ValidationResult(valid=valid, errors=errors, warnings=warnings_list)
@@ -315,71 +179,6 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
315
179
  # Internal helpers
316
180
  # -------------------------------------------------------------------------
317
181
 
318
- def _resolve_stack(self) -> LayerStack:
319
- """Resolve the layer stack from PDK or YAML."""
320
- from gsim.common.stack import get_stack
321
-
322
- yaml_path = self._stack_kwargs.pop("yaml_path", None)
323
- stack = get_stack(yaml_path=yaml_path, **self._stack_kwargs)
324
- self._stack_kwargs["yaml_path"] = yaml_path
325
-
326
- for name, props in self.materials.items():
327
- stack.materials[name] = props.to_dict()
328
-
329
- self.stack = stack
330
- return stack
331
-
332
- def _build_mesh_config(
333
- self,
334
- preset: Literal["coarse", "default", "fine"] | None,
335
- refined_mesh_size: float | None,
336
- max_mesh_size: float | None,
337
- margin: float | None,
338
- air_above: float | None,
339
- fmax: float | None,
340
- show_gui: bool,
341
- ) -> MeshConfig:
342
- """Build mesh config from preset with optional overrides."""
343
- if preset == "coarse":
344
- mesh_config = MeshConfig.coarse()
345
- elif preset == "fine":
346
- mesh_config = MeshConfig.fine()
347
- else:
348
- mesh_config = MeshConfig.default()
349
-
350
- overrides = []
351
- if preset is not None:
352
- if refined_mesh_size is not None:
353
- overrides.append(f"refined_mesh_size={refined_mesh_size}")
354
- if max_mesh_size is not None:
355
- overrides.append(f"max_mesh_size={max_mesh_size}")
356
- if margin is not None:
357
- overrides.append(f"margin={margin}")
358
- if air_above is not None:
359
- overrides.append(f"air_above={air_above}")
360
- if fmax is not None:
361
- overrides.append(f"fmax={fmax}")
362
-
363
- if overrides:
364
- warnings.warn(
365
- f"Preset '{preset}' values overridden by: {', '.join(overrides)}",
366
- stacklevel=4,
367
- )
368
-
369
- if refined_mesh_size is not None:
370
- mesh_config.refined_mesh_size = refined_mesh_size
371
- if max_mesh_size is not None:
372
- mesh_config.max_mesh_size = max_mesh_size
373
- if margin is not None:
374
- mesh_config.margin = margin
375
- if air_above is not None:
376
- mesh_config.air_above = air_above
377
- if fmax is not None:
378
- mesh_config.fmax = fmax
379
- mesh_config.show_gui = show_gui
380
-
381
- return mesh_config
382
-
383
182
  def _generate_mesh_internal(
384
183
  self,
385
184
  output_dir: Path,
@@ -427,10 +226,6 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
427
226
  mesh_stats=mesh_result.mesh_stats,
428
227
  )
429
228
 
430
- def _get_ports_for_preview(self, stack: LayerStack) -> list:
431
- """Get ports for preview (none for electrostatic)."""
432
- return []
433
-
434
229
  # -------------------------------------------------------------------------
435
230
  # Preview
436
231
  # -------------------------------------------------------------------------
@@ -465,11 +260,9 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
465
260
 
466
261
  component = self.geometry.component if self.geometry else None
467
262
 
468
- validation = self.validate()
263
+ validation = self.validate_config()
469
264
  if not validation.valid:
470
- raise ValueError(
471
- f"Invalid configuration:\n" + "\n".join(validation.errors)
472
- )
265
+ raise ValueError("Invalid configuration:\n" + "\n".join(validation.errors))
473
266
 
474
267
  mesh_config = self._build_mesh_config(
475
268
  preset=preset,
@@ -503,37 +296,12 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
503
296
  config=legacy_mesh_config,
504
297
  )
505
298
 
506
- # -------------------------------------------------------------------------
507
- # Convenience methods
508
- # -------------------------------------------------------------------------
509
-
510
- def show_stack(self) -> None:
511
- """Print the layer stack table."""
512
- from gsim.common.stack import print_stack_table
513
-
514
- if self.stack is None:
515
- self._resolve_stack()
516
-
517
- if self.stack is not None:
518
- print_stack_table(self.stack)
519
-
520
- def plot_stack(self) -> None:
521
- """Plot the layer stack visualization."""
522
- from gsim.common.stack import plot_stack
523
-
524
- if self.stack is None:
525
- self._resolve_stack()
526
-
527
- if self.stack is not None:
528
- plot_stack(self.stack)
529
-
530
299
  # -------------------------------------------------------------------------
531
300
  # Mesh generation
532
301
  # -------------------------------------------------------------------------
533
302
 
534
303
  def mesh(
535
304
  self,
536
- output_dir: str | Path,
537
305
  *,
538
306
  preset: Literal["coarse", "default", "fine"] | None = None,
539
307
  refined_mesh_size: float | None = None,
@@ -545,23 +313,35 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
545
313
  model_name: str = "palace",
546
314
  verbose: bool = True,
547
315
  ) -> SimulationResult:
548
- """Generate the mesh and configuration files.
316
+ """Generate the mesh for Palace simulation.
317
+
318
+ Requires set_output_dir() to be called first.
549
319
 
550
320
  Args:
551
- output_dir: Directory for output files
552
321
  preset: Mesh quality preset ("coarse", "default", "fine")
553
- refined_mesh_size: Mesh size near conductors (um)
554
- max_mesh_size: Max mesh size in air/dielectric (um)
555
- margin: XY margin around design (um)
556
- air_above: Air above top metal (um)
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
557
326
  fmax: Max frequency for mesh sizing (Hz) - less relevant for electrostatic
558
327
  show_gui: Show gmsh GUI during meshing
559
328
  model_name: Base name for output files
560
329
  verbose: Print progress messages
561
330
 
562
331
  Returns:
563
- SimulationResult with mesh and config paths
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}")
564
341
  """
342
+ if self._output_dir is None:
343
+ raise ValueError("Output directory not set. Call set_output_dir() first.")
344
+
565
345
  mesh_config = self._build_mesh_config(
566
346
  preset=preset,
567
347
  refined_mesh_size=refined_mesh_size,
@@ -572,15 +352,11 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
572
352
  show_gui=show_gui,
573
353
  )
574
354
 
575
- validation = self.validate()
355
+ validation = self.validate_config()
576
356
  if not validation.valid:
577
- raise ValueError(
578
- f"Invalid configuration:\n" + "\n".join(validation.errors)
579
- )
357
+ raise ValueError("Invalid configuration:\n" + "\n".join(validation.errors))
580
358
 
581
- output_dir = Path(output_dir)
582
- output_dir.mkdir(parents=True, exist_ok=True)
583
- self._output_dir = output_dir
359
+ output_dir = self._output_dir
584
360
 
585
361
  self._resolve_stack()
586
362
 
@@ -28,7 +28,16 @@ Usage:
28
28
 
29
29
  from __future__ import annotations
30
30
 
31
- from gsim.palace.mesh.generator import generate_mesh as generate_mesh_direct
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
  ]