Skip to content

Non-isothermal charge simulations #2425

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 1 commit 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
72 changes: 59 additions & 13 deletions tests/test_components/test_heat_charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,23 +952,30 @@ def Si_p(self):
semiconductor = CHARGE_SIMULATION.intrinsic_Si.charge
semiconductor = semiconductor.updated_copy(
N_a=CHARGE_SIMULATION.acceptors,
)
return CHARGE_SIMULATION.intrinsic_Si.updated_copy(
charge=semiconductor,
heat=td.SolidMedium(conductivity=1),
name="Si_p",
)
return CHARGE_SIMULATION.intrinsic_Si.updated_copy(charge=semiconductor)

@pytest.fixture(scope="class")
def Si_n(self):
semiconductor = CHARGE_SIMULATION.intrinsic_Si.charge
semiconductor = semiconductor.updated_copy(
N_d=CHARGE_SIMULATION.donors,
)
return CHARGE_SIMULATION.intrinsic_Si.updated_copy(
charge=semiconductor,
heat=td.SolidMedium(conductivity=1),
name="Si_n",
)
return CHARGE_SIMULATION.intrinsic_Si.updated_copy(charge=semiconductor)

@pytest.fixture(scope="class")
def SiO2(self):
return td.MultiPhysicsMedium(
charge=td.ChargeInsulatorMedium(permittivity=3.9),
heat=td.SolidMedium(conductivity=2),
name="SiO2",
)

Expand Down Expand Up @@ -1049,18 +1056,13 @@ def capacitance_global_mnt(self):
# Define charge settings as fixtures within the class
@pytest.fixture(scope="class")
def charge_tolerance(self):
return td.IsothermalSteadyChargeDCAnalysis(
temperature=300,
tolerance_settings=td.ChargeToleranceSpec(rel_tol=1e5, abs_tol=1e3, max_iters=400),
fermi_dirac=True,
)

@pytest.fixture(scope="class")
def charge_dc_regime(self):
return td.DCVoltageSource(voltage=[1])
return td.ChargeToleranceSpec(rel_tol=1e5, abs_tol=1e3, max_iters=400)

