Skip to content

Commit 6034853

Browse files
committed
fix: updated simulation types check
1 parent f86f3fd commit 6034853

File tree

2 files changed

+117
-31
lines changed

2 files changed

+117
-31
lines changed

tests/test_components/test_heat_charge.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from matplotlib import pyplot as plt
99

1010
import tidy3d as td
11+
from tidy3d.components.tcad.simulation.heat_charge import TCADAnalysisTypes
1112
from tidy3d.components.tcad.types import (
1213
AugerRecombination,
1314
CaugheyThomasMobility,
@@ -2513,3 +2514,86 @@ def test_generation_recombination():
25132514
beta_n=1,
25142515
beta_p=1,
25152516
)
2517+
2518+
2519+
def test_heat_only_simulation_with_semiconductor():
2520+
"""Test that a heat-only simulation with semiconductors does not trigger charge simulation.
2521+
Charge simulations are only triggered when `analysis_spec` is provided, not just when
2522+
semiconductors are present in the simulation.
2523+
"""
2524+
2525+
# Create a semiconductor medium
2526+
semiconductor_medium = td.MultiPhysicsMedium(
2527+
optical=td.Medium(permittivity=5, conductivity=0.01),
2528+
heat=td.SolidMedium(conductivity=3, capacity=2),
2529+
charge=td.SemiconductorMedium(
2530+
N_c=td.ConstantEffectiveDOS(N=1e10),
2531+
N_v=td.ConstantEffectiveDOS(N=1e10),
2532+
E_g=td.ConstantEnergyBandGap(eg=1),
2533+
mobility_n=td.ConstantMobilityModel(mu=1500),
2534+
mobility_p=td.ConstantMobilityModel(mu=1500),
2535+
),
2536+
name="semiconductor",
2537+
)
2538+
2539+
# Create a non-semiconductor solid medium
2540+
solid_medium = td.MultiPhysicsMedium(
2541+
optical=td.Medium(permittivity=5, conductivity=0.01),
2542+
heat=td.SolidMedium(conductivity=1, capacity=1),
2543+
charge=td.ChargeConductorMedium(conductivity=1),
2544+
name="solid",
2545+
)
2546+
2547+
# Create structures with both semiconductor and other materials
2548+
semiconductor_structure = td.Structure(
2549+
geometry=td.Box(center=(-0.5, 0, 0), size=(1, 1, 1)),
2550+
medium=semiconductor_medium,
2551+
name="semiconductor_structure",
2552+
)
2553+
2554+
solid_structure = td.Structure(
2555+
geometry=td.Box(center=(0.5, 0, 0), size=(1, 1, 1)),
2556+
medium=solid_medium,
2557+
name="solid_structure",
2558+
)
2559+
2560+
# Create heat-only boundary conditions (no electric BCs)
2561+
thermal_bc = td.HeatChargeBoundarySpec(
2562+
condition=td.TemperatureBC(temperature=300),
2563+
placement=td.StructureBoundary(structure="solid_structure"),
2564+
)
2565+
2566+
# Create heat source
2567+
heat_source = td.HeatSource(structures=["solid_structure"], rate=100)
2568+
2569+
# Create heat monitor (no charge monitors)
2570+
temp_monitor = td.TemperatureMonitor(
2571+
center=(0, 0, 0), size=(2, 1, 1), name="temp_monitor", unstructured=True
2572+
)
2573+
2574+
# Create heat-only simulation (no analysis_spec, no electric BCs)
2575+
heat_sim = td.HeatChargeSimulation(
2576+
medium=td.MultiPhysicsMedium(
2577+
heat=td.FluidMedium(), charge=td.ChargeInsulatorMedium(), name="air"
2578+
),
2579+
structures=[semiconductor_structure, solid_structure],
2580+
center=(0, 0, 0),
2581+
size=(3, 3, 3),
2582+
boundary_spec=[thermal_bc],
2583+
grid_spec=td.UniformUnstructuredGrid(dl=0.1),
2584+
sources=[heat_source],
2585+
monitors=[temp_monitor],
2586+
)
2587+
2588+
# Verify that only HEAT simulation type is returned, not CHARGE
2589+
simulation_types = heat_sim._get_simulation_types()
2590+
assert TCADAnalysisTypes.HEAT in simulation_types, (
2591+
"Heat simulation should be triggered when heat sources/BCs are present."
2592+
)
2593+
assert TCADAnalysisTypes.CHARGE not in simulation_types, (
2594+
"Charge simulation should NOT be triggered when ChargeTypes analysis_spec is not provided, "
2595+
"even if semiconductors are present in the simulation."
2596+
)
2597+
assert TCADAnalysisTypes.CONDUCTION not in simulation_types, (
2598+
"Conduction simulation should NOT be triggered when no electric BCs are present."
2599+
)

