Skip to content

[FXC-1512]: Add natural convection BC #2696

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tidy3d/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from tidy3d.components.spice.sources.dc import DCCurrentSource, DCVoltageSource
from tidy3d.components.spice.sources.types import VoltageSourceType
from tidy3d.components.tcad.analysis.heat_simulation_type import UnsteadyHeatAnalysis, UnsteadySpec
from tidy3d.components.tcad.boundary.heat import VerticalNaturalConvectionCoeffModel
from tidy3d.components.tcad.boundary.specification import (
HeatBoundarySpec,
HeatChargeBoundarySpec,
Expand Down Expand Up @@ -693,6 +694,7 @@ def set_logging_level(level: str) -> None:
"UnsteadyHeatAnalysis",
"UnsteadySpec",
"Updater",
"VerticalNaturalConvectionCoeffModel",
"VisualizationSpec",
"VoltageBC",
"VoltageSourceType",
Expand Down
98 changes: 95 additions & 3 deletions tidy3d/components/material/tcad/heat.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
from tidy3d.components.base import Tidy3dBaseModel
from tidy3d.constants import (
DENSITY,
DYNAMIC_VISCOSITY,
SPECIFIC_HEAT,
SPECIFIC_HEAT_CAPACITY,
THERMAL_CONDUCTIVITY,
THERMAL_EXPANSIVITY,
)


Expand Down Expand Up @@ -46,11 +49,100 @@ class FluidMedium(AbstractHeatMedium):
"""Fluid medium. Heat simulations will not solve for temperature
in a structure that has a medium with this 'heat_spec'.

Example
-------
>>> solid = FluidMedium()
The full set of parameters is primarily intended for calculations involving natural
convection, where they are used to determine the heat transfer coefficient.
In the current version, these specific properties may not be utilized for
other boundary condition types.

Attributes
----------
thermal_conductivity : float, optional
Thermal conductivity ($k$) of the fluid in $W/(\\mu m \\cdot K)$.
viscosity : float, optional
Dynamic viscosity ($\\mu$) of the fluid in $kg/(\\mu m \\cdot s)$.
specific_heat : float, optional
Specific heat ($c_p$) of the fluid in $\\mu m^2/(s^2 \\cdot K)$.
density : float, optional
Density ($\rho$) of the fluid in $kg/\\mu m^3$.
expansivity : float, optional
Thermal expansion coefficient ($\beta$) of the fluid in $1/K$.

Examples
--------
>>> # It is most convenient to define the fluid from standard SI units
>>> # using the `from_si_units` classmethod.
>>> # The following defines air at approximately 20°C.
>>> air_from_si = FluidMedium.from_si_units(
... thermal_conductivity=0.0257, # Unit: W/(m*K)
... viscosity=1.81e-5, # Unit: Pa*s
... specific_heat=1005, # Unit: J/(kg*K)
... density=1.204, # Unit: kg/m^3
... expansivity=1/293.15 # Unit: 1/K
... )

>>> # One can also define the medium directly in Tidy3D units.
>>> # The following is equivalent to the example above.
>>> air_direct = FluidMedium(
... thermal_conductivity=2.57e-8,
... viscosity=1.81e-11,
... specific_heat=1.005e+15,
... density=1.204e-18,
... expansivity=0.00341
... )
"""

thermal_conductivity: pd.NonNegativeFloat = pd.Field(
default=None,
title="Fluid Thermal Conductivity",
description="Thermal conductivity (k) of the fluid.",
units=THERMAL_CONDUCTIVITY,
)
viscosity: pd.NonNegativeFloat = pd.Field(
default=None,
title="Fluid Dynamic Viscosity",
description="Dynamic viscosity (μ) of the fluid.",
units=DYNAMIC_VISCOSITY,
)
specific_heat: pd.NonNegativeFloat = pd.Field(
default=None,
title="Fluid Specific Heat",
description="Specific heat of the fluid at constant pressure.",
units=SPECIFIC_HEAT,
)
density: pd.NonNegativeFloat = pd.Field(
default=None,
title="Fluid Density",
description="Density (ρ) of the fluid.",
units=DENSITY,
)
expansivity: pd.NonNegativeFloat = pd.Field(
default=None,
title="Fluid Thermal Expansivity",
description="Thermal expansion coefficient (β) of the fluid.",
units=THERMAL_EXPANSIVITY,
)

def from_si_units(
thermal_conductivity: pd.NonNegativeFloat,
viscosity: pd.NonNegativeFloat,
specific_heat: pd.NonNegativeFloat,
density: pd.NonNegativeFloat,
expansivity: pd.NonNegativeFloat,
):
thermal_conductivity_tidy = thermal_conductivity / 1e6 # W/(m*K) -> W/(um*K)
viscosity_tidy = viscosity / 1e6 # Pa*s -> kg/(um*s)
specific_heat_tidy = specific_heat * 1e12 # J/(kg*K) -> um**2/(s**2*K)
density_tidy = density / 1e18 # kg/m**3 -> kg/um**3
expansivity_tidy = expansivity # 1/K -> 1/K (no change)

return FluidMedium(
thermal_conductivity=thermal_conductivity_tidy,
viscosity=viscosity_tidy,
specific_heat=specific_heat_tidy,
density=density_tidy,
expansivity=expansivity_tidy,
)


class FluidSpec(FluidMedium):
"""Fluid medium class for backwards compatibility"""
Expand Down
73 changes: 71 additions & 2 deletions tidy3d/components/tcad/boundary/heat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@

from __future__ import annotations

from typing import Union

import pydantic.v1 as pd

from tidy3d.components.base import Tidy3dBaseModel
from tidy3d.components.material.tcad.heat import FluidMedium
from tidy3d.components.tcad.boundary.abstract import HeatChargeBC
from tidy3d.constants import HEAT_FLUX, HEAT_TRANSFER_COEFF, KELVIN
from tidy3d.constants import (
ACCELERATION,
GRAV_ACC,
HEAT_FLUX,
HEAT_TRANSFER_COEFF,
KELVIN,
MICROMETER,
)


class TemperatureBC(HeatChargeBC):
Expand Down Expand Up @@ -40,6 +51,64 @@ class HeatFluxBC(HeatChargeBC):
)


