Skip to content

Commit

Permalink
Added the TFSF source feature
Browse files Browse the repository at this point in the history
- added symmetry and boundary validators for tfsf

- modified TFSF plotting so that the arrows are on the injection plane; added injection_plane_center convenience property

- updated surfaces to handle the case of a surface at infinity

- added an option in CustomGrid to use a custom offset for defining the global coords of the custom grid, rather than the sim center

- adjusted tfsf validator description

- bloch boundary supports TFSF sources

- add sim method to get intersecting structures

- add sim pre upload validator to check that all side faces of a TFSF box see the same epsilon profile

- modification to source normalization for Bloch+TFSF

- add TFSF boundary and bloch validators

- updated tfsf tests

- added more frontend tests and fixed a validator bug

- plotting improvement and other fixes based on PR672 review

- minor plotting fix

- update bloch vec checker

- update tests for bloch checker

- update tests after rebase and deleted orig files

- revised based on PR672 review

- added a validator for custom media intersecting a TFSF box

- modify assert volume validator to hint at handling 2D simulations
  • Loading branch information
shashwat-sh committed Mar 15, 2023
1 parent db445bc commit be42200
Show file tree
Hide file tree
Showing 16 changed files with 2,098 additions and 112 deletions.
1,382 changes: 1,382 additions & 0 deletions tests/sims/simulation_1_10_0rc2.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/sims/simulation_1_8_0.json

Large diffs are not rendered by default.

30 changes: 29 additions & 1 deletion tests/sims/simulation_1_9_0.json
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,33 @@
"Hy": null,
"Hz": null
}
},
{
"type": "TFSF",
"center": [
1.0,
2.0,
-3.0
],
"size": [
2.5,
2.5,
0.5
],
"source_time": {
"amplitude": 1.0,
"phase": 0.0,
"type": "GaussianPulse",
"freq0": 200000000000000.0,
"fwidth": 40000000000000.0,
"offset": 5.0
},
"name": null,
"direction": "+",
"angle_theta": 0.5235987755982988,
"angle_phi": 0.6283185307179586,
"pol_angle": 0.0,
"injection_axis": 2
}
],
"boundary_spec": {
Expand Down Expand Up @@ -1293,7 +1320,8 @@
0.04,
0.04,
0.04
]
],
"custom_offset": null
},
"grid_z": {
"type": "UniformGrid",
Expand Down
8 changes: 4 additions & 4 deletions tests/test_components/test_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ def test_integration_surfaces():
assert surface.name[-2:] == expected_surfs[idx]

# volume monitor with all infinite dimensions
with pytest.raises(SetupError):
surfaces = td.FieldProjectionAngleMonitor(
size=(td.inf, td.inf, td.inf), theta=[1], phi=[0], name="f", freqs=[2e12]
).integration_surfaces
surfaces = td.FieldProjectionAngleMonitor(
size=(td.inf, td.inf, td.inf), theta=[1], phi=[0], name="f", freqs=[2e12]
).integration_surfaces
assert len(surfaces) == 0


def test_fieldproj_surfaces():
Expand Down
188 changes: 188 additions & 0 deletions tests/test_components/test_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,194 @@ def test_mode_object_syms():
)


def test_tfsf_symmetry():
"""Test that a TFSF source cannot be set in the presence of symmetries."""
src_time = td.GaussianPulse(freq0=1, fwidth=0.1)

source = td.TFSF(
size=[1, 1, 1],
source_time=src_time,
pol_angle=0,
angle_theta=np.pi / 4,
angle_phi=np.pi / 6,
direction="+",
injection_axis=2,
)

with pytest.raises(pydantic.ValidationError) as e:
_ = td.Simulation(
size=(2.0, 2.0, 2.0),
grid_spec=td.GridSpec.auto(wavelength=td.C_0 / 1.0),
run_time=1e-12,
symmetry=(0, -1, 0),
sources=[source],
)


def test_tfsf_boundaries(caplog):
"""Test that a TFSF source is allowed to cross boundaries only in particular cases."""
src_time = td.GaussianPulse(freq0=td.C_0, fwidth=0.1)

source = td.TFSF(
size=[1, 1, 1],
source_time=src_time,
pol_angle=0,
angle_theta=np.pi / 4,
angle_phi=np.pi / 6,
direction="+",
injection_axis=2,
)

# can cross periodic boundaries in the transverse directions
_ = td.Simulation(
size=(2.0, 0.5, 2.0),
grid_spec=td.GridSpec.auto(wavelength=1.0),
run_time=1e-12,
sources=[source],
)

