Skip to content
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `Grid.fine_mesh_info` property to identify and report locations where grid cell sizes are fine for understanding meshing hotspots.
- Added visualization of finest grid regions in `Simulation.plot_grid()` with shaded regions highlighting areas of fine meshing.
- Added autograd support for `Sphere`.
- Added validation warning in `HeatChargeSimulation` for very small `Cylinder` radii to help users avoid meshing and numerical issues.

### Breaking Changes
- Added `structure_priority_mode` for `TerminalComponentModeler` and default to `"conductor"` to ensure metal structures override dielectrics regardless of structure order, preventing order-dependent results in RF simulations.
- 1D lumped elements (with zero lateral extent) are no longer allowed. Use a small finite lateral extent (e.g., `1e-6`) instead.
Copy link

Choose a reason for hiding this comment

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

Duplicate Breaking Changes section in CHANGELOG

Low Severity

The CHANGELOG.md file now contains two separate ### Breaking Changes sections under [Unreleased]. Lines 18-20 introduce a new "Breaking Changes" section with two entries (structure_priority_mode and 1D lumped elements), but these exact same entries already exist in another "Breaking Changes" section at lines 27-29. This creates duplicate content and a malformed changelog structure.

Additional Locations (1)

Fix in Cursor Fix in Web

- Added `GaussianOverlapMonitor` and `AstigmaticGaussianOverlapMonitor` for decomposing electromagnetic fields onto Gaussian beam profiles.
- Added `GaussianPort` and `AstigmaticGaussianPort` for S-matrix calculations using Gaussian beam sources and overlap monitors.
- Added `symmetric_pseudo` option for `s_param_def` in `TerminalComponentModeler` which applies a scaling factor that ensures the S-matrix is symmetric in reciprocal systems.
Expand Down
69 changes: 69 additions & 0 deletions tests/test_components/test_heat_charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -2832,3 +2832,72 @@ def test_heat_charge_simulation_plot():
"for CHARGE simulations, resulting in at least 2 more visual elements "
"than charge_sim.scene.plot()"
)


def test_cylinder_small_radius_warning():
"""Test that warning is issued for very small cylinder radii in HeatChargeSimulation."""
solid = td.MultiPhysicsMedium(
heat=td.SolidSpec(conductivity=1, capacity=1),
name="solid",
)
background = td.MultiPhysicsMedium(
heat=td.FluidSpec(),
name="background",
)

# Test non-tapered cylinder with tiny radius
tiny_cylinder = td.Structure(
geometry=td.Cylinder(center=(0, 0, 0), radius=1e-8, length=1, axis=2),
medium=solid,
name="tiny",
)
with AssertLogLevel("WARNING", contains_str="radius"):
_ = td.HeatChargeSimulation(
center=(0, 0, 0),
size=(2, 2, 2),
medium=background,
structures=[tiny_cylinder],
grid_spec=td.UniformUnstructuredGrid(dl=0.1),
monitors=[td.TemperatureMonitor(size=(1, 1, 1), name="tmp")],
)

# Test transformed (translated) cylinder with tiny radius
tiny_cylinder_transformed = td.Structure(
geometry=td.Cylinder(center=(0, 0, 0), radius=1e-8, length=1, axis=2).translated(
x=0.1, y=0.0, z=0.0
),
medium=solid,
name="tiny_transformed",
)
with AssertLogLevel("WARNING", contains_str="radius"):
_ = td.HeatChargeSimulation(
center=(0, 0, 0),
size=(2, 2, 2),
medium=background,
structures=[tiny_cylinder_transformed],
grid_spec=td.UniformUnstructuredGrid(dl=0.1),
monitors=[td.TemperatureMonitor(size=(1, 1, 1), name="tmp")],
)

