-
Notifications
You must be signed in to change notification settings - Fork 63
[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
base: develop
Are you sure you want to change the base?
Changes from all commits
a058736
bbe0d31
4e30131
fa13026
6541620
e7036eb
c3ea537
144650f
3494f6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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): | ||
|
@@ -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). | ||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add an example of how to create this BC. It'll be good for our docs |
||
|
||
medium: FluidMedium = pd.Field( | ||
default=None, | ||
title="Interface medium", | ||
description="Medium to use for heat transfer coefficient.", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mention in the description what |
||
) | ||
|
||
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. | ||
|
||
|
@@ -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, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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: | ||
|
@@ -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__}'." | ||
) | ||
Comment on lines
+534
to
+539
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is not needed as we have |
||
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.""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed in our chat, keep this in.