class VerticalNaturalConvectionCoeffModel(Tidy3dBaseModel):
"""
Specification for natural convection from a vertical plate.

This class calculates the heat transfer coefficient (h) based on fluid
properties and an expected temperature difference, then provides these
values as 'base' and 'exponent' for a generalized heat flux equation
q = base * (T_surf - T_fluid)^exponent + base * (T_surf - T_fluid).
"""

medium: FluidMedium = pd.Field(
default=None,
title="Interface medium",
description="Medium to use for heat transfer coefficient.",
)

plate_length: pd.NonNegativeFloat = pd.Field(
title="Plate Characteristic Length",
description="Characteristic length (L), defined as the height of the vertical plate.",
units=MICROMETER,
)

gravity: pd.NonNegativeFloat = pd.Field(
default=GRAV_ACC,
title="Gravitational Acceleration",
description="Gravitational acceleration (g).",
units=ACCELERATION,
)

def from_si_units(
plate_length: pd.NonNegativeFloat,
medium: FluidMedium = None,
gravity: pd.NonNegativeFloat = GRAV_ACC * 1e-6,
):
"""
Create an instance from standard SI units.

Args:
plate_length: Plate characteristic length in [m].
gravity: Gravitational acceleration in [m/s**2].

Returns:
An instance of VerticalNaturalConvectionCoeffModel with all values
converted to Tidy3D's internal unit system.
"""

# --- Apply conversion factors ---
# value_tidy = value_si * factor
plate_length_tidy = plate_length * 1e6 # m -> um
g_tidy = gravity * 1e6 # m/s**2 -> um/s**2

return VerticalNaturalConvectionCoeffModel(
medium=medium,
plate_length=plate_length_tidy,
gravity=g_tidy,
)


