Skip to content

Commit 1cbc02d

Browse files
committed
Unsteady heat support
Heat capacity is now not a required parameter unless we run unsteady heat
1 parent 7b53ee9 commit 1cbc02d

File tree

12 files changed

+300
-27
lines changed

12 files changed

+300
-27
lines changed

tests/test_components/test_heat.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ def test_heat_medium():
5656
with pytest.raises(pd.ValidationError):
5757
_ = solid_medium.heat_spec.updated_copy(conductivity=-1)
5858

59+
# check we can create solid medium from SI units
60+
_ = td.SolidSpec.from_si_units(
61+
conductivity=1,
62+
capacity=1,
63+
density=1,
64+
)
65+
5966

6067
def make_heat_structures():
6168
fluid_medium, solid_medium = make_heat_mediums()

tests/test_components/test_heat_charge.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,3 +1603,71 @@ def test_symmetry_capacitance(symmetry):
16031603
assert (
16041604
mnt_data.symmetry_expanded_copy.electron_capacitance.data[n] == data[n] * scaling_factor
16051605
)
1606+
1607+
1608+
def test_unsteady_parameters():
1609+
"""Test that unsteady parameters are set correctly."""
1610+
1611+
_ = td.UnsteadyHeatAnalysis(
1612+
initial_temperature=300,
1613+
unsteady_spec=td.UnsteadySpec(time_step=0.1, total_time_steps=1),
1614+
)
1615+
1616+
# test non-positive initial temperature raises error
1617+
with pytest.raises(pd.ValidationError):
1618+
_ = td.UnsteadyHeatAnalysis(
1619+
initial_temperature=0,
1620+
unsteady_spec=td.UnsteadySpec(time_step=0.1, total_time_steps=1),
1621+
)
1622+
1623+
# test negative time step raises error
1624+
with pytest.raises(pd.ValidationError):
1625+
_ = td.UnsteadyHeatAnalysis(
1626+
initial_temperature=10,
1627+
unsteady_spec=td.UnsteadySpec(time_step=-0.1, total_time_steps=1),
1628+
)
1629+
1630+
# test negative total time steps raises error
1631+
with pytest.raises(pd.ValidationError):
1632+
_ = td.UnsteadyHeatAnalysis(
1633+
initial_temperature=10,
1634+
unsteady_spec=td.UnsteadySpec(time_step=0.1, total_time_steps=-1),
1635+
)
1636+
1637+
1638+
def test_unsteady_heat_analysis(heat_simulation):
1639+
"""Test that the validators for unsteady heat analysis are working."""
1640+
1641+
unsteady_analysis_spec = td.UnsteadyHeatAnalysis(
1642+
initial_temperature=300,
1643+
unsteady_spec=td.UnsteadySpec(time_step=0.1, total_time_steps=1),
1644+
)
1645+
1646+
temp_mnt = td.TemperatureMonitor(
1647+
center=(0, 0, 0),
1648+
size=(td.inf, td.inf, td.inf),
1649+
name="temperature",
1650+
unstructured=True,
1651+
interval=2,
1652+
)
1653+
1654+
# this should work since the monitor is unstructured
1655+
unsteady_sim = heat_simulation.updated_copy(
1656+
analysis_spec=unsteady_analysis_spec, monitors=[temp_mnt]
1657+
)
1658+
1659+
with pytest.raises(pd.ValidationError):
1660+
temp_mnt = temp_mnt.updated_copy(unstructured=False)
1661+
_ = unsteady_sim.updated_copy(monitors=[temp_mnt])
1662+
1663+
with pytest.raises(pd.ValidationError):
1664+
temp_mnt = temp_mnt.updated_copy(unstructured=True, interval=0)
1665+
_ = unsteady_sim.updated_copy(monitors=[temp_mnt])
1666+
1667+
# try simulation with excessive time steps
1668+
with pytest.raises(pd.ValidationError):
1669+
mew_spex = td.UnsteadyHeatAnalysis(
1670+
initial_temperature=300,
1671+
unsteady_spec=td.UnsteadySpec(time_step=0.1, total_time_steps=100000),
1672+
)
1673+
_ = unsteady_sim.updated_copy(analysis_spec=mew_spex)