# can cross Bloch boundaries in the transverse directions
_ = td.Simulation(
size=(0.5, 0.5, 2.0),
grid_spec=td.GridSpec.auto(wavelength=1.0),
run_time=1e-12,
sources=[source],
boundary_spec=td.BoundarySpec(
x=td.Boundary.bloch_from_source(source=source, domain_size=0.5, axis=0, medium=None),
y=td.Boundary.bloch_from_source(source=source, domain_size=0.5, axis=1, medium=None),
z=td.Boundary.pml(),
),
)

# warn if Bloch boundaries are crossed in the transverse directions but
# the Bloch vector is incorrect
_ = td.Simulation(
size=(0.5, 0.5, 2.0),
grid_spec=td.GridSpec.auto(wavelength=1.0),
run_time=1e-12,
sources=[source],
boundary_spec=td.BoundarySpec(
x=td.Boundary.bloch_from_source(
source=source, domain_size=0.5 * 1.1, axis=0, medium=None # wrong domain size
),
y=td.Boundary.bloch_from_source(
source=source, domain_size=0.5 * 1.1, axis=1, medium=None # wrong domain size
),
z=td.Boundary.pml(),
),
)
assert_log_level(caplog, "warning")

# cannot cross any boundary in the direction of injection
with pytest.raises(pydantic.ValidationError) as e:
_ = td.Simulation(
size=(2.0, 2.0, 0.5),
grid_spec=td.GridSpec.auto(wavelength=1.0),
run_time=1e-12,
sources=[source],
)

# cannot cross any non-periodic boundary in the transverse direction
with pytest.raises(pydantic.ValidationError) as e:
_ = td.Simulation(
center=(0.5, 0, 0), # also check the case when the boundary is crossed only on one side
size=(0.5, 0.5, 2.0),
grid_spec=td.GridSpec.auto(wavelength=1.0),
run_time=1e-12,
sources=[source],
boundary_spec=td.BoundarySpec(
x=td.Boundary.pml(),
y=td.Boundary.absorber(),
),
)


def test_tfsf_structures_grid(caplog):
"""Test that a TFSF source is allowed to intersect structures only in particular cases."""
src_time = td.GaussianPulse(freq0=td.C_0, fwidth=0.1)

source = td.TFSF(
size=[1, 1, 1],
source_time=src_time,
pol_angle=0,
angle_theta=np.pi / 4,
angle_phi=np.pi / 6,
direction="+",
injection_axis=2,
)

# a non-uniform mesh along the transverse directions should issue a warning
sim = td.Simulation(
size=(2.0, 2.0, 2.0),
grid_spec=td.GridSpec.auto(wavelength=1.0),
run_time=1e-12,
sources=[source],
structures=[
td.Structure(
geometry=td.Box(center=(0, 0, -1), size=(0.5, 0.5, 0.5)),
medium=td.Medium(permittivity=2),
)
],
)
sim.validate_pre_upload()
assert_log_level(caplog, "warning")

# must not have different material profiles on different faces along the injection axis
with pytest.raises(SetupError) as e:
sim = td.Simulation(
size=(2.0, 2.0, 2.0),
grid_spec=td.GridSpec.auto(wavelength=1.0),
run_time=1e-12,
sources=[source],
structures=[
td.Structure(
geometry=td.Box(center=(0.5, 0, 0), size=(0.25, 0.25, 0.25)),
medium=td.Medium(permittivity=2),
)
],
)
sim.validate_pre_upload()

# different structures *are* allowed on different faces as long as material properties match
sim = td.Simulation(
size=(2.0, 2.0, 2.0),
grid_spec=td.GridSpec.auto(wavelength=1.0),
run_time=1e-12,
sources=[source],
structures=[
td.Structure(
geometry=td.Box(center=(0.5, 0, 0), size=(0.25, 0.25, 0.25)), medium=td.Medium()
)
],
)
sim.validate_pre_upload()

# TFSF box must not intersect a custom medium
Nx, Ny, Nz = 10, 9, 8
X = np.linspace(-1, 1, Nx)
Y = np.linspace(-1, 1, Ny)
Z = np.linspace(-1, 1, Nz)
data = np.ones((Nx, Ny, Nz, 1))
eps_diagonal_data = td.ScalarFieldDataArray(data, coords=dict(x=X, y=Y, z=Z, f=[td.C_0]))
eps_components = {f"eps_{d}{d}": eps_diagonal_data for d in "xyz"}
eps_dataset = td.PermittivityDataset(**eps_components)
custom_medium = td.CustomMedium(eps_dataset=eps_dataset, name="my_medium")
sim = td.Simulation(
size=(2.0, 2.0, 2.0),
grid_spec=td.GridSpec.auto(wavelength=1.0),
run_time=1e-12,
sources=[source],
structures=[
td.Structure(
geometry=td.Box(center=(0.5, 0, 0), size=(td.inf, td.inf, 0.25)),
medium=custom_medium,
)
],
)
with pytest.raises(SetupError) as e:
sim.validate_pre_upload()


