Skip to content

Commit 02f85c4

Browse files
committed
Feat: Add ACVoltageSource for small-signal analysis
1 parent 6c070c7 commit 02f85c4

File tree

9 files changed

+243
-3
lines changed

9 files changed

+243
-3
lines changed

docs/api/spice.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Sources
1010
:toctree: _autosummary/
1111
:template: module.rst
1212

13+
tidy3d.ACVoltageSource
1314
tidy3d.DCVoltageSource
1415
tidy3d.DCCurrentSource
1516

tests/test_components/test_heat_charge.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,38 @@ def test_heat_charge_bcs_validation(boundary_conditions):
897897
with pytest.raises(pd.ValidationError):
898898
td.VoltageBC(source=td.DCVoltageSource(voltage=np.array([td.inf, 0, 1])))
899899

900+
# Invalid ACVoltageSource: infinite voltage
901+
with pytest.raises(pd.ValidationError):
902+
td.VoltageBC(
903+
source=td.ACVoltageSource(
904+
voltage=np.array([td.inf, 0, 1]), freqs=[1e2, 1e3], amplitude=1e-2
905+
)
906+
)
907+
908+
# Invalid ACVoltageSource: infinite frequency
909+
with pytest.raises(pd.ValidationError):
910+
td.VoltageBC(
911+
source=td.ACVoltageSource(
912+
voltage=np.array([-0.1, 0.2, 0.3]), freqs=[1e2, td.inf, 1e4], amplitude=1e-2
913+
)
914+
)
915+
916+
# Invalid ACVoltageSource: negative frequency
917+
with pytest.raises(pd.ValidationError):
918+
td.VoltageBC(
919+
source=td.ACVoltageSource(
920+
voltage=np.array([-0.1, 0.2, 0.3]), freqs=[-1e2, 1e1, 1e2], amplitude=1e-2
921+
)
922+
)
923+
924+
# Invalid ACVoltageSource: infinite amplitude
925+
with pytest.raises(pd.ValidationError):
926+
td.VoltageBC(
927+
source=td.ACVoltageSource(
928+
voltage=np.array([-0.1, 0.2, 0.3]), freqs=[1e2, 1e3, 1e4], amplitude=td.inf
929+
)
930+
)
931+
900932

901933
def test_vertical_natural_convection():
902934
solid_box_l = td.Box(center=(0, 0, 0), size=(2, 2, 2))
@@ -1220,6 +1252,54 @@ def test_heat_charge_simulation(simulation_data):
12201252
assert mesher is not None, "VolumeMesher should be created successfully."
12211253

12221254

1255+
def test_heat_charge_multiple_ac_sources(grid_specs):
1256+
"""Tests that a ValidationError is raised when multiple AC sources are defined."""
1257+
solid_box_1 = td.Box(center=(0, 0, 0), size=(2, 2, 2))
1258+
solid_box_2 = td.Box(center=(2, 2, 2), size=(2, 2, 2))
1259+
solid_box_3 = td.Box(center=(4, 4, 4), size=(2, 2, 2))
1260+
1261+
solid_medium = td.MultiPhysicsMedium(
1262+
heat=td.SolidMedium(conductivity=1, capacity=1), name="solid"
1263+
)
1264+
air = td.MultiPhysicsMedium(heat=td.FluidMedium(), name="air")
1265+
1266+
def createSolid(geometry, name):
1267+
return td.Structure(geometry=geometry, medium=solid_medium, name=name)
1268+
1269+
solid_structure_1 = createSolid(solid_box_1, "solid_1")
1270+
solid_structure_2 = createSolid(solid_box_2, "solid_2")
1271+
solid_structure_3 = createSolid(solid_box_3, "solid_3")
1272+
1273+
bc_ssac1 = td.VoltageBC(
1274+
source=td.ACVoltageSource(
1275+
voltage=[0, 1], freqs=[1e3, 1e4], amplitude=1e-3, name="ac_source1"
1276+
)
1277+
)
1278+
bc_ssac2 = td.VoltageBC(
1279+
source=td.ACVoltageSource(
1280+
voltage=[0, 1], freqs=[1e3, 1e4], amplitude=1e-3, name="ac_source2"
1281+
)
1282+
)
1283+
1284+
placement1 = td.StructureStructureInterface(structures=["solid_1", "solid_2"])
1285+
placement2 = td.StructureStructureInterface(structures=["solid_2", "solid_3"])
1286+
1287+
boundary_spec = [
1288+
td.HeatChargeBoundarySpec(condition=bc_ssac1, placement=placement1),
1289+
td.HeatChargeBoundarySpec(condition=bc_ssac2, placement=placement2),
1290+
]
1291+
1292+
with pytest.raises(pd.ValidationError, match="Only a single AC source can be supplied"):
1293+
td.HeatChargeSimulation(
1294+
structures=[solid_structure_1, solid_structure_2, solid_structure_3],
1295+
center=(2, 2, 2),
1296+
size=(6, 6, 6),
1297+
boundary_spec=boundary_spec,
1298+
medium=air,
1299+
grid_spec=grid_specs["uniform"],
1300+
)
1301+
1302+
12231303
def test_sim_data_plotting(simulation_data):
12241304
"""Tests whether simulation data can be plotted and appropriate errors are raised."""
12251305
heat_sim_data, cond_sim_data, cap_sim_data, fc_sim_data, mesh_data = simulation_data