def test_charge_simulation(
self,
Si_n,
Si_p,
SiO2,
oxide,
p_side,
n_side,
Expand All @@ -1070,9 +1072,14 @@ def test_charge_simulation(
bc_n,
bc_p,
charge_tolerance,
charge_dc_regime,
):
"""Ensure charge simulation produces the correct errors when needed."""
# NOTE: start tests with isothermal spec
isothermal_spec = td.IsothermalSteadyChargeDCAnalysis(
temperature=300,
tolerance_settings=charge_tolerance,
fermi_dirac=True,
)
sim = td.HeatChargeSimulation(
structures=[oxide, p_side, n_side],
medium=td.MultiPhysicsMedium(
Expand All @@ -1083,7 +1090,7 @@ def test_charge_simulation(
size=CHARGE_SIMULATION.sim_size,
grid_spec=td.UniformUnstructuredGrid(dl=0.05),
boundary_spec=[bc_n, bc_p],
analysis_spec=charge_tolerance,
analysis_spec=isothermal_spec,
)

# At least one ChargeSimulationMonitor should be added
Expand Down Expand Up @@ -1118,6 +1125,45 @@ def test_charge_simulation(
)
_ = sim.updated_copy(boundary_spec=[new_bc_p, bc_n])

# test non isothermal spec
non_isothermal_spec = td.SteadyChargeDCAnalysis(tolerance_settings=charge_tolerance)

sim = sim.updated_copy(analysis_spec=non_isothermal_spec)
with pytest.raises(pd.ValidationError):
# remove heat from mediums
new_structs = []
for struct in sim.structures:
new_structs.append(
struct.updated_copy(medium=struct.medium.updated_copy(heat=None))
)
_ = sim.updated_copy(structures=new_structs)

with pytest.raises(pd.ValidationError):
# remove charge from mediums
new_structs = []
for struct in sim.structures:
new_structs.append(
struct.updated_copy(medium=struct.medium.updated_copy(charge=None))
)
_ = sim.updated_copy(structures=new_structs)

with pytest.raises(pd.ValidationError):
# make sure there is at least one semiconductor
new_structs = []
for struct in sim.structures:
if isinstance(struct.medium.charge, td.SemiconductorMedium):
new_structs.append(
struct.updated_copy(
medium=struct.medium.updated_copy(
charge=td.ChargeInsulatorMedium(permittivity=1),
heat=None,
)
)
)
else:
new_structs.append(struct)
_ = sim.updated_copy(structures=new_structs)

def test_doping_distributions(self):
"""Test doping distributions."""
# Implementation needed
Expand Down
2 changes: 2 additions & 0 deletions tidy3d/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from tidy3d.components.spice.analysis.dc import (
ChargeToleranceSpec,
IsothermalSteadyChargeDCAnalysis,
SteadyChargeDCAnalysis,
)
from tidy3d.components.spice.sources.dc import DCCurrentSource, DCVoltageSource
from tidy3d.components.spice.sources.types import VoltageSourceType
Expand Down Expand Up @@ -651,6 +652,7 @@ def set_logging_level(level: str) -> None:
"Staircasing",
"SteadyCapacitanceData",
"SteadyCapacitanceMonitor",
"SteadyChargeDCAnalysis",
"SteadyEnergyBandData",
"SteadyEnergyBandMonitor",
"SteadyFreeCarrierData",
Expand Down
24 changes: 15 additions & 9 deletions tidy3d/components/spice/analysis/dc.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,11 @@ class ChargeToleranceSpec(Tidy3dBaseModel):
)


class IsothermalSteadyChargeDCAnalysis(Tidy3dBaseModel):
class SteadyChargeDCAnalysis(Tidy3dBaseModel):
"""
Configures relevant steady-state DC simulation parameters for a charge simulation.
"""
Comment on lines +52 to 55
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Base class docstring should be more descriptive, mentioning this is for both isothermal and non-isothermal simulations


temperature: pd.PositiveFloat = pd.Field(
300,
title="Temperature",
description="Lattice temperature. Assumed constant throughout the device. "
"Carriers are assumed to be at thermodynamic equilibrium with the lattice.",
units=KELVIN,
)

tolerance_settings: ChargeToleranceSpec = pd.Field(
default=ChargeToleranceSpec(), title="Tolerance settings"
)
Expand All @@ -83,3 +75,17 @@ class IsothermalSteadyChargeDCAnalysis(Tidy3dBaseModel):
"where very high doping may lead the pseudo-Fermi energy level to approach "
"either the conduction or valence energy bands.",
)


class IsothermalSteadyChargeDCAnalysis(SteadyChargeDCAnalysis):
"""
Configures relevant steady-state DC simulation parameters for a charge simulation.
"""
Comment on lines +81 to +83
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Docstring should clarify this is specifically for isothermal (constant temperature) simulations


temperature: pd.PositiveFloat = pd.Field(
300,
title="Temperature",
description="Lattice temperature. Assumed constant throughout the device. "
"Carriers are assumed to be at thermodynamic equilibrium with the lattice.",
units=KELVIN,
)
7 changes: 5 additions & 2 deletions tidy3d/components/spice/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

from typing import Union

from tidy3d.components.spice.analysis.dc import IsothermalSteadyChargeDCAnalysis
from tidy3d.components.spice.analysis.dc import (
IsothermalSteadyChargeDCAnalysis,
SteadyChargeDCAnalysis,
)

ElectricalAnalysisType = Union[IsothermalSteadyChargeDCAnalysis]
ElectricalAnalysisType = Union[SteadyChargeDCAnalysis, IsothermalSteadyChargeDCAnalysis]
47 changes: 45 additions & 2 deletions tidy3d/components/tcad/simulation/heat_charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@
from tidy3d.components.medium import Medium
from tidy3d.components.scene import Scene
from tidy3d.components.spice.sources.dc import DCVoltageSource
from tidy3d.components.spice.types import ElectricalAnalysisType
from tidy3d.components.spice.types import (
ElectricalAnalysisType,
IsothermalSteadyChargeDCAnalysis,
SteadyChargeDCAnalysis,
)
from tidy3d.components.structure import Structure
from tidy3d.components.tcad.analysis.heat_simulation_type import UnsteadyHeatAnalysis
from tidy3d.components.tcad.boundary.specification import (
Expand Down Expand Up @@ -951,6 +955,44 @@ def check_transient_heat(cls, values):
)
return values

@pd.root_validator(skip_on_failure=True)
def check_non_isothermal_is_possible(cls, values):
"""Make sure that when a non-isothermal case is defined the structrures
have both electrical and thermal properties."""

analysis_spec = values.get("analysis_spec")
if isinstance(analysis_spec, SteadyChargeDCAnalysis) and not isinstance(
analysis_spec, IsothermalSteadyChargeDCAnalysis
):
has_heat = False
has_elec = False
structures = values.get("structures")
for struct in structures:
if isinstance(struct.medium, MultiPhysicsMedium):
if struct.medium.heat is not None:
if isinstance(struct.medium.heat, SolidMedium):
has_heat = True
if struct.medium.charge is not None:
if isinstance(struct.medium.charge, SemiconductorMedium):
has_elec = True

if not has_heat and has_elec:
raise SetupError(
"The current simulation is defined as non-isothermal but no solid "
"materials with heat properties have been defined. "
)
elif not has_elec and has_heat:
raise SetupError(
"The current simulation is defined as non-isothermal but no "
"semiconductor materials have been defined. "
)
elif not has_heat and not has_elec:
raise SetupError(
"The current simulation is defined as non-isothermal but no "
"solid or semiconductor materials have been defined. "
)
return values

@equal_aspect
@add_ax_if_none
def plot_property(
Expand Down Expand Up @@ -1735,7 +1777,8 @@ def _get_simulation_types(self) -> list[TCADAnalysisTypes]:

# NOTE: for the time being, if a simulation has SemiconductorMedium
# then we consider it of being a 'TCADAnalysisTypes.CHARGE'
if isinstance(self.analysis_spec, ElectricalAnalysisType):
ChargeTypes = (SteadyChargeDCAnalysis, IsothermalSteadyChargeDCAnalysis)
if isinstance(self.analysis_spec, ChargeTypes):
if self._check_if_semiconductor_present(self.structures):
return [TCADAnalysisTypes.CHARGE]

Expand Down