@pytest.mark.parametrize(
"size, num_struct, log_level", [(1, 1, None), (50, 1, "warning"), (1, 11000, "warning")]
)
Expand Down
8 changes: 7 additions & 1 deletion tests/test_components/test_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,13 @@ def test_FieldSource():

from tidy3d.components.source import TFSF

s = TFSF(size=(1, 1, 1), direction="+", source_time=g, injection_axis=2)
tfsf = td.TFSF(size=(1, 1, 1), direction="+", source_time=g, injection_axis=2)
_ = tfsf.injection_plane_center

# assert that TFSF must be volumetric
with pytest.raises(pydantic.ValidationError) as e_info:
_ = td.TFSF(size=(1, 1, 0), direction="+", source_time=g, injection_axis=2)

# s.plot(z=0)


Expand Down
12 changes: 12 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,18 @@ def prepend_tmp(path):
)
),
),
TFSF(
center=(1, 2, -3),
size=(2.5, 2.5, 0.5),
source_time=GaussianPulse(
freq0=2e14,
fwidth=4e13,
),
direction="+",
angle_theta=np.pi / 6,
angle_phi=np.pi / 5,
injection_axis=2,
),
],
monitors=(
FieldMonitor(
Expand Down
2 changes: 1 addition & 1 deletion tidy3d/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from .components.source import GaussianPulse, ContinuousWave
from .components.source import UniformCurrentSource, PlaneWave, ModeSource, PointDipole
from .components.source import GaussianBeam, AstigmaticGaussianBeam
from .components.source import CustomFieldSource
from .components.source import CustomFieldSource, TFSF

# monitors
from .components.monitor import FieldMonitor, FieldTimeMonitor, FluxMonitor, FluxTimeMonitor
Expand Down
8 changes: 4 additions & 4 deletions tidy3d/components/boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from .base import Tidy3dBaseModel, cached_property
from .types import Complex, Axis, TYPE_TAG_STR
from .source import GaussianBeam, ModeSource, PlaneWave
from .source import GaussianBeam, ModeSource, PlaneWave, TFSF
from .medium import Medium

from ..log import log, SetupError, DataError
Expand Down Expand Up @@ -40,7 +40,7 @@ class PMCBoundary(BoundaryEdge):
# """ Bloch boundary """

# sources from which Bloch boundary conditions can be defined
BlochSourceType = Union[GaussianBeam, ModeSource, PlaneWave]
BlochSourceType = Union[GaussianBeam, ModeSource, PlaneWave, TFSF]


class BlochBoundary(BoundaryEdge):
Expand Down Expand Up @@ -102,8 +102,8 @@ def from_source(

if not isinstance(source, BlochSourceType.__args__):
raise SetupError(
"The `source` parameter must be `GaussianBeam`, `ModeSource`, or `PlaneWave` "
"in order to define a Bloch boundary condition."
"The `source` parameter must be `GaussianBeam`, `ModeSource`, `PlaneWave`, "
"or `TFSF` in order to define a Bloch boundary condition."
)

if axis == source.injection_axis:
Expand Down
2 changes: 2 additions & 0 deletions tidy3d/components/data/sim_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from ..base import Tidy3dBaseModel
from ..simulation import Simulation
from ..boundary import BlochBoundary
from ..source import TFSF
from ..types import Ax, Axis, annotate_type, Literal, PlotVal
from ..viz import equal_aspect, add_ax_if_none
from ...log import DataError, log, Tidy3dKeyError, ValidationError
Expand Down Expand Up @@ -128,6 +129,7 @@ def source_spectrum(self, source_index: int) -> Callable:
boundaries = self.simulation.boundary_spec.to_list
boundaries_1d = [boundary_1d for dim_boundary in boundaries for boundary_1d in dim_boundary]
complex_fields = any(isinstance(boundary, BlochBoundary) for boundary in boundaries_1d)
complex_fields = complex_fields and not isinstance(source, TFSF)

# plug in mornitor_data frequency domain information
def source_spectrum_fn(freqs):
Expand Down
Loading

0 comments on commit be42200

Please sign in to comment.