tidy3d/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
IsothermalSteadyChargeDCAnalysis,
2626
SteadyChargeDCAnalysis,
2727
)
28+
from tidy3d.components.spice.sources.ac import ACVoltageSource
2829
from tidy3d.components.spice.sources.dc import DCCurrentSource, DCVoltageSource
2930
from tidy3d.components.spice.sources.types import VoltageSourceType
3031
from tidy3d.components.tcad.analysis.heat_simulation_type import UnsteadyHeatAnalysis, UnsteadySpec
@@ -444,6 +445,7 @@ def set_logging_level(level: str) -> None:
444445
"PML",
445446
"TFSF",
446447
"ABCBoundary",
448+
"ACVoltageSource",
447449
"Absorber",
448450
"AbsorberParams",
449451
"AbstractFieldProjectionData",

tidy3d/components/data/data_array.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,24 @@ class FreqDataArray(DataArray):
535535
_dims = ("f",)
536536

537537

538+
class FreqVoltageDataArray(DataArray):
539+
"""Frequency-domain array.
540+
541+
Example
542+
-------
543+
>>> f = [2e14, 3e14]
544+
>>> v = [0.1, 0.2, 0.3]
545+
>>> coords = dict(f=f, v=v)
546+
>>> fd = FreqVoltageDataArray((1+1j) * np.random.random((2, 3)), coords=coords)
547+
"""
548+
549+
__slots__ = ()
550+
_dims = (
551+
"f",
552+
"v",
553+
)
554+
555+
538556
class FreqModeDataArray(DataArray):
539557
"""Array over frequency and mode index.
540558
@@ -1559,6 +1577,7 @@ def _make_impedance_data_array(result: DataArray) -> ImpedanceResultTypes:
15591577
FreqDataArray,
15601578
TimeDataArray,
15611579
FreqModeDataArray,
1580+
FreqVoltageDataArray,
15621581
TriangleMeshDataArray,
15631582
HeatDataArray,
15641583
EMEScalarFieldDataArray,
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from __future__ import annotations
2+
3+
from typing import Optional
4+
5+
import pydantic.v1 as pd
6+
7+
from tidy3d.components.base import Tidy3dBaseModel
8+
from tidy3d.components.types import ArrayFloat1D
9+
from tidy3d.constants import HERTZ, VOLT
10+
from tidy3d.constants import inf as td_inf
11+
12+
13+
class ACVoltageSource(Tidy3dBaseModel):
14+
"""
15+
Small-signal AC voltage source.
16+
17+
Notes
18+
-----
19+
This source represents a small-signal AC excitation defined by a list of bias voltages,
20+
a set of small-signal analysis frequencies, and a small-signal amplitude (linear scale).
21+
22+
The bias ``voltage`` refers to the DC operating point above the simulation ground. Currently, full circuit simulation though electrical ports is not supported.
23+
24+
Examples
25+
--------
26+
>>> import tidy3d as td
27+
>>> voltages = [-0.5, 0, 1, 2, 3, 4]
28+
>>> frequency = [1e3, 1e4, 1e5]
29+
>>> amplitude = 1e-3
30+
>>> voltage_source = td.ACVoltageSource(
31+
... voltage=voltages,
32+
... freqs=frequency,
33+
... amplitude=amplitude,
34+
... )
35+
"""
36+
37+
name: Optional[str] = pd.Field(
38+
None,
39+
title="Name",
40+
description="Unique name for the AC voltage source",
41+
min_length=1,
42+
)
43+
44+
voltage: ArrayFloat1D = pd.Field(
45+
...,
46+
title="DC Bias Voltages",
47+
description="List of DC operating point voltages (above ground) used with :class:`VoltageBC`.",
48+
units=VOLT,
49+
)
50+
51+
freqs: ArrayFloat1D = pd.Field(
52+
...,
53+
title="AC Analysis Frequencies",
54+
description="List of small-signal analysis frequencies.",
55+
units=HERTZ,
56+
)
57+
58+
amplitude: pd.FiniteFloat = pd.Field(
59+
...,
60+
title="Small-Signal Amplitude",
61+
description="Small-signal AC amplitude.",
62+
units=VOLT,
63+
)
64+
65+
@pd.validator("voltage")
66+
def validate_voltage(cls, val):
67+
for v in val:
68+
if v == td_inf:
69+
raise ValueError(f"Voltages must be finite. Current voltage= {val}.")
70+
return val
71+
72+
@pd.validator("freqs")
73+
def validate_frequency(cls, val):
74+
for v in val:
75+
if v == td_inf or v <= 0:
76+
raise ValueError(
77+
f"Frequencies must be strictly positive and finite. Current frequency={val}."
78+
)
79+
return val
80+
81+
@pd.validator("amplitude")
82+
def validate_amplitude(cls, val):
83+
if val == td_inf:
84+
raise ValueError(f"Small signal amplitude must be finite. Current amplitude={val}.")
85+
return val

tidy3d/components/spice/sources/dc.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,13 @@ class DCVoltageSource(Tidy3dBaseModel):
4848
>>> voltage_source = td.DCVoltageSource(voltage=voltages)
4949
"""
5050