tests/test_data/test_datasets.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
np.random.seed(4)
1111

1212

13-
@pytest.mark.parametrize("dataset_type_ind", [0, 1])
13+
@pytest.mark.parametrize("dataset_type_ind", [0, 1, 2])
1414
@pytest.mark.parametrize("ds_name", ["test123", None])
1515
def test_triangular_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False):
1616
import tidy3d as td
@@ -26,6 +26,11 @@ def test_triangular_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False):
2626
values_type = td.IndexedVoltageDataArray
2727
extra_dims = {"voltage": [0, 1, 2]}
2828

29+
if dataset_type_ind == 2:
30+
dataset_type = td.TriangularGridDataset
31+
values_type = td.IndexedTimeDataArray
32+
extra_dims = {"t": [0, 1, 2]}
33+
2934
# basic create
3035
tri_grid_points = td.PointDataArray(
3136
[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]],
@@ -322,7 +327,7 @@ def operation(arr):
322327
assert result.name == ds_name
323328

324329

325-
@pytest.mark.parametrize("dataset_type_ind", [0, 1])
330+
@pytest.mark.parametrize("dataset_type_ind", [0, 1, 2])
326331
@pytest.mark.parametrize("ds_name", ["test123", None])
327332
def test_tetrahedral_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False):
328333
import tidy3d as td
@@ -338,6 +343,11 @@ def test_tetrahedral_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False):
338343
values_type = td.IndexedVoltageDataArray
339344
extra_dims = {"voltage": [0, 1, 2]}
340345