tidy3d/components/tcad/simulation/heat_charge.py

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,18 @@
109109
HeatSourceTypes = (UniformHeatSource, HeatSource, HeatFromElectricSource)
110110
ChargeSourceTypes = ()
111111
ElectricBCTypes = (VoltageBC, CurrentBC, InsulatingBC)
112+
ChargeTypes = (
113+
SteadyChargeDCAnalysis,
114+
IsothermalSteadyChargeDCAnalysis,
115+
SSACAnalysis,
116+
IsothermalSSACAnalysis,
117+
)
118+
ChargeMonitorTypes = (
119+
SteadyPotentialMonitor,
120+
SteadyFreeCarrierMonitor,
121+
SteadyCapacitanceMonitor,
122+
SteadyCurrentDensityMonitor,
123+
)
112124

113125
AnalysisSpecType = Union[ElectricalAnalysisType, UnsteadyHeatAnalysis]
114126

@@ -683,13 +695,6 @@ def check_freqs_requires_ac_source(cls, values):
683695
def check_charge_simulation(cls, values):
684696
"""Makes sure that Charge simulations are set correctly."""
685697

686-
ChargeMonitorType = (
687-
SteadyPotentialMonitor,
688-
SteadyFreeCarrierMonitor,
689-
SteadyCapacitanceMonitor,
690-
SteadyCurrentDensityMonitor,
691-
)
692-
693698
simulation_types = cls._check_simulation_types(values=values)
694699

695700
if TCADAnalysisTypes.CHARGE in simulation_types:
@@ -707,7 +712,7 @@ def check_charge_simulation(cls, values):
707712

708713
# check that we have at least one charge monitor
709714
monitors = values["monitors"]
710-
if not any(isinstance(mnt, ChargeMonitorType) for mnt in monitors):
715+
if not any(isinstance(mnt, ChargeMonitorTypes) for mnt in monitors):
711716
raise SetupError(
712717
"Charge simulations require the definition of, at least, one of these monitors: "
713718
"'[SteadyPotentialMonitor, SteadyFreeCarrierMonitor, SteadyCapacitanceMonitor, SteadyCurrentDensityMonitor]' "
@@ -723,7 +728,13 @@ def check_charge_simulation(cls, values):
723728
"Currently, Charge simulations support only unstructured monitors. Please set "
724729
f"monitor '{mnt.name}' to 'unstructured = True'."
725730
)
726-
731+
# check that we have at least one semiconductor medium
732+
structures = values["structures"]
733+
sc_present = HeatChargeSimulation._check_if_semiconductor_present(structures=structures)
734+
if not sc_present:
735+
raise SetupError(
736+
f"{TCADAnalysisTypes.CHARGE} simulations require the definition of at least one semiconductor medium."
737+
)
727738
return values
728739