class ConvectionBC(HeatChargeBC):
"""Convective thermal boundary conditions.

Expand All @@ -55,7 +124,7 @@ class ConvectionBC(HeatChargeBC):
units=KELVIN,
)

transfer_coeff: pd.NonNegativeFloat = pd.Field(
transfer_coeff: Union[pd.NonNegativeFloat, VerticalNaturalConvectionCoeffModel] = pd.Field(
title="Heat Transfer Coefficient",
description=f"Heat flux value in units of {HEAT_TRANSFER_COEFF}.",
units=HEAT_TRANSFER_COEFF,
Expand Down
85 changes: 85 additions & 0 deletions tidy3d/components/tcad/simulation/heat_charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import numpy as np
import pydantic.v1 as pd

from tidy3d import FluidMedium, VerticalNaturalConvectionCoeffModel

try:
from matplotlib import colormaps
except ImportError:
Expand Down Expand Up @@ -455,6 +457,89 @@ def check_voltage_array_if_capacitance(cls, values):
)
return values

@pd.root_validator(skip_on_failure=True)
def check_natural_convection_bc(cls, values):
"""Make sure that natural convection BCs are defined correctly."""
boundary_spec = values.get("boundary_spec")
if not boundary_spec:
return values

structures = values["structures"]
boundary_spec = values["boundary_spec"]
bg_medium = values["medium"]

# Create mappings for easy lookup of media and structures by name.
media = {s.medium.name: s.medium for s in structures if s.medium.name}
if bg_medium and bg_medium.name:
media[bg_medium.name] = bg_medium
structures_map = {s.name: s for s in structures if s.name}

def check_fluid_medium_attr(fluid_medium):
if (
(fluid_medium.thermal_conductivity is None)
or (fluid_medium.viscosity is None)
or (fluid_medium.specific_heat is None)
or (fluid_medium.density is None)
or (fluid_medium.expansivity is None)
):
raise SetupError(
f"Boundary spec at index {i}: The fluid medium at the natural convection interface "
f"must have 'thermal_conductivity', 'viscosity', 'specific_heat', 'density' and 'expansivity' defined."
)

for i, bc in enumerate(boundary_spec):
if not (
isinstance(bc.condition, ConvectionBC)
and isinstance(bc.condition.transfer_coeff, VerticalNaturalConvectionCoeffModel)
):
continue

natural_conv_model = bc.condition.transfer_coeff
placement = bc.placement

# Case 1: The fluid medium is inferred from the placement interface.
# We use direct dictionary access, assuming 'names_exist_bcs' validator has already run.
if natural_conv_model.medium is None:
if isinstance(placement, MediumMediumInterface):
med1 = media[placement.mediums[0]]
med2 = media[placement.mediums[1]]
elif isinstance(placement, StructureStructureInterface):
med1 = structures_map[placement.structures[0]].medium
med2 = structures_map[placement.structures[1]].medium
else:
raise SetupError(
f"Boundary spec at index {i}: 'VerticalNaturalConvectionCoeffModel' with no medium specified requires "
f"the 'placement' to be of type 'MediumMediumInterface' or 'StructureStructureInterface', "
f"but got '{type(placement).__name__}'."
)
specs = [
med1.heat if isinstance(med1, MultiPhysicsMedium) else med1,
med2.heat if isinstance(med2, MultiPhysicsMedium) else med2,
]

# Check for a single fluid in the interface.
is_fluid = [isinstance(s, FluidMedium) for s in specs]
if is_fluid.count(True) != 1:
raise SetupError(
f"Boundary spec at index {i}: A natural convection boundary at an interface "
f"must be between exactly one solid and one fluid medium. "
f"Found types '{type(specs[0]).__name__}' and '{type(specs[1]).__name__}'."
)
fluid_medium = specs[is_fluid.index(True)]
check_fluid_medium_attr(fluid_medium)

# Case 2: The fluid medium IS specified directly in the convection model.
else:
fluid_medium = natural_conv_model.medium
if not isinstance(fluid_medium, FluidMedium):
raise SetupError(
f"Boundary spec at index {i}: The medium '{fluid_medium.name}' specified in "
f"'VerticalNaturalConvectionCoeffModel' must be a fluid, but it has a heat "
f"spec of type '{type(fluid_medium).__name__}'."
)
check_fluid_medium_attr(fluid_medium)
return values

@pd.validator("size", always=True)
def check_zero_dim_domain(cls, val, values):
"""Error if heat domain have zero dimensions."""
Expand Down
6 changes: 5 additions & 1 deletion tidy3d/components/tcad/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

from tidy3d.components.tcad.bandgap import SlotboomBandGapNarrowing
from tidy3d.components.tcad.boundary.charge import CurrentBC, InsulatingBC, VoltageBC
from tidy3d.components.tcad.boundary.heat import ConvectionBC, HeatFluxBC, TemperatureBC
from tidy3d.components.tcad.boundary.heat import (
ConvectionBC,
HeatFluxBC,
TemperatureBC,
)
from tidy3d.components.tcad.generation_recombination import (
AugerRecombination,
RadiativeRecombination,
Expand Down
25 changes: 25 additions & 0 deletions tidy3d/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
Boltzmann constant [eV/K]
"""

GRAV_ACC = 9.80665 * 1e6
"""
Gravitational acceleration (g) [um/s^2].",
"""

# floating point precisions
dp_eps = np.finfo(np.float64).eps
"""
Expand Down Expand Up @@ -221,6 +226,26 @@
Amperes per square micrometer
"""

DYNAMIC_VISCOSITY = "kg/(um*s)"
"""
Kilograms per (micrometer second)
"""

SPECIFIC_HEAT = "um^2/(s^2*K)"
"""
Square micrometers per (square second Kelvin).
"""

THERMAL_EXPANSIVITY = "1/K"
"""
Inverse Kelvin.
"""

ACCELERATION = "um/s^2"
"""
Acceleration unit.
"""

LARGE_NUMBER = 1e10
"""
Large number used for comparing infinity.
Expand Down
Loading