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,53 @@
|
|
|
1
|
+
"""Pydantic models for Palace EM simulation configuration.
|
|
2
|
+
|
|
3
|
+
This module provides Pydantic v2 models for configuring Palace simulations,
|
|
4
|
+
offering validation, serialization, and a clean API.
|
|
5
|
+
|
|
6
|
+
Submodules:
|
|
7
|
+
- geometry: GeometryConfig
|
|
8
|
+
- stack: MaterialConfig (Layer/Stack are in gsim.common.stack)
|
|
9
|
+
- ports: PortConfig, CPWPortConfig, TerminalConfig, WavePortConfig
|
|
10
|
+
- mesh: MeshConfig
|
|
11
|
+
- numerical: NumericalConfig
|
|
12
|
+
- problems: DrivenConfig, EigenmodeConfig, ElectrostaticConfig, etc.
|
|
13
|
+
- results: SimulationResult, ValidationResult
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from gsim.palace.models.geometry import GeometryConfig
|
|
19
|
+
from gsim.palace.models.mesh import MeshConfig
|
|
20
|
+
from gsim.palace.models.numerical import NumericalConfig
|
|
21
|
+
from gsim.palace.models.ports import (
|
|
22
|
+
CPWPortConfig,
|
|
23
|
+
PortConfig,
|
|
24
|
+
TerminalConfig,
|
|
25
|
+
WavePortConfig,
|
|
26
|
+
)
|
|
27
|
+
from gsim.palace.models.problems import (
|
|
28
|
+
DrivenConfig,
|
|
29
|
+
EigenmodeConfig,
|
|
30
|
+
ElectrostaticConfig,
|
|
31
|
+
MagnetostaticConfig,
|
|
32
|
+
TransientConfig,
|
|
33
|
+
)
|
|
34
|
+
from gsim.palace.models.results import SimulationResult, ValidationResult
|
|
35
|
+
from gsim.palace.models.stack import MaterialConfig
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"CPWPortConfig",
|
|
39
|
+
"DrivenConfig",
|
|
40
|
+
"EigenmodeConfig",
|
|
41
|
+
"ElectrostaticConfig",
|
|
42
|
+
"GeometryConfig",
|
|
43
|
+
"MagnetostaticConfig",
|
|
44
|
+
"MaterialConfig",
|
|
45
|
+
"MeshConfig",
|
|
46
|
+
"NumericalConfig",
|
|
47
|
+
"PortConfig",
|
|
48
|
+
"SimulationResult",
|
|
49
|
+
"TerminalConfig",
|
|
50
|
+
"TransientConfig",
|
|
51
|
+
"ValidationResult",
|
|
52
|
+
"WavePortConfig",
|
|
53
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Geometry configuration models for Palace simulations.
|
|
2
|
+
|
|
3
|
+
This module contains Pydantic models for geometry-related settings.
|
|
4
|
+
Note: The actual gdsfactory Component is stored directly on the simulation
|
|
5
|
+
classes since it's not serializable with Pydantic.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GeometryConfig(BaseModel):
|
|
14
|
+
"""Configuration for geometry settings.
|
|
15
|
+
|
|
16
|
+
This model stores metadata about the component being simulated.
|
|
17
|
+
The actual Component object is stored separately on simulation classes.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
component_name: Name of the component being simulated
|
|
21
|
+
bounds: Bounding box as (xmin, ymin, xmax, ymax)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
25
|
+
|
|
26
|
+
component_name: str | None = None
|
|
27
|
+
bounds: tuple[float, float, float, float] | None = Field(
|
|
28
|
+
default=None, description="Bounding box (xmin, ymin, xmax, ymax)"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"GeometryConfig",
|
|
34
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Mesh configuration models for Palace simulations.
|
|
2
|
+
|
|
3
|
+
This module contains Pydantic models for mesh generation configuration.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Self
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MeshConfig(BaseModel):
|
|
14
|
+
"""Configuration for mesh generation with COMSOL-style presets.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
refined_mesh_size: Mesh size near conductors (um)
|
|
18
|
+
max_mesh_size: Maximum mesh size in air/dielectric (um)
|
|
19
|
+
cells_per_wavelength: Number of mesh cells per wavelength
|
|
20
|
+
margin: XY margin around design (um)
|
|
21
|
+
air_above: Air above top metal (um)
|
|
22
|
+
fmax: Maximum frequency for mesh sizing (Hz)
|
|
23
|
+
boundary_conditions: List of boundary conditions for each face
|
|
24
|
+
show_gui: Show gmsh GUI during meshing
|
|
25
|
+
preview_only: Generate preview only, don't save mesh
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
29
|
+
|
|
30
|
+
refined_mesh_size: float = Field(default=5.0, gt=0)
|
|
31
|
+
max_mesh_size: float = Field(default=300.0, gt=0)
|
|
32
|
+
cells_per_wavelength: int = Field(default=10, ge=1)
|
|
33
|
+
margin: float = Field(default=50.0, ge=0)
|
|
34
|
+
air_above: float = Field(default=100.0, ge=0)
|
|
35
|
+
fmax: float = Field(default=100e9, gt=0)
|
|
36
|
+
boundary_conditions: list[str] | None = None
|
|
37
|
+
show_gui: bool = False
|
|
38
|
+
preview_only: bool = False
|
|
39
|
+
|
|
40
|
+
@model_validator(mode="after")
|
|
41
|
+
def set_default_boundary_conditions(self) -> Self:
|
|
42
|
+
"""Set default boundary conditions if not provided."""
|
|
43
|
+
if self.boundary_conditions is None:
|
|
44
|
+
self.boundary_conditions = ["ABC", "ABC", "ABC", "ABC", "ABC", "ABC"]
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def coarse(cls, **kwargs: float | bool | list[str] | None) -> Self:
|
|
49
|
+
"""Fast mesh for quick iteration (~2.5 elements per wavelength).
|
|
50
|
+
|
|
51
|
+
This preset is suitable for initial debugging and quick checks.
|
|
52
|
+
Not recommended for accurate results.
|
|
53
|
+
"""
|
|
54
|
+
defaults: dict[str, float | int | bool | list[str] | None] = {
|
|
55
|
+
"refined_mesh_size": 10.0,
|
|
56
|
+
"max_mesh_size": 600.0,
|
|
57
|
+
"cells_per_wavelength": 5,
|
|
58
|
+
}
|
|
59
|
+
defaults.update(kwargs)
|
|
60
|
+
return cls(**defaults) # type: ignore[arg-type]
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def default(cls, **kwargs: float | bool | list[str] | None) -> Self:
|
|
64
|
+
"""Balanced mesh matching COMSOL defaults (~5 elements per wavelength).
|
|
65
|
+
|
|
66
|
+
This preset provides a good balance between accuracy and computation time.
|
|
67
|
+
Suitable for most simulations.
|
|
68
|
+
"""
|
|
69
|
+
defaults: dict[str, float | int | bool | list[str] | None] = {
|
|
70
|
+
"refined_mesh_size": 5.0,
|
|
71
|
+
"max_mesh_size": 300.0,
|
|
72
|
+
"cells_per_wavelength": 10,
|
|
73
|
+
}
|
|
74
|
+
defaults.update(kwargs)
|
|
75
|
+
return cls(**defaults) # type: ignore[arg-type]
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def fine(cls, **kwargs: float | bool | list[str] | None) -> Self:
|
|
79
|
+
"""High accuracy mesh (~10 elements per wavelength).
|
|
80
|
+
|
|
81
|
+
This preset provides higher accuracy at the cost of increased
|
|
82
|
+
computation time. Use for final production simulations.
|
|
83
|
+
"""
|
|
84
|
+
defaults: dict[str, float | int | bool | list[str] | None] = {
|
|
85
|
+
"refined_mesh_size": 2.0,
|
|
86
|
+
"max_mesh_size": 70.0,
|
|
87
|
+
"cells_per_wavelength": 20,
|
|
88
|
+
}
|
|
89
|
+
defaults.update(kwargs)
|
|
90
|
+
return cls(**defaults) # type: ignore[arg-type]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
__all__ = [
|
|
94
|
+
"MeshConfig",
|
|
95
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Numerical solver configuration models for Palace simulations.
|
|
2
|
+
|
|
3
|
+
This module contains Pydantic models for numerical solver settings.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NumericalConfig(BaseModel):
|
|
14
|
+
"""Numerical solver configuration.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
order: Finite element order (1-4)
|
|
18
|
+
tolerance: Linear solver tolerance
|
|
19
|
+
max_iterations: Maximum solver iterations
|
|
20
|
+
solver_type: Linear solver type
|
|
21
|
+
preconditioner: Preconditioner type
|
|
22
|
+
device: Compute device (CPU or GPU)
|
|
23
|
+
num_processors: Number of processors (None = auto)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
27
|
+
|
|
28
|
+
# Element order
|
|
29
|
+
order: int = Field(default=2, ge=1, le=4)
|
|
30
|
+
|
|
31
|
+
# Linear solver
|
|
32
|
+
tolerance: float = Field(default=1e-6, gt=0)
|
|
33
|
+
max_iterations: int = Field(default=400, ge=1)
|
|
34
|
+
solver_type: Literal["Default", "SuperLU", "STRUMPACK", "MUMPS"] = "Default"
|
|
35
|
+
|
|
36
|
+
# Preconditioner
|
|
37
|
+
preconditioner: Literal["Default", "AMS", "BoomerAMG"] = "Default"
|
|
38
|
+
|
|
39
|
+
# Device
|
|
40
|
+
device: Literal["CPU", "GPU"] = "CPU"
|
|
41
|
+
|
|
42
|
+
# Partitioning
|
|
43
|
+
num_processors: int | None = None # None = auto
|
|
44
|
+
|
|
45
|
+
def to_palace_config(self) -> dict:
|
|
46
|
+
"""Convert to Palace JSON config format."""
|
|
47
|
+
solver_config: dict[str, str | int | float] = {
|
|
48
|
+
"Tolerance": self.tolerance,
|
|
49
|
+
"MaxIterations": self.max_iterations,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if self.solver_type != "Default":
|
|
53
|
+
solver_config["Type"] = self.solver_type
|
|
54
|
+
|
|
55
|
+
if self.preconditioner != "Default":
|
|
56
|
+
solver_config["Preconditioner"] = self.preconditioner
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
"Order": self.order,
|
|
60
|
+
"Solver": solver_config,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
"NumericalConfig",
|
|
66
|
+
]
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Port configuration models for Palace simulations.
|
|
2
|
+
|
|
3
|
+
This module contains Pydantic models for port definitions:
|
|
4
|
+
- PortConfig: Single-element lumped port configuration
|
|
5
|
+
- CPWPortConfig: Coplanar waveguide (two-element) port configuration
|
|
6
|
+
- TerminalConfig: Terminal for electrostatic simulations
|
|
7
|
+
- WavePortConfig: Wave port (domain boundary with mode solving)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Literal, Self
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PortConfig(BaseModel):
|
|
18
|
+
"""Configuration for a single-element lumped port.
|
|
19
|
+
|
|
20
|
+
Lumped ports can be inplane (horizontal, on single layer) or
|
|
21
|
+
via (vertical, between two layers).
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
name: Port name (must match component port name)
|
|
25
|
+
layer: Target layer for inplane ports
|
|
26
|
+
from_layer: Bottom layer for via ports
|
|
27
|
+
to_layer: Top layer for via ports
|
|
28
|
+
length: Port extent along direction (um)
|
|
29
|
+
impedance: Port impedance (Ohms)
|
|
30
|
+
excited: Whether this port is excited
|
|
31
|
+
geometry: Port geometry type ("inplane" or "via")
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
35
|
+
|
|
36
|
+
name: str
|
|
37
|
+
layer: str | None = None
|
|
38
|
+
from_layer: str | None = None
|
|
39
|
+
to_layer: str | None = None
|
|
40
|
+
length: float | None = Field(default=None, gt=0)
|
|
41
|
+
impedance: float = Field(default=50.0, gt=0)
|
|
42
|
+
excited: bool = True
|
|
43
|
+
geometry: Literal["inplane", "via"] = "inplane"
|
|
44
|
+
|
|
45
|
+
@model_validator(mode="after")
|
|
46
|
+
def validate_layer_config(self) -> Self:
|
|
47
|
+
"""Validate layer configuration based on geometry type."""
|
|
48
|
+
if self.geometry == "inplane" and self.layer is None:
|
|
49
|
+
raise ValueError("Inplane ports require 'layer' to be specified")
|
|
50
|
+
if self.geometry == "via" and (
|
|
51
|
+
self.from_layer is None or self.to_layer is None
|
|
52
|
+
):
|
|
53
|
+
raise ValueError("Via ports require both 'from_layer' and 'to_layer'")
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CPWPortConfig(BaseModel):
|
|
58
|
+
"""Configuration for a coplanar waveguide (CPW) port.
|
|
59
|
+
|
|
60
|
+
CPW ports consist of two elements (upper and lower gaps) that are
|
|
61
|
+
excited with opposite E-field directions to create the CPW mode.
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
upper: Name of the upper gap port on the component
|
|
65
|
+
lower: Name of the lower gap port on the component
|
|
66
|
+
layer: Target conductor layer
|
|
67
|
+
length: Port extent along direction (um)
|
|
68
|
+
impedance: Port impedance (Ohms)
|
|
69
|
+
excited: Whether this port is excited
|
|
70
|
+
name: Optional name for the CPW port (default: "cpw_{lower}")
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
74
|
+
|
|
75
|
+
upper: str = Field(description="Name of upper gap port")
|
|
76
|
+
lower: str = Field(description="Name of lower gap port")
|
|
77
|
+
layer: str = Field(description="Target conductor layer")
|
|
78
|
+
length: float = Field(gt=0, description="Port extent in um")
|
|
79
|
+
impedance: float = Field(default=50.0, gt=0)
|
|
80
|
+
excited: bool = True
|
|
81
|
+
name: str | None = Field(
|
|
82
|
+
default=None, description="CPW port name (default: cpw_{lower})"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def effective_name(self) -> str:
|
|
87
|
+
"""Get the effective port name."""
|
|
88
|
+
return self.name if self.name else f"cpw_{self.lower}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TerminalConfig(BaseModel):
|
|
92
|
+
"""Configuration for a terminal (for electrostatic capacitance extraction).
|
|
93
|
+
|
|
94
|
+
Terminals define conductor surfaces for capacitance matrix extraction
|
|
95
|
+
in electrostatic simulations.
|
|
96
|
+
|
|
97
|
+
Attributes:
|
|
98
|
+
name: Terminal name
|
|
99
|
+
layer: Target conductor layer
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
103
|
+
|
|
104
|
+
name: str
|
|
105
|
+
layer: str
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class WavePortConfig(BaseModel):
|
|
109
|
+
"""Configuration for a wave port (domain boundary with mode solving).
|
|
110
|
+
|
|
111
|
+
Wave ports are used for domain-boundary ports where mode solving
|
|
112
|
+
is needed. This is an alternative to lumped ports for more accurate
|
|
113
|
+
S-parameter extraction.
|
|
114
|
+
|
|
115
|
+
Attributes:
|
|
116
|
+
name: Port name (must match component port name)
|
|
117
|
+
layer: Target conductor layer
|
|
118
|
+
mode: Mode number to excite
|
|
119
|
+
excited: Whether this port is excited
|
|
120
|
+
offset: De-embedding distance in um
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
124
|
+
|
|
125
|
+
name: str
|
|
126
|
+
layer: str
|
|
127
|
+
mode: int = Field(default=1, ge=1, description="Mode number to excite")
|
|
128
|
+
excited: bool = True
|
|
129
|
+
offset: float = Field(default=0.0, ge=0, description="De-embedding distance in um")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
__all__ = [
|
|
133
|
+
"CPWPortConfig",
|
|
134
|
+
"PortConfig",
|
|
135
|
+
"TerminalConfig",
|
|
136
|
+
"WavePortConfig",
|
|
137
|
+
]
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Problem-specific configuration models for Palace simulations.
|
|
2
|
+
|
|
3
|
+
This module contains Pydantic models for different simulation types:
|
|
4
|
+
- DrivenConfig: Frequency-domain driven simulation (S-parameters)
|
|
5
|
+
- EigenmodeConfig: Eigenmode/resonance simulation
|
|
6
|
+
- ElectrostaticConfig: Electrostatic capacitance extraction
|
|
7
|
+
- MagnetostaticConfig: Magnetostatic inductance extraction
|
|
8
|
+
- TransientConfig: Time-domain simulation
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Literal, Self
|
|
14
|
+
|
|
15
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DrivenConfig(BaseModel):
|
|
19
|
+
"""Configuration for driven (frequency sweep) simulation.
|
|
20
|
+
|
|
21
|
+
This is used for S-parameter extraction and frequency response analysis.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
fmin: Minimum frequency in Hz
|
|
25
|
+
fmax: Maximum frequency in Hz
|
|
26
|
+
num_points: Number of frequency points
|
|
27
|
+
scale: Frequency spacing ("linear" or "log")
|
|
28
|
+
adaptive_tol: Adaptive tolerance (0 disables adaptive)
|
|
29
|
+
adaptive_max_samples: Maximum samples for adaptive refinement
|
|
30
|
+
compute_s_params: Whether to compute S-parameters
|
|
31
|
+
reference_impedance: Reference impedance for S-params (Ohms)
|
|
32
|
+
excitation_port: Name of port to excite (None = first port)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
36
|
+
|
|
37
|
+
fmin: float = Field(default=1e9, gt=0, description="Min frequency in Hz")
|
|
38
|
+
fmax: float = Field(default=100e9, gt=0, description="Max frequency in Hz")
|
|
39
|
+
num_points: int = Field(default=40, ge=1, description="Number of frequency points")
|
|
40
|
+
scale: Literal["linear", "log"] = "linear"
|
|
41
|
+
|
|
42
|
+
# Adaptive options
|
|
43
|
+
adaptive_tol: float = Field(
|
|
44
|
+
default=0.02, ge=0, description="Adaptive tolerance (0 disables adaptive)"
|
|
45
|
+
)
|
|
46
|
+
adaptive_max_samples: int = Field(default=20, ge=1)
|
|
47
|
+
|
|
48
|
+
# S-parameter options
|
|
49
|
+
compute_s_params: bool = True
|
|
50
|
+
reference_impedance: float = Field(default=50.0, gt=0)
|
|
51
|
+
|
|
52
|
+
# Excitation
|
|
53
|
+
excitation_port: str | None = Field(
|
|
54
|
+
default=None, description="Port to excite (None = first port)"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@model_validator(mode="after")
|
|
58
|
+
def validate_frequency_range(self) -> Self:
|
|
59
|
+
"""Validate that fmin < fmax."""
|
|
60
|
+
if self.fmin >= self.fmax:
|
|
61
|
+
raise ValueError(f"fmin ({self.fmin}) must be less than fmax ({self.fmax})")
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
def to_palace_config(self) -> dict:
|
|
65
|
+
"""Convert to Palace JSON config format."""
|
|
66
|
+
freq_step = (self.fmax - self.fmin) / max(1, self.num_points - 1) / 1e9
|
|
67
|
+
return {
|
|
68
|
+
"Samples": [
|
|
69
|
+
{
|
|
70
|
+
"Type": "Linear" if self.scale == "linear" else "Log",
|
|
71
|
+
"MinFreq": self.fmin / 1e9,
|
|
72
|
+
"MaxFreq": self.fmax / 1e9,
|
|
73
|
+
"FreqStep": freq_step,
|
|
74
|
+
"SaveStep": 0,
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"AdaptiveTol": max(0, self.adaptive_tol),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class EigenmodeConfig(BaseModel):
|
|
82
|
+
"""Configuration for eigenmode (resonance) simulation.
|
|
83
|
+
|
|
84
|
+
This is used for finding resonant frequencies and mode shapes.
|
|
85
|
+
|
|
86
|
+
Attributes:
|
|
87
|
+
num_modes: Number of modes to find
|
|
88
|
+
target: Target frequency in Hz for mode search
|
|
89
|
+
tolerance: Eigenvalue solver tolerance
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
93
|
+
|
|
94
|
+
num_modes: int = Field(
|
|
95
|
+
default=10, ge=1, alias="N", description="Number of modes to find"
|
|
96
|
+
)
|
|
97
|
+
target: float | None = Field(default=None, description="Target frequency in Hz")
|
|
98
|
+
tolerance: float = Field(
|
|
99
|
+
default=1e-6, gt=0, description="Eigenvalue solver tolerance"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def to_palace_config(self) -> dict:
|
|
103
|
+
"""Convert to Palace JSON config format."""
|
|
104
|
+
config: dict = {
|
|
105
|
+
"N": self.num_modes,
|
|
106
|
+
"Tol": self.tolerance,
|
|
107
|
+
}
|
|
108
|
+
if self.target is not None:
|
|
109
|
+
config["Target"] = self.target / 1e9 # Convert to GHz
|
|
110
|
+
return config
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class ElectrostaticConfig(BaseModel):
|
|
114
|
+
"""Configuration for electrostatic (capacitance matrix) simulation.
|
|
115
|
+
|
|
116
|
+
Attributes:
|
|
117
|
+
save_fields: Number of field solutions to save
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
121
|
+
|
|
122
|
+
save_fields: int = Field(default=0, ge=0, description="Number of fields to save")
|
|
123
|
+
|
|
124
|
+
def to_palace_config(self) -> dict:
|
|
125
|
+
"""Convert to Palace JSON config format."""
|
|
126
|
+
return {
|
|
127
|
+
"Save": self.save_fields,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class MagnetostaticConfig(BaseModel):
|
|
132
|
+
"""Configuration for magnetostatic (inductance matrix) simulation.
|
|
133
|
+
|
|
134
|
+
Attributes:
|
|
135
|
+
save_fields: Number of field solutions to save
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
139
|
+
|
|
140
|
+
save_fields: int = Field(default=0, ge=0, description="Number of fields to save")
|
|
141
|
+
|
|
142
|
+
def to_palace_config(self) -> dict:
|
|
143
|
+
"""Convert to Palace JSON config format."""
|
|
144
|
+
return {
|
|
145
|
+
"Save": self.save_fields,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TransientConfig(BaseModel):
|
|
150
|
+
"""Configuration for transient (time-domain) simulation.
|
|
151
|
+
|
|
152
|
+
Attributes:
|
|
153
|
+
max_time: Maximum simulation time in ns
|
|
154
|
+
excitation: Excitation waveform type
|
|
155
|
+
excitation_freq: Excitation frequency in Hz (for sinusoidal)
|
|
156
|
+
excitation_width: Pulse width in ns (for gaussian)
|
|
157
|
+
time_step: Time step in ns (None = adaptive)
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
161
|
+
|
|
162
|
+
excitation: Literal["sinusoidal", "gaussian", "ramp", "smoothstep"] = "sinusoidal"
|
|
163
|
+
excitation_freq: float | None = Field(
|
|
164
|
+
default=None, description="Excitation frequency in Hz"
|
|
165
|
+
)
|
|
166
|
+
excitation_width: float | None = Field(
|
|
167
|
+
default=None, description="Pulse width in ns (for gaussian)"
|
|
168
|
+
)
|
|
169
|
+
max_time: float = Field(description="Maximum simulation time in ns")
|
|
170
|
+
time_step: float | None = Field(
|
|
171
|
+
default=None, description="Time step in ns (None = adaptive)"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def to_palace_config(self) -> dict:
|
|
175
|
+
"""Convert to Palace JSON config format."""
|
|
176
|
+
config: dict = {
|
|
177
|
+
"Type": self.excitation.capitalize(),
|
|
178
|
+
"MaxTime": self.max_time,
|
|
179
|
+
}
|
|
180
|
+
if self.excitation_freq is not None:
|
|
181
|
+
config["ExcitationFreq"] = self.excitation_freq / 1e9 # Convert to GHz
|
|
182
|
+
if self.excitation_width is not None:
|
|
183
|
+
config["ExcitationWidth"] = self.excitation_width
|
|
184
|
+
if self.time_step is not None:
|
|
185
|
+
config["TimeStep"] = self.time_step
|
|
186
|
+
return config
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
__all__ = [
|
|
190
|
+
"DrivenConfig",
|
|
191
|
+
"EigenmodeConfig",
|
|
192
|
+
"ElectrostaticConfig",
|
|
193
|
+
"MagnetostaticConfig",
|
|
194
|
+
"TransientConfig",
|
|
195
|
+
]
|