729740
@pd.root_validator(skip_on_failure=True)
@@ -880,23 +891,23 @@ def _check_simulation_types(
880891

881892
boundaries = list(values["boundary_spec"])
882893
sources = list(values["sources"])
894+
analysis_spec = values["analysis_spec"]
883895

884896
structures = list(values["structures"])
897+
898+
if isinstance(analysis_spec, ChargeTypes):
899+
simulation_types.append(TCADAnalysisTypes.CHARGE)
900+
885901
semiconductor_present = HeatChargeSimulation._check_if_semiconductor_present(
886902
structures=structures
887903
)
888-
if semiconductor_present:
889-
simulation_types.append(TCADAnalysisTypes.CHARGE)
890904

891905
for boundary in boundaries:
892906
if isinstance(boundary.condition, HeatBCTypes):
893907
simulation_types.append(TCADAnalysisTypes.HEAT)
894908
if isinstance(boundary.condition, ElectricBCTypes):
895-
# for the time being, assume tha the simulation will be of
896-
# type CHARGE if we have semiconductors
897-
if semiconductor_present:
898-
simulation_types.append(TCADAnalysisTypes.CHARGE)
899-
else:
909+
# Add CONDUCTION type if we have no semiconductors
910+
if not semiconductor_present:
900911
simulation_types.append(TCADAnalysisTypes.CONDUCTION)
901912

902913
for source in sources:
@@ -1060,7 +1071,7 @@ def check_transient_heat(cls, values):
10601071
raise SetupError(
10611072
f"Unsteady simulations require the temperature monitor '{mnt.name}' to be unstructured."
10621073
)
1063-
# additionaly check that the SolidSpec has capacity and density defined
1074+
# additionally check that the SolidSpec has capacity and density defined
10641075
capacities = []
10651076
densities = []
10661077
conductivities = []
@@ -1115,7 +1126,7 @@ def check_transient_heat(cls, values):
11151126

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

11211132
analysis_spec = values.get("analysis_spec")
@@ -1492,7 +1503,7 @@ def _construct_forward_boundaries(
14921503
) -> tuple[tuple[HeatChargeBoundarySpec, Shapely], ...]:
14931504
"""Construct Simulation, StructureSimulation, Structure, and MediumMedium boundaries."""
14941505

1495-
# forward foop to take care of Simulation, StructureSimulation, Structure,
1506+
# forward loop to take care of Simulation, StructureSimulation, Structure,
14961507
# and MediumMediums
14971508
boundaries = [] # bc_spec, structure name, shape, bounds
14981509
background_shapes = []
@@ -1583,7 +1594,7 @@ def _construct_reverse_boundaries(
15831594
) -> tuple[tuple[HeatChargeBoundarySpec, Shapely], ...]:
15841595
"""Construct StructureStructure boundaries."""
15851596

1586-
# backward foop to take care of StructureStructure
1597+
# backward loop to take care of StructureStructure
15871598
# we do it in this way because we define the boundary between
15881599
# two overlapping structures A and B, where A comes before B, as
15891600
# boundary(B) intersected by A
@@ -1690,7 +1701,7 @@ def _construct_heat_charge_boundaries(
16901701

16911702
# construct boundaries in 2 passes:
16921703

1693-
# 1. forward foop to take care of Simulation, StructureSimulation, Structure,
1704+
# 1. forward loop to take care of Simulation, StructureSimulation, Structure,
16941705
# and MediumMediums
16951706
boundaries = HeatChargeSimulation._construct_forward_boundaries(
16961707
shapes=shapes,
@@ -1933,17 +1944,8 @@ def _get_simulation_types(self) -> list[TCADAnalysisTypes]:
19331944
"""
19341945
simulation_types = []
19351946

1936-
# NOTE: for the time being, if a simulation has SemiconductorMedium
1937-
# then we consider it of being a 'TCADAnalysisTypes.CHARGE'
1938-
ChargeTypes = (
1939-
SteadyChargeDCAnalysis,
1940-
IsothermalSteadyChargeDCAnalysis,
1941-
SSACAnalysis,
1942-
IsothermalSSACAnalysis,
1943-
)
19441947
if isinstance(self.analysis_spec, ChargeTypes):
1945-
if self._check_if_semiconductor_present(self.structures):
1946-
return [TCADAnalysisTypes.CHARGE]
1948+
return [TCADAnalysisTypes.CHARGE]
19471949

19481950
# check if unsteady heat
19491951
if isinstance(self.analysis_spec, UnsteadyHeatAnalysis):

0 commit comments

Comments
 (0)