51-
name: Optional[str]
51+
name: Optional[str] = pd.Field(
52+
None,
53+
title="Name",
54+
description="Unique name for the DC voltage source",
55+
min_length=1,
56+
)
57+
5258
voltage: ArrayFloat1D = pd.Field(
5359
...,
5460
title="Voltage",
@@ -78,7 +84,13 @@ class DCCurrentSource(Tidy3dBaseModel):
7884
>>> current_source = td.DCCurrentSource(current=0.4)
7985
"""
8086

81-
name: Optional[str]
87+
name: Optional[str] = pd.Field(
88+
None,
89+
title="Name",
90+
description="Unique name for the DC current source",
91+
min_length=1,
92+
)
93+
8294
current: pd.FiniteFloat = pd.Field(
8395
title="Current",
8496
description="DC current usually used as source in :class:`CurrentBC` boundary conditions.",

tidy3d/components/spice/sources/types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
from typing import Union
44

5+
from .ac import ACVoltageSource
56
from .dc import DCCurrentSource, DCVoltageSource
67

7-
VoltageSourceType = Union[DCVoltageSource]
8+
VoltageSourceType = Union[DCVoltageSource, ACVoltageSource]
89
CurrentSourceType = Union[DCCurrentSource]

tidy3d/components/tcad/data/sim_data.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from tidy3d.components.base import Tidy3dBaseModel
1212
from tidy3d.components.base_sim.data.sim_data import AbstractSimulationData
1313
from tidy3d.components.data.data_array import (
14+
FreqVoltageDataArray,
1415
SpatialDataArray,
1516
SteadyVoltageDataArray,
1617
)
@@ -84,6 +85,12 @@ class DeviceCharacteristics(Tidy3dBaseModel):
8485
"is given in Ohms. Note that in 2D the resistance is given in :math:`\\Omega \\mu`.",
8586
)
8687

88+
ac_current_voltage: Optional[FreqVoltageDataArray] = pd.Field(
89+
None,
90+
title="AC current vs voltage and frequency",
91+
description="Complex small-signal current I(v, f).",
92+
)
93+
8794

8895
class AbstractHeatChargeSimulationData(AbstractSimulationData, ABC):
8996
"""Abstract class for HeatChargeSimulation results, or VolumeMesher results."""

tidy3d/components/tcad/simulation/heat_charge.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from tidy3d.components.material.types import MultiPhysicsMedium, StructureMediumType
3636
from tidy3d.components.medium import Medium
3737
from tidy3d.components.scene import Scene
38+
from tidy3d.components.spice.sources.ac import ACVoltageSource
3839
from tidy3d.components.spice.sources.dc import DCVoltageSource
3940
from tidy3d.components.spice.types import (
4041
ElectricalAnalysisType,
@@ -468,6 +469,19 @@ def check_voltage_array_if_capacitance(cls, values):
468469
)
469470
return values
470471

472+
@pd.root_validator(skip_on_failure=True)
473+
def check_single_ssac(cls, values):
474+
boundary_spec = values["boundary_spec"]
475+
ssac_present = False
476+
for bc in boundary_spec:
477+
if isinstance(bc.condition, VoltageBC):
478+
if isinstance(bc.condition.source, ACVoltageSource):
479+
if ssac_present:
480+
raise SetupError("Only a single AC source can be supplied")
481+
else:
482+
ssac_present = True
483+
return values
484+
471485
@pd.root_validator(skip_on_failure=True)
472486
def check_natural_convection_bc(cls, values):
473487
"""Make sure that natural convection BCs are defined correctly."""
@@ -1928,3 +1942,22 @@ def _useHeatSourceFromConductionSim(self):
19281942
"""Returns True if 'HeatFromElectricSource' has been defined."""
19291943

19301944
return any(isinstance(source, HeatFromElectricSource) for source in self.sources)
1945+
1946+
def _get_charge_type(self):
1947+
for bc in self.boundary_spec:
1948+
if isinstance(bc.condition, VoltageBC):
1949+
if isinstance(bc.condition.source, ACVoltageSource):
1950+
return "ac"
1951+
return "dc"
1952+
1953+
def _get_ssac_frequency_and_amplitude(self):
1954+
for bc in self.boundary_spec:
1955+
if isinstance(bc.condition, VoltageBC):
1956+
if isinstance(bc.condition.source, ACVoltageSource):
1957+
return (
1958+
bc.condition.source.freqs.tolist(),
1959+
bc.condition.source.amplitude,
1960+
)
1961+
raise SetupError(
1962+
"'HeatChargeSimulation' does not include any 'ACVoltageSource' in 'boundary_spec')."
1963+
)

0 commit comments

Comments
 (0)