Skip to content

Commit 8ecb2e3

Browse files
Damian's comments
1 parent 743e79f commit 8ecb2e3

File tree

6 files changed

+115
-98
lines changed

6 files changed

+115
-98
lines changed

tests/test_components/test_absorbers.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def test_port_absorbers_simulations():
6262
],
6363
)
6464

65-
_ = td.Simulation(
65+
sim = td.Simulation(
6666
center=[0, 0, 0],
6767
size=[1, 1, 1],
6868
grid_spec=td.GridSpec.auto(
@@ -79,6 +79,7 @@ def test_port_absorbers_simulations():
7979
)
8080
],
8181
)
82+
sim.plot(x=0)
8283

8384
# validate no fully anisotropic mediums
8485
with pytest.raises(SetupError):
@@ -290,7 +291,7 @@ def test_abc_boundaries_simulations():
290291
)
291292

292293
# in Simulation
293-
_ = td.Simulation(
294+
sim = td.Simulation(
294295
center=[0, 0, 0],
295296
size=[1, 1, 1],
296297
grid_spec=td.GridSpec.auto(
@@ -301,6 +302,7 @@ def test_abc_boundaries_simulations():
301302
run_time=1e-20,
302303
boundary_spec=td.BoundarySpec.all_sides(td.ABCBoundary()),
303304
)
305+
sim.plot(x=0)
304306

305307
# validate ABC medium is not anisotorpic
306308
with pytest.raises(pydantic.ValidationError):
@@ -402,7 +404,7 @@ def test_abc_boundaries_simulations():
402404
)
403405

404406
# disallow ABC boundaries in zero dimensions
405-
with pytest.raises(pydantic.ValidationError):
407+
with AssertLogLevel("WARNING", contains_str="The simulation has zero size along the z axis"):
406408
_ = td.Simulation(
407409
center=[0, 0, 0],
408410
size=[1, 1, 0],

tidy3d/components/boundary.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
PlotParams,
1616
plot_params_absorber,
1717
)
18-
from tidy3d.constants import EPSILON_0, MU_0, PML_SIGMA
18+
from tidy3d.constants import CONDUCTIVITY, EPSILON_0, MU_0, PML_SIGMA
1919
from tidy3d.exceptions import DataError, SetupError, ValidationError
2020
from tidy3d.log import log
2121

@@ -86,7 +86,6 @@ class PMCBoundary(BoundaryEdge):
8686
"""Perfect magnetic conductor boundary condition class."""
8787

8888

89-
# ABC keyword
9089
class AbstractABCBoundary(BoundaryEdge, ABC):
9190
"""One-way wave equation absorbing boundary conditions abstract base class."""
9291

@@ -96,12 +95,13 @@ class ABCBoundary(AbstractABCBoundary):
9695
See, for example, John B. Schneider, Understanding the Finite-Difference Time-Domain Method, Chapter 6.
9796
"""
9897

99-
permittivity: Optional[pd.PositiveFloat] = pd.Field(
98+
permittivity: Optional[float] = pd.Field(
10099
None,
101100
title="Effective Permittivity",
102101
description="Effective permittivity for determining propagation constant. "
103102
"If ``None``, this value will be automatically inferred from the medium at "
104103
"the domain boundary and the central frequency of the source.",
104+
ge=1.0,
105105
)
106106

107107
conductivity: Optional[pd.NonNegativeFloat] = pd.Field(
@@ -110,6 +110,7 @@ class ABCBoundary(AbstractABCBoundary):
110110
description="Effective conductivity for determining propagation constant. "
111111
"If ``None``, this value will be automatically inferred from the medium at "
112112
"the domain boundary and the central frequency of the source.",
113+
units=CONDUCTIVITY,
113114
)
114115

115116
@pd.validator("conductivity", always=True)
@@ -237,7 +238,7 @@ class InternalAbsorber(Box):
237238
direction: Direction = pd.Field(
238239
...,
239240
title="Absorption Direction",
240-
description="Direction in which field is absorbed.",
241+
description="Indicates which direction of traveling waves are absorbed.",
241242
)
242243

243244
grid_shift: int = pd.Field(

tidy3d/components/geometry/utils.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
from tidy3d.components.types import (
1616
ArrayFloat2D,
1717
Axis,
18+
Bound,
1819
Coordinate,
20+
Direction,
1921
MatrixReal4x4,
2022
PlanePosition,
2123
Shapely,
@@ -486,3 +488,77 @@ def snap_point_to_grid(
486488
snapped_point[axis] = get_closest_value(point[axis], snap_coords, min_upper_bound_idx)
487489

488490
return tuple(snapped_point)
491+
492+
493+
def _shift_value_signed(
494+
obj: Box,
495+
grid: Grid,
496+
bounds: Bound,
497+
direction: Direction,
498+
shift: int,
499+
name: Optional[str] = None,
500+
) -> float:
501+
"""Calculate the signed distance corresponding to moving the object by ``shift`` number
502+
of cells in the positive or negative ``direction`` along the dimension given by
503+
``obj._normal_axis``.
504+
"""
505+
if name is None:
506+
name = f"A '{obj.type}'"
507+
508+
# get the grid boundaries and sizes along obj normal from the simulation
509+
normal_axis = obj._normal_axis
510+
grid_boundaries = grid.boundaries.to_list[normal_axis]
511+
grid_centers = grid.centers.to_list[normal_axis]
512+
513+
# get the index of the grid cell where the obj lies
514+
obj_position = obj.center[normal_axis]
515+
obj_pos_gt_grid_bounds = np.argwhere(obj_position > grid_boundaries)
516+
517+
# no obj index can be determined
518+
if len(obj_pos_gt_grid_bounds) == 0 or obj_position > grid_boundaries[-1]:
519+
raise SetupError(
520+
f"{name} position '{obj_position}' is outside of simulation bounds '({grid_boundaries[0]}, {grid_boundaries[-1]})' along dimension '{'xyz'[normal_axis]}'."
521+
)
522+
obj_index = obj_pos_gt_grid_bounds[-1]
523+
524+
# shift the obj to the left
525+
signed_shift = shift if direction == "+" else -shift
526+
if signed_shift < 0:
527+
shifted_index = obj_index + signed_shift
528+
if shifted_index < 0 or grid_centers[shifted_index] <= bounds[0][normal_axis]:
529+
raise SetupError(
530+
f"{name} normal is less than 2 cells to the boundary "
531+
f"on -{'xyz'[normal_axis]} side. "
532+
"Please either increase the mesh resolution near the obj or "
533+
"move the obj away from the boundary."
534+
)
535+
536+
# shift the obj to the right
537+
else:
538+
shifted_index = obj_index + signed_shift
539+
if (
540+
shifted_index >= len(grid_centers)
541+
or grid_centers[shifted_index] >= bounds[1][normal_axis]
542+
):
543+
raise SetupError(
544+
f"{name} normal is less than 2 cells to the boundary "
545+
f"on +{'xyz'[normal_axis]} side."
546+
"Please either increase the mesh resolution near the obj or "
547+
"move the obj away from the boundary."
548+
)
549+
550+
new_pos = grid_centers[shifted_index]
551+
return new_pos - obj_position
552+
553+
554+
def _shift_object(obj: Box, grid: Grid, bounds: Bound, direction: Direction, shift: int) -> Box:
555+
"""Move a plane-like object by ``shift`` number
556+
of cells in the positive or negative ``direction`` along the dimension given by
557+
``obj._normal_axis``.
558+
"""
559+
shift = _shift_value_signed(obj=obj, grid=grid, bounds=bounds, direction=direction, shift=shift)
560+
new_center = np.array(obj.center)
561+
new_center[obj._normal_axis] += shift
562+
# note: if this needs to be generalized beyond absorber, one would probably
563+
# slightly adjust the code below regarding grid_shift
564+
return obj.updated_copy(center=tuple(new_center), grid_shift=0)

tidy3d/components/simulation.py

Lines changed: 20 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
from .data.utils import CustomSpatialDataType
5353
from .geometry.base import Box, Geometry, GeometryGroup
5454
from .geometry.mesh import TriangleMesh
55-
from .geometry.utils import flatten_groups, traverse_geometries
55+
from .geometry.utils import _shift_object, flatten_groups, traverse_geometries
5656
from .geometry.utils_2d import get_bounds, get_thickened_geom, snap_coordinate_to_grid, subdivide
5757
from .grid.grid import Coords, Coords1D, Grid
5858
from .grid.grid_spec import AutoGrid, GridSpec, UniformGrid
@@ -113,7 +113,6 @@
113113
Ax,
114114
Axis,
115115
CoordinateOptional,
116-
Direction,
117116
FreqBound,
118117
InterpMethod,
119118
PermittivityComponent,
@@ -470,8 +469,10 @@ def _shifted_internal_absorbers(self) -> list[InternalAbsorber]:
470469
"""List of absorber shifted to their actual locations based on their grid_shift's."""
471470

472471
return [
473-
self._shift_object(
472+
_shift_object(
474473
obj=absorber,
474+
grid=self.grid,
475+
bounds=self.bounds,
475476
direction=absorber.direction,
476477
shift=absorber.grid_shift,
477478
)
@@ -1876,7 +1877,7 @@ def subsection(
18761877
Used internally.
18771878
deep_copy: bool = True
18781879
Recursively copy all nested objects in the generated simulation object.
1879-
internal_absorbers : Tuple[SourceType, ...] = None
1880+
internal_absorbers : Tuple[InternalAbsorber, ...] = None
18801881
New list of internal absorbers. If ``None``, then the absorbers intersecting the new simulation
18811882
domain are inherited from the original simulation.
18821883
**kwargs
@@ -2108,73 +2109,6 @@ def _invalidate_solver_cache(self) -> None:
21082109
"""Clear cached attributes that become stale when subpixel changes."""
21092110
self._cached_properties.pop("_mode_solver", None)
21102111

2111-
def _shift_value_signed(
2112-
self, obj: Box, direction: Direction, shift: int, name: Optional[str] = None
2113-
) -> float:
2114-
"""Calculate the signed distance corresponding to moving the object by ``shift`` number
2115-
of cells in the positive or negative ``direction`` along the dimension given by
2116-
``obj._normal_axis``.
2117-
"""
2118-
if name is None:
2119-
name = f"A '{obj.type}'"
2120-
2121-
# get the grid boundaries and sizes along obj normal from the simulation
2122-
normal_axis = obj._normal_axis
2123-
grid_boundaries = self.grid.boundaries.to_list[normal_axis]
2124-
grid_centers = self.grid.centers.to_list[normal_axis]
2125-
2126-
# get the index of the grid cell where the obj lies
2127-
obj_position = obj.center[normal_axis]
2128-
obj_pos_gt_grid_bounds = np.argwhere(obj_position > grid_boundaries)
2129-
2130-
# no obj index can be determined
2131-
if len(obj_pos_gt_grid_bounds) == 0 or obj_position > grid_boundaries[-1]:
2132-
raise SetupError(
2133-
f"{name} position '{obj_position}' is outside of simulation bounds '({grid_boundaries[0]}, {grid_boundaries[-1]})' along dimension '{'xyz'[normal_axis]}'."
2134-
)
2135-
obj_index = obj_pos_gt_grid_bounds[-1]
2136-
2137-
# shift the obj to the left
2138-
signed_shift = shift if direction == "+" else -shift
2139-
if signed_shift < 0:
2140-
shifted_index = obj_index + signed_shift
2141-
if shifted_index < 0 or grid_centers[shifted_index] <= self.bounds[0][normal_axis]:
2142-
raise SetupError(
2143-
f"{name} normal is less than 2 cells to the boundary "
2144-
f"on -{'xyz'[normal_axis]} side. "
2145-
"Please either increase the mesh resolution near the obj or "
2146-
"move the obj away from the boundary."
2147-
)
2148-
2149-
# shift the obj to the right
2150-
else:
2151-
shifted_index = obj_index + signed_shift
2152-
if (
2153-
shifted_index >= len(grid_centers)
2154-
or grid_centers[shifted_index] >= self.bounds[1][normal_axis]
2155-
):
2156-
raise SetupError(
2157-
f"{name} normal is less than 2 cells to the boundary "
2158-
f"on +{'xyz'[normal_axis]} side."
2159-
"Please either increase the mesh resolution near the obj or "
2160-
"move the obj away from the boundary."
2161-
)
2162-
2163-
new_pos = grid_centers[shifted_index]
2164-
return new_pos - obj_position
2165-
2166-
def _shift_object(self, obj: InternalAbsorber, direction: Direction, shift: int) -> float:
2167-
"""Move a plane-like object by ``shift`` number
2168-
of cells in the positive or negative ``direction`` along the dimension given by
2169-
``obj._normal_axis``.
2170-
"""
2171-
shift = self._shift_value_signed(obj, direction, shift)
2172-
new_center = np.array(obj.center)
2173-
new_center[obj._normal_axis] += shift
2174-
# note: if this needs to be generalized beyond absorber, one would probably
2175-
# slightly adjust the code below regarding grid_shift
2176-
return obj.updated_copy(center=tuple(new_center), grid_shift=0)
2177-
21782112

21792113
class Simulation(AbstractYeeGridSimulation):
21802114
"""
@@ -3496,7 +3430,10 @@ def _projection_monitors_homogeneous(cls, val, values):
34963430

34973431
@classmethod
34983432
def _get_mediums_on_abc(
3499-
cls, boundary_spec, medium, center, size, structures
3433+
cls,
3434+
boundary_spec: BoundarySpec,
3435+
sim_structure: Structure,
3436+
structures: tuple[Structure, ...],
35003437
) -> tuple[
35013438
list[MediumType3D],
35023439
list[MediumType3D],
@@ -3510,19 +3447,14 @@ def _get_mediums_on_abc(
35103447
"""
35113448

35123449
# list of structures including background as a Box()
3513-
structure_bg = Structure(
3514-
geometry=Box(
3515-
size=size,
3516-
center=center,
3517-
),
3518-
medium=medium,
3519-
)
3520-
3521-
surfaces = Box.surfaces(
3522-
center=structure_bg.geometry.center, size=structure_bg.geometry.size
3450+
surface_box = sim_structure.geometry
3451+
# expand zero dimensions to make sure surface are extracted correctly and treatment is uniform
3452+
surface_box = surface_box.updated_copy(
3453+
size=[1e-6 if s == 0 else s for s in surface_box.size]
35233454
)
3455+
surfaces = Box.surfaces(center=surface_box.center, size=surface_box.size)
35243456

3525-
total_structures = [structure_bg, *list(structures)]
3457+
total_structures = [sim_structure, *list(structures)]
35263458

35273459
mediums = []
35283460
for boundary, surface in zip(np.ravel(boundary_spec.to_list), surfaces):
@@ -3541,14 +3473,14 @@ def _abc_boundaries_homogeneous(cls, val, values):
35413473
if val is None:
35423474
return val
35433475

3544-
# expand zero dimensions to make the treatment uniform
3545-
size = [fp_eps if s == 0 else s for s in values.get("size")]
3476+
sim_structure = Structure(
3477+
geometry=Box(size=values.get("size"), center=values.get("center")),
3478+
medium=values.get("medium"),
3479+
)
35463480

35473481
mediums_all_sides = cls._get_mediums_on_abc(
35483482
boundary_spec=val,
3549-
medium=values.get("medium"),
3550-
size=size,
3551-
center=values.get("center"),
3483+
sim_structure=sim_structure,
35523484
structures=values.get("structures") or [],
35533485
)
35543486

tidy3d/components/source/field.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ class ModeSource(DirectionalSource, PlanarSource, BroadbandSource):
406406

407407
frame: Optional[PECFrame] = pydantic.Field(
408408
None,
409-
title="Source Frame.",
409+
title="Source Frame",
410410
description="Add a thin frame around the source during the FDTD run to improve "
411411
"the injection quality. The frame is positioned along the primal grid lines "
412412
"so that it aligns with the boundaries of the mode solver used to obtain the source profile.",

tidy3d/plugins/smatrix/component_modelers/base.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from tidy3d.components.base import Tidy3dBaseModel, cached_property
1313
from tidy3d.components.data.data_array import DataArray
1414
from tidy3d.components.data.sim_data import SimulationData
15+
from tidy3d.components.geometry.utils import _shift_value_signed
1516
from tidy3d.components.simulation import Simulation
1617
from tidy3d.components.types import FreqArray
1718
from tidy3d.config import config
@@ -255,8 +256,13 @@ def inv(matrix: DataArray):
255256
def _shift_value_signed(self, port: Union[Port, WavePort]) -> float:
256257
"""How far (signed) to shift the source from the monitor."""
257258

258-
return self.simulation._shift_value_signed(
259-
obj=port, direction=port.direction, shift=-2, name=f"Port {port.name}"
259+
return _shift_value_signed(
260+
obj=port,
261+
grid=self.simulation.grid,
262+
bounds=self.simulation.bounds,
263+
direction=port.direction,
264+
shift=-2,
265+
name=f"Port {port.name}",
260266
)
261267

262268
def sim_data_by_task_name(self, task_name: str) -> SimulationData:

0 commit comments

Comments
 (0)