Skip to content
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

TFSF #672

Merged
merged 3 commits into from
Mar 16, 2023
Merged

TFSF #672

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
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