346+
if dataset_type_ind == 2:
347+
dataset_type = td.TetrahedralGridDataset
348+
values_type = td.IndexedTimeDataArray
349+
extra_dims = {"t": [0, 1, 2]}
350+
341351
# basic create
342352
tet_grid_points = td.PointDataArray(
343353
[

tidy3d/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
)
2222
from tidy3d.components.spice.sources.dc import DCCurrentSource, DCVoltageSource
2323
from tidy3d.components.spice.sources.types import VoltageSourceType
24+
from tidy3d.components.tcad.analysis.heat_simulation_type import UnsteadyHeatAnalysis, UnsteadySpec
2425
from tidy3d.components.tcad.boundary.specification import (
2526
HeatBoundarySpec,
2627
HeatChargeBoundarySpec,
@@ -131,6 +132,7 @@
131132
FluxTimeDataArray,
132133
HeatDataArray,
133134
IndexedDataArray,
135+
IndexedTimeDataArray,
134136
IndexedVoltageDataArray,
135137
ModeAmpsDataArray,
136138
ModeIndexDataArray,
@@ -603,6 +605,8 @@ def set_logging_level(level: str) -> None:
603605
"UniformHeatSource",
604606
"HeatSource",
605607
"HeatFromElectricSource",
608+
"UnsteadyHeatAnalysis",
609+
"UnsteadySpec",
606610
"UniformUnstructuredGrid",
607611
"DistanceUnstructuredGrid",
608612
"GridRefinementRegion",
@@ -635,6 +639,7 @@ def set_logging_level(level: str) -> None:
635639
"PointDataArray",
636640
"CellDataArray",
637641
"IndexedDataArray",
642+
"IndexedTimeDataArray",
638643
"IndexedVoltageDataArray",
639644
"SteadyVoltageDataArray",
640645
"TriangularGridDataset",

tidy3d/components/data/data_array.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,22 @@ class IndexedVoltageDataArray(DataArray):
12481248
_dims = ("index", "voltage")
12491249

12501250

1251+
class IndexedTimeDataArray(DataArray):
1252+
"""Stores a two-dimensional array with coordinates ``index`` and ``t``, where
1253+
``index`` is usually associated with ``PointDataArray`` and ``t`` indicates at what
1254+
simulated time the data was obtained.
1255+
1256+
Example
1257+
-------
1258+
>>> indexed_array = IndexedTimeDataArray(
1259+
... (1+1j) * np.random.random((3,2)), coords=dict(index=np.arange(3), t=[0, 1])
1260+
... )
1261+
"""
1262+
1263+
__slots__ = ()
1264+
_dims = ("index", "t")
1265+
1266+
12511267
class SpatialVoltageDataArray(AbstractSpatialDataArray):
12521268
"""Spatial distribution with voltage mapping.
12531269
@@ -1305,7 +1321,8 @@ class PerturbationCoefficientDataArray(DataArray):
13051321
IndexedVoltageDataArray,
13061322
SpatialVoltageDataArray,
13071323
PerturbationCoefficientDataArray,
1324+
IndexedTimeDataArray,
13081325
]
13091326
DATA_ARRAY_MAP = {data_array.__name__: data_array for data_array in DATA_ARRAY_TYPES}
13101327

1311-
IndexedDataArrayTypes = Union[IndexedDataArray, IndexedVoltageDataArray]
1328+
IndexedDataArrayTypes = Union[IndexedDataArray, IndexedVoltageDataArray, IndexedTimeDataArray]

tidy3d/components/material/tcad/heat.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from tidy3d.components.base import Tidy3dBaseModel
1111
from tidy3d.constants import (
12+
DENSITY,
1213
SPECIFIC_HEAT_CAPACITY,
1314
THERMAL_CONDUCTIVITY,
1415
)
@@ -69,8 +70,9 @@ class SolidMedium(AbstractHeatMedium):
6970
"""
7071

7172
capacity: pd.PositiveFloat = pd.Field(
73+
None,
7274
title="Heat capacity",
73-
description=f"Volumetric heat capacity in unit of {SPECIFIC_HEAT_CAPACITY}.",
75+
description=f"Specific heat capacity in unit of {SPECIFIC_HEAT_CAPACITY}.",
7476
units=SPECIFIC_HEAT_CAPACITY,
7577
)
7678

@@ -80,6 +82,32 @@ class SolidMedium(AbstractHeatMedium):
8082
units=THERMAL_CONDUCTIVITY,
8183
)
8284

85+
density: pd.PositiveFloat = pd.Field(
86+
None,
87+
title="Density",
88+
description=f"Mass density of material in units of {DENSITY}.",
89+
units=DENSITY,
90+
)
91+
92+
def from_si_units(
93+
conductivity: pd.PositiveFloat,
94+
capacity: pd.PositiveFloat = None,
95+
density: pd.PositiveFloat = None,
96+
):
97+
"""Create a SolidMedium using SI units"""
98+
new_conductivity = conductivity * 1e-6 # Convert from W/(m*K) to W/(um*K)
99+
new_capacity = capacity
100+
new_density = density
101+
102+
if density is not None:
103+
new_density = density * 1e-18
104+
105+
return SolidMedium(
106+
capacity=new_capacity,
107+
conductivity=new_conductivity,
108+
density=new_density,
109+
)
110+
83111

84112
class SolidSpec(SolidMedium):
85113
"""Solid medium class for backwards compatibility"""

tidy3d/components/tcad/analysis/__init__.py

Whitespace-only changes.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""Dealing with time specifications for DeviceSimulation"""
2+
3+
import pydantic.v1 as pd
4+
5+
from ....constants import KELVIN, SECOND
6+
from ...base import Tidy3dBaseModel
7+
8+
9+
class UnsteadySpec(Tidy3dBaseModel):
10+
"""Defines an unsteady specification
11+
12+
Example
13+
--------
14+
>>> import tidy3d as td
15+
>>> time_spec = td.UnsteadySpec(
16+
... time_step=0.01,
17+
... total_time_steps=200,
18+
... )
19+
"""
20+
21+
time_step: pd.PositiveFloat = pd.Field(
22+
...,
23+
title="Time-step",
24+
description="Time step taken for each iteration of the time integration loop.",
25+
units=SECOND,
26+
)
27+
28+
total_time_steps: pd.PositiveInt = pd.Field(
29+
...,
30+
title="Total time steps",
31+
description="Specifies the total number of time steps run during the simulation.",
32+
)
33+
34+
35+
class UnsteadyHeatAnalysis(Tidy3dBaseModel):
36+
"""
37+
Configures relevant unsteady-state heat simulation parameters.
38+
39+
Example
40+
-------
41+
>>> import tidy3d as td
42+
>>> time_spec = td.UnsteadyHeatAnalysis(
43+
... initial_temperature=300,
44+
... unsteady_spec=td.UnsteadySpec(
45+
... time_step=0.01,
46+
... total_time_steps=200,
47+
... ),
48+
... )
49+
"""
50+
51+
initial_temperature: pd.PositiveFloat = pd.Field(
52+
...,
53+
title="Initial temperature.",
54+
description="Initial value for the temperature field.",
55+
units=KELVIN,
56+
)
57+
58+
unsteady_spec: UnsteadySpec = pd.Field(
59+
...,
60+
title="Unsteady specification",
61+
description="Time step and total time steps for the unsteady simulation.",
62+
)

tidy3d/components/tcad/data/monitor_data/heat.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from tidy3d.components.base import skip_if_fields_missing
1010
from tidy3d.components.data.data_array import (
1111
DataArray,
12-
IndexedDataArray,
12+
ScalarFieldTimeDataArray,
1313
SpatialDataArray,
1414
)
1515
from tidy3d.components.data.utils import TetrahedralGridDataset, TriangularGridDataset
@@ -22,7 +22,9 @@
2222
from tidy3d.log import log
2323

2424
FieldDataset = Union[
25-
SpatialDataArray, annotate_type(Union[TriangularGridDataset, TetrahedralGridDataset])
25+
SpatialDataArray,
26+
ScalarFieldTimeDataArray,
27+
annotate_type(Union[TriangularGridDataset, TetrahedralGridDataset]),
2628
]
2729
UnstructuredFieldType = Union[TriangularGridDataset, TetrahedralGridDataset]
2830

@@ -75,22 +77,6 @@ def warn_no_data(cls, val, values):
7577

7678
return val
7779

78-
@pd.validator("temperature", always=True)
79-
@skip_if_fields_missing(["monitor"])
80-
def check_correct_data_type(cls, val, values):
81-
"""Issue error if incorrect data type is used"""
82-
83-
mnt = values.get("monitor")
84-
85-
if isinstance(val, TetrahedralGridDataset) or isinstance(val, TriangularGridDataset):
86-
if not isinstance(val.values, IndexedDataArray):
87-
raise ValueError(
88-
f"Monitor {mnt} of type 'TemperatureMonitor' cannot be associated with data arrays "
89-
"of type 'IndexVoltageDataArray'."
90-
)
91-
92-
return val
93-
9480
def field_name(self, val: str = "") -> str:
9581
"""Gets the name of the fields to be plot."""
9682
if val == "abs^2":
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
"""Objects that define how data is recorded from simulation."""
22

3+
import pydantic.v1 as pd
4+
35
from tidy3d.components.tcad.monitors.abstract import HeatChargeMonitor
46

57

68
class TemperatureMonitor(HeatChargeMonitor):
79
"""Temperature monitor."""
10+
11+
interval: pd.PositiveInt = pd.Field(
12+
1,
13+
title="Interval",
14+
description="Sampling rate of the monitor: number of time steps between each measurement. "
15+
"Set ``interval`` to 1 for the highest possible resolution in time. "
16+
"Higher integer values down-sample the data by measuring every ``interval`` time steps. "
17+
"This can be useful for reducing data storage as needed by the application."
18+
"NOTE: this is only relevant for unsteady (transient) Heat simulations. ",
19+
)

0 commit comments

Comments
 (0)