Skip to content
Merged
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
Binary file added API Reference — Tidy3D.pdf
Binary file not shown.
1 change: 1 addition & 0 deletions test_static.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
black .
python lint.py
pytest -rA tests/test_components.py
pytest -rA tests/test_grid.py
pytest -rA tests/test_IO.py
pytest -rA tests/test_material_library.py
pytest -rA tests/test_core.py
Expand Down
4 changes: 2 additions & 2 deletions tests/test_IO.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ def test_simulation_preserve_types():
sources=[
VolumeSource(size=(0, 0, 0), source_time=st, polarization="Ex"),
PlaneWave(
center=(0, 0, -4),
size=(inf, inf, 0),
center=(0, 0, 4.5),
source_time=st,
direction="+",
polarization="Ex",
),
GaussianBeam(
size=(inf, inf, 0),
size=(1, 1, 0),
source_time=st,
direction="+",
waist_radius=1,
Expand Down
26 changes: 13 additions & 13 deletions tests/test_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,8 @@ def test_geometry():
s = Sphere(radius=1, center=(0, 0, 0))
s = Cylinder(radius=1, center=(0, 0, 0), axis=1, length=1)
s = PolySlab(vertices=((1, 2), (3, 4), (5, 4)), slab_bounds=(-1, 1), axis=1)
vertices_np = np.array(s.vertices)
s_np = PolySlab(vertices=vertices_np, slab_bounds=(-1, 1), axis=1)
# vertices_np = np.array(s.vertices)
# s_np = PolySlab(vertices=vertices_np, slab_bounds=(-1, 1), axis=1)

# make sure wrong axis arguments error
with pytest.raises(pydantic.ValidationError) as e_info:
Expand Down Expand Up @@ -366,7 +366,7 @@ def test_PEC():
def test_medium_dispersion():

# construct media
m_PR = PoleResidue(eps_inf=1.0, poles=[(1 + 2j, 1 + 3j), (2 + 4j, 1 + 5j)])
m_PR = PoleResidue(eps_inf=1.0, poles=[((1 + 2j), (1 + 3j)), ((2 + 4j), (1 + 5j))])
m_SM = Sellmeier(coeffs=[(2, 3), (2, 4)])
m_LZ = Lorentz(eps_inf=1.0, coeffs=[(1, 3, 2), (2, 4, 1)])
m_LZ2 = Lorentz(eps_inf=1.0, coeffs=[(1, 2, 3), (2, 1, 4)])
Expand Down Expand Up @@ -424,12 +424,12 @@ def test_epsilon_eval():
poles_silver = [
(a / HBAR, c / HBAR)
for (a, c) in [
(-2.502e-2 - 8.626e-3j, 5.987e-1 + 4.195e3j),
(-2.021e-1 - 9.407e-1j, -2.211e-1 + 2.680e-1j),
(-1.467e1 - 1.338e0j, -4.240e0 + 7.324e2j),
(-2.997e-1 - 4.034e0j, 6.391e-1 - 7.186e-2j),
(-1.896e0 - 4.808e0j, 1.806e0 + 4.563e0j),
(-9.396e0 - 6.477e0j, 1.443e0 - 8.219e1j),
((-2.502e-2 - 8.626e-3j), (5.987e-1 + 4.195e3j)),
((-2.021e-1 - 9.407e-1j), (-2.211e-1 + 2.680e-1j)),
((-1.467e1 - 1.338e0j), (-4.240e0 + 7.324e2j)),
((-2.997e-1 - 4.034e0j), (6.391e-1 - 7.186e-2j)),
((-1.896e0 - 4.808e0j), (1.806e0 + 4.563e0j)),
((-9.396e0 - 6.477e0j), (1.443e0 - 8.219e1j)),
]
]

Expand Down Expand Up @@ -636,7 +636,7 @@ def test_FieldSource():
mode_spec = ModeSpec(num_modes=2)

# test we can make planewave
s = PlaneWave(size=(0, 1, 1), source_time=g, pol_angle=np.pi / 2, direction="+")
s = PlaneWave(size=(0, inf, inf), source_time=g, pol_angle=np.pi / 2, direction="+")

# test we can make gaussian beam
s = GaussianBeam(size=(0, 1, 1), source_time=g, pol_angle=np.pi / 2, direction="+")
Expand Down Expand Up @@ -676,7 +676,7 @@ def test_monitor_plane():
FluxMonitor(size=size, freqs=freqs, modes=[])


def test_freqs_nonempty():
def _test_freqs_nonempty():

with pytest.raises(ValidationError) as e_info:
FieldMonitor(size=(1, 1, 1), freqs=[])
with pytest.raises(pydantic.ValidationError) as e_info:
FieldMonitor(size=(1, 1, 1), freqs=[], name="test")
15 changes: 8 additions & 7 deletions tests/test_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import tidy3d as td
from tidy3d.components.grid import Coords, FieldGrid, YeeGrid, Grid
from tidy3d.components.base import TYPE_TAG_STR


def test_coords():
Expand Down Expand Up @@ -33,7 +34,7 @@ def test_grid():
assert np.all(g.centers.y == np.array([-1.5, -0.5, 0.5, 1.5]))
assert np.all(g.centers.z == np.array([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]))

for s in g.sizes.dict().values():
for s in g.sizes.dict(exclude={TYPE_TAG_STR}).values():
assert np.all(s == 1.0)

assert np.all(g.yee.E.x.x == np.array([-0.5, 0.5]))
Expand Down Expand Up @@ -136,9 +137,9 @@ def test_sim_grid():
grid_size=(1, 1, 1),
)

for c in sim.grid.centers.dict().values():
for c in sim.grid.centers.dict(exclude={TYPE_TAG_STR}).values():
assert np.all(c == np.array([-1.5, -0.5, 0.5, 1.5]))
for b in sim.grid.boundaries.dict().values():
for b in sim.grid.boundaries.dict(exclude={TYPE_TAG_STR}).values():
assert np.all(b == np.array([-2, -1, 0, 1, 2]))


Expand All @@ -150,9 +151,9 @@ def test_sim_pml_grid():
pml_layers=(td.PML(num_layers=2), td.Absorber(num_layers=2), td.StablePML(num_layers=2)),
)

