gsim 0.0.2__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/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
- if TYPE_CHECKING:
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("feed", from_layer="metal1", to_layer="topmetal2", geometry="via")
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 validate(self) -> ValidationResult:
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}': inplane ports require 'layer'"
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
- for cpw in self.cpw_ports:
443
- if not cpw.layer:
444
- errors.append(
445
- f"CPW port ({cpw.upper}, {cpw.lower}): 'layer' is required"
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 _resolve_stack(self) -> LayerStack:
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.validate()
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.validate()
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