# Test tapered cylinder with steep sidewall causing negative radius_top
tapered_cylinder = td.Structure(
geometry=td.Cylinder(
center=(0, 0, 0),
radius=0.1,
length=1,
axis=2,
sidewall_angle=np.pi / 3, # 60 degrees - causes negative radius_top
reference_plane="bottom",
),
medium=solid,
name="tapered",
)
with AssertLogLevel("WARNING", contains_str="radius_top"):
_ = td.HeatChargeSimulation(
center=(0, 0, 0),
size=(2, 2, 2),
medium=background,
structures=[tapered_cylinder],
grid_spec=td.UniformUnstructuredGrid(dl=0.1),
monitors=[td.TemperatureMonitor(size=(1, 1, 1), name="tmp")],
)
5 changes: 4 additions & 1 deletion tidy3d/components/geometry/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,8 @@ def flatten_groups(
def traverse_geometries(geometry: GeometryType) -> GeometryType:
"""Iterator over all geometries within the given geometry.

Iterates over groups and clip operations within the given geometry, yielding each one.
Iterates over groups, clip operations, and transformed geometries within the given geometry,
yielding each one.

Parameters
----------
Expand All @@ -267,6 +268,8 @@ def traverse_geometries(geometry: GeometryType) -> GeometryType:
elif isinstance(geometry, base.ClipOperation):
yield from traverse_geometries(geometry.geometry_a)
yield from traverse_geometries(geometry.geometry_b)
elif isinstance(geometry, base.Transformed):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I see, this will have side-effects on other uses of this function too, but this addition actually seems correct here, because a Transformed geometry could itself be a group or a clip operation, and previously we were not traversing those!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

actually, I forgot to ask @weiliangjin2021 about this. I think he's using this somewhere in the backend

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes it is definitely used and I was first going to suggest making a separate function but then realized this is more correct. But yeah maybe run backend tests too.

yield from traverse_geometries(geometry.geometry)
yield geometry


Expand Down
46 changes: 46 additions & 0 deletions tidy3d/components/tcad/simulation/heat_charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
StructureStructureInterface,
)
from tidy3d.components.geometry.base import Box
from tidy3d.components.geometry.primitives import Cylinder
from tidy3d.components.geometry.utils import traverse_geometries
from tidy3d.components.material.tcad.charge import (
ChargeConductorMedium,
SemiconductorMedium,
Expand Down Expand Up @@ -127,6 +129,11 @@
# define some limits for transient heat simulations
TRANSIENT_HEAT_MAX_STEPS = 1000

# OpenCASCADE minimum tolerance for cylinder radii
OPENCASCADE_CYLINDER_RADIUS_TOL = 1e-6
# Minimum radius as fraction of the larger radius (for tapered cylinders)
MIN_CYLINDER_RADIUS_FRACTION = 0.01


class TCADAnalysisTypes(str, Enum):
"""Enumeration of the types of simulations currently supported"""
Expand Down Expand Up @@ -363,6 +370,45 @@ def check_unsupported_geometries(cls, val):
)
return val

@pd.validator("structures", always=True)
def _warn_small_cylinder_radius(cls, val):
"""Warn if any Cylinder geometry has radius too small for meshing."""
for structure in val:
for geometry in traverse_geometries(structure.geometry):
if isinstance(geometry, Cylinder):
r_bottom = geometry.radius_bottom
r_top = geometry.radius_top
is_tapered = not np.isclose(r_bottom, r_top)

# Compute minimum allowed radius (matches backend heat_mesh.py logic)
min_radius = max(
OPENCASCADE_CYLINDER_RADIUS_TOL,
MIN_CYLINDER_RADIUS_FRACTION * max(abs(r_bottom), abs(r_top)),
)

# Warn if radii are below minimum
if is_tapered:
if r_bottom < min_radius:
log.warning(
f"Cylinder 'radius_bottom' ({r_bottom:.3e}) is below the minimum "
f"radius for meshing ({min_radius:.3e}). The sidewall angle may be "
f"too steep. Will be clamped to minimum radius or mesh size, whichever is larger."
)
if r_top < min_radius:
log.warning(
f"Cylinder 'radius_top' ({r_top:.3e}) is below the minimum "
f"radius for meshing ({min_radius:.3e}). The sidewall angle may be "
f"too steep. Will be clamped to minimum radius or mesh size, whichever is larger."
)
else:
if r_bottom < min_radius:
log.warning(
f"Cylinder 'radius' ({r_bottom:.3e}) is below the minimum "
f"radius for meshing ({min_radius:.3e}). "
f"Will be clamped to minimum radius or mesh size, whichever is larger."
)
return val

@staticmethod
def _check_cross_solids(objs: tuple[Box, ...], values: dict) -> tuple[int, ...]:
"""Given model dictionary ``values``, check whether objects in list ``objs`` cross
Expand Down
Loading