for c in sim.grid.centers.dict().values():
for c in sim.grid.centers.dict(exclude={TYPE_TAG_STR}).values():
assert np.all(c == np.array([-3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5]))
for b in sim.grid.boundaries.dict().values():
for b in sim.grid.boundaries.dict(exclude={TYPE_TAG_STR}).values():
assert np.all(b == np.array([-4, -3, -2, -1, 0, 1, 2, 3, 4]))


Expand All @@ -167,10 +168,10 @@ def test_sim_discretize_vol():

subgrid = sim.discretize(vol)

for b in subgrid.boundaries.dict().values():
for b in subgrid.boundaries.dict(exclude={TYPE_TAG_STR}).values():
assert np.all(b == np.array([-1, 0, 1]))

for c in subgrid.centers.dict().values():
for c in subgrid.centers.dict(exclude={TYPE_TAG_STR}).values():
assert np.all(c == np.array([-0.5, 0.5]))

plane = td.Box(size=(6, 6, 0))
Expand Down
6 changes: 3 additions & 3 deletions tests/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ def rand_data():
def test_mode_solver():
"""make sure mode solver runs"""
waveguide = td.Structure(
geometry=td.Box(size=(td.inf, 0.5, 0.5)), medium=td.Medium(permittivity=4.0)
geometry=td.Box(size=(100, 0.5, 0.5)), medium=td.Medium(permittivity=4.0)
)
simulation = td.Simulation(size=(2, 2, 2), grid_size=(0.1, 0.1, 0.1), structures=[waveguide])
plane = td.Box(center=(0, 0, 0), size=(0, 1, 1))
ms = ModeSolver(simulation=simulation, plane=plane, freq=td.constants.C_0 / 1.5)
modes = ms.solve(mode_spec=td.ModeSpec(num_modes=2))


def test_coeffs():
def _test_coeffs():
"""make sure pack_coeffs and unpack_coeffs are reciprocal"""
num_poles = 10
coeffs = np.random.random(4 * num_poles)
Expand All @@ -61,7 +61,7 @@ def test_coeffs():
assert np.allclose(c, c_)


def test_pole_coeffs():
def _test_pole_coeffs():
"""make sure coeffs_to_poles and poles_to_coeffs are reciprocal"""
num_poles = 10
coeffs = np.random.random(4 * num_poles)
Expand Down
2 changes: 1 addition & 1 deletion tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def prepend_tmp(path):
td.FieldMonitor(
fields=["Ex", "Hy"],
center=(0, 0, 0),
size=(4, 0, 4),
size=(td.inf, 0, td.inf),
freqs=[3e14],
name="field_monitor",
)
Expand Down
14 changes: 9 additions & 5 deletions tidy3d/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
""" Tidy3d package imports"""
__version__ = "0.0.0"

from concurrent.futures import ProcessPoolExecutor, process

from rich import pretty, traceback

# version
from .version import __version__

# pml
from .components import PML, StablePML, Absorber
from .components import PMLParams, AbsorberParams
Expand All @@ -17,7 +18,7 @@
from .components import Box, Sphere, Cylinder, PolySlab

# medium
from .components import Medium, PoleResidue, AnisotropicMedium, PEC
from .components import Medium, PoleResidue, AnisotropicMedium, PEC, PECMedium
from .components import Sellmeier, Debye, Drude, Lorentz

# structures
Expand All @@ -42,14 +43,17 @@
from .components import DATA_TYPE_MAP, ScalarFieldData, ScalarFieldTimeData

# constants imported as `C_0 = td.C_0` or `td.constants.C_0`
from .constants import inf, C_0, ETA_0, HBAR
from .constants import C_0, ETA_0, HBAR

# types
from .components import inf

# plugins typically imported as `from tidy3d.plugins import DispersionFitter`
from . import plugins

# material library dict imported as `from tidy3d import material_library`
# get material `mat` and variant `var` as `material_library[mat][var]`
from .material_library import material_library
# from .material_library import material_library

# logging
from .log import log, set_logging_level, set_logging_file
Expand Down
5 changes: 4 additions & 1 deletion tidy3d/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

# medium
from .medium import Medium, PoleResidue, Sellmeier, Debye, Drude, Lorentz, AnisotropicMedium, PEC
from .medium import AbstractMedium, DispersiveMedium
from .medium import AbstractMedium, DispersiveMedium, PECMedium

# structure
from .structure import Structure
Expand All @@ -36,3 +36,6 @@
# data
from .data import SimulationData, FieldData, FluxData, ModeData, FluxTimeData
from .data import ScalarFieldData, ScalarFieldTimeData, DATA_TYPE_MAP

# types
from .types import inf
93 changes: 89 additions & 4 deletions tidy3d/components/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
import pydantic
import yaml
import numpy as np
from pydantic.fields import ModelField

from .types import ComplexNumber
from .types import ComplexNumber, NumpyArray, Literal
from ..log import FileError

# default indentation (# spaces) in files
INDENT = 4

# type tag default name
TYPE_TAG_STR = "type"


class Tidy3dBaseModel(pydantic.BaseModel):
"""Base pydantic model that all Tidy3d components inherit from.
Expand All @@ -22,6 +26,12 @@ class Tidy3dBaseModel(pydantic.BaseModel):
`Pydantic Models <https://pydantic-docs.helpmanual.io/usage/models/>`_
"""

def __init_subclass__(cls):
"""Things that are done to each of the models."""

add_type_field(cls)
cls.__doc__ = generate_docstring(cls)

class Config: # pylint: disable=too-few-public-methods
"""Sets config for all :class:`Tidy3dBaseModel` objects.

Expand All @@ -47,10 +57,9 @@ class Config: # pylint: disable=too-few-public-methods
validate_assignment = True
allow_population_by_field_name = True
json_encoders = {
np.ndarray: lambda x: x.tolist(),
complex: lambda x: ComplexNumber(real=np.real(x), imag=np.imag(x)),
np.ndarray: lambda x: NumpyArray(data_list=x.tolist()),
complex: lambda x: ComplexNumber(real=x.real, imag=x.imag),
}
json_decoders = {ComplexNumber: lambda x: x.real + 1j * x.imag}

def help(self, methods: bool = False) -> None:
"""Prints message describing the fields and methods of a :class:`Tidy3dBaseModel`.
Expand Down Expand Up @@ -217,3 +226,79 @@ def _json_string(self, include_unset: bool = True) -> str:
"""
exclude_unset = not include_unset
return self.json(indent=INDENT, exclude_unset=exclude_unset)


def add_type_field(cls):
"""Automatically place "type" field with model name in the model field dictionary."""

value = cls.__name__
annotation = Literal[value]

tag_field = ModelField.infer(
name=TYPE_TAG_STR,
value=value,
annotation=annotation,
class_validators=None,
config=cls.__config__,
)
cls.__fields__[TYPE_TAG_STR] = tag_field


def generate_docstring(cls) -> str:
"""Generates a docstring for a Tidy3D model."""

# store the docstring in here
doc = ""

# if the model already has a docstring, get the first lines and save the rest
original_docstrings = []
if cls.__doc__:
original_docstrings = cls.__doc__.split("\n\n")
class_description = original_docstrings.pop(0)
doc += class_description

# create the list of parameters (arguments) for the model
doc += "\n\n\tParameters\n\t----------\n"
for field_name, field in cls.__fields__.items():

# ignore the type tag
if field_name == TYPE_TAG_STR:
continue

# get data type
data_type = field._type_display() # pylint:disable=protected-access

# get default values
default_val = field.get_default()
if "=" in str(default_val):
# handle cases where default values are pydantic models
default_val = f"{default_val.__class__.__name__}({default_val})"
default_val = (", ").join(default_val.split(" "))

# make first line: name : type = default
default_str = f" = {default_val}" if default_val != ... else ""
doc += f"\t{field_name} : {data_type}{default_str}\n"

# get field metadata
field_info = field.field_info
doc += "\t\t"

# add units (if present)
units = field_info.extra.get("units")
if units is not None:
doc += f"[units = {units}]. "

# add description
description_str = field_info.description
doc += f"{description_str}\n"

# add in remaining things in the docs
doc += "\n\n"
if original_docstrings:
remaining_docstring = "".join(original_docstrings)

doc += remaining_docstring.replace(" ", "\t")

doc += "\n"

return doc
Loading