diff --git a/.pylintrc b/.pylintrc
index f34cd452bc..fdd9293c40 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -1,7 +1,7 @@
[MASTER]
extension-pkg-allow-list=pydantic
ignore=material_library.py, plugins
-good-names=ax, im, Lx, Ly, Lz, x0, y0, z0, x, y, z, f, t, y1, y2, x1, x2, xs, ys, zs, Ax, Nx, Ny, Nz, dl, rr, E, H, xx, yy, zz, dx, dy, Jx, Jy, Hx, Hy, dz, e, fp, dt
+good-names=ax, im, Lx, Ly, Lz, x0, y0, z0, x, y, z, f, t, y1, y2, x1, x2, xs, ys, zs, Ax, Nx, Ny, Nz, dl, rr, E, H, xx, yy, zz, dx, dy, Jx, Jy, Hx, Hy, dz, e, fp, dt, a, c
[BASIC]
@@ -10,4 +10,4 @@ max-line-length=100
[pre-commit-hook]
command=custom_pylint
-disable=pointless-string-statement, too-many-ancestors, too-few-public-methods, fixme, logging-not-lazy, logging-fstring-interpolation, no-self-argument, no-self-use
+disable=pointless-string-statement, too-many-ancestors, too-few-public-methods, fixme, logging-not-lazy, logging-fstring-interpolation, no-self-argument, no-self-use, duplicate-code
diff --git a/PR.json b/PR.json
new file mode 100644
index 0000000000..e473a34b83
--- /dev/null
+++ b/PR.json
@@ -0,0 +1,21 @@
+{
+ "name": null,
+ "frequency_range": [
+ -1e+16,
+ 1e+16
+ ],
+ "eps_inf": 1.0,
+ "poles": [
+ [
+ [
+ 1.0,
+ 1.0
+ ],
+ [
+ 0.0,
+ 2.2
+ ]
+ ]
+ ],
+ "type": "PoleResidue"
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index df564c9588..42a2e5335b 100644
--- a/README.md
+++ b/README.md
@@ -199,20 +199,20 @@ git push origin x.x.x
- [ ] Near2far with new API (3 days)
- [ ] Mode Monitor consistent with new .epsilon() (1 day)
- [ ] API changes (discuss first, implementation in 1 day)
- - [ ] Freqs and times store start, end, stop / number instead of raw values.
- - [ ] Change source polarization to E instead of J.
- - [ ] named Meidums?
- - [ ] Symmetry, PML, grid spec. Less clunky interface?
+ - [x] Freqs and times store start, end, stop / number instead of raw values.
+ - [x] Change source polarization to E instead of J.
+ - [x] named Meidums?
+ - [x] Symmetry, PML, grid spec. Less clunky interface?
- [ ] Covering features of existing code (1 day)
- - [ ] support diagonal anisotropy (permittivity as 3-tuple)
+ - [x] support diagonal anisotropy (permittivity as 3-tuple)
+ - [x] Conversion of dispersive materials into pole-residue.
+ - [x] gaussian beam.
+ - [x] option to display cell boundaries in plot.
- [ ] gds slab / gds importing.
- - [ ] Conversion of dispersive materials into pole-residue.
- - [ ] gaussian beam.
- - [ ] option to display cell boundaries in plot.
- - [ ] Add PEC medium
+ - [x] Add PEC medium
- [ ] Documentation (1 week)
- - [ ] Add more discussion into Simulation docs.
- - [ ] Write docstrings and examples for all callables.
+ - [x] Add more discussion into Simulation docs.
+ - [x] Write docstrings and examples for all callables.
- [ ] How Do I?
- [ ] Developer guide
- [ ] Package structure guide / explanation.
@@ -222,7 +222,7 @@ git push origin x.x.x
- [ ] Add more info / debug logging and more comprehensive error handling (file IO, etc).
- [ ] Add more intelligent 'inf' handling.
- [ ] setup.cfg for installing dependencies for different parts of the code (base, docs, tests)
- - [ ] web.monitor using running status for progress updates.
+ - [ ] web.monitor using running status for progress updates <- waiting on victor.
---
diff --git a/Untitled.ipynb b/Untitled.ipynb
new file mode 100644
index 0000000000..e203c71cf8
--- /dev/null
+++ b/Untitled.ipynb
@@ -0,0 +1,59 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "cd7b162c-38b8-493f-87eb-91e6c263f0fe",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import sys; sys.path.append('.')\n",
+ "import tidy3d as td\n",
+ "from tidy3d.components.base import Tidy3dBaseModel"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "443f96c7-ae3a-4f6a-8963-e8e76abf7a76",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Inf(Tidy3dBaseModel):\n",
+ "\n",
+ " self.sign : bool = True\n",
+ " self.shift : \n",
+ " def __neg__(self):\n",
+ " if self.is_positive:\n",
+ " self.is_positive = False\n",
+ " else:\n",
+ " self.is_positive = True\n",
+ " return self\n",
+ "\n",
+ " def __lt__(self, other):\n",
+ " return "
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/api.rst b/docs/api.rst
index e8b66147b2..7b82ce8b33 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -4,279 +4,359 @@ API Reference
.. currentmodule:: tidy3d
-Defining Simulations
-====================
-Base Simulation Definition
---------------------------
+Simulation
+==========
.. autosummary::
:toctree: _autosummary/
Simulation
+Methods
+-------
-.. Absorbing Boundaries
-.. --------------------
+.. autosummary::
+ :toctree: _autosummary/
-.. .. autosummary::
-.. :toctree: _autosummary/
+ Simulation.plot
+ Simulation.plot_eps
+ Simulation.plot_structures
+ Simulation.plot_structures_eps
+ Simulation.plot_sources
+ Simulation.plot_monitors
+ Simulation.plot_symmetries
+ Simulation.plot_pml
+ Simulation.grid
+ Simulation.dt
+ Simulation.tmesh
+ Simulation.wvl_mat_min
+ Simulation.frequency_range
+ Simulation.pml_thicknesses
+ Simulation.num_pml_layers
+ Simulation.discretize
+ Simulation.epsilon
+
+
+Grid
+====
-.. PML
-.. StablePML
-.. Absorber
+.. autosummary::
+ :toctree: _autosummary/
+ Coords
+ FieldGrid
+ YeeGrid
+ Coords1D
+ Grid
+ Grid.centers
+ Grid.sizes
+ Grid.yee
-.. Geometry
-.. --------
-.. .. autosummary::
-.. :toctree: _autosummary/
+Absorbing Boundaries
+====================
-.. Box
-.. Sphere
-.. Cylinder
-.. PolySlab
+.. autosummary::
+ :toctree: _autosummary/
+ PML
+ StablePML
+ Absorber
-.. Physical Objects
-.. ----------------
+Absorber Parameters
+-------------------
-.. .. autosummary::
-.. :toctree: _autosummary/
+.. autosummary::
+ :toctree: _autosummary/
-.. Structure
-.. Medium
-.. PoleResidue
-.. Sellmeier
-.. Debye
-.. Lorentz
-.. plugins.DispersionFitter
-.. .. material_library
+ AbsorberParams
+ PMLParams
-.. Monitors
-.. --------
+Geometry
+========
-.. .. autosummary::
-.. :toctree: _autosummary/
+.. autosummary::
+ :toctree: _autosummary/
-.. FieldMonitor
-.. FieldTimeMonitor
-.. FluxMonitor
-.. FluxTimeMonitor
-.. ModeMonitor
-.. Mode
+ Box
+ Sphere
+ Cylinder
+ PolySlab
+Methods
+-------
-.. Simulation Output Data
-.. ----------------------
+.. autosummary::
+ :toctree: _autosummary/
-.. .. autosummary::
-.. :toctree: _autosummary/
+ Geometry.plot
+ Geometry.inside
+ Geometry.intersections
+ Geometry.intersects
+ Geometry.intersects_plane
+ Geometry.bounds
+ Geometry.bounding_box
+ Geometry.pop_axis
+ Geometry.unpop_axis
-.. SimulationData
-.. SimulationData.export
-.. SimulationData.load
-.. FieldData
-.. FluxData
-.. FluxTimeData
-.. ModeData
+Mediums
+=======
-.. Submitting Simulations
-.. ======================
+.. autosummary::
+ :toctree: _autosummary/
-.. .. currentmodule:: tidy3d
+ Medium
+ AnisotropicMedium
+ PEC
+ PoleResidue
+ Sellmeier
+ Debye
+ Lorentz
-.. Web API
-.. -------
+Methods
+-------
-.. .. autosummary::
-.. :toctree: _autosummary/
+.. autosummary::
+ :toctree: _autosummary/
-.. web.upload
-.. web.get_info
-.. web.get_run_info
-.. web.run
-.. web.monitor
-.. web.download
-.. web.load_data
-.. web.delete
+ AbstractMedium.eps_model
-.. Job Interface
-.. -------------
-.. .. autosummary::
-.. :toctree: _autosummary/
+Structures
+==========
-.. web.Job
+.. autosummary::
+ :toctree: _autosummary/
-.. Batch Processing
-.. ----------------
+ Structure
-.. .. autosummary::
-.. :toctree: _autosummary/
+Methods
+-------
-.. web.Batch
+.. autosummary::
+ :toctree: _autosummary/
-.. Info Containers
-.. ---------------
+ Structure.plot
-.. .. autosummary::
-.. :toctree: _autosummary/
-.. web.task.Task
-.. web.task.TaskInfo
-.. web.task.TaskStatus
+Modes
+=====
+.. autosummary::
+ :toctree: _autosummary/
-.. Plugins
-.. =======
+ Mode
-.. Dispersive Model Fitting Tool
-.. -----------------------------
-.. .. autosummary::
-.. :toctree: _autosummary/
+Sources
+=======
-.. plugins.DispersionFitter
-.. plugins.DispersionFitter.load
-.. plugins.DispersionFitter.fit
-.. plugins.DispersionFitter.plot
+.. autosummary::
+ :toctree: _autosummary/
-.. Mode Solver
-.. -----------
+ VolumeSource
+ PlaneWave
+ ModeSource
+ GaussianPulse
-.. .. autosummary::
-.. :toctree: _autosummary/
+Methods
+-------
-.. plugins.ModeSolver
-.. plugins.ModeSolver.solve
-.. plugins.mode.mode_solver.ModeInfo
+.. autosummary::
+ :toctree: _autosummary/
-.. Near Field to Far Field Transformation
-.. --------------------------------------
+ Source.geometry
+ Source.plot
+ Source.inside
+ Source.intersections
+ Source.intersects
+ Source.intersects_plane
+ Source.bounds
+ Source.bounding_box
+ Source.pop_axis
+ Source.unpop_axis
-.. .. autosummary::
-.. :toctree: _autosummary/
+Source Time Dependence
+----------------------
-.. plugins.Near2Far
-.. plugins.Near2Far.fields_cartesian
-.. plugins.Near2Far.fields_spherical
-.. plugins.Near2Far.power_cartesian
-.. plugins.Near2Far.power_spherical
-.. plugins.Near2Far.radar_cross_section
+.. autosummary::
+ :toctree: _autosummary/
+ GaussianPulse
+ ContinuousWave
+ SourceTime.amp_time
+ SourceTime.plot
+ SourceTime.frequency_range
-.. Simulation
-.. ==========
-.. .. autosummary::
-.. :toctree: _autosummary/
+Monitors
+========
-.. Simulation
-.. PMLLayer
+.. autosummary::
+ :toctree: _autosummary/
+ FieldMonitor
+ FieldTimeMonitor
+ FluxMonitor
+ FluxTimeMonitor
+ ModeMonitor
-.. Geometry
-.. ========
+Methods
+-------
-.. .. autosummary::
-.. :toctree: _autosummary/
+.. autosummary::
+ :toctree: _autosummary/
-.. Box
-.. Sphere
-.. Cylinder
-.. PolySlab
+ Monitor.geometry
+ Monitor.plot
+ Monitor.inside
+ Monitor.intersections
+ Monitor.intersects
+ Monitor.intersects_plane
+ Monitor.bounds
+ Monitor.bounding_box
+ Monitor.pop_axis
+ Monitor.unpop_axis
-.. Medium
-.. ======
-.. .. autosummary::
-.. :toctree: _autosummary/
+Output Data
+===========
+
+.. autosummary::
+ :toctree: _autosummary/
-.. Medium
+ SimulationData
+ FieldData
+ FluxData
+ FluxTimeData
+ ModeData
-.. Dispersive Media
-.. ----------------
-.. .. autosummary::
-.. :toctree: _autosummary/
+Tidy3dBaseModel
+===============
-.. PoleResidue
-.. Sellmeier
-.. Lorentz
-.. Debye
+.. autosummary::
+ :toctree: _autosummary/
-.. Material Library
-.. ----------------
+ components.base.Tidy3dBaseModel
+ components.base.Tidy3dBaseModel.export
+ components.base.Tidy3dBaseModel.load
+ components.base.Tidy3dBaseModel.help
+.. Constants
+.. =========
.. .. autosummary::
.. :toctree: _autosummary/
+.. constants
-.. material_library
+Log
+===
+.. autosummary::
+ :toctree: _autosummary/
-.. Structure
-.. =========
+ logging_level
-.. .. autosummary::
-.. :toctree: _autosummary/
-.. Structure
+Submitting Simulations
+======================
+
+Web API
+-------
+
+.. autosummary::
+ :toctree: _autosummary/
+ web.run
+ web.upload
+ web.get_info
+ web.start
+ web.monitor
+ web.download
+ web.load_data
+ web.delete
-.. Source
-.. ======
+Job Interface
+-------------
-.. .. autosummary::
-.. :toctree: _autosummary/
+.. autosummary::
+ :toctree: _autosummary/
-.. VolumeSource
-.. ModeSource
-.. PlaneWave
-.. ..GaussianBeam
+ web.Job
+ web.Job.run
+ web.Job.upload
+ web.Job.get_info
+ web.Job.start
+ web.Job.monitor
+ web.Job.download
+ web.Job.load_data
+ web.Job.delete
-.. Source Time Dependence
-.. ----------------------
+Batch Processing
+----------------
-.. .. autosummary::
-.. :toctree: _autosummary/
+.. autosummary::
+ :toctree: _autosummary/
-.. GaussianPulse
-.. ..CW
+ web.Batch
+ web.Batch.run
+ web.Batch.upload
+ web.Batch.get_info
+ web.Batch.start
+ web.Batch.monitor
+ web.Batch.download
+ web.Batch.load_data
+ web.Batch.delete
+Info Containers
+---------------
-.. Monitor
-.. =======
+.. autosummary::
+ :toctree: _autosummary/
-.. .. autosummary::
-.. :toctree: _autosummary/
+ web.task.Task
+ web.task.TaskInfo
+ web.task.TaskStatus
-.. FluxMonitor
-.. FieldMonitor
-.. ModeMonitor
-.. Monitor Samplers
-.. ----------------
+Plugins
+=======
-.. .. autosummary::
-.. :toctree: _autosummary/
+Dispersive Model Fitting Tool
+-----------------------------
-.. TimeSampler
-.. FreqSampler
+.. autosummary::
+ :toctree: _autosummary/
-.. uniform_times
-.. uniform_freqs
+ plugins.DispersionFitter
+ plugins.DispersionFitter.load
+ plugins.DispersionFitter.fit
+ plugins.DispersionFitter.plot
+Mode Solver
+-----------
-.. Modes
-.. =====
+.. autosummary::
+ :toctree: _autosummary/
-.. .. autosummary::
-.. :toctree: _autosummary/
+ plugins.ModeSolver
+ plugins.ModeSolver.solve
+ plugins.mode.mode_solver.ModeInfo
+
+Near Field to Far Field Transformation
+--------------------------------------
+
+.. autosummary::
+ :toctree: _autosummary/
-.. Mode
+ plugins.Near2Far
+ plugins.Near2Far.fields_cartesian
+ plugins.Near2Far.fields_spherical
+ plugins.Near2Far.power_cartesian
+ plugins.Near2Far.power_spherical
+ plugins.Near2Far.radar_cross_section
diff --git a/docs/conf.py b/docs/conf.py
index 1e51d752f3..fb966d21c0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -24,8 +24,6 @@ def find_version(*file_paths):
raise RuntimeError("Unable to find version string.")
-#
-
# -- Project information -----------------------------------------------------
project = "Tidy3d"
@@ -64,7 +62,7 @@ def find_version(*file_paths):
autodoc_pydantic_model_show_validator_summary = False
autodoc_pydantic_model_show_validator_members = False
autodoc_pydantic_model_show_field_summary = False
-autodoc_pydantic_model_members = True
+autodoc_pydantic_model_members = False
extlinks = {}
diff --git a/explore/Inf.ipynb b/explore/Inf.ipynb
index b47b088ee0..0e9e20a660 100644
--- a/explore/Inf.ipynb
+++ b/explore/Inf.ipynb
@@ -2,120 +2,181 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 1,
"id": "b5b474ee-7312-46a7-ad1d-fb7e43c0ec2c",
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
- "sys.path.append('../tidy3d/components')\n",
+ "sys.path.append('..')\n",
"\n",
- "from base import Tidy3dBaseModel\n",
+ "from tidy3d.components.base import Tidy3dBaseModel\n",
"from typing import Literal"
]
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 53,
"id": "ec27769e-03ee-40b2-b0b9-078243b5e8d3",
"metadata": {},
"outputs": [],
"source": [
"class Inf(Tidy3dBaseModel):\n",
" type: Literal['Inf'] = 'Inf'\n",
- " center: float = 0.0\n",
- " scale: float = 1.0\n",
- " \n",
- " def get_value(self, bmin, bmax):\n",
- " bcenter = (bmin + bmax) / 2.0\n",
- " bsize = abs(bmax - bmin)\n",
- " return # something\n",
+ " value: float = 1 # fictional \"value\" in the inf world, units of np.inf\n",
"\n",
" def __neg__(self):\n",
- " return Inf(center=self.center, scale=-1*self.scale)\n",
+ " return Inf(value=-self.value)\n",
" \n",
" def __add__(self, other):\n",
- " other = float(other)\n",
- " new_center = other + self.center\n",
- " return Inf(center=new_center, scale=self.scale)\n",
+ " if isinstance(other, Inf):\n",
+ " new_value = self.value + other.value\n",
+ " # special case, if it's exactly, 0 just return zero\n",
+ " if new_value == 0.0:\n",
+ " return 0.0\n",
+ " return Inf(value=new_value)\n",
+ " return Inf(value=self.value + other)\n",
"\n",
" def __sub__(self, other):\n",
- " return self.__add__(-1*other)\n",
+ " return self + -other\n",
"\n",
" def __mul__(self, other):\n",
- " other = float(other)\n",
- " new_scale = self.scale * other\n",
- " return Inf(center=self.center, scale=new_scale)\n",
+ " if isinstance(other, Inf):\n",
+ " new_value = self.value * other.value\n",
+ " # special case, if it's exactly, 0 just return zero\n",
+ " if new_value == 0.0:\n",
+ " return 0.0 \n",
+ " return Inf(value=new_value)\n",
+ " return Inf(value=self.value * other)\n",
"\n",
" def __div__(self, other):\n",
- " return self.__mul__(1/other) "
+ " return self.__mul__(1.0 / other)\n",
+ "\n",
+ " def __truediv__(self, other):\n",
+ " return self.__mul__(1.0 / other)\n",
+ "\n",
+ " def __eq__(self, other):\n",
+ " if isinstance(other, Inf):\n",
+ " return self.value == other.value\n",
+ " return False\n",
+ "\n",
+ " def __lt__(self, other):\n",
+ " if isinstance(other, Inf):\n",
+ " return self.value < other.value\n",
+ " return True if self.value < 0 else False\n",
+ "\n",
+ " def __gt__(self, other):\n",
+ " if isinstance(other, Inf):\n",
+ " return self.value > other.value\n",
+ " return True if self.value > 0 else False\n",
+ "\n",
+ " def __le__(self, other):\n",
+ " if isinstance(other, Inf):\n",
+ " return self.value < other.value\n",
+ " return True if self.value < 0 else False\n",
+ "\n",
+ " def __ge__(self, other):\n",
+ " if isinstance(other, Inf):\n",
+ " return self.value >= other.value\n",
+ " return True if self.value >= 0 else False"
]
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": 54,
"id": "91058320-429c-49b2-9908-bce983a113a7",
"metadata": {},
+ "outputs": [],
+ "source": [
+ "inf = Inf()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "id": "6957c5d9-617f-462f-a7f7-6eb43d01547c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# properties to support\n",
+ "\n",
+ "# can be negative\n",
+ "assert -inf < inf\n",
+ "\n",
+ "# is larger (smaller) than any number\n",
+ "assert inf > 1e122\n",
+ "assert -inf < -1e122\n",
+ "\n",
+ "# can be compared to versions of itself modified with basic algebra\n",
+ "assert inf < inf*2\n",
+ "assert inf < inf + 1.0\n",
+ "assert inf > inf/2\n",
+ "assert inf > inf - 1.0\n",
+ "assert -inf > -inf*2\n",
+ "assert -inf < -inf + 1.0\n",
+ "assert -inf < -inf/2\n",
+ "assert -inf > -inf - 1.0\n",
+ "\n",
+ "# algebra between two infs leads to values that are expected intuitively\n",
+ "assert inf/2 - inf/2 == 0.0\n",
+ "assert -inf/3 + inf/3 == 0.0\n",
+ "assert inf/2 + inf/3 < inf\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "id": "8f0b6360-314d-4d8a-9b49-eefefd03ce62",
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "{\"type\": \"Inf\", \"center\": 0.0, \"scale\": 1.0}\n"
+ "type='Inf' value=1.5\n"
]
}
],
"source": [
- "inf = Inf()\n",
- "print(inf.json())"
+ "print(inf+inf/2)"
]
},
{
"cell_type": "code",
- "execution_count": 12,
- "id": "6957c5d9-617f-462f-a7f7-6eb43d01547c",
+ "execution_count": 63,
+ "id": "a64c4a0f-f003-4942-bb6e-2a882ce51008",
"metadata": {},
"outputs": [
{
"data": {
+ "text/html": [
+ "
0.5\n",
+ "
\n"
+ ],
"text/plain": [
- "'{\"type\": \"Inf\", \"center\": 0.0, \"scale\": -1.0}'"
+ "\u001b[1;36m0.5\u001b[0m\n"
]
},
- "execution_count": 12,
"metadata": {},
- "output_type": "execute_result"
+ "output_type": "display_data"
}
],
"source": [
- "(-inf).json()"
+ "center.value"
]
},
{
"cell_type": "code",
- "execution_count": 13,
- "id": "4c249540-60be-448d-a66f-82a192e7f523",
+ "execution_count": null,
+ "id": "e99b2fe0-8b83-409f-a046-1d11d4daf9e8",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "'{\"type\": \"Inf\", \"center\": 1.0, \"scale\": -1.0}'"
- ]
- },
- "execution_count": 13,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(-inf+1.0).json()"
- ]
+ "outputs": [],
+ "source": []
},
{
"cell_type": "code",
"execution_count": null,
- "id": "791d3495-04b2-467a-9a15-26d98a378cca",
+ "id": "2e5eebe5-55fc-4079-8921-6b580782de60",
"metadata": {},
"outputs": [],
"source": []
diff --git a/test_all.sh b/test_all.sh
index f2348f03bc..5ea6c282fa 100755
--- a/test_all.sh
+++ b/test_all.sh
@@ -3,4 +3,8 @@
black .
python lint.py
pytest -rA tests/
-pytest --doctest-modules tidy3d --ignore=tidy3d/__main__.py
\ No newline at end of file
+pytest --doctest-modules tidy3d \
+--ignore=tidy3d/__main__.py \
+--ignore=tidy3d/components/base.py \
+--ignore=tidy3d/web/webapi.py \
+--ignore=tidy3d/web/container.py \
\ No newline at end of file
diff --git a/test_static.sh b/test_static.sh
index caabd608f6..0c82c78d0c 100755
--- a/test_static.sh
+++ b/test_static.sh
@@ -8,4 +8,8 @@ pytest -rA tests/test_material_library.py
pytest -rA tests/test_core.py
pytest -rA tests/test_plugins.py
-pytest --doctest-modules tidy3d --ignore=tidy3d/__main__.py
+pytest --doctest-modules tidy3d \
+--ignore=tidy3d/__main__.py \
+--ignore=tidy3d/components/base.py \
+--ignore=tidy3d/web/webapi.py \
+--ignore=tidy3d/web/container.py \
diff --git a/tests/test_IO.py b/tests/test_IO.py
index a18ebc2082..90f90c63bc 100644
--- a/tests/test_IO.py
+++ b/tests/test_IO.py
@@ -36,7 +36,7 @@ def test_simulation_preserve_types():
geometry=PolySlab(vertices=[[0, 0], [2, 3], [4, 3]], slab_bounds=(-1, 1), axis=2),
medium=Sellmeier(coeffs=[]),
),
- Structure(geometry=Sphere(radius=1), medium=Debye(eps_inf=1.0, coeffs=[])),
+ Structure(geometry=Sphere(radius=1), medium=Debye(eps_inf=1.0, coeffs=[]), name="t2"),
],
sources=[
VolumeSource(size=(0, 0, 0), source_time=st, polarization="Ex"),
@@ -46,7 +46,7 @@ def test_simulation_preserve_types():
source_time=st,
direction="+",
polarization="Ex",
- waist_size=(1, 1),
+ waist_radius=1,
),
],
monitors=[
@@ -101,7 +101,12 @@ def test_validation_speed():
for n in num_structures:
S = SIM.copy()
- S.structures = n * [SIM.structures[0]]
+ new_structures = []
+ for i in range(n):
+ new_structure = SIM.structures[0].copy()
+ new_structure.name = str(i)
+ new_structures.append(new_structure)
+ S.structures = new_structures
S.export(path)
time_start = time()
diff --git a/tests/test_components.py b/tests/test_components.py
index 881da938f8..dafb5e95e5 100644
--- a/tests/test_components.py
+++ b/tests/test_components.py
@@ -207,10 +207,15 @@ def test_medium_conversions():
assert np.isclose(k, k_)
+def test_PEC():
+
+ struct = Structure(geometry=Box(size=(1, 1, 1)), medium=PEC)
+
+
def test_medium_dispersion():
# construct media
- m_PR = PoleResidue(eps_inf=1.0, poles=[((1, 2), (1, 3)), ((2, 4), (1, 5))])
+ 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_DB = Debye(eps_inf=1.0, coeffs=[(1, 3), (2, 4)])
@@ -220,6 +225,153 @@ def test_medium_dispersion():
eps_c = medium.eps_model(freqs)
+def test_medium_dispersion_conversion():
+
+ 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_DB = Debye(eps_inf=1.0, coeffs=[(1, 3), (2, 4)])
+
+ freqs = np.linspace(0.01, 1, 1001)
+ for medium in [m_PR, m_SM, m_DB, m_LZ]: # , m_DB]:
+ eps_model = medium.eps_model(freqs)
+ eps_pr = medium.pole_residue.eps_model(freqs)
+ np.testing.assert_allclose(eps_model, eps_pr)
+
+
+""" modes """
+
+
+def test_modes():
+
+ m = Mode(mode_index=0)
+ m = Mode(mode_index=0, num_modes=1)
+
+ # not enough modes
+ with pytest.raises(SetupError) as e:
+ m = Mode(mode_index=1, num_modes=1)
+
+
+""" names """
+
+
+def test_names_default():
+ """makes sure default names are set"""
+
+ sim = Simulation(
+ size=(2.0, 2.0, 2.0),
+ grid_size=(0.01, 0.01, 0.01),
+ run_time=1e-12,
+ structures=[
+ Structure(
+ geometry=Box(size=(1, 1, 1), center=(-1, 0, 0)),
+ medium=Medium(permittivity=2.0),
+ ),
+ Structure(
+ geometry=Box(size=(1, 1, 1), center=(0, 0, 0)),
+ medium=Medium(permittivity=2.0),
+ ),
+ Structure(geometry=Sphere(radius=1.4, center=(1.0, 0.0, 1.0)), medium=Medium()),
+ Structure(
+ geometry=Cylinder(radius=1.4, length=2.0, center=(1.0, 0.0, -1.0), axis=1),
+ medium=Medium(),
+ ),
+ ],
+ sources=[
+ VolumeSource(
+ size=(0, 0, 0),
+ center=(0, -0.5, 0),
+ polarization="Hx",
+ source_time=GaussianPulse(freq0=1e14, fwidth=1e12),
+ ),
+ VolumeSource(
+ size=(0, 0, 0),
+ center=(0, -0.5, 0),
+ polarization="Ex",
+ source_time=GaussianPulse(freq0=1e14, fwidth=1e12),
+ ),
+ VolumeSource(
+ size=(0, 0, 0),
+ center=(0, -0.5, 0),
+ polarization="Ey",
+ source_time=GaussianPulse(freq0=1e14, fwidth=1e12),
+ ),
+ ],
+ monitors=[
+ FluxMonitor(size=(1, 1, 0), center=(0, -0.5, 0), freqs=[1], name="mon1"),
+ FluxMonitor(size=(0, 1, 1), center=(0, -0.5, 0), freqs=[1], name="mon2"),
+ FluxMonitor(size=(1, 0, 1), center=(0, -0.5, 0), freqs=[1], name="mon3"),
+ ],
+ )
+
+ for i, structure in enumerate(sim.structures):
+ assert structure.name == f"structures[{i}]"
+
+ for i, source in enumerate(sim.sources):
+ assert source.name == f"sources[{i}]"
+
+ distinct_mediums = [f"mediums[{i}]" for i in range(len(sim.mediums))]
+ for i, medium in enumerate(sim.mediums):
+ assert medium.name in distinct_mediums
+ distinct_mediums.pop(distinct_mediums.index(medium.name))
+
+
+def test_names_unique():
+
+ with pytest.raises(SetupError) as e:
+ sim = Simulation(
+ size=(2.0, 2.0, 2.0),
+ grid_size=(0.01, 0.01, 0.01),
+ run_time=1e-12,
+ structures=[
+ Structure(
+ geometry=Box(size=(1, 1, 1), center=(-1, 0, 0)),
+ medium=Medium(permittivity=2.0),
+ name="struct1",
+ ),
+ Structure(
+ geometry=Box(size=(1, 1, 1), center=(0, 0, 0)),
+ medium=Medium(permittivity=2.0),
+ name="struct1",
+ ),
+ ],
+ )
+
+ with pytest.raises(SetupError) as e:
+ sim = Simulation(
+ size=(2.0, 2.0, 2.0),
+ grid_size=(0.01, 0.01, 0.01),
+ run_time=1e-12,
+ sources=[
+ VolumeSource(
+ size=(0, 0, 0),
+ center=(0, -0.5, 0),
+ polarization="Hx",
+ source_time=GaussianPulse(freq0=1e14, fwidth=1e12),
+ name="source1",
+ ),
+ VolumeSource(
+ size=(0, 0, 0),
+ center=(0, -0.5, 0),
+ polarization="Ex",
+ source_time=GaussianPulse(freq0=1e14, fwidth=1e12),
+ name="source1",
+ ),
+ ],
+ )
+
+ with pytest.raises(SetupError) as e:
+ sim = Simulation(
+ size=(2.0, 2.0, 2.0),
+ grid_size=(0.01, 0.01, 0.01),
+ run_time=1e-12,
+ monitors=[
+ FluxMonitor(size=(1, 1, 0), center=(0, -0.5, 0), freqs=[1], name="mon1"),
+ FluxMonitor(size=(0, 1, 1), center=(0, -0.5, 0), freqs=[1], name="mon1"),
+ ],
+ )
+
+
""" VolumeSources """
diff --git a/tests/test_grid.py b/tests/test_grid.py
index 978045675c..65362139d2 100644
--- a/tests/test_grid.py
+++ b/tests/test_grid.py
@@ -33,7 +33,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.cell_sizes.dict().values():
+ for s in g.sizes.dict().values():
assert np.all(s == 1.0)
assert np.all(g.yee.E.x.x == np.array([-0.5, 0.5]))
diff --git a/tidy3d/__init__.py b/tidy3d/__init__.py
index 38ebaa8f9c..1107dcae77 100644
--- a/tidy3d/__init__.py
+++ b/tidy3d/__init__.py
@@ -4,20 +4,41 @@
from rich import pretty, traceback
# import component as `from tidy3d import Simulation` or `td.Simulation`
+
+# pml
from .components import PML, StablePML, Absorber
+from .components import PMLParams, AbsorberParams
+from .components import DefaultPMLParameters, DefaultStablePMLParameters, DefaultAbsorberParameters
+
+# grid
from .components import Grid, Coords
+
+# geometry
from .components import Box, Sphere, Cylinder, PolySlab
-from .components import Geometry
-from .components import Structure
-from .components import Medium, PoleResidue, Sellmeier, Debye, Lorentz
+
+# medium
+from .components import Medium, PoleResidue, Sellmeier, Debye, Lorentz, AnisotropicMedium, PEC
from .components import nk_to_eps_complex, nk_to_eps_sigma, eps_complex_to_nk
from .components import nk_to_medium, eps_sigma_to_eps_complex
-from .components import GaussianPulse
+
+# structures
+from .components import Structure
+
+# modes
+from .components import Mode
+
+# sources
+from .components import GaussianPulse, ContinuousWave
from .components import VolumeSource, PlaneWave, ModeSource, GaussianBeam
+
+# monitors
from .components import FieldMonitor, FieldTimeMonitor, FluxMonitor, FluxTimeMonitor
from .components import ModeMonitor
-from .components import Mode
+
+# simulation
from .components import Simulation
+
+# data
from .components import SimulationData, FieldData, FluxData, ModeData, FluxTimeData
from .components import data_type_map, ScalarFieldData, ScalarFieldTimeData
@@ -31,7 +52,15 @@
# get material `mat` and variant `var` as `material_library[mat][var]`
from .material_library import material_library
-from .log import log
+# logging
+from .log import log, logging_level
+
+# for docs
+from .components.medium import AbstractMedium
+from .components.geometry import Geometry
+from .components.source import Source, SourceTime
+from .components.monitor import Monitor
+from .components.grid import YeeGrid, FieldGrid, Coords1D
# make all stdout and errors pretty
pretty.install()
diff --git a/tidy3d/components/__init__.py b/tidy3d/components/__init__.py
index 4f1349014c..420c7d68b0 100644
--- a/tidy3d/components/__init__.py
+++ b/tidy3d/components/__init__.py
@@ -1,24 +1,39 @@
""" Imports all tidy3d """
-from .simulation import Simulation
-
+# pml
from .pml import PML, StablePML, Absorber
+from .pml import PMLParams, AbsorberParams
+from .pml import DefaultPMLParameters, DefaultStablePMLParameters, DefaultAbsorberParameters
+
+# grid
from .grid import Grid, Coords
+# geometry
from .geometry import Box, Sphere, Cylinder, PolySlab
from .geometry import Geometry
-from .structure import Structure
-from .medium import Medium, PoleResidue, Sellmeier, Debye, Lorentz
+
+# medium
+from .medium import Medium, PoleResidue, Sellmeier, Debye, Lorentz, AnisotropicMedium, PEC
from .medium import nk_to_eps_complex, nk_to_eps_sigma, eps_complex_to_nk
from .medium import nk_to_medium, eps_sigma_to_eps_complex
-from .source import GaussianPulse
+# structure
+from .structure import Structure
+
+# mode
+from .mode import Mode
+
+# source
+from .source import GaussianPulse, ContinuousWave
from .source import VolumeSource, PlaneWave, ModeSource, GaussianBeam
+# monitor
from .monitor import FieldMonitor, FieldTimeMonitor, FluxMonitor, FluxTimeMonitor
from .monitor import ModeMonitor
-from .mode import Mode
+# simulation
+from .simulation import Simulation
+# data
from .data import SimulationData, FieldData, FluxData, ModeData, FluxTimeData
from .data import ScalarFieldData, ScalarFieldTimeData, data_type_map
diff --git a/tidy3d/components/base.py b/tidy3d/components/base.py
index 31cee8623c..5619c0c631 100644
--- a/tidy3d/components/base.py
+++ b/tidy3d/components/base.py
@@ -1,4 +1,4 @@
-""" global configuration / base class for pydantic models used to make simulation """
+"""global configuration / base class for pydantic models used to make simulation."""
import json
@@ -7,61 +7,213 @@
import yaml
import numpy as np
+from .types import ComplexNumber
+from ..log import FileError
+
# default indentation (# spaces) in files
INDENT = 4
class Tidy3dBaseModel(pydantic.BaseModel):
- """https://pydantic-docs.helpmanual.io/usage/model_config/"""
+ """Base pydantic model that all Tidy3d components inherit from.
+ Defines configuration for handling data structures
+ as well as methods for imporing, exporting, and hashing tidy3d objects.
+ For more details on pydantic base models, see:
+ `Pydantic Models `_
+ """
class Config: # pylint: disable=too-few-public-methods
- """sets config for all Tidy3dBaseModel objects"""
+ """Sets config for all :class:`Tidy3dBaseModel` objects.
+
+ Configuration Options
+ ---------------------
+ allow_population_by_field_name : bool = True
+ Allow properties to stand in for fields(?).
+ arbitrary_types_allowed : bool = True
+ Allow types like numpy arrays.
+ extra : str = 'forbid'
+ Forbid extra kwargs not specified in model.
+ json_encoders : Dict[type, Callable]
+ Defines how to encode type in json file.
+ validate_all : bool = True
+ Validate default values just to be safe.
+ validate_assignment : bool
+ Re-validate after re-assignment of field in model.
+ """
arbitrary_types_allowed = True
- validate_all = True # validate default values too
- extra = "forbid" # forbid extra kwargs not specified in model
- validate_assignment = True # validate when attributes are set after initialization
+ validate_all = True
+ extra = "forbid"
+ validate_assignment = True
allow_population_by_field_name = True
- json_encoders = {np.ndarray: lambda x: x.tolist()} # pylint: disable=unnecessary-lambda
+ json_encoders = {
+ np.ndarray: lambda x: x.tolist(),
+ complex: lambda x: ComplexNumber(real=np.real(x), imag=np.imag(x)),
+ }
+ json_decoders = {ComplexNumber: lambda x: x.real + 1j * x.imag}
def help(self, methods: bool = False) -> None:
- """get help for this object"""
+ """Prints message describing the fields and methods of a :class:`Tidy3dBaseModel`.
+
+ Parameters
+ ----------
+ methods : bool = False
+ Whether to also print out information about object's methods.
+
+ Example
+ -------
+ >>> simulation.help(methods=True)
+ """
rich.inspect(self, methods=methods)
- def __hash__(self) -> int:
- """hash tidy3dBaseModel objects using their json strings"""
- return hash(self.json())
+ @classmethod
+ def load(cls, fname: str):
+ """Loads a :class:`Tidy3dBaseModel` from .yaml or .json file.
- def __lt__(self, other):
- """define < for getting unique indices"""
- return hash(self) < hash(other)
+ Parameters
+ ----------
+ fname : str
+ Full path to the .yaml or .json file to load the :class:`Tidy3dBaseModel` from.
- def _json_string(self, exclude_unset: bool = False) -> str:
- """returns string representation of self"""
- return self.json(indent=INDENT, exclude_unset=exclude_unset)
+ Returns
+ -------
+ :class:`Tidy3dBaseModel`
+ An instance of the component class calling `load`.
+
+ Example
+ -------
+ >>> simulation = Simulation.load(fname='folder/sim.json')
+ """
+ if ".json" in fname:
+ return cls.load_json(fname=fname)
+ if ".yaml" in fname:
+ return cls.load_yaml(fname=fname)
+ raise FileError(f"File must be .json or .yaml, given {fname}")
+
+ def export(self, fname: str) -> None:
+ """Exports :class:`Tidy3dBaseModel` instance to .yaml or .json file
+
+ Parameters
+ ----------
+ fname : str
+ Full path to the .yaml or .json file to save the :class:`Tidy3dBaseModel` to.
+
+ Example
+ -------
+ >>> simulation.export(fname='folder/sim.json')
+ """
+ if ".json" in fname:
+ return self.export_json(fname=fname)
+ if ".yaml" in fname:
+ return self.export_yaml(fname=fname)
+ raise FileError(f"File must be .json or .yaml, given {fname}")
@classmethod
- def load(cls, fname: str):
- """load Simulation from .json file"""
+ def load_json(cls, fname: str):
+ """Load a :class:`Tidy3dBaseModel` from .json file.
+
+ Parameters
+ ----------
+ fname : str
+ Full path to the .json file to load the :class:`Tidy3dBaseModel` from.
+
+ Returns
+ -------
+ :class:`Tidy3dBaseModel`
+ An instance of the component class calling `load`.
+
+ Example
+ -------
+ >>> simulation = Simulation.load_json(fname='folder/sim.json')
+ """
return cls.parse_file(fname)
- def export(self, fname: str) -> None:
- """Exports Tidy3dBaseModel instance to .json file"""
+ def export_json(self, fname: str) -> None:
+ """Exports :class:`Tidy3dBaseModel` instance to .json file
+
+ Parameters
+ ----------
+ fname : str
+ Full path to the .json file to save the :class:`Tidy3dBaseModel` to.
+
+ Example
+ -------
+ >>> simulation.export_json(fname='folder/sim.json')
+ """
json_string = self._json_string()
with open(fname, "w", encoding="utf-8") as file_handle:
file_handle.write(json_string)
@classmethod
def load_yaml(cls, fname: str):
- """load Simulation from .yaml file"""
+ """Loads :class:`Tidy3dBaseModel` from .yaml file.
+
+ Parameters
+ ----------
+ fname : str
+ Full path to the .yaml file to load the :class:`Tidy3dBaseModel` from.
+
+ Returns
+ -------
+ :class:`Tidy3dBaseModel`
+ An instance of the component class calling `load_yaml`.
+
+ Example
+ -------
+ >>> simulation = Simulation.load_yaml(fname='folder/sim.yaml')
+ """
with open(fname, "r", encoding="utf-8") as yaml_in:
json_dict = yaml.safe_load(yaml_in)
json_raw = json.dumps(json_dict, indent=INDENT)
return cls.parse_raw(json_raw)
def export_yaml(self, fname: str) -> None:
- """Exports Tidy3dBaseModel instance to .yaml file"""
+ """Exports :class:`Tidy3dBaseModel` instance to .yaml file.
+
+ Parameters
+ ----------
+ fname : str
+ Full path to the .yaml file to save the :class:`Tidy3dBaseModel` to.
+
+ Example
+ -------
+ >>> simulation.export_yaml(fname='folder/sim.yaml')
+ """
json_string = self._json_string()
json_dict = json.loads(json_string)
with open(fname, "w+", encoding="utf-8") as file_handle:
yaml.dump(json_dict, file_handle, indent=INDENT)
+
+ def __hash__(self) -> int:
+ """Hash a :class:`Tidy3dBaseModel` objects using its json string.
+
+ Returns
+ -------
+ int
+ Integer representation of the hash of the :class:`Tidy3dBaseModel`.
+
+ Example
+ -------
+ >>> hash_integer = hash(simulation)
+ """
+ return hash(self.json())
+
+ def __lt__(self, other):
+ """define < for getting unique indices based on hash."""
+ return hash(self) < hash(other)
+
+ def _json_string(self, include_unset: bool = True) -> str:
+ """Returns string representation of a :class:`Tidy3dBaseModel`.
+
+ Parameters
+ ----------
+ include_unset : bool = True
+ Whether to include default fields in json string.
+
+ Returns
+ -------
+ str
+ Json-formatted string holding :class:`Tidy3dBaseModel` data.
+ """
+ exclude_unset = not include_unset
+ return self.json(indent=INDENT, exclude_unset=exclude_unset)
diff --git a/tidy3d/components/data.py b/tidy3d/components/data.py
index 84e2ec2b0f..8b21caf182 100644
--- a/tidy3d/components/data.py
+++ b/tidy3d/components/data.py
@@ -20,19 +20,19 @@
def save_string(hdf5_grp, string_key: str, string_value: str) -> None:
- """save a string to an hdf5 group"""
+ """Save a string to an hdf5 group."""
str_type = h5py.special_dtype(vlen=str)
hdf5_grp.create_dataset(string_key, (1,), dtype=str_type)
hdf5_grp[string_key][0] = string_value
def decode_bytes(bytes_dataset) -> str:
- """decode an hdf5 dataset containing bytes to a string"""
+ """Decode an hdf5 dataset containing bytes to a string."""
return bytes_dataset[0].decode("utf-8")
def load_string(hdf5_grp, string_key: str) -> str:
- """load a string from an hdf5 group"""
+ """Load a string from an hdf5 group."""
string_value_bytes = hdf5_grp.get(string_key)
if not string_value_bytes:
return None
@@ -40,7 +40,7 @@ def load_string(hdf5_grp, string_key: str) -> str:
def decode_bytes_array(array_of_bytes: Numpy) -> List[str]:
- """convert numpy array containing bytes to list of strings"""
+ """Convert numpy array containing bytes to list of strings."""
list_of_bytes = array_of_bytes.tolist()
list_of_str = [v.decode("utf-8") for v in list_of_bytes]
return list_of_str
@@ -50,10 +50,10 @@ def decode_bytes_array(array_of_bytes: Numpy) -> List[str]:
class Tidy3dData(Tidy3dBaseModel):
- """base class for data associated with a simulation."""
+ """Base class for data associated with a simulation."""
class Config: # pylint: disable=too-few-public-methods
- """sets config for all Tidy3dBaseModel objects"""
+ """Configuration for all Tidy3dData objects."""
validate_all = True # validate default values too
extra = "allow" # allow extra kwargs not specified in model (like dir=['+', '-'])
@@ -68,22 +68,19 @@ class Config: # pylint: disable=too-few-public-methods
@abstractmethod
def add_to_group(self, hdf5_grp):
- """add data contents to an hdf5 group"""
+ """Add data contents to an hdf5 group."""
@classmethod
@abstractmethod
def load_from_group(cls, hdf5_grp):
- """add data contents to an hdf5 group"""
+ """Load data contents from an hdf5 group."""
class MonitorData(Tidy3dData, ABC):
- """Abstract base class. Stores data.
+ """Abstract base class for objects storing individual data from simulation."""
- Attributes
- ----------
- data : ``Union[xarray.DataArray xarray.Dataset]``
- Representation of the data as an xarray object.
- """
+ values: Union[Array[float], Array[complex]]
+ type: str = None
""" explanation of values
`values` is a numpy array that stores the raw data associated with each
@@ -96,8 +93,7 @@ class MonitorData(Tidy3dData, ABC):
:class:`MonitorData` subclass
"""
- values: Union[Array[float], Array[complex]]
- type: str = None
+ _dims = ()
""" explanation of``_dims``
`_dims` is an attribute of all `MonitorData` objects.
@@ -107,23 +103,24 @@ class MonitorData(Tidy3dData, ABC):
The dims are used to construct xarray objects as it tells the _make_xarray method what
attribute to use for the keys in the `coords` coordinate dictionary.
"""
- _dims = ()
@property
def data(self) -> xr.DataArray:
- """make xarray representation of data
+ """Returns an xarray representation of the montitor data.
Returns
-------
- ``xarray.DataArray``
- Representation of the underlying data using xarray.
+ xarray.DataArray
+ Representation of the monitor data using xarray.
+ For more details refer to `xarray's Documentaton `_.
"""
+
data_dict = self.dict()
coords = {dim: data_dict[dim] for dim in self._dims}
return xr.DataArray(self.values, coords=coords)
def __eq__(self, other) -> bool:
- """check equality against another MonitorData instance
+ """Check equality against another MonitorData instance.
Parameters
----------
@@ -132,14 +129,14 @@ def __eq__(self, other) -> bool:
Returns
-------
- ``bool``
+ bool
Whether the other :class:`MonitorData` instance has the same data.
"""
assert isinstance(other, MonitorData), "can only check eqality on two monitor data objects"
return np.all(self.values == self.values)
def add_to_group(self, hdf5_grp) -> None:
- """add data contents to an hdf5 group"""
+ """Add data contents to an hdf5 group."""
# save the type information of MonitorData to the group
save_string(hdf5_grp, "type", self.type)
@@ -151,7 +148,7 @@ def add_to_group(self, hdf5_grp) -> None:
@classmethod
def load_from_group(cls, hdf5_grp):
- """load the solver data dict for a specific monitor into a MonitorData instance"""
+ """Load Monitor data instance from an hdf5 group."""
# kwargs that gets passed to MonitorData.__init__() to make new MonitorData
kwargs = {}
@@ -177,12 +174,12 @@ def load_from_group(cls, hdf5_grp):
class CollectionData(Tidy3dData):
- """Abstract base class. Stores collection of data with similar dimensions.
+ """Abstract base class. Stores a collection of data with same dimension types (such as field).
Parameters
----------
- data_dict : ``{str : :class:`MonitorData`}
- mapping of field name to corresponding :class:`MonitorData`.
+ data_dict : Dict[str, :class:`MonitorData`]
+ Mapping of collection member name to corresponding :class:`MonitorData`.
"""
data_dict: Dict[str, MonitorData]
@@ -195,8 +192,9 @@ def data(self) -> xr.Dataset:
Returns
-------
- ```xarray.Dataset `__``
+ xarray.Dataset
Representation of the underlying data using xarray.
+ For more details refer to `xarray's Documentaton `_.
"""
data_arrays = {name: arr.data for name, arr in self.data_dict.items()}
@@ -204,7 +202,7 @@ def data(self) -> xr.Dataset:
return xr.Dataset(data_arrays)
def __eq__(self, other):
- """check for equality against other :class:`CollectionData` object."""
+ """Check for equality against other :class:`CollectionData` object."""
# same keys?
if not all(k in other.data_dict.keys() for k in self.data_dict.keys()):
@@ -218,7 +216,7 @@ def __eq__(self, other):
return True
def add_to_group(self, hdf5_grp) -> None:
- """add data from a :class:`CollectionData` to an hdf5 group ."""
+ """Add data from a :class:`CollectionData` to an hdf5 group ."""
# put collection's type information into the group
save_string(hdf5_grp, "type", self.type)
@@ -230,7 +228,7 @@ def add_to_group(self, hdf5_grp) -> None:
@classmethod
def load_from_group(cls, hdf5_grp):
- """load a :class:`CollectionData` from hdf5 group containing data."""
+ """Load a :class:`CollectionData` from hdf5 group containing data."""
data_dict = {}
for data_name, data_value in hdf5_grp.items():
@@ -245,68 +243,55 @@ def load_from_group(cls, hdf5_grp):
return cls(data_dict=data_dict)
-""" The following
-are abstract classes that separate the :class:`MonitorData` instances into
- different types depending on what they store.
- They can be useful for keeping argument types and validations separated.
- For example, monitors that should always be defined on planar geometries can have an
- ``_assert_plane()`` validation in the abstract base class ``PlanarData``.
- This way, ``_assert_plane()`` will always be used if we add more ``PlanarData`` objects in
- the future.
- This organization is also useful when doing conditions based on monitor / data type.
- For example, instead of
- ``if isinstance(mon_data, (FieldData, FieldTimeData)):`` we can simply do
- ``if isinstance(mon_data, AbstractFieldData)`` and this will generalize if we add more
- ``AbstractFieldData`` objects in the future.
-"""
+""" Classes of Monitor Data """
class FreqData(MonitorData, ABC):
- """Stores frequency-domain data using an ``f`` attribute for frequency (Hz)."""
+ """Stores frequency-domain data using an ``f`` dimension for frequency in Hz."""
f: Array[float]
class TimeData(MonitorData, ABC):
- """Stores time-domain data using a ``t`` attribute for time (sec)."""
+ """Stores time-domain data using a ``t`` attribute for time in seconds."""
t: Array[float]
class AbstractScalarFieldData(MonitorData, ABC):
- """Stores a single field as a functio of x,y,z and sampler"""
+ """Stores a single, scalar field as a function of spatial coordinates x,y,z."""
x: Array[float]
y: Array[float]
z: Array[float]
- values: Union[Array[complex], Array[float]]
+ # values: Union[Array[complex], Array[float]]
class PlanarData(MonitorData, ABC):
- """Stores data that is constrained to the plane."""
+ """Stores data that must be found via a planar monitor."""
class AbstractFluxData(PlanarData, ABC):
- """Stores electromagnetic flux through a planar :class:`Monitor`"""
+ """Stores electromagnetic flux through a plane."""
""" usable monitors """
class ScalarFieldData(AbstractScalarFieldData, FreqData):
- """stores a single scalar field in frequency-domain
+ """Stores a single scalar field in frequency-domain.
Parameters
----------
- x : ``numpy.ndarray``
+ x : numpy.ndarray
Data coordinates in x direction (um).
- y : ``numpy.ndarray``
+ y : numpy.ndarray
Data coordinates in y direction (um).
- z : ``numpy.ndarray``
+ z : numpy.ndarray
Data coordinates in z direction (um).
- f : ``numpy.ndarray``
+ f : numpy.ndarray
Frequency coordinates (Hz).
- values : ``numpy.ndarray``
+ values : numpy.ndarray
Complex-valued array of shape ``(len(x), len(y), len(z), len(f))`` storing field values.
Example
@@ -330,15 +315,15 @@ class ScalarFieldTimeData(AbstractScalarFieldData, TimeData):
Parameters
----------
- x : ``numpy.ndarray``
+ x : numpy.ndarray
Data coordinates in x direction (um).
- y : ``numpy.ndarray``
+ y : numpy.ndarray
Data coordinates in y direction (um).
- z : ``numpy.ndarray``
+ z : numpy.ndarray
Data coordinates in z direction (um).
- t : ``numpy.ndarray``
+ t : numpy.ndarray
Time coordinates (sec).
- values : ``numpy.ndarray``
+ values : numpy.ndarray
Real-valued array of shape ``(len(x), len(y), len(z), len(t))`` storing field values.
Example
@@ -358,11 +343,12 @@ class ScalarFieldTimeData(AbstractScalarFieldData, TimeData):
class FieldData(CollectionData):
- """Stores a collectio of scalar field quantities as a function of x, y, and z.
+ """Stores a collection of scalar fields
+ from a :class:`FieldMonitor` or :class:`FieldTimeMonitor`.
Parameters
----------
- data_dict : ``{str : Union[:class:`ScalarFieldData`, :class:`ScalarFieldTimeData`]}
+ data_dict : Dict[str, :class:`ScalarFieldData`] or Dict[str, :class:`ScalarFieldTimeData`]
Mapping of field name to its scalar field data.
Example
@@ -380,18 +366,18 @@ class FieldData(CollectionData):
>>> data_t = FieldData(data_dict={'Ex': field_t, 'Ey': field_t})
"""
- data_dict: Dict[str, Union[ScalarFieldData, ScalarFieldTimeData]]
+ data_dict: Union[Dict[str, ScalarFieldData], Dict[str, ScalarFieldTimeData]]
type: Literal["FieldData"] = "FieldData"
class FluxData(AbstractFluxData, FreqData):
- """Stores power flux data through a planar :class:`FluxMonitor`.
+ """Stores frequency-domain power flux data from a :class:`FluxMonitor`.
Parameters
----------
- f : ``numpy.ndarray``
+ f : numpy.ndarray
Frequency coordinates (Hz).
- values : ``numpy.ndarray``
+ values : numpy.ndarray
Complex-valued array of shape ``(len(f),)`` storing field values.
Example
@@ -409,13 +395,13 @@ class FluxData(AbstractFluxData, FreqData):
class FluxTimeData(AbstractFluxData, TimeData):
- """Stores power flux data through a planar :class:`FluxTimeMonitor`
+ """Stores time-domain power flux data from a :class:`FluxTimeMonitor`.
Parameters
----------
- t : ``numpy.ndarray``
+ t : numpy.ndarray
Time coordinates (sec).
- values : ``numpy.ndarray``
+ values : numpy.ndarray
Real-valued array of shape ``(len(t),)`` storing field values.
Example
@@ -437,15 +423,16 @@ class ModeData(PlanarData, FreqData):
Parameters
----------
- direction : ``[Literal["+", "-"]]``
+ direction : List[str]
List of strings corresponding to the mode propagation direction.
- mode_index : ``numpy.ndarray``
+ Allowed elements are ``'+'`` and ``'-'``.
+ mode_index : numpy.ndarray
Array of integer indices into the original monitor's :attr:`ModeMonitor.modes`.
- f : ``numpy.ndarray``
+ f : numpy.ndarray
Frequency coordinates (Hz).
- values : ``numpy.ndarray``
+ values : numpy.ndarray
Complex-valued array of mode amplitude values
- with shape``values.shape=(len(direction), len(mode_index), len(f))``
+ with shape ``values.shape=(len(direction), len(mode_index), len(f))``.
Example
-------
@@ -481,11 +468,11 @@ class SimulationData(Tidy3dBaseModel):
Parameters
----------
simulation : :class:`Simulation`
- Original :class:`Simulation`.
- monitor_data : ``Dict[str, :class:`Tidy3dData`]``
+ Original :class:`Simulation` that was run to create data.
+ monitor_data : Dict[str, :class:`Tidy3dData`]
Mapping of monitor name to :class:`Tidy3dData` intance.
- log_string : ``str``, optional
- string containing the log from server.
+ log_string : str = None
+ A string containing the log information from the simulation run.
"""
simulation: Simulation
@@ -494,21 +481,21 @@ class SimulationData(Tidy3dBaseModel):
@property
def log(self):
- """prints the server-side log."""
+ """Prints the server-side log."""
print(self.log_string if self.log_string else "no log stored")
def __getitem__(self, monitor_name: str) -> Union[xr.DataArray, xr.Dataset]:
- """get the :class:`MonitorData` xarray representation by name (``sim_data[monitor_name]``).
+ """Get the :class:`MonitorData` xarray representation by name (``sim_data[monitor_name]``).
Parameters
----------
monitor_name : ``str``
- Name of :class:`Monitor` to return data for.
+ Name of the :class:`Monitor` to return data for.
Returns
-------
- ``Union[xarray.DataArray``, xarray.Dataset]``
- The ``xarray`` representation of the data.
+ xarray.DataArray or xarray.Dataset
+ The xarray representation of the data.
"""
monitor_data = self.monitor_data.get(monitor_name)
if not monitor_data:
@@ -590,8 +577,8 @@ def export(self, fname: str) -> None:
Parameters
----------
- fname : ``str``
- Path to data file (including filename).
+ fname : str
+ Path to .hdf5 data file (including filename).
"""
with h5py.File(fname, "a") as f_handle:
@@ -616,8 +603,8 @@ def load(cls, fname: str):
Parameters
----------
- fname : ``str``
- Path to data file (including filename).
+ fname : str
+ Path to .hdf5 data file (including filename).
Returns
-------
@@ -651,7 +638,7 @@ def load(cls, fname: str):
)
def __eq__(self, other):
- """check equality against another SimulationData instance
+ """Check equality against another :class:`SimulationData` instance.
Parameters
----------
diff --git a/tidy3d/components/geometry.py b/tidy3d/components/geometry.py
index 25aaa7009f..55d5ae01c1 100644
--- a/tidy3d/components/geometry.py
+++ b/tidy3d/components/geometry.py
@@ -1,4 +1,4 @@
-"""defines objects in space"""
+"""Defines spatial extent of objects."""
from abc import ABC, abstractmethod
from typing import List, Tuple, Union, Any
@@ -14,7 +14,11 @@
from .types import Vertices, Ax, Shapely
from .viz import add_ax_if_none
-PLOT_BUFFER = 0.3 # add this around extents of .visualize()
+# add this around extents of plots
+PLOT_BUFFER = 0.3
+
+
+# TODO: GDS file importing.
class Geometry(Tidy3dBaseModel, ABC):
@@ -22,24 +26,22 @@ class Geometry(Tidy3dBaseModel, ABC):
center: Coordinate = (0.0, 0.0, 0.0)
- """ volume and intersections """
-
def inside(self, x, y, z) -> bool:
- """Returns true if point ``(x,y,z)`` inside volume of geometry.
+ """Returns ``True`` if point ``(x,y,z)`` is inside volume of :class:`Geometry`.
Parameters
----------
- x : ``float``
+ x : float
Position of point in x direction.
- y : ``float``
+ y : float
Position of point in y direction.
- z : ``float``
+ z : float
Position of point in z direction.
Returns
-------
- ``bool``
- Whether point ``(x,y,z)`` is inside geometry.
+ bool
+ True if point ``(x,y,z)`` is inside geometry.
"""
shapes_intersect = self.intersections(z=z)
loc = Point(x, y)
@@ -47,25 +49,26 @@ def inside(self, x, y, z) -> bool:
@abstractmethod
def intersections(self, x: float = None, y: float = None, z: float = None) -> List[Shapely]:
- """Returns list of shapely geoemtries at plane specified by one non-None value of x,y,z
+ """Returns list of shapely geoemtries at plane specified by one non-None value of x,y,z.
Parameters
----------
- x : ``float``, optional
- Position of plane in x direction.
- y : ``float``, optional
- Position of plane in y direction.
- z : ``float``, optional
- Position of plane in z direction.
+ x : float = None
+ Position of plane in x direction, only one of x,y,z can be specified to define plane.
+ y : float = None
+ Position of plane in y direction, only one of x,y,z can be specified to define plane.
+ z : float = None
+ Position of plane in z direction, only one of x,y,z can be specified to define plane.
Returns
-------
- ``[shapely.geometry.base.BaseGeometry]``
+ List[shapely.geometry.base.BaseGeometry]
List of 2D shapes that intersect plane.
+ For more details refer to `Shapely's Documentaton `_.
"""
def intersects(self, other) -> bool:
- """Determines whether two :class:`Geometry` have intersecting `.bounds`.
+ """Returns ``True`` if two :class:`Geometry` have intersecting `.bounds`.
Parameters
----------
@@ -74,7 +77,7 @@ def intersects(self, other) -> bool:
Returns
-------
- ``bool``
+ bool
Whether the rectangular bounding boxes of the two geometries intersect.
"""
@@ -95,30 +98,28 @@ def intersects_plane(self, x: float = None, y: float = None, z: float = None) ->
Parameters
----------
- x : ``float``, optional
- Position of plane in x direction.
- y : ``float``, optional
- Position of plane in y direction.
- z : ``float``, optional
- Position of plane in z direction.
+ x : float = None
+ Position of plane in x direction, only one of x,y,z can be specified to define plane.
+ y : float = None
+ Position of plane in y direction, only one of x,y,z can be specified to define plane.
+ z : float = None
+ Position of plane in z direction, only one of x,y,z can be specified to define plane.
Returns
-------
- ``bool``
- Whether this geometry intersects the plane
+ bool
+ Whether this geometry intersects the plane.
"""
intersections = self.intersections(x=x, y=y, z=z)
return bool(intersections)
- """ Bounding boxes """
-
@property
def bounds(self) -> Bound: # pylint:disable=too-many-locals
- """Returns bounding box for geometry.
+ """Returns bounding box min and max coordinates..
Returns
-------
- ``(float, float, float), (float, float float)``
+ Tuple[float, float, float], Tuple[float, float float]
Min and max bounds packaged as ``(minx, miny, minz), (maxx, maxy, maxz)``.
"""
@@ -139,7 +140,7 @@ def bounds(self) -> Bound: # pylint:disable=too-many-locals
@property
def bounding_box(self):
- """Returns :class:`Box` representation of ``self.bounds``.
+ """Returns :class:`Box` representation of the bounding box of a :class:`Geometry`.
Returns
-------
@@ -156,26 +157,24 @@ def bounding_box(self):
return Box(center=(x0, y0, z0), size=(Lx, Ly, Lz))
def _pop_bounds(self, axis: Axis) -> Tuple[Coordinate2D, Tuple[Coordinate2D, Coordinate2D]]:
- """Returns min and max bounds in plane normal to and tangential to `axis`
+ """Returns min and max bounds in plane normal to and tangential to ``axis``.
Parameters
----------
- axis : ``int``
+ axis : int
Integer index into 'xyz' (0,1,2).
Returns
-------
- ``(float, float), ((float, float), (float, float))``
+ Tuple[float, float], Tuple[Tuple[float, float], Tuple[float, float]]
Bounds along axis and a tuple of bounds in the ordered planar coordinates.
- Packed as ``(zmin, zmax), ((xmin, ymin), (xmax, ymax))``
+ Packed as ``(zmin, zmax), ((xmin, ymin), (xmax, ymax))``.
"""
b_min, b_max = self.bounds
zmin, (xmin, ymin) = self.pop_axis(b_min, axis=axis)
zmax, (xmax, ymax) = self.pop_axis(b_max, axis=axis)
return (zmin, zmax), ((xmin, ymin), (xmax, ymax))
- """ Plotting """
-
@add_ax_if_none
def plot(
self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **patch_kwargs
@@ -184,43 +183,51 @@ def plot(
Parameters
----------
- x : ``float``, optional
- Position of plane in x direction.
- y : ``float``, optional
- Position of plane in y direction.
- z : ``float``, optional
- Position of plane in z direction.
- ax : ``matplotlib.axes._subplots.Axes``, optional
- matplotlib axes to plot on, if not specified, one is created.
+ x : float = None
+ Position of plane in x direction, only one of x,y,z can be specified to define plane.
+ y : float = None
+ Position of plane in y direction, only one of x,y,z can be specified to define plane.
+ z : float = None
+ Position of plane in z direction, only one of x,y,z can be specified to define plane.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
**patch_kwargs
- Optional keyword arguments passed to ``add_artist(patch, **patch_kwargs)``.
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_. #pylint:disable=line-too-long
Returns
-------
- ``matplotlib.axes._subplots.Axes``
+ matplotlib.axes._subplots.Axes
The supplied or created matplotlib axes.
"""
+
+ # find shapes that intersect self at plane
axis, position = self._parse_xyz_kwargs(x=x, y=y, z=z)
shapes_intersect = self.intersections(x=x, y=y, z=z)
+
+ # for each intersection, plot the shape
for shape in shapes_intersect:
patch = PolygonPatch(shape, **patch_kwargs)
ax.add_artist(patch)
+
+ # clean up the axis display
ax = self.add_ax_labels_lims(axis=axis, ax=ax)
ax.set_aspect("equal")
ax.set_title(f"cross section at {'xyz'[axis]}={position:.2f}")
return ax
def _get_plot_labels(self, axis: Axis) -> Tuple[str, str]:
- """returns correct x and y axis labels for cross section plots
+ """Returns planar coordinate x and y axis labels for cross section plots.
Parameters
----------
- axis : ``int``
+ axis : int
Integer index into 'xyz' (0,1,2).
Returns
-------
- ``(str, str)``
+ str, str
Labels of plot, packaged as ``(xlabel, ylabel)``.
"""
_, (xlabel, ylabel) = self.pop_axis("xyz", axis=axis)
@@ -229,18 +236,18 @@ def _get_plot_labels(self, axis: Axis) -> Tuple[str, str]:
def _get_plot_limits(
self, axis: Axis, buffer: float = PLOT_BUFFER
) -> Tuple[Coordinate2D, Coordinate2D]:
- """gets (xmin, ymin, xmax, ymax) limits for cross section plots
+ """Gets planar coordinate limits for cross section plots.
Parameters
----------
- axis : ``int``
+ axis : int
Integer index into 'xyz' (0,1,2).
- buffer : ``float``, optional
- Amount of space to place around the limits
+ buffer : float = 0.3
+ Amount of space to add around the limits on the + and - sides.
Returns
-------
- ``(float, float), (float, float)``
+ Tuple[float, float], Tuple[float, float]
The x and y plot limits, packed as ``(xmin, xmax), (ymin, ymax)``.
"""
_, ((xmin, ymin), (xmax, ymax)) = self._pop_bounds(axis=axis)
@@ -251,16 +258,16 @@ def add_ax_labels_lims(self, axis: Axis, ax: Ax, buffer: float = PLOT_BUFFER) ->
Parameters
----------
- axis : ``int``
+ axis : int
Integer index into 'xyz' (0,1,2).
- ax : ``matplotlib.axes._subplots.Axes``
- matplotlib axes to add labels and limits on.
- buffer : ``float``, optional
- Amount of space to place around the limits
+ ax : matplotlib.axes._subplots.Axes
+ Matplotlib axes to add labels and limits on.
+ buffer : float = 0.3
+ Amount of space to place around the limits on the + and - sides.
Returns
-------
- ``matplotlib.axes._subplots.Axes``
+ matplotlib.axes._subplots.Axes
The supplied or created matplotlib axes.
"""
xlabel, ylabel = self._get_plot_labels(axis=axis)
@@ -271,24 +278,23 @@ def add_ax_labels_lims(self, axis: Axis, ax: Ax, buffer: float = PLOT_BUFFER) ->
ax.set_ylabel(ylabel)
return ax
- """ Utility """
-
@staticmethod
def pop_axis(coord: Tuple[Any, Any, Any], axis: int) -> Tuple[Any, Tuple[Any, Any]]:
"""Separates coordinate at ``axis`` index from coordinates on the plane tangent to ``axis``.
Parameters
----------
- coord : ``(Any, Any, Any)``
+ coord : Tuple[Any, Any, Any]
Tuple of three values in original coordinate system.
- axis : ``int``
+ axis : int
Integer index into 'xyz' (0,1,2).
Returns
-------
- ``Any, (Any, Any)``
- The coordinates separated into that in the axis and those in the planar dimensions.
- Packaged as ``axis_coord, (planar_coord1, planar_coord2)``.
+ Any, Tuple[Any, Any]
+ The input coordinates are separated into the one along the axis provided
+ and the two on the planar coordinates,
+ like ``axis_coord, (planar_coord1, planar_coord2)``.
"""
plane_vals = list(coord)
axis_val = plane_vals.pop(axis)
@@ -296,21 +302,21 @@ def pop_axis(coord: Tuple[Any, Any, Any], axis: int) -> Tuple[Any, Tuple[Any, An
@staticmethod
def unpop_axis(ax_coord: Any, plane_coords: Tuple[Any, Any], axis: int) -> Tuple[Any, Any, Any]:
- """Combine coordinate from `axis` index with coordinates on the plane tangent to `axis`.
+ """Combine coordinate along axis with coordinates on the plane tangent to the axis.
Parameters
----------
- ax_coord : ``Any``
- Value along ``axis`` direction.
- plane_coords : ``(Any, Any)``
+ ax_coord : Any
+ Value along axis direction.
+ plane_coords : Tuple[Any, Any]
Values along ordered planar directions.
- axis : ``int``
+ axis : int
Integer index into 'xyz' (0,1,2).
Returns
-------
- ``(Any, Any, Any)``
- The three values in original coordinate system.
+ Tuple[Any, Any, Any]
+ The three values in the xyz coordinate system.
"""
coords = list(plane_coords)
coords.insert(axis, ax_coord)
@@ -318,21 +324,21 @@ def unpop_axis(ax_coord: Any, plane_coords: Tuple[Any, Any], axis: int) -> Tuple
@staticmethod
def _parse_xyz_kwargs(**xyz) -> Tuple[Axis, float]:
- """Turns x,y,z kwargs into plane axis and position.
+ """Turns x,y,z kwargs into index of the normal axis and position along that axis.
Parameters
----------
- x : ``float``
- Position of point in x direction.
- y : ``float``
- Position of point in y direction.
- z : ``float``
- Position of point in z direction.
+ x : float = None
+ Position of plane in x direction, only one of x,y,z can be specified to define plane.
+ y : float = None
+ Position of plane in y direction, only one of x,y,z can be specified to define plane.
+ z : float = None
+ Position of plane in z direction, only one of x,y,z can be specified to define plane.
Returns
-------
- ``(int, float)``
- Index into xyz axis (0,1,2) and position along axis.
+ int, float
+ Index into xyz axis (0,1,2) and position along that axis.
"""
xyz_filtered = {k: v for k, v in xyz.items() if v is not None}
assert len(xyz_filtered) == 1, "exatly one kwarg in [x,y,z] must be specified."
@@ -341,7 +347,7 @@ def _parse_xyz_kwargs(**xyz) -> Tuple[Axis, float]:
return axis, position
-""" abstract subclasses """
+""" Abstract subclasses """
class Planar(Geometry, ABC):
@@ -351,21 +357,22 @@ class Planar(Geometry, ABC):
length: pydantic.NonNegativeFloat = None
def intersections(self, x: float = None, y: float = None, z: float = None):
- """returns shapely geometry at plane specified by one non None value of x,y,z
+ """Returns shapely geometry at plane specified by one non None value of x,y,z.
Parameters
----------
- x : ``float``
- Position of point in x direction.
- y : ``float``
- Position of point in y direction.
- z : ``float``
- Position of point in z direction.
+ x : float
+ Position of plane in x direction, only one of x,y,z can be specified to define plane.
+ y : float
+ Position of plane in y direction, only one of x,y,z can be specified to define plane.
+ z : float
+ Position of plane in z direction, only one of x,y,z can be specified to define plane.
Returns
-------
- ``[shapely.geometry.base.BaseGeometry]``
+ List[shapely.geometry.base.BaseGeometry]
List of 2D shapes that intersect plane.
+ For more details refer to `Shapely's Documentaton `_.
"""
axis, position = self._parse_xyz_kwargs(x=x, y=y, z=z)
if axis == self.axis:
@@ -377,31 +384,31 @@ def intersections(self, x: float = None, y: float = None, z: float = None):
@abstractmethod
def _intersections_normal(self) -> list:
- """Find shapely geometries intersecting planar geometry with axis normal to slab
+ """Find shapely geometries intersecting planar geometry with axis normal to slab.
Returns
-------
- ``list[shapely.geometry.base.BaseGeometries]``
- List containing the shapely representation of the normal cross section of the planar
- geometry.
+ List[shapely.geometry.base.BaseGeometry]
+ List of 2D shapes that intersect plane.
+ For more details refer to `Shapely's Documentaton `_.
"""
@abstractmethod
def _intersections_side(self, position: float, axis: Axis) -> list:
- """Find shapely geometries intersecting planar geometry with axis orthogonal to plane
+ """Find shapely geometries intersecting planar geometry with axis orthogonal to plane.
Parameters
----------
- position : ``float``
- Position along ``axis``
- axis : ``int``
+ position : float
+ Position along axis.
+ axis : int
Integer index into 'xyz' (0,1,2).
Returns
-------
- ``list[shapely.geometry.base.BaseGeometries]``
- List of 2D geometries intersecting with planar geometry at ``position`` along side
- ``axis``.
+ List[shapely.geometry.base.BaseGeometry]
+ List of 2D shapes that intersect plane.
+ For more details refer to `Shapely's Documentaton `_.
"""
@property
@@ -410,7 +417,7 @@ def bounds(self):
Returns
-------
- ``(float, float, float), (float, float float)``
+ Tuple[float, float, float], Tuple[float, float float]
Min and max bounds packaged as ``(minx, miny, minz), (maxx, maxy, maxz)``.
"""
z0, _ = self.pop_axis(self.center, axis=self.axis)
@@ -430,11 +437,11 @@ def _order_by_axis(self, plane_val: Any, axis_val: Any, axis: int) -> Tuple[Any,
Parameters
----------
- plane_val : ``Any``
+ plane_val : Any
The value in the planar coordinate.
- axis_val : ``Any``
+ axis_val : Any
The value in the ``axis`` coordinate.
- axis : ``int``
+ axis : int
Integer index into the structure's planar axis.
Returns
@@ -454,19 +461,19 @@ class Circular(Geometry):
radius: pydantic.NonNegativeFloat
def _intersect_dist(self, position, z0) -> float:
- """distance between points on circle at z=position where center of circle at z=z0
+ """Distance between points on circle at z=position where center of circle at z=z0.
Parameters
----------
- position : ``float``
+ position : float
position along z.
- z0 : ``float``
+ z0 : float
center of circle in z.
Returns
-------
- ``float``
- distance between points on the circle intersecting z=z, if no points, ``None``.
+ float
+ Distance between points on the circle intersecting z=z, if no points, ``None``.
"""
dz = np.abs(z0 - position)
if dz > self.radius:
@@ -483,9 +490,9 @@ class Box(Geometry):
Parameters
----------
- center : ``(float, float, float)``
- center of box in x,y,z. Defaults to ``(0,0,0)``.
- size : ``(float, float, float)``
+ center : Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ Center of box in x,y,z.
+ size : Tuple[float, float, float]
Size of box in x,y,z.
Example
@@ -496,21 +503,22 @@ class Box(Geometry):
size: Size
def intersections(self, x: float = None, y: float = None, z: float = None):
- """returns shapely geoemtry at plane specified by one non None value of x,y,z
+ """Returns shapely geometry at plane specified by one non None value of x,y,z.
Parameters
----------
- x : ``float``
- Position of point in x direction.
- y : ``float``
- Position of point in y direction.
- z : ``float``
- Position of point in z direction.
+ x : float = None
+ Position of plane in x direction, only one of x,y,z can be specified to define plane.
+ y : float = None
+ Position of plane in y direction, only one of x,y,z can be specified to define plane.
+ z : float = None
+ Position of plane in z direction, only one of x,y,z can be specified to define plane.
Returns
-------
- ``[shapely.geometry.base.BaseGeometry]``
+ List[shapely.geometry.base.BaseGeometry]
List of 2D shapes that intersect plane.
+ For more details refer to `Shapely's Documentaton `_.
"""
axis, position = self._parse_xyz_kwargs(x=x, y=y, z=z)
z0, (x0, y0) = self.pop_axis(self.center, axis=axis)
@@ -521,20 +529,20 @@ def intersections(self, x: float = None, y: float = None, z: float = None):
return [box(minx=x0 - Lx / 2, miny=y0 - Ly / 2, maxx=x0 + Lx / 2, maxy=y0 + Ly / 2)]
def inside(self, x, y, z) -> bool:
- """Returns true if point ``(x,y,z)`` inside volume of geometry.
+ """Returns ``True`` if point ``(x,y,z)`` inside volume of geometry.
Parameters
----------
- x : ``float``
+ x : float
Position of point in x direction.
- y : ``float``
+ y : float
Position of point in y direction.
- z : ``float``
+ z : float
Position of point in z direction.
Returns
-------
- ``bool``
+ bool
Whether point ``(x,y,z)`` is inside geometry.
"""
x0, y0, z0 = self.center
@@ -546,11 +554,11 @@ def inside(self, x, y, z) -> bool:
@property
def bounds(self) -> Bound:
- """Returns bounding box for geometry
+ """Returns bounding box min and max coordinates.
Returns
-------
- ``(float, float, float), (float, float float)``
+ Tuple[float, float, float], Tuple[float, float float]
Min and max bounds packaged as ``(minx, miny, minz), (maxx, maxy, maxz)``.
"""
size = self.size
@@ -561,7 +569,7 @@ def bounds(self) -> Bound:
@property
def geometry(self):
- """:class:`Box` representation of self (used for subclasses of Box)
+ """:class:`Box` representation of self (used for subclasses of Box).
Returns
-------
@@ -572,13 +580,13 @@ def geometry(self):
class Sphere(Circular):
- """Sphere geometry.
+ """Spherical geometry.
Parameters
----------
- center : ``(float, float, float)``
- center of sphere in x,y,z. Defaults to ``(0,0,0)``.
- radius : ``float``
+ center : Tuple[float, float, float] = 0.0, 0.0, 0.0
+ Center of sphere in x,y,z.
+ radius : float
Radius of sphere.
Example
@@ -589,15 +597,15 @@ class Sphere(Circular):
type: Literal["Sphere"] = "Sphere"
def inside(self, x, y, z) -> bool:
- """Returns true if point ``(x,y,z)`` inside volume of geometry.
+ """Returns True if point ``(x,y,z)`` inside volume of geometry.
Parameters
----------
- x : ``float``
+ x : float
Position of point in x direction.
- y : ``float``
+ y : float
Position of point in y direction.
- z : ``float``
+ z : float
Position of point in z direction.
Returns
@@ -612,21 +620,22 @@ def inside(self, x, y, z) -> bool:
return (dist_x ** 2 + dist_y ** 2 + dist_z ** 2) <= (self.radius ** 2)
def intersections(self, x: float = None, y: float = None, z: float = None):
- """returns shapely geoemtry at plane specified by one non None value of x,y,z
+ """Returns shapely geometry at plane specified by one non None value of x,y,z.
Parameters
----------
- x : ``float``, optional
- Description
- y : ``float``, optional
- Description
- z : ``float``, optional
- Description
+ x : float = None
+ Position of plane in x direction, only one of x,y,z can be specified to define plane.
+ y : float = None
+ Position of plane in x direction, only one of x,y,z can be specified to define plane.
+ z : float = None
+ Position of plane in x direction, only one of x,y,z can be specified to define plane.
Returns
-------
- ``[shapely.geometry.base.BaseGeometry]``
+ List[shapely.geometry.base.BaseGeometry]
List of 2D shapes that intersect plane.
+ For more details refer to `Shapely's Documentaton `_.
"""
axis, position = self._parse_xyz_kwargs(x=x, y=y, z=z)
z0, (x0, y0) = self.pop_axis(self.center, axis=axis)
@@ -637,11 +646,11 @@ def intersections(self, x: float = None, y: float = None, z: float = None):
@property
def bounds(self):
- """Returns bounding box for geometry
+ """Returns bounding box min and max coordinates.
Returns
-------
- ``(float, float, float), (float, float float)``
+ Tuple[float, float, float], Tuple[float, float, float]
Min and max bounds packaged as ``(minx, miny, minz), (maxx, maxy, maxz)``.
"""
coord_min = tuple(c - self.radius for c in self.center)
@@ -650,18 +659,18 @@ def bounds(self):
class Cylinder(Circular, Planar):
- """Cylinder geometry.
+ """Cylindrical geometry.
Parameters
----------
- center : ``(float, float, float)``
- center of cylinder in x,y,z. Defaults to ``(0,0,0)``.
- radius : ``float``
+ center : Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ center of cylinder in x,y,z.
+ radius : float
Radius of cylinder.
- length : ``float``
- Length of sphere along axis.
- axis : ``int``
- Integer index into the cylinder's ``length`` axis (0,1,2) -> (x,y,z)
+ length : float
+ Length of cylinder along axis.
+ axis : int
+ Cylinder's length axis index (0, 1, 2) -> (x, y, z)
Example
-------
@@ -672,31 +681,32 @@ class Cylinder(Circular, Planar):
type: Literal["Cylinder"] = "Cylinder"
def _intersections_normal(self):
- """Find shapely geometries intersecting cylindrical geometry with axis normal to slab
+ """Find shapely geometries intersecting cylindrical geometry with axis normal to slab.
Returns
-------
- ``list[shapely.geometry.base.BaseGeometries]``
- List containing the shapely representation of the polygon.
+ List[shapely.geometry.base.BaseGeometry]
+ List of 2D shapes that intersect plane.
+ For more details refer to `Shapely's Documentaton `_.
"""
_, (x0, y0) = self.pop_axis(self.center, axis=self.axis)
return [Point(x0, y0).buffer(self.radius)]
def _intersections_side(self, position, axis):
- """Find shapely geometries intersecting cylindrical geometry with axis orthogonal to length
+ """Find shapely geometries intersecting cylindrical geometry with axis orthogonal to length.
Parameters
----------
- position : ``float``
- Position along ``axis``
- axis : ``int``
- Integer index into 'xyz' (0,1,2).
+ position : float
+ Position along axis direction.
+ axis : int
+ Integer index into 'xyz' (0, 1, 2).
Returns
-------
- ``list[shapely.geometry.base.BaseGeometries]``
- List of 2D geometries intersecting with cylinder geometry at ``position`` along side
- ``axis``.
+ List[shapely.geometry.base.BaseGeometry]
+ List of 2D shapes that intersect plane.
+ For more details refer to `Shapely's Documentaton `_.
"""
z0_axis, _ = self.pop_axis(self.center, axis=self.axis)
intersect_dist = self._intersect_dist(position, z0_axis)
@@ -704,30 +714,29 @@ def _intersections_side(self, position, axis):
return []
Lx, Ly = self._order_by_axis(plane_val=intersect_dist, axis_val=self.length, axis=axis)
_, (x0_plot_plane, y0_plot_plane) = self.pop_axis(self.center, axis=axis)
- return [
- box(
- minx=x0_plot_plane - Lx / 2,
- miny=y0_plot_plane - Ly / 2,
- maxx=x0_plot_plane + Lx / 2,
- maxy=y0_plot_plane + Ly / 2,
- )
- ]
+ int_box = box(
+ minx=x0_plot_plane - Lx / 2,
+ miny=y0_plot_plane - Ly / 2,
+ maxx=x0_plot_plane + Lx / 2,
+ maxy=y0_plot_plane + Ly / 2,
+ )
+ return [int_box]
def inside(self, x, y, z) -> bool:
- """Returns true if point ``(x,y,z)`` inside volume of geometry.
+ """Returns True if point ``(x,y,z)`` inside volume of geometry.
Parameters
----------
- x : ``float``
+ x : float
Position of point in x direction.
- y : ``float``
+ y : float
Position of point in y direction.
- z : ``float``
+ z : float
Position of point in z direction.
Returns
-------
- ``bool``
+ bool
Whether point ``(x,y,z)`` is inside geometry.
"""
z0, (x0, y0) = self.pop_axis(self.center, axis=self.axis)
@@ -740,11 +749,11 @@ def inside(self, x, y, z) -> bool:
@property
def _bounds(self):
- """Returns bounding box for geometry
+ """Returns bounding box min and max coordinates.
Returns
-------
- ``(float, float, float), (float, float float)``
+ Tuple[float, float, float], Tuple[float, float, float]
Min and max bounds packaged as ``(minx, miny, minz), (maxx, maxy, maxz)``.
"""
coord_min = list(c - self.radius for c in self.center)
@@ -755,16 +764,16 @@ def _bounds(self):
class PolySlab(Planar):
- """Polygon with constant thickness along 3rd axis.
+ """Polygon with constant thickness (slab) along axis direction.
Parameters
----------
- vertices : ``[(float, float)]``
- List of (x,y) vertices defining the polygon face.
- axis : ``int``
+ vertices : List[Tuple[float, float]]
+ List of vertices defining the polygon face along dimensions parallel to slab normal axis.
+ axis : int
Integer index into the polygon's slab axis. (0,1,2) -> (x,y,z)
- slab_bounds: ``(float, float)``
- Minimum and maximum position in slab axis.
+ slab_bounds: Tuple[float, float]
+ Minimum and maximum positions of the slab along axis.
Example
-------
@@ -793,20 +802,20 @@ def set_center(cls, val, values):
return val
def inside(self, x, y, z) -> bool: # pylint:disable=too-many-locals
- """Returns true if point ``(x,y,z)`` inside volume of geometry.
+ """Returns True if point ``(x,y,z)`` inside volume of geometry.
Parameters
----------
- x : ``float``
+ x : float
Position of point in x direction.
- y : ``float``
+ y : float
Position of point in y direction.
- z : ``float``
+ z : float
Position of point in z direction.
Returns
-------
- ``bool``
+ bool
Whether point ``(x,y,z)`` is inside geometry.
"""
z0, _ = self.pop_axis(self.center, axis=self.axis)
@@ -836,8 +845,9 @@ def _intersections_normal(self):
Returns
-------
- ``list[shapely.geometry.base.BaseGeometries]``
- List containing the shapely representation of the polygon.
+ List[shapely.geometry.base.BaseGeometry]
+ List of 2D shapes that intersect plane.
+ For more details refer to `Shapely's Documentaton `_.
"""
return [Polygon(self.vertices)]
@@ -846,16 +856,16 @@ def _intersections_side(self, position, axis) -> list: # pylint:disable=too-man
Parameters
----------
- position : ``float``
+ position : float
Position along ``axis``
- axis : ``int``
+ axis : int
Integer index into 'xyz' (0,1,2).
Returns
-------
- ``list[shapely.geometry.base.BaseGeometries]``
- List of 2D geometries intersecting with planar geometry at ``position`` along side
- ``axis``.
+ List[shapely.geometry.base.BaseGeometry]
+ List of 2D shapes that intersect plane.
+ For more details refer to `Shapely's Documentaton `_.
"""
z0, _ = self.pop_axis(self.center, axis=self.axis)
@@ -878,17 +888,17 @@ def _find_intersecting_vertices(
self, position: float, axis: int
) -> Tuple[np.ndarray, np.ndarray]:
"""Finds pairs of forward and backwards vertices where polygon intersects position at axis.
- Assumed xy plane.
+ Assumes axis is handles so this function works on xy plane.
Parameters
----------
- position : ``float``
+ position : float
position along axis
- axis : ``int``
+ axis : int
Integer index into 'xyz' (0,1,2).
Returns
- ``(np.ndarray, np.ndarray)``
+ np.ndarray, np.ndarray
Backward (xy) vertices and forward (xy) vertices.
"""
@@ -914,16 +924,21 @@ def _find_intersecting_vertices(
def _find_intersecting_ys(
iverts_b: np.ndarray, iverts_f: np.ndarray, position: float
) -> List[float]:
- """For each intersecting segment, find intersection point (in y) assuming straight line
+ """For each intersecting segment, find intersection point (in y) assuming straight line.
Parameters
----------
- iverts_b : ``np.ndarray``
- backward (x,y) vertices
- iverts_f : ``np.ndarray``
- forward (x,y) vertices
- position : ``float``
- position along coordinate x
+ iverts_b : np.ndarray
+ Backward (x,y) vertices.
+ iverts_f : np.ndarray
+ Forward (x,y) vertices.
+ position : float
+ Position along coordinate x.
+
+ Returns
+ -------
+ List[float]
+ List of intersection points along y direction.
"""
ints_y = []
@@ -938,11 +953,11 @@ def _find_intersecting_ys(
@property
def _bounds(self):
- """Returns bounding box for geometry
+ """Returns bounding box min and max coordinates.
Returns
-------
- ``(float, float, float), (float, float float)``
+ Tuple[float, float, float], Tuple[float, float, float]
Min and max bounds packaged as ``(minx, miny, minz), (maxx, maxy, maxz)``.
"""
@@ -959,5 +974,6 @@ def _bounds(self):
return (tuple(coords_min), tuple(coords_max))
+# geometries that can be used to define structures.
GeometryFields = (Box, Sphere, Cylinder, PolySlab)
GeometryType = Union[GeometryFields]
diff --git a/tidy3d/components/grid.py b/tidy3d/components/grid.py
index 1eca81821c..28f6ce03cf 100644
--- a/tidy3d/components/grid.py
+++ b/tidy3d/components/grid.py
@@ -1,25 +1,24 @@
-""" defines the FDTD grid """
+"""Defines the FDTD grid."""
import numpy as np
from .base import Tidy3dBaseModel
from .types import Array, Axis
-""" Grid data """
-# type of one dimensional coordinate array
+# data type of one dimensional coordinate array.
Coords1D = Array[float]
class Coords(Tidy3dBaseModel):
- """Holds data about a set of x,y,z positions on a grid
+ """Holds data about a set of x,y,z positions on a grid.
Parameters
----------
- x : ``np.ndarray``
+ x : np.ndarray
Positions of coordinates along x direction.
- y : ``np.ndarray``
+ y : np.ndarray
Positions of coordinates along y direction.
- z : ``np.ndarray``
+ z : np.ndarray
Positions of coordinates along z direction.
Example
@@ -79,6 +78,7 @@ class YeeGrid(Tidy3dBaseModel):
>>> coords = Coords(x=x, y=y, z=z)
>>> field_grid = FieldGrid(x=coords, y=coords, z=coords)
>>> yee_grid = YeeGrid(E=field_grid, H=field_grid)
+ >>> Ex_coords = yee_grid.E.x
"""
E: FieldGrid
@@ -86,7 +86,7 @@ class YeeGrid(Tidy3dBaseModel):
class Grid(Tidy3dBaseModel):
- """contains all information about the spatial positions of the FDTD grid
+ """Contains all information about the spatial positions of the FDTD grid.
Parameters
----------
@@ -100,51 +100,104 @@ class Grid(Tidy3dBaseModel):
>>> z = np.linspace(-1, 1, 12)
>>> coords = Coords(x=x, y=y, z=z)
>>> grid = Grid(boundaries=coords)
+ >>> centers = grid.centers
+ >>> sizes = grid.sizes
+ >>> yee_grid = grid.yee
"""
boundaries: Coords
@staticmethod
def _avg(coords1d: Coords1D):
- """average an array of 1D coordinates"""
+ """Return average positions of an array of 1D coordinates."""
return (coords1d[1:] + coords1d[:-1]) / 2.0
@staticmethod
def _min(coords1d: Coords1D):
- """get minus positions of 1D coordinates"""
+ """Return minus positions of 1D coordinates."""
return coords1d[:-1]
@property
def centers(self) -> Coords:
- """get centers of the cells in the :class:`Grid`.
+ """Return centers of the cells in the :class:`Grid`.
Returns
-------
:class:`Coords`
centers of the FDTD cells in x,y,z stored as :class:`Coords` object.
+
+ Example
+ -------
+ >>> x = np.linspace(-1, 1, 10)
+ >>> y = np.linspace(-1, 1, 11)
+ >>> z = np.linspace(-1, 1, 12)
+ >>> coords = Coords(x=x, y=y, z=z)
+ >>> grid = Grid(boundaries=coords)
+ >>> centers = grid.centers
"""
return Coords(**{key: self._avg(val) for key, val in self.boundaries.dict().items()})
@property
- def cell_sizes(self) -> Coords:
- """get sizes of the cells in the :class:`Grid`.
+ def sizes(self) -> Coords:
+ """Return sizes of the cells in the :class:`Grid`.
Returns
-------
:class:`Coords`
Sizes of the FDTD cells in x,y,z stored as :class:`Coords` object.
+
+ Example
+ -------
+ >>> x = np.linspace(-1, 1, 10)
+ >>> y = np.linspace(-1, 1, 11)
+ >>> z = np.linspace(-1, 1, 12)
+ >>> coords = Coords(x=x, y=y, z=z)
+ >>> grid = Grid(boundaries=coords)
+ >>> sizes = grid.sizes
"""
return Coords(**{key: np.diff(val) for key, val in self.boundaries.dict().items()})
+ @property
+ def _primal_steps(self) -> Coords:
+ """Return primal steps of the cells in the :class:`Grid`.
+
+ Returns
+ -------
+ :class:`Coords`
+ Distances between each of the cell boundaries along each dimension.
+ """
+ return self.sizes
+
+ @property
+ def _dual_steps(self) -> Coords:
+ """Return dual steps of the cells in the :class:`Grid`.
+
+ Returns
+ -------
+ :class:`Coords`
+ Distances between each of the cell centers along each dimension.
+ """
+ return Coords(**{key: np.diff(val) for key, val in self.centers.dict().items()})
+
@property
def yee(self) -> YeeGrid:
- """return the :class:`YeeGrid` defining the yee cell locations for this :class:`Grid`.
+ """Return the :class:`YeeGrid` defining the yee cell locations for this :class:`Grid`.
Returns
-------
:class:`YeeGrid`
- Coordinates of all of the components on the yee lattice.
+ Stores coordinates of all of the components on the yee lattice.
+
+ Example
+ -------
+ >>> x = np.linspace(-1, 1, 10)
+ >>> y = np.linspace(-1, 1, 11)
+ >>> z = np.linspace(-1, 1, 12)
+ >>> coords = Coords(x=x, y=y, z=z)
+ >>> grid = Grid(boundaries=coords)
+ >>> yee_cells = grid.yee
+ >>> Ex_positions = yee_cells.E.x
"""
yee_e_kwargs = {key: self._yee_e(axis=axis) for axis, key in enumerate("xyz")}
yee_h_kwargs = {key: self._yee_h(axis=axis) for axis, key in enumerate("xyz")}
@@ -153,8 +206,8 @@ def yee(self) -> YeeGrid:
yee_h = FieldGrid(**yee_h_kwargs)
return YeeGrid(E=yee_e, H=yee_h)
- def _yee_e(self, axis: Axis): #
- """E field yee lattice sites for axis"""
+ def _yee_e(self, axis: Axis):
+ """E field yee lattice sites for axis."""
boundary_coords = self.boundaries.dict()
@@ -168,7 +221,7 @@ def _yee_e(self, axis: Axis): #
return Coords(**yee_coords)
def _yee_h(self, axis: Axis):
- """E field yee lattice sites for axis"""
+ """E field yee lattice sites for axis."""
boundary_coords = self.boundaries.dict()
diff --git a/tidy3d/components/medium.py b/tidy3d/components/medium.py
index af57fbfd33..3b5f27a2a1 100644
--- a/tidy3d/components/medium.py
+++ b/tidy3d/components/medium.py
@@ -11,14 +11,24 @@
from .viz import add_ax_if_none
from .validators import validate_name_str
-from ..constants import C_0, inf
+from ..constants import C_0, inf, pec_val
from ..log import log
+
""" Medium Definitions """
class AbstractMedium(ABC, Tidy3dBaseModel):
- """A medium within which electromagnetic waves propagate"""
+ """A medium within which electromagnetic waves propagate.
+
+ Parameters
+ ----------
+ frequeuncy_range : Tuple[float, float] = (-inf, inf)
+ Range of validity for the medium in Hz.
+ If simulation or plotting functions use frequency out of this range, a warning is thrown.
+ name : str = None
+ Optional name for the medium.
+ """
name: str = None
frequency_range: Tuple[FreqBound, FreqBound] = (-inf, inf)
@@ -27,18 +37,36 @@ class AbstractMedium(ABC, Tidy3dBaseModel):
@abstractmethod
def eps_model(self, frequency: float) -> complex:
- """complex permittivity as a function of frequency
+ """Complex-valued permittivity as a function of frequency.
Parameters
----------
frequency : float
- Description
- name: ``str``
+ Frequency to evaluate permittivity at (Hz).
+
+ Returns
+ -------
+ complex
+ Complex-valued relative permittivity evaluated at ``frequency``.
"""
@add_ax_if_none
def plot(self, freqs: float, ax: Ax = None) -> Ax: # pylint: disable=invalid-name
- """plot n, k of medium as a function of frequencies."""
+ """Plot n, k of a :class:`Medium` as a function of frequency.
+
+ Parameters
+ ----------
+ freqs: float
+ Frequencies (Hz) to evaluate the medium properties at.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
+ """
+
freqs = np.array(freqs)
eps_complex = self.eps_model(freqs)
n, k = eps_complex_to_nk(eps_complex)
@@ -71,9 +99,62 @@ def _eps_model(self, frequency: float) -> complex:
""" Dispersionless Medium """
+# PEC keyword
+class PECMedium(AbstractMedium):
+ """Perfect electrical conductor class.
+
+ Note
+ ----
+ To avoid confusion from duplicate PECs,
+ use the pre-defined instance ``PEC`` rather than creating your own :class:`PECMedium` instance.
+ """
+
+ def eps_model(self, frequency: float) -> complex:
+ """Complex-valued permittivity as a function of frequency.
+
+ Parameters
+ ----------
+ frequency : float
+ Frequency to evaluate permittivity at (Hz).
+
+ Returns
+ -------
+ complex
+ Complex-valued relative permittivity evaluated at ``frequency``.
+ """
+
+ # return something like frequency with value of pec_val + 0j
+ return 0j * frequency + pec_val
+
+
+# PEC instance (usable)
+PEC = PECMedium(name="PEC")
+
class Medium(AbstractMedium):
- """Dispersionless medium."""
+ """Dispersionless medium.
+
+ Parameters
+ ----------
+ permittivity : float = 1.0
+ Relative permittivity in dimensionless units.
+ Must be greater than or equal to 1.
+ conductivity : float = 0.0
+ Electric conductivity in dimensions of (S/micron)
+ Defined such that the imaginary part of the complex permittivity at angular frequency omega
+ is given by conductivity/omega.
+ Must be greater than or equal to 0.
+ frequeuncy_range : Tuple[float, float] = (-inf, inf)
+ Range of validity for the medium in Hz.
+ If simulation or plotting functions use frequency out of this range, a warning is thrown.
+ name : str = None
+ Optional name for the medium.
+
+ Example
+ -------
+ >>> dielectric = Medium(permittivity=4.0, name='my_medium')
+ >>> eps = dielectric.eps_model(200e12)
+ """
permittivity: pydantic.confloat(ge=1.0) = 1.0
conductivity: pydantic.confloat(ge=0.0) = 0.0
@@ -85,12 +166,12 @@ def eps_model(self, frequency: float) -> complex:
Parameters
----------
- frequency : ``float``
+ frequency : float
Frequency to evaluate permittivity at (Hz).
Returns
-------
- ``complex``
+ complex
Complex-valued relative permittivity evaluated at ``frequency``.
"""
return eps_sigma_to_eps_complex(self.permittivity, self.conductivity, frequency)
@@ -105,27 +186,129 @@ def __str__(self) -> str:
)
+class AnisotropicMedium(AbstractMedium):
+ """Diagonally anisotripic medium.
+
+ Parameters
+ ----------
+ xx : :class:`Medium`
+ :class:`Medium` describing the :math:`\\epsilon_{xx}`-component of the permittivity tensor.
+ yy : :class:`Medium`
+ :class:`Medium` describing the :math:`\\epsilon_{yy}`-component of the permittivity tensor.
+ zz : :class:`Medium`
+ :class:`Medium` describing the :math:`\\epsilon_{zz}`-component of the permittivity tensor.
+ name : str = None
+ Optional name for the medium.
+
+ Note
+ ----
+ Only diagonal anisotropy and non-dispersive components are currently supported.
+
+ Example
+ -------
+ >>> medium_xx = Medium(permittivity=4.0)
+ >>> medium_yy = Medium(permittivity=4.1)
+ >>> medium_zz = Medium(permittivity=3.9)
+ >>> anisotropic_dielectric = AnisotropicMedium(xx=medium_xx, yy=medium_yy, zz=medium_zz)
+ """
+
+ xx: Medium
+ yy: Medium
+ zz: Medium
+
+ @ensure_freq_in_range
+ def eps_model(self, frequency: float) -> complex:
+ """Complex-valued permittivity as a function of frequency.
+
+ Parameters
+ ----------
+ frequency : float
+ Frequency to evaluate permittivity at (Hz).
+
+ Returns
+ -------
+ Tuple[complex, complex, complex]
+ Complex-valued relative permittivity for each component evaluated at ``frequency``.
+ """
+ eps_xx = self.xx.eps_model(frequency)
+ eps_yy = self.yy.eps_model(frequency)
+ eps_zz = self.zz.eps_model(frequency)
+ return (eps_xx, eps_yy, eps_zz)
+
+ @add_ax_if_none
+ def plot(self, freqs: float, ax: Ax = None) -> Ax:
+ """Plot n, k of a :class:`Medium` as a function of frequency.
+
+ Parameters
+ ----------
+ freqs: float
+ Frequencies (Hz) to evaluate the medium properties at.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
+ """
+
+ freqs = np.array(freqs)
+ freqs_thz = freqs / 1e12
+
+ for label, medium_component in zip(("xx", "yy", "zz"), (self.xx, self.yy, self.zz)):
+
+ eps_complex = medium_component.eps_model(freqs)
+ n, k = eps_complex_to_nk(eps_complex)
+ ax.plot(freqs_thz, n, label=f"n, eps_{label}")
+ ax.plot(freqs_thz, k, label=f"k, eps_{label}")
+
+ ax.set_xlabel("frequency (THz)")
+ ax.set_title("medium dispersion")
+ ax.legend()
+ ax.set_aspect("auto")
+ return ax
+
+
""" Dispersive Media """
class DispersiveMedium(AbstractMedium, ABC):
"""A Medium with dispersion (propagation characteristics depend on frequency)"""
+ @property
+ @abstractmethod
+ def pole_residue(self):
+ """Representation of Medium as a pole-residue model."""
+
class PoleResidue(DispersiveMedium):
- """defines a dispersion model through pole-residue pair model.
+ """A dispersive medium described by the pole-residue pair model.
+ The frequency-dependence of the complex-valued permittivity is described by:
+
+ .. math::
+
+ \\epsilon(\\omega) = \\epsilon_\\infty - \\sum_i
+ \\left[\\frac{c_i}{j \\omega + a_i} +
+ \\frac{c_i^*}{j \\omega + a_i^*}\\right]
+
+ where :math:`a_i` is in Hz and :math:`c_i` is unitless.
Parameters
----------
- eps_inf : ``float`` = 1.0
- Permittivity at infinite frequency.
- poles : ``[((float, float), (float, float))]`` = []
- List of poles, ``poles[0]`` contain the real and imaginary parts of
- the frequency and amplitude of the first, respecitvely.
+ eps_inf : float = 1.0
+ Relative permittivity at infinite frequency (:math:`\\epsilon_\\infty`).
+ poles : List[Tuple[complex, complex]]
+ List of complex-valued (:math:`a_i, c_i`) poles for the model.
+ frequeuncy_range : Tuple[float, float] = (-inf, inf)
+ Range of validity for the medium in Hz.
+ If simulation or plotting functions use frequency out of this range, a warning is thrown.
+ name : str = None
+ Optional name for the medium.
Example
-------
- >>> pole_res = PoleResidue(eps_inf=2.0, poles=[((1,2),(3,4)), ((5,6),(7,8))])
+ >>> pole_res = PoleResidue(eps_inf=2.0, poles=[(1+2j, 3+4j), (5+6j, 7+8j)])
+ >>> eps = pole_res.eps_model(200e12)
"""
eps_inf: float = 1.0
@@ -138,34 +321,35 @@ def eps_model(self, frequency: float) -> complex:
Parameters
----------
- frequency : ``float``
+ frequency : float
Frequency to evaluate permittivity at (Hz).
Returns
-------
- ``complex``
- Complex-valued relative permittivity evaluated at ``frequency``.
+ complex
+ Complex-valued relative permittivity evaluated at the frequency.
"""
omega = 2 * np.pi * frequency
eps = self.eps_inf + 0.0j
- for p in self.poles:
- (ar, ai), (cr, ci) = p
- a = ar + 1j * ai
- c = cr + 1j * ci
+ for (a, c) in self.poles:
a_cc = np.conj(a)
c_cc = np.conj(c)
eps -= c / (1j * omega + a)
eps -= c_cc / (1j * omega + a_cc)
return eps
- def __str__(self):
- """string representation
+ @property
+ def pole_residue(self):
+ """Representation of Medium as a pole-residue model."""
+ return PoleResidue(
+ eps_inf=self.eps_inf,
+ poles=self.poles,
+ frequency_range=self.frequency_range,
+ name=self.name,
+ )
- Returns
- -------
- TYPE
- Description
- """
+ def __str__(self):
+ """string representation"""
return (
f"td.PoleResidue("
f"\n\tpoles={self.poles}, "
@@ -174,7 +358,30 @@ def __str__(self):
class Sellmeier(DispersiveMedium):
- """Sellmeier model for dispersion"""
+ """A dispersive medium described by the Sellmeier model.
+ The frequency-dependence of the refractive index is described by:
+
+ .. math::
+
+ n(\\lambda)^2 = 1 + \\sum_i \\frac{B_i \\lambda^2}{\\lambda^2 - C_i}
+
+ where :math:`\\lambda` is in microns, :math:`B_i` is unitless and :math:`C_i` is in microns^2.
+
+ Parameters
+ ----------
+ coeffs : List[Tuple[float, float]]
+ List of Sellmeier (:math:`B_i, C_i`) coefficients.
+ frequeuncy_range : Tuple[float, float] = (-inf, inf)
+ Range of validity for the medium in Hz.
+ If simulation or plotting functions use frequency out of this range, a warning is thrown.
+ name : str = None
+ Optional name for the medium.
+
+ Example
+ -------
+ >>> sellmeier_medium = Sellmeier(coeffs=[(1,2), (3,4)])
+ >>> eps = sellmeier_medium.eps_model(200e12)
+ """
coeffs: List[Tuple[float, float]]
type: Literal["Sellmeier"] = "Sellmeier"
@@ -194,20 +401,64 @@ def eps_model(self, frequency: float) -> complex:
Parameters
----------
- frequency : ``float``
+ frequency : float
Frequency to evaluate permittivity at (Hz).
Returns
-------
- ``complex``
- Complex-valued relative permittivity evaluated at ``frequency``.
+ complex
+ Complex-valued relative permittivity evaluated at the frequency.
"""
n = self._n_model(frequency)
return nk_to_eps_complex(n)
+ @property
+ def pole_residue(self):
+ """Representation of Medium as a pole-residue model."""
+
+ poles = []
+ for (B, C) in self.coeffs:
+ beta = 2 * np.pi * C_0 / np.sqrt(C)
+ alpha = -0.5 * beta * B
+ a = 1j * beta
+ c = 1j * alpha
+ poles.append((a, c))
+
+ return PoleResidue(
+ eps_inf=1,
+ poles=poles,
+ frequency_range=self.frequency_range,
+ name=self.name,
+ )
+
class Lorentz(DispersiveMedium):
- """Lorentz model for dispersion"""
+ """A dispersive medium described by the Lorentz model.
+ The frequency-dependence of the complex-valued permittivity is described by:
+
+ .. math::
+ \\epsilon(f) = \\epsilon_\\infty + \\sum_i
+ \\frac{\\Delta\\epsilon_i f_i^2}{f_i^2 + 2jf\\delta_i - f^2}
+
+ where :math:`f, f_i, \\delta_i` are in Hz.
+
+ Parameters
+ ----------
+ eps_inf : float = 1.0
+ Relative permittivity at infinite frequency (:math:`\\epsilon_\\infty`).
+ coeffs : List[Tuple[float, float, float]]
+ List of (:math:`\\Delta\\epsilon_i, f_i, \\delta_i`) values for model.
+ frequeuncy_range : Tuple[float, float] = (-inf, inf)
+ Range of validity for the medium in Hz.
+ If simulation or plotting functions use frequency out of this range, a warning is thrown.
+ name : str = None
+ Optional name for the medium.
+
+ Example
+ -------
+ >>> lorentz_medium = Lorentz(eps_inf=2.0, coeffs=[(1,2,3), (4,5,6)])
+ >>> eps = lorentz_medium.eps_model(200e12)
+ """
eps_inf: float = 1.0
coeffs: List[Tuple[float, float, float]]
@@ -219,22 +470,74 @@ def eps_model(self, frequency: float) -> complex:
Parameters
----------
- frequency : ``float``
+ frequency : float
Frequency to evaluate permittivity at (Hz).
Returns
-------
- ``complex``
- Complex-valued relative permittivity evaluated at ``frequency``.
+ complex
+ Complex-valued relative permittivity evaluated at the frequency.
"""
eps = self.eps_inf + 0.0j
for (de, f, delta) in self.coeffs:
- eps += (de * f ** 2) / (f ** 2 + 2j * f * delta - frequency ** 2)
+ eps += (de * f ** 2) / (f ** 2 + 2j * frequency * delta - frequency ** 2)
return eps
+ @property
+ def pole_residue(self):
+ """Representation of Medium as a pole-residue model."""
+
+ poles = []
+ for (de, f, delta) in self.coeffs:
+
+ w = 2 * np.pi * f
+ d = 2 * np.pi * delta
+
+ if d > w:
+ r = 1j * np.sqrt(d * d - w * w)
+ else:
+ r = np.sqrt(w * w - d * d)
+
+ a = d - 1j * r
+ c = 1j * de * w ** 2 / 2 / r
+
+ poles.append((a, c))
+
+ return PoleResidue(
+ eps_inf=self.eps_inf,
+ poles=poles,
+ frequency_range=self.frequency_range,
+ name=self.name,
+ )
+
class Debye(DispersiveMedium):
- """Debye model for dispersion"""
+ """A dispersive medium described by the Debye model.
+ The frequency-dependence of the complex-valued permittivity is described by:
+
+ .. math::
+ \\epsilon(f) = \\epsilon_\\infty + \\sum_i
+ \\frac{\\Delta\\epsilon_i}{1 + jf\\tau_i}
+
+ where :math:`f` is in Hz, and :math:`\\tau_i` is in seconds.
+
+ Parameters
+ ----------
+ eps_inf : float = 1.0
+ Relative permittivity at infinite frequency (:math:`\\epsilon_\\infty`).
+ coeffs : List[Tuple[float, float, float]]
+ List of (:math:`\\Delta\\epsilon_i, \\tau_i`) values for model.
+ frequeuncy_range : Tuple[float, float] = (-inf, inf)
+ Range of validity for the medium in Hz.
+ If simulation or plotting functions use frequency out of this range, a warning is thrown.
+ name : str = None
+ Optional name for the medium.
+
+ Example
+ -------
+ >>> debye_medium = Debye(eps_inf=2.0, coeffs=[(1,2),(3,4)])
+ >>> eps = debye_medium.eps_model(200e12)
+ """
eps_inf: float = 1.0
coeffs: List[Tuple[float, float]]
@@ -246,23 +549,41 @@ def eps_model(self, frequency: float) -> complex:
Parameters
----------
- frequency : ``float``
+ frequency : float
Frequency to evaluate permittivity at (Hz).
Returns
-------
- ``complex``
- Complex-valued relative permittivity evaluated at ``frequency``.
+ complex
+ Complex-valued relative permittivity evaluated at the frequency.
"""
eps = self.eps_inf + 0.0j
for (de, tau) in self.coeffs:
eps += de / (1 + 1j * frequency * tau)
return eps
+ @property
+ def pole_residue(self):
+ """Representation of Medium as a pole-residue model."""
+
+ poles = []
+ for (de, tau) in self.coeffs:
+ a = 2 * np.pi / tau + 0j
+ c = -0.5 * de * a
+ poles.append((a, c))
+
+ return PoleResidue(
+ eps_inf=self.eps_inf,
+ poles=poles,
+ frequency_range=self.frequency_range,
+ name=self.name,
+ )
-MediumType = Union[Medium, PoleResidue, Sellmeier, Lorentz, Debye]
-""" conversion helpers """
+# types of mediums that can be used in Simulation and Structures
+MediumType = Union[Literal[PEC], Medium, AnisotropicMedium, PoleResidue, Sellmeier, Lorentz, Debye]
+
+""" Conversion helper functions """
def nk_to_eps_complex(n: float, k: float = 0.0) -> complex:
@@ -270,14 +591,14 @@ def nk_to_eps_complex(n: float, k: float = 0.0) -> complex:
Parameters
----------
- n : ``float``
- real part of refractive index
- k : ``float`` = 0
- imaginary part of refrative index.
+ n : float
+ Real part of refractive index.
+ k : float = 0.0
+ Imaginary part of refrative index.
Returns
-------
- ``complex``
+ complex
Complex-valued relative permittivty.
"""
eps_real = n ** 2 - k ** 2
@@ -286,16 +607,16 @@ def nk_to_eps_complex(n: float, k: float = 0.0) -> complex:
def eps_complex_to_nk(eps_c: complex) -> Tuple[float, float]:
- """convert complex permittivity to n, k
+ """Convert complex permittivity to n, k values.
Parameters
----------
- eps_c : ``complex``
+ eps_c : complex
Complex-valued relative permittivity.
Returns
-------
- ``(float, float)``
+ Tuple[float, float]
Real and imaginary parts of refractive index (n & k).
"""
ref_index = np.sqrt(eps_c)
@@ -303,21 +624,21 @@ def eps_complex_to_nk(eps_c: complex) -> Tuple[float, float]:
def nk_to_eps_sigma(n: float, k: float, freq: float) -> Tuple[float, float]:
- """convert n, k at freq to permittivity and conductivity
+ """Convert ``n``, ``k`` at frequency ``freq`` to permittivity and conductivity values.
Parameters
----------
- n : ``float``
- real part of refractive index
- k : ``float`` = 0
- imaginary part of refrative index.
- frequency : ``float``
+ n : float
+ Real part of refractive index.
+ k : float = 0.0
+ Imaginary part of refrative index.
+ frequency : float
Frequency to evaluate permittivity at (Hz).
Returns
-------
- ``(float, float)``
- Real part of relative permittivity & conductivity.
+ Tuple[float, float]
+ Real part of relative permittivity & electric conductivity.
"""
eps_complex = nk_to_eps_complex(n, k)
eps_real, eps_imag = eps_complex.real, eps_complex.imag
@@ -327,15 +648,15 @@ def nk_to_eps_sigma(n: float, k: float, freq: float) -> Tuple[float, float]:
def nk_to_medium(n: float, k: float, freq: float) -> Medium:
- """Convert ``n`` and ``k`` values at ``frequency`` to :class:`Medium`.
+ """Convert ``n`` and ``k`` values at frequency ``freq`` to :class:`Medium`.
Parameters
----------
- n : ``float``
- real part of refractive index
- k : ``float`` = 0
- imaginary part of refrative index.
- frequency : ``float``
+ n : float
+ Real part of refractive index.
+ k : float = 0
+ Imaginary part of refrative index.
+ frequency : float
Frequency to evaluate permittivity at (Hz).
Returns
@@ -352,11 +673,11 @@ def eps_sigma_to_eps_complex(eps_real: float, sigma: float, freq: float) -> comp
Parameters
----------
- eps_real : ``float``
+ eps_real : float
Real-valued relative permittivity.
- sigma : ``float``
+ sigma : float
Conductivity.
- freq : ``float``
+ freq : float
Frequency to evaluate permittivity at (Hz).
Returns
diff --git a/tidy3d/components/mode.py b/tidy3d/components/mode.py
index 4247b8a0fd..aa87b1ed3e 100644
--- a/tidy3d/components/mode.py
+++ b/tidy3d/components/mode.py
@@ -6,24 +6,50 @@
from .base import Tidy3dBaseModel
from .types import Symmetry
+from ..log import SetupError
class Mode(Tidy3dBaseModel):
- """Stores Specifications of a Mode to input into mode solver.
+ """Stores specifications for the mode solver to find an electromagntic mode.
+ Note, the planar axes are found by popping the propagation axis from {x,y,z}.
+ For example, if propagation axis is y, the planar axes are ordered {x,z}.
+
Parameters
----------
- mode_index : ``int``
+ mode_index : int
Return the mode solver output at ``mode_index``.
- target_neff : ``float = None``
+ Must be >= 0.
+ num_modes : int = None
+ Number of modes returned by mode solver before selecting mode at ``mode_index``.
+ Must be > ``mode_index`` to accomodate ``mode_index``-th mode.
+ target_neff : float = None
Guess for effective index of mode.
- symmetries : ``(int, int) = (0,0)``
- Symmetries (0, 1,-1) = (none, even, odd) in (x,y) of mode plane.
- num_pml: ``(int, int) = (0,0)``
- number of standard pml layers to add in (x,y) of mode plane.
+ Must be > 0.
+ symmetries : Tuple[int, int] = (0,0)
+ Symmetries to apply to mode solver for first two non-propagation axes.
+ Values of (0, 1,-1) correspond to (none, even, odd) symmetries, respectvely.
+ num_pml: Tuple[int, int] = (0,0)
+ Number of standard pml layers to add in the first two non-propagation axes.
+
+ Example
+ -------
+ >>> mode = Mode(mode_index=1, num_modes=3, target_neff=1.5, symmetries=(1,-1))
"""
mode_index: pd.NonNegativeInt
num_modes: pd.PositiveInt = None
- target_neff: float = None
+ target_neff: pd.PositiveFloat = None
symmetries: Tuple[Symmetry, Symmetry] = (0, 0)
num_pml: Tuple[pd.NonNegativeInt, pd.NonNegativeInt] = (0, 0)
+
+ @pd.validator("num_modes", always=True)
+ def check_num_modes(cls, val, values):
+ """Make sure num_modes is > mode_index or None"""
+ if val is not None:
+ mode_index = values.get("mode_index")
+ if not val > mode_index:
+ raise SetupError(
+ "`num_modes` must be greater than `mode_index`"
+ f"given {val} and {mode_index}, respectively"
+ )
+ return val
diff --git a/tidy3d/components/monitor.py b/tidy3d/components/monitor.py
index a27f0499e2..d60ee1e06c 100644
--- a/tidy3d/components/monitor.py
+++ b/tidy3d/components/monitor.py
@@ -1,4 +1,4 @@
-""" Objects that define how data is recorded from simulation """
+"""Objects that define how data is recorded from simulation."""
from abc import ABC
from typing import List, Union
@@ -11,54 +11,64 @@
from .viz import add_ax_if_none, MonitorParams
from ..log import SetupError
-""" Monitors """
-
class Monitor(Box, ABC):
- """base class for monitors, which all have Box shape"""
+ """Abstract base class for monitors."""
name: str
_name_validator = validate_name_str()
@add_ax_if_none
- def plot(
+ def plot( #pylint:disable=duplicate-code
self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **kwargs
) -> Ax:
- """plot monitor geometry"""
+ """Plot the monitor geometry on a cross section plane.
+
+ Parameters
+ ----------
+ x : float = None
+ Position of plane in x direction, only one of x,y,z can be specified to define plane.
+ y : float = None
+ Position of plane in y direction, only one of x,y,z can be specified to define plane.
+ z : float = None
+ Position of plane in z direction, only one of x,y,z can be specified to define plane.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+ **patch_kwargs
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_. #pylint:disable=line-too-long # pylint: disable=line-too-long
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
+ """
kwargs = MonitorParams().update_params(**kwargs)
ax = self.geometry.plot(x=x, y=y, z=z, ax=ax, **kwargs)
return ax
@property
def geometry(self):
- """box representation of self"""
- return Box(center=self.center, size=self.size)
-
+ """:class:`Box` representation of monitor.
-""" The following are abstract classes that separate the ``Monitor`` instances into different
- types depending on what they store.
- They can be useful for keeping argument types and validations separated.
- For example, monitors that should always be defined on planar geometries can have an
- ``_assert_plane()`` validation in the abstract base class ``PlanarMonitor``.
- This way, ``_assert_plane()`` will always be used if we add more ``PlanarMonitor`` objects in
- the future.
- This organization is also useful when doing conditions based on monitor / data type.
- For example, instead of
- ``if isinstance(mon_data, (FieldMonitor, FieldTimeMonitor)):`` we can simply do
- ``if isinstance(mon_data, ScalarFieldMonitor)`` and this will generalize if we add more
- ``ScalarFieldMonitor`` objects in the future.
-"""
+ Returns
+ -------
+ :class:`Box`
+ Representation of the monitor geometry as a :class:`Box`.
+ """
+ return Box(center=self.center, size=self.size)
class FreqMonitor(Monitor, ABC):
- """stores data in frequency domain"""
+ """Stores data in the frequency-domain."""
freqs: Union[List[float], Array[float]]
class TimeMonitor(Monitor, ABC):
- """stores data in time domain"""
+ """Stores data in the time-domain."""
start: pydantic.NonNegativeFloat = 0.0
stop: pydantic.NonNegativeFloat = None
@@ -66,7 +76,7 @@ class TimeMonitor(Monitor, ABC):
@pydantic.validator("stop", always=True)
def stop_greater_than_start(cls, val, values):
- """make sure stop is greater than or equal to start"""
+ """Ensure sure stop is greater than or equal to start."""
start = values.get("start")
if val and val < start:
raise SetupError("Monitor start time is greater than stop time.")
@@ -74,13 +84,13 @@ def stop_greater_than_start(cls, val, values):
class AbstractFieldMonitor(Monitor, ABC):
- """stores data as a function of x,y,z"""
+ """Stores electromagnetic field data as a function of x,y,z."""
fields: List[EMField] = ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"]
class PlanarMonitor(Monitor, ABC):
- """stores quantities on a plane"""
+ """Monitors that must have planar geometry."""
_plane_validator = assert_plane()
@@ -89,24 +99,31 @@ class AbstractFluxMonitor(PlanarMonitor, ABC):
"""stores flux through a plane"""
-""" usable """
-
-
class FieldMonitor(AbstractFieldMonitor, FreqMonitor):
- """Stores EM fields or permittivity as a function of frequency.
+ """Stores a collection of electromagnetic fields in the frequency domain.
Parameters
----------
- center: ``(float, float, float)``, optional.
- Center of monitor ``Box``, defaults to (0, 0, 0)
- size: ``(float, float, float)``
- Size of monitor ``Box``, must have one element = 0.0 to define plane.
- fields: ``[str]``, optional
- Electromagnetic field(s) to measure (E, H), defaults to ``['Ex', 'Ey', 'Ez', 'Hx', 'Hy',
- 'Hz']``.
- freqs: ``[float]``
- Frequencies to measure fields at at (Hz),
-
+ center: Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ Center of monitor.
+ size: Tuple[float, float, float]
+ Size of monitor.
+ All elements must be non-negative.
+ fields: List[str] = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
+ Specifies the electromagnetic field components to record.
+ If wanting to conserve data, can specify fewer components.
+ freqs: List[float] or np.ndarray
+ List of frequencies in Hertz to store fields at.
+ name : str
+ (Required) name used to access data after simulation is finished.
+
+ Example
+ -------
+ >>> monitor = FieldMonitor(
+ ... size=(2,2,2),
+ ... freqs=[200e12, 210e12],
+ ... fields=['Ex', 'Ey', 'Hz'],
+ ... name='freq_domain_fields')
"""
fields: List[FieldType] = ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"]
@@ -115,23 +132,40 @@ class FieldMonitor(AbstractFieldMonitor, FreqMonitor):
class FieldTimeMonitor(AbstractFieldMonitor, TimeMonitor):
- """Stores EM fields as a function of time.
+ """Stores a collection of electromagnetic fields in the time domain.
Parameters
----------
- center: Tuple[float, float, float], optional.
- Center of monitor ``Box``, defaults to (0, 0, 0)
- size: Tuple[float, float, float].
- Size of monitor ``Box``, must have one element = 0.0 to define plane.
- fields: List[str], optional
- Electromagnetic field(s) to measure (E, H), defaults to ``['Ex', 'Ey', 'Ez', 'Hx', 'Hy',
- 'Hz']``.
- start: ``float = 0.0``
- (seconds) Time to start monitoring fields.
- stop: ``float = None``
- (seconds) Time to stop monitoring fields, end of simulation if not specified.
- interval: ``int = 1``
- Records data at every ``interval`` time steps in the simulation.
+ center: Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ Center of monitor.
+ size: Tuple[float, float, float]
+ Size of monitor.
+ All elements must be non-negative.
+ fields: List[str] = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
+ Specifies the electromagnetic field components to record.
+ If wanting to conserve data, can specify fewer components.
+ start : float = 0.0
+ Time (seconds) to start recording fields.
+ stop : float = None
+ Time (seconds) to stop recording fields.
+ Must be greater than or equal to ``start``.
+ If not specified, records until the end of the simulation.
+ interval : int = 1
+ Number of time steps between measurements.
+ To conserve data, intervals > 1 may be specified to record data more sparsely sampled data.
+ Must be positive.
+ name : str
+ (Required) name used to access data after simulation is finished.
+
+ Example
+ -------
+ >>> monitor = FieldTimeMonitor(
+ ... size=(2,2,2),
+ ... fields=['Hx'],
+ ... start=1e-13,
+ ... stop=5e-13,
+ ... interval=2,
+ ... name='movie_monitor')
"""
type: Literal["FieldTimeMonitor"] = "FieldTimeMonitor"
@@ -143,12 +177,20 @@ class FluxMonitor(AbstractFluxMonitor, FreqMonitor):
Parameters
----------
- center: Tuple[float, float, float], optional.
- Center of monitor ``Box``, defaults to (0, 0, 0)
- size: Tuple[float, float, float].
- Size of monitor ``Box``, must have one element = 0.0 to define plane.
- freqs: List[float]
- Frequencies to measure flux at.
+ center: Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ Center of monitor.
+ size: Tuple[float, float, float]
+ Size of monitor.
+ All elements must be non-negative.
+ One element must be 0.0 to define flux plane.
+ freqs: List[float] or np.ndarray
+ List of frequencies in Hertz to store fields at.
+ name : str
+ (Required) name used to access data after simulation is finished.
+
+ Example
+ -------
+ >>> monitor = FluxMonitor(size=(2,2,0), freqs=[200e12, 210e12], name='flux_monitor')
"""
type: Literal["FluxMonitor"] = "FluxMonitor"
@@ -156,20 +198,37 @@ class FluxMonitor(AbstractFluxMonitor, FreqMonitor):
class FluxTimeMonitor(AbstractFluxMonitor, TimeMonitor):
- """Stores power flux through a plane as a function of frequency.
+ """Stores power flux through a plane as a function of time.
Parameters
----------
- center: Tuple[float, float, float], optional.
- Center of monitor ``Box``, defaults to (0, 0, 0)
- size: Tuple[float, float, float].
- Size of monitor ``Box``, must have one element = 0.0 to define plane.
- start: ``float = 0.0``
- (seconds) Time to start monitoring flux.
- stop: ``float = None``
- (seconds) Time to stop monitoring flux, end of simulation if not specified.
- interval: ``int = 1``
- Records data at every ``interval`` time steps in the simulation.
+ center: Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ Center of monitor.
+ size: Tuple[float, float, float]
+ Size of monitor.
+ All elements must be non-negative.
+ One element must be 0.0 to define flux plane.
+ start : float = 0.0
+ Time (seconds) to start recording fields.
+ stop : float = None
+ Time (seconds) to stop recording fields.
+ Must be greater than or equal to ``start``.
+ If not specified, records until the end of the simulation.
+ interval : int = 1
+ Number of time steps between measurements.
+ To conserve data, intervals > 1 may be specified to record data more sparsely sampled data.
+ Must be positive.
+ name : str
+ (Required) name used to access data after simulation is finished.
+
+ Example
+ -------
+ >>> monitor = FluxTimeMonitor(
+ ... size=(2,2,0),
+ ... start=1e-13,
+ ... stop=5e-13,
+ ... interval=2,
+ ... name='flux_time')
"""
type: Literal["FluxTimeMonitor"] = "FluxTimeMonitor"
@@ -177,18 +236,31 @@ class FluxTimeMonitor(AbstractFluxMonitor, TimeMonitor):
class ModeMonitor(PlanarMonitor, FreqMonitor):
- """stores overlap amplitudes associated with modes.
+ """Stores amplitudes found through modal decomposition of fields on plane.
Parameters
----------
- center: Tuple[float, float, float], optional.
- Center of monitor ``Box``, defaults to (0, 0, 0)
- size: Tuple[float, float, float].
- Size of monitor ``Box``, must have one element = 0.0 to define plane.
- freqs: List[float]
- Frequencies to measure flux at.
- modes: List[``Mode``]
- List of ``Mode`` objects specifying the modal profiles to measure amplitude overalap with.
+ center: Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ Center of monitor.
+ size: Tuple[float, float, float]
+ Size of monitor.
+ All elements must be non-negative.
+ One element must be 0.0 to define mode plane.
+ freqs: List[float] or np.ndarray
+ List of frequencies in Hertz to compute the modal decomposition on.
+ modes : List[:class:`Mode`]
+ List of mode specifications to compute modal overlaps with.
+ name : str
+ (Required) name used to access data after simulation is finished.
+
+ Example
+ -------
+ >>> modes = [Mode(mode_index=0), Mode(mode_index=1)]
+ >>> monitor = ModeMonitor(
+ ... size=(2,2,0),
+ ... freqs=[200e12, 210e12],
+ ... modes=modes,
+ ... name='mode_monitor')
"""
direction: List[Direction] = ["+", "-"]
@@ -197,14 +269,5 @@ class ModeMonitor(PlanarMonitor, FreqMonitor):
data_type: Literal["ModeData"] = "ModeData"
-""" explanation of monitor_type_map:
- When we load monitor data from file, we need some way to know what type of ``Monitor`` created
- the data.
- The ``Monitor``'s' ``type`` itself is not serilizable, so we can't store that directly in json.
- However, the ``Montior.type`` attribute stores a string representation of the ``MonitorType``,
- so we can use that.
- This map allows one to recover the ``Monitor`` type from the ``.type`` attribute in the json
- object and therefore load the correct monitor.
-"""
-
+# types of monitors that are accepted by simulation
MonitorType = Union[FieldMonitor, FieldTimeMonitor, FluxMonitor, FluxTimeMonitor, ModeMonitor]
diff --git a/tidy3d/components/pml.py b/tidy3d/components/pml.py
index 345291c395..ecfe559710 100644
--- a/tidy3d/components/pml.py
+++ b/tidy3d/components/pml.py
@@ -1,36 +1,98 @@
-""" Defines profile of Perfectly-matched layers (absorber) """
+"""Defines profile of Perfectly-matched layers (absorber)"""
from typing import Union, Literal
+from abc import ABC
import pydantic
from .base import Tidy3dBaseModel
-"""TODO: better docstrings."""
+# TODO: More explanation on parameters, when to use various PMLs.
class AbsorberParams(Tidy3dBaseModel):
- """Specifies parameters for an Absorber or PML. Sigma is in units of 2*EPSILON_0/dt."""
+ """Specifies parameters common to Absorbers and PMLs.
- sigma_order: pydantic.NonNegativeInt
- sigma_min: pydantic.NonNegativeFloat
- sigma_max: pydantic.NonNegativeFloat
+ Parameters
+ ----------
+ sigma_order : int = 3
+ Order of the polynomial describing the absorber profile (~dist^sigma_order).
+ Must be non-negative.
+ sigma_min : float = 0.0
+ Minimum value of the absorber conductivity.
+ Units of 2*EPSILON_0/dt.
+ Must be non non-negative.
+ sigma_max : float = 1.5
+ Maximum value of the absorber conductivity.
+ Units of 2*EPSILON_0/dt.
+ Must be non non-negative.
+
+ Example
+ -------
+ >>> params = AbsorberParams(sigma_order=3, sigma_min=0.0, sigma_max=1.5)
+ """
+
+ sigma_order: pydantic.NonNegativeInt = 3
+ sigma_min: pydantic.NonNegativeFloat = 0.0
+ sigma_max: pydantic.NonNegativeFloat = 1.5
class PMLParams(AbsorberParams):
- """Extra parameters needed for complex frequency-shifted PML. Kappa is dimensionless, alpha
- is in the same units as sigma."""
+ """Specifies full set of parameters needed for complex, frequency-shifted PML.
+
+ Parameters
+ ----------
+ sigma_order : int = 3
+ Order of the polynomial describing the absorber profile (sigma~dist^sigma_order).
+ Must be non-negative.
+ sigma_min : float = 0.0
+ Minimum value of the absorber conductivity.
+ Units of 2*EPSILON_0/dt.
+ Must be non-negative.
+ sigma_max : float = 1.5
+ Maximum value of the absorber conductivity.
+ Units of 2*EPSILON_0/dt.
+ Must be non-negative.
+ kappa_order : int = 3
+ Order of the polynomial describing the PML kappa profile (kappa~dist^kappa_order).
+ Must be non-negative.
+ kappa_min : float = 0.0
+ Minimum value of the PML kappa.
+ Dimensionless.
+ Must be non-negative.
+ kappa_max : float = 1.5
+ Maximum value of the PML kappa.
+ Dimensionless.
+ Must be non-negative.
+ alpha_order : int = 3
+ Order of the polynomial describing the PML alpha profile (alpha~dist^alpha_order).
+ Must be non-negative.
+ alpha_min : float = 0.0
+ Minimum value of the PML alpha.
+ Units of 2*EPSILON_0/dt.
+ Must be non-negative.
+ alpha_max : float = 1.5
+ Maximum value of the PML alpha.
+ Units of 2*EPSILON_0/dt.
+ Must be non-negative.
+
+ Example
+ -------
+ >>> params = PMLParams(sigma_order=3, sigma_min=0.0, sigma_max=1.5, kappa_min=0.0)
+ """
+
+ kappa_order: pydantic.NonNegativeInt = 3
+ kappa_min: pydantic.NonNegativeFloat = 0.0
+ kappa_max: pydantic.NonNegativeFloat = 1.5
+ alpha_order: pydantic.NonNegativeInt = 3
+ alpha_min: pydantic.NonNegativeFloat = 0.0
+ alpha_max: pydantic.NonNegativeFloat = 1.5
- kappa_order: pydantic.NonNegativeInt
- kappa_min: pydantic.NonNegativeFloat
- kappa_max: pydantic.NonNegativeFloat
- alpha_order: pydantic.NonNegativeInt
- alpha_min: pydantic.NonNegativeFloat
- alpha_max: pydantic.NonNegativeFloat
+""" Default parameters """
-AbsorberPs = AbsorberParams(sigma_order=3, sigma_min=0.0, sigma_max=6.4)
-StandardPs = PMLParams(
+DefaultAbsorberParameters = AbsorberParams(sigma_order=3, sigma_min=0.0, sigma_max=6.4)
+DefaultPMLParameters = PMLParams(
sigma_order=3,
sigma_min=0.0,
sigma_max=1.5,
@@ -41,7 +103,7 @@ class PMLParams(AbsorberParams):
alpha_min=0.0,
alpha_max=0.0,
)
-StablePs = PMLParams(
+DefaultStablePMLParameters = PMLParams(
sigma_order=3,
sigma_min=0.0,
sigma_max=1.0,
@@ -53,11 +115,24 @@ class PMLParams(AbsorberParams):
alpha_max=0.9,
)
+""" PML specifications """
-class AbsorberSpec(Tidy3dBaseModel):
- """Specifies the absorber along a single dimension."""
+
+class AbsorberSpec(Tidy3dBaseModel, ABC):
+ """Abstract base class.
+ Specifies the generic absorber properties along a single dimension.
+
+ Parameters
+ ----------
+ num_layers : int = 12
+ Number of layers of standard PML to add to + and - boundaries.
+ Must be non-negative.
+ parameters : :class:`AbsorberParams`
+ Parameters to fine tune the absorber profile and properties.
+ """
num_layers: pydantic.NonNegativeInt
+ parameters: AbsorberParams
class PML(AbsorberSpec):
@@ -65,9 +140,10 @@ class PML(AbsorberSpec):
Parameters
----------
- num_layers : ``int``, optional
- Number of layers of PML to add to + and - boundaries, default = 12.
- pml_params : :class:PMLParams
+ num_layers : int = 12
+ Number of layers of standard PML to add to + and - boundaries.
+ Must be non-negative.
+ parameters : :class:`PMLParams` = DefaultPMLParameters
Parameters of the complex frequency-shifted absorption poles.
Example
@@ -76,41 +152,50 @@ class PML(AbsorberSpec):
"""
num_layers: pydantic.NonNegativeInt = 12
- parameters: PMLParams = StandardPs
+ parameters: PMLParams = DefaultPMLParameters
class StablePML(AbsorberSpec):
"""Specifies a 'stable' PML along a single dimension.
+ This PML deals handles possbly divergent simulations better, but at the expense of more layers.
Parameters
----------
- num_layers : ``int``, optional
- Number of layers of PML to add to + and - boundaries, default = 40.
+ num_layers : int = 40
+ Number of layers of stable PML to add to + and - boundaries.
+ Must be non-negative.
+ parameters : Literal[DefaultStablePMLParameters] = DefaultStablePMLParameters
+ "Stable" parameters of the complex frequency-shifted absorption poles.
Example
-------
- >>> pml = StablePML(num_layers=100)
+ >>> pml = StablePML(num_layers=40)
"""
num_layers: pydantic.NonNegativeInt = 40
- parameters: Literal[StablePs] = StablePs
+ parameters: Literal[DefaultStablePMLParameters] = DefaultStablePMLParameters
class Absorber(AbsorberSpec):
- """Specifies an adiab absorber along a single dimension.
+ """Specifies an adiabatic absorber along a single dimension.
+ This absorber is well-suited for dispersive materials
+ intersecting with absorbing edges of the simulation at the expense of more layers.
Parameters
----------
- num_layers : ``int``, optional
- Number of layers of PML to add to + and - boundaries, default = 40.
+ num_layers : int = 40
+ Number of layers of absorber to add to + and - boundaries.
+ parameters : :class:`AbsorberParams` = DefaultAbsorberParameters
+ General absorber parameters.
Example
-------
- >>> pml = Absorber(num_layers=100)
+ >>> pml = Absorber(num_layers=40)
"""
num_layers: pydantic.NonNegativeInt = 40
- parameters: AbsorberParams = AbsorberPs
+ parameters: AbsorberParams = DefaultAbsorberParameters
+# pml types allowed in simulation init
PMLTypes = Union[PML, StablePML, Absorber, None]
diff --git a/tidy3d/components/simulation.py b/tidy3d/components/simulation.py
index 699ab38e4c..d44793e5d1 100644
--- a/tidy3d/components/simulation.py
+++ b/tidy3d/components/simulation.py
@@ -1,4 +1,3 @@
-# pylint: disable=unused-import
""" Container holding all information about simulation and its components"""
from typing import Dict, Tuple, List
@@ -10,31 +9,34 @@
from mpl_toolkits.axes_grid1 import make_axes_locatable
from descartes import PolygonPatch
-from .types import Symmetry, Ax, Shapely, FreqBound
from .validators import assert_unique_names, assert_objects_in_sim_bounds, set_names
-from .geometry import Box, PolySlab, Cylinder, Sphere
-from .types import Symmetry, Ax, Shapely, FreqBound, Numpy
from .geometry import Box
+from .types import Symmetry, Ax, Shapely, FreqBound
from .grid import Coords1D, Grid, Coords
from .medium import Medium, MediumType, eps_complex_to_nk
from .structure import Structure
-from .source import SourceType, VolumeSource, GaussianPulse
-from .monitor import MonitorType, FieldMonitor, FluxMonitor
+from .source import SourceType
+from .monitor import MonitorType
from .pml import PMLTypes, PML
from .viz import StructMediumParams, StructEpsParams, PMLParams, SymParams, add_ax_if_none
from ..constants import inf, C_0
-from ..log import log, SetupError
+from ..log import log
+
+# for docstring examples
+from .geometry import Sphere, Cylinder, PolySlab # pylint:disable=unused-import
+from .source import VolumeSource, GaussianPulse # pylint:disable=unused-import
+from .monitor import FieldMonitor, FluxMonitor # pylint:disable=unused-import
# technically this is creating a circular import issue because it calls tidy3d/__init__.py
# from .. import __version__ as version_number
class Simulation(Box): # pylint:disable=too-many-public-methods
- """Contains all information about simulation.
+ """Contains all information about Tidy3d simulation.
Parameters
----------
- center : Tuple[float, float, float] = ``(0.0, 0.0, 0.0)``
+ center : Tuple[float, float, float] = (0.0, 0.0, 0.0)
(microns) Center of simulation domain in x, y, and z.
size : Tuple[float, float, float]
(microns) Size of simulation domain in x, y, and z.
@@ -42,35 +44,42 @@ class Simulation(Box): # pylint:disable=too-many-public-methods
grid_size : Tuple[float, float, float]
(microns) Grid size along x, y, and z.
Each element must be non-negative.
- run_time : float = ``0.0``
- (seconds) Maximum run time of simulation.
- If ``shutoff`` specified, simulation will terminate early when shutoff condition met.
+ run_time : float = 0.0
+ Total electromagnetic evolution time in seconds.
+ Note: If ``shutoff`` specified, simulation will terminate early when shutoff condition met.
Must be non-negative.
- medium : :class:`Medium` = ``Medium(permittivity=1.0)``
+ medium : :class:`Medium` or :class:`PoleResidue` or :class:`Lorentz` or :class:`Sellmeier` or :class:`Debye` = ``Medium(permittivity=1.0)``
Background :class:`tidy3d.Medium` of simulation, defaults to air.
- structures : List[:class:`Structure`] = ``{}``
- Structures in simulation.
- Structures defined later in this list override the simulation material properties in
+ structures : List[:class:`Structure`] = []
+ List of structures in simulation.
+ Note: Structures defined later in this list override the simulation material properties in
regions of spatial overlap.
- sources : List[:class:`Source`] = ``[]``
- Named mapping of electric current sources in the simulation.
- monitors : List[:class:`Monitor`] = ``[]``
- Named mapping of field and data monitors in the simulation.
+ sources : List[:class:`VolumeSource` or :class:`PlaneWave` or :class:`ModeSource`] = []
+ List of electric current sources injecting fields into the simulation.
+ monitors : List[:class:`FieldMonitor` or :class:`FieldTimeMonitor` or :class:`FluxMonitor` or :class:`FluxTimeMonitor` or :class:`ModeMonitor`] = []
+ List of monitors in the simulation.
+ Note: names stored in ``monitor.name`` are used to access data after simulation is run.
pml_layers : Tuple[:class:`AbsorberSpec`, :class:`AbsorberSpec`, :class:`AbsorberSpec`]
= ``(None, None, None)``
Specifications for the absorbing layers on x, y, and z edges.
Elements of ``None`` are assumed to have no absorber and use periodic boundary conditions.
- symmetry : Tuple[int, int, int] = ``(0, 0, 0)``
- Specifies symmetry in x, y, and z dimensions.
- Only values of 0, 1, and -1 are accepted and specify no symmetry, even symmetry, and
- odd symmetry, respectively.
- shutoff : float = ``1e-5``
- Value of the average intensity in the simulation relative to the maximum at which the
- simulation terminates.
- subpixel : bool = ``True``
+ symmetry : Tuple[int, int, int] = (0, 0, 0)
+ Tuple of integers defining reflection symmetry across a
+ plane bisecting the simulation domain normal to the x-, y-, and
+ z-axis, respectively. Each element can be ``0`` (no symmetry),
+ ``1`` (even, i.e. 'PMC' symmetry) or ``-1`` (odd, i.e. 'PEC'
+ symmetry).
+ Note that the vectorial nature of the fields must be taken into account to correctly
+ determine the symmetry value.
+ shutoff : float = 1e-5
+ Ratio of the instantaneous integrated E-field intensity to the maximum value
+ at which the simulation will automatically shut down.
+ Used to prevent extraneous run time of simulations with fully decayed fields.
+ Set to ``0`` to disable this feature.
+ subpixel : bool = True
If ``True``, uses subpixel averaging of the permittivity based on structure definition,
resulting in much higher accuracy for a given grid size.
- courant : float = ``0.9``
+ courant : float = 0.9
Courant stability factor, controls time step to spatial step ratio.
Lower values lead to more stable simulations for dispersive materials,
but result in longer simulation times.
@@ -173,51 +182,77 @@ def set_medium_names(cls, val, values):
# make sure all names are unique
_unique_structure_names = assert_unique_names("structures")
- _unique_medium_names = assert_unique_names("structures", check_mediums=True)
_unique_source_names = assert_unique_names("sources")
_unique_monitor_names = assert_unique_names("monitors")
+ # _unique_medium_names = assert_unique_names("structures", check_mediums=True)
# TODO:
# - check sources in medium freq range
# - check PW in homogeneous medium
# - check nonuniform grid covers the whole simulation domain
+ # - check any structures close to PML (in lambda) without intersecting.
""" Accounting """
@property
- def medium_map(self) -> Dict[Medium, pydantic.NonNegativeInt]:
- """``medium_map[medium]`` returns unique global index of :class:`Medium` in simulation.
+ def mediums(self) -> List[MediumType]:
+ """Returns set of distinct :class:`AbstractMedium` in simulation.
+
+ Returns
+ -------
+ Set[:class:`Medium` or :class:`PoleResidue` or :class:`Lorentz` or :class:`Sellmeier` or :class:`Debye`]
+ Set of distinct mediums in the simulation.
+ """
+ return {structure.medium for structure in self.structures}
+
+ @property
+ def medium_map(self) -> Dict[MediumType, pydantic.NonNegativeInt]:
+ """Returns dict mapping medium to index in material.
+ ``medium_map[medium]`` returns unique global index of :class:`AbstractMedium` in simulation.
Returns
-------
- {:class:`Medium`, ``int``}
- Mapping between a :class:`Medium` and it's index in the simulation.
+ Dict[:class:`Medium` or :class:`PoleResidue` or :class:`Lorentz` or :class:`Sellmeier` or :class:`Debye`, int]
+ Mapping between distinct mediums to index in simulation.
"""
- mediums = {structure.medium for structure in self.structures}
- return {medium: index for index, medium in enumerate(mediums)}
+ return {medium: index for index, medium in enumerate(self.mediums)}
""" Plotting """
@add_ax_if_none
- def plot(
- self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **kwargs
+ def plot( # pylint:disable=too-many-arguments
+ self,
+ x: float = None,
+ y: float = None,
+ z: float = None,
+ grid_lines: bool = False,
+ ax: Ax = None,
+ **kwargs,
) -> Ax:
- """Plot each of simulation's components on a plan defined by one nonzero x,y,z
- coordinate.
+ """Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate.
Parameters
----------
- x : ``float``
- Position of point in x direction.
- y : ``float``
- Position of point in y direction.
- z : ``float``
- Position of point in z direction.
- ax : Ax, optional
- Description
+ x : float = None
+ position of plane in x direction, only one of x, y, z must be specified to define plane.
+ y : float = None
+ position of plane in y direction, only one of x, y, z must be specified to define plane.
+ z : float = None
+ position of plane in z direction, only one of x, y, z must be specified to define plane.
+ grid_lines : bool = False
+ If true, displays FDTD cell boundaries on plot.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
**kwargs
- Description
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
"""
ax = self.plot_structures(ax=ax, x=x, y=y, z=z, **kwargs)
@@ -225,7 +260,9 @@ def plot(
ax = self.plot_monitors(ax=ax, x=x, y=y, z=z, **kwargs)
ax = self.plot_symmetries(ax=ax, x=x, y=y, z=z, **kwargs)
ax = self.plot_pml(ax=ax, x=x, y=y, z=z, **kwargs)
- ax = self.set_plot_bounds(ax=ax, x=x, y=y, z=z)
+ if grid_lines:
+ ax = self.plot_cells(ax=ax, x=x, y=y, z=z)
+ ax = self._set_plot_bounds(ax=ax, x=x, y=y, z=z)
return ax
@add_ax_if_none
@@ -235,11 +272,37 @@ def plot_eps( # pylint: disable=too-many-arguments
y: float = None,
z: float = None,
freq: float = None,
+ grid_lines: bool = False,
ax: Ax = None,
**kwargs,
) -> Ax:
- """Plot each of simulation's components on a plane where structures permittivities are
- plotted in grayscale.
+ """Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate.
+ The permittivity is plotted in grayscale based on its value at the specified frequency.
+
+ Parameters
+ ----------
+ x : float = None
+ position of plane in x direction, only one of x, y, z must be specified to define plane.
+ y : float = None
+ position of plane in y direction, only one of x, y, z must be specified to define plane.
+ z : float = None
+ position of plane in z direction, only one of x, y, z must be specified to define plane.
+ freq : float = None
+ Frequency to evaluate the relative permittivity of all mediums.
+ If not specified, evaluates at infinite frequency.
+ grid_lines : bool = False
+ If true, displays FDTD cell boundaries on plot.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+ **kwargs
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
"""
ax = self.plot_structures_eps(freq=freq, cbar=True, ax=ax, x=x, y=y, z=z, **kwargs)
@@ -247,14 +310,37 @@ def plot_eps( # pylint: disable=too-many-arguments
ax = self.plot_monitors(ax=ax, x=x, y=y, z=z, **kwargs)
ax = self.plot_symmetries(ax=ax, x=x, y=y, z=z, **kwargs)
ax = self.plot_pml(ax=ax, x=x, y=y, z=z, **kwargs)
- ax = self.set_plot_bounds(ax=ax, x=x, y=y, z=z)
+ if grid_lines:
+ ax = self.plot_cells(ax=ax, x=x, y=y, z=z)
+ ax = self._set_plot_bounds(ax=ax, x=x, y=y, z=z)
return ax
@add_ax_if_none
def plot_structures(
self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **kwargs
) -> Ax:
- """plot all of simulation's structures as distinct materials."""
+ """Plot each of simulation's structures on a plane defined by one nonzero x,y,z coordinate.
+
+ Parameters
+ ----------
+ x : float = None
+ position of plane in x direction, only one of x, y, z must be specified to define plane.
+ y : float = None
+ position of plane in y direction, only one of x, y, z must be specified to define plane.
+ z : float = None
+ position of plane in z direction, only one of x, y, z must be specified to define plane.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+ **kwargs
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
+ """
medium_map = self.medium_map
medium_shapes = self._filter_plot_structures(x=x, y=y, z=z)
for (medium, shape) in medium_shapes:
@@ -265,12 +351,12 @@ def plot_structures(
kwargs_struct["facecolor"] = "white"
patch = PolygonPatch(shape, **kwargs_struct)
ax.add_artist(patch)
- ax = self.set_plot_bounds(ax=ax, x=x, y=y, z=z)
+ ax = self._set_plot_bounds(ax=ax, x=x, y=y, z=z)
return ax
@staticmethod
def _add_cbar(eps_min: float, eps_max: float, ax: Ax = None) -> None:
- """add colorbar to eps plot"""
+ """Add a colorbar to eps plot."""
norm = mpl.colors.Normalize(vmin=eps_min, vmax=eps_max)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.15)
@@ -288,7 +374,32 @@ def plot_structures_eps( # pylint: disable=too-many-arguments,too-many-locals
ax: Ax = None,
**kwargs,
) -> Ax:
- """Plots all of simulation's structures as permittivity grayscale."""
+ """Plot each of simulation's structures on a plane defined by one nonzero x,y,z coordinate.
+ The permittivity is plotted in grayscale based on its value at the specified frequency.
+
+ Parameters
+ ----------
+ x : float = None
+ position of plane in x direction, only one of x, y, z must be specified to define plane.
+ y : float = None
+ position of plane in y direction, only one of x, y, z must be specified to define plane.
+ z : float = None
+ position of plane in z direction, only one of x, y, z must be specified to define plane.
+ freq : float = None
+ Frequency to evaluate the relative permittivity of all mediums.
+ If not specified, evaluates at infinite frequency.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+ **kwargs
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
+ """
if freq is None:
freq = inf
eps_list = [s.medium.eps_model(freq).real for s in self.structures]
@@ -305,36 +416,99 @@ def plot_structures_eps( # pylint: disable=too-many-arguments,too-many-locals
ax.add_artist(patch)
if cbar:
self._add_cbar(eps_min=eps_min, eps_max=eps_max, ax=ax)
- ax = self.set_plot_bounds(ax=ax, x=x, y=y, z=z)
+ ax = self._set_plot_bounds(ax=ax, x=x, y=y, z=z)
return ax
@add_ax_if_none
def plot_sources(
self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **kwargs
) -> Ax:
- """Plots each of simulation's sources on plane."""
+ """Plot each of simulation's sources on a plane defined by one nonzero x,y,z coordinate.
+
+ Parameters
+ ----------
+ x : float = None
+ position of plane in x direction, only one of x, y, z must be specified to define plane.
+ y : float = None
+ position of plane in y direction, only one of x, y, z must be specified to define plane.
+ z : float = None
+ position of plane in z direction, only one of x, y, z must be specified to define plane.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+ **kwargs
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
+ """
for source in self.sources:
if source.intersects_plane(x=x, y=y, z=z):
ax = source.plot(ax=ax, x=x, y=y, z=z, **kwargs)
- ax = self.set_plot_bounds(ax=ax, x=x, y=y, z=z)
+ ax = self._set_plot_bounds(ax=ax, x=x, y=y, z=z)
return ax
@add_ax_if_none
def plot_monitors(
self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **kwargs
) -> Ax:
- """Plots each of simulation's monitors on plane."""
+ """Plot each of simulation's monitors on a plane defined by one nonzero x,y,z coordinate.
+
+ Parameters
+ ----------
+ x : float = None
+ position of plane in x direction, only one of x, y, z must be specified to define plane.
+ y : float = None
+ position of plane in y direction, only one of x, y, z must be specified to define plane.
+ z : float = None
+ position of plane in z direction, only one of x, y, z must be specified to define plane.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+ **kwargs
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
+ """
for monitor in self.monitors:
if monitor.intersects_plane(x=x, y=y, z=z):
ax = monitor.plot(ax=ax, x=x, y=y, z=z, **kwargs)
- ax = self.set_plot_bounds(ax=ax, x=x, y=y, z=z)
+ ax = self._set_plot_bounds(ax=ax, x=x, y=y, z=z)
return ax
@add_ax_if_none
def plot_symmetries(
self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **kwargs
) -> Ax:
- """plots each of the non-zero symmetries"""
+ """Plot each of simulation's symmetries on a plane defined by one nonzero x,y,z coordinate.
+
+ Parameters
+ ----------
+ x : float = None
+ position of plane in x direction, only one of x, y, z must be specified to define plane.
+ y : float = None
+ position of plane in y direction, only one of x, y, z must be specified to define plane.
+ z : float = None
+ position of plane in z direction, only one of x, y, z must be specified to define plane.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+ **kwargs
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
+ """
for sym_axis, sym_value in enumerate(self.symmetry):
if sym_value == 0:
continue
@@ -346,13 +520,19 @@ def plot_symmetries(
if sym_box.intersects_plane(x=x, y=y, z=z):
new_kwargs = SymParams(sym_value=sym_value).update_params(**kwargs)
ax = sym_box.plot(ax=ax, x=x, y=y, z=z, **new_kwargs)
- ax = self.set_plot_bounds(ax=ax, x=x, y=y, z=z)
- ax = self.set_plot_bounds(ax=ax, x=x, y=y, z=z)
+ ax = self._set_plot_bounds(ax=ax, x=x, y=y, z=z)
+ ax = self._set_plot_bounds(ax=ax, x=x, y=y, z=z)
return ax
@property
def num_pml_layers(self) -> List[Tuple[float, float]]:
- """Number of PML layers in all three axes and directions (-, +)."""
+ """Number of absorbing layers in all three axes and directions (-, +).
+
+ Returns
+ -------
+ List[Tuple[float, float]]
+ List containing the number of absorber layers in - and + boundaries.
+ """
num_layers = []
for pml_axis, pml_layer in enumerate(self.pml_layers):
if self.symmetry[pml_axis] != 0:
@@ -363,12 +543,18 @@ def num_pml_layers(self) -> List[Tuple[float, float]]:
@property
def pml_thicknesses(self) -> List[Tuple[float, float]]:
- """Thicknesses (um) of PML in all three axes and directions (-, +)"""
+ """Thicknesses (um) of absorbers in all three axes and directions (-, +)
+
+ Returns
+ -------
+ List[Tuple[float, float]]
+ List containing the absorber thickness (micron) in - and + boundaries.
+ """
num_layers = self.num_pml_layers
pml_thicknesses = []
- for boundaries in self.grid.boundaries.dict().values():
- thick_l = boundaries[num_layers[0]] - boundaries[0]
- thick_r = boundaries[-1] - boundaries[-1 - num_layers[1]]
+ for num_layer, boundaries in zip(num_layers, self.grid.boundaries.dict().values()):
+ thick_l = boundaries[num_layer[0]] - boundaries[0]
+ thick_r = boundaries[-1] - boundaries[-1 - num_layer[1]]
pml_thicknesses.append((thick_l, thick_r))
return pml_thicknesses
@@ -376,7 +562,29 @@ def pml_thicknesses(self) -> List[Tuple[float, float]]:
def plot_pml(
self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **kwargs
) -> Ax:
- """plots each of simulation's PML regions"""
+ """Plot each of simulation's absorbing boundaries
+ on a plane defined by one nonzero x,y,z coordinate.
+
+ Parameters
+ ----------
+ x : float = None
+ position of plane in x direction, only one of x, y, z must be specified to define plane.
+ y : float = None
+ position of plane in y direction, only one of x, y, z must be specified to define plane.
+ z : float = None
+ position of plane in z direction, only one of x, y, z must be specified to define plane.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+ **kwargs
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
+ """
kwargs = PMLParams().update_params(**kwargs)
pml_thicks = self.pml_thicknesses
for pml_axis, pml_layer in enumerate(self.pml_layers):
@@ -391,11 +599,59 @@ def plot_pml(
pml_box = Box(center=pml_center, size=pml_size)
if pml_box.intersects_plane(x=x, y=y, z=z):
ax = pml_box.plot(ax=ax, x=x, y=y, z=z, **kwargs)
- ax = self.set_plot_bounds(ax=ax, x=x, y=y, z=z)
+ ax = self._set_plot_bounds(ax=ax, x=x, y=y, z=z)
return ax
- def set_plot_bounds(self, ax: Ax, x: float = None, y: float = None, z: float = None) -> Ax:
- """sets the xy limits of the simulation, useful after plotting"""
+ @add_ax_if_none
+ def plot_cells(self, x: float = None, y: float = None, z: float = None, ax: Ax = None) -> Ax:
+ """Plot the cell boundaries as lines on a plane defined by one nonzero x,y,z coordinate.
+
+ Parameters
+ ----------
+ x : float = None
+ position of plane in x direction, only one of x, y, z must be specified to define plane.
+ y : float = None
+ position of plane in y direction, only one of x, y, z must be specified to define plane.
+ z : float = None
+ position of plane in z direction, only one of x, y, z must be specified to define plane.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
+ """
+ cell_boundaries = self.grid.boundaries
+ axis, _ = self._parse_xyz_kwargs(x=x, y=y, z=z)
+ _, (axis_x, axis_y) = self.pop_axis([0, 1, 2], axis=axis)
+ boundaries_x = cell_boundaries.dict()["xyz"[axis_x]]
+ boundaries_y = cell_boundaries.dict()["xyz"[axis_y]]
+ for x_pos in boundaries_x:
+ ax.axvline(x=x_pos, linestyle="-", color="black", linewidth=0.2)
+ for y_pos in boundaries_y:
+ ax.axhline(y=y_pos, linestyle="-", color="black", linewidth=0.2)
+ return ax
+
+ def _set_plot_bounds(self, ax: Ax, x: float = None, y: float = None, z: float = None) -> Ax:
+ """Sets the xy limits of the simulation at a plane, useful after plotting.
+
+ Parameters
+ ----------
+ ax : matplotlib.axes._subplots.Axes
+ Matplotlib axes to set bounds on.
+ x : float = None
+ position of plane in x direction, only one of x, y, z must be specified to define plane.
+ y : float = None
+ position of plane in y direction, only one of x, y, z must be specified to define plane.
+ z : float = None
+ position of plane in z direction, only one of x, y, z must be specified to define plane.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The axes after setting the boundaries.
+ """
axis, _ = self._parse_xyz_kwargs(x=x, y=y, z=z)
_, ((xmin, ymin), (xmax, ymax)) = self._pop_bounds(axis=axis)
@@ -408,8 +664,22 @@ def set_plot_bounds(self, ax: Ax, x: float = None, y: float = None, z: float = N
def _filter_plot_structures(
self, x: float = None, y: float = None, z: float = None
) -> List[Tuple[Medium, Shapely]]:
- """Compute list of (medium, shapely) to plot on plane specified by {x,y,z}.
+ """Compute list of shapes to plot on plane specified by {x,y,z}.
Overlaps are removed or merged depending on medium.
+
+ Parameters
+ ----------
+ x : float = None
+ position of plane in x direction, only one of x, y, z must be specified to define plane.
+ y : float = None
+ position of plane in y direction, only one of x, y, z must be specified to define plane.
+ z : float = None
+ position of plane in z direction, only one of x, y, z must be specified to define plane.
+
+ Returns
+ -------
+ List[Tuple[:class:`Medium` or :class:`PoleResidue` or :class:`Lorentz` or :class:`Sellmeier` or :class:`Debye`, shapely.geometry.base.BaseGeometry]]
+ List of shapes and mediums on the plane after merging.
"""
shapes = []
@@ -431,8 +701,19 @@ def _filter_plot_structures(
@staticmethod
def _merge_shapes(shapes: List[Tuple[Medium, Shapely]]) -> List[Tuple[Medium, Shapely]]:
- """Merge list of (Medium, Shapely) by intersection with same medium
- edit background shapes to remove volume by intersection.
+ """Merge list of shapes and mediums on plae by intersection with same medium.
+ Edit background shapes to remove volume by intersection.
+
+ Parameters
+ ----------
+ shapes : List[Tuple[:class:`Medium` or :class:`PoleResidue` or :class:`Lorentz` or :class:`Sellmeier` or :class:`Debye`, shapely.geometry.base.BaseGeometry]]
+ Ordered list of shapes and their mediums on a plane.
+
+ Returns
+ -------
+ List[Tuple[:class:`Medium` or :class:`PoleResidue` or :class:`Lorentz` or :class:`Sellmeier` or :class:`Debye`, shapely.geometry.base.BaseGeometry]]
+ Shapes and their mediums on a plane
+ after merging and removing intersections with background.
"""
background_shapes = []
for medium, shape in shapes:
@@ -464,7 +745,14 @@ def _merge_shapes(shapes: List[Tuple[Medium, Shapely]]) -> List[Tuple[Medium, Sh
@property
def frequency_range(self) -> FreqBound:
- """range of frequencies spanning all sources' frequency dependence"""
+ """Range of frequencies spanning all sources' frequency dependence.
+
+ Returns
+ -------
+ Tuple[float, float]
+ Minumum and maximum frequencies of the power spectrum of the sources
+ at 5 standard deviations.
+ """
freq_min = min(source.frequency_range[0] for source in self.sources)
freq_max = max(source.frequency_range[1] for source in self.sources)
return (freq_min, freq_max)
@@ -473,21 +761,39 @@ def frequency_range(self) -> FreqBound:
@property
def dt(self) -> float:
- """compute time step (distance)"""
- dl_mins = [np.min(sizes) for sizes in self.grid.cell_sizes.dict().values()]
+ """Simulation time step (distance).
+
+ Returns
+ -------
+ float
+ Time step (seconds).
+ """
+ dl_mins = [np.min(sizes) for sizes in self.grid.sizes.dict().values()]
dl_sum_inv_sq = sum([1 / dl ** 2 for dl in dl_mins])
dl_avg = 1 / np.sqrt(dl_sum_inv_sq)
return self.courant * dl_avg / C_0
@property
def tmesh(self) -> Coords1D:
- """compute time steps"""
+ """FDTD time stepping points.
+
+ Returns
+ -------
+ np.ndarray
+ Times (seconds) that the simulation time steps through.
+ """
dt = self.dt
return np.arange(0.0, self.run_time + dt, dt)
@property
def grid(self) -> Grid:
- """:class:`Grid` interface to the spatial locations in Simulation"""
+ """FDTD grid spatial locations and information.
+
+ Returns
+ -------
+ :class:`Grid`
+ :class:`Grid` storing the spatial locations relevant to the simulation.
+ """
cell_boundary_dict = {}
zipped_vals = zip("xyz", self.grid_size, self.center, self.size, self.num_pml_layers)
for key, dl, center, size, num_layers in zipped_vals:
@@ -498,14 +804,28 @@ def grid(self) -> Grid:
if size_snapped != size:
log.warning(f"dl = {dl} not commensurate with simulation size = {size}")
bound_coords = center + np.linspace(-size_snapped / 2, size_snapped / 2, num_cells + 1)
- bound_coords = self.add_pml_to_bounds(num_layers, bound_coords)
+ bound_coords = self._add_pml_to_bounds(num_layers, bound_coords)
cell_boundary_dict[key] = bound_coords
boundaries = Coords(**cell_boundary_dict)
return Grid(boundaries=boundaries)
@staticmethod
- def add_pml_to_bounds(num_layers: Tuple[int, int], bounds: Numpy):
- """Append PML pixels at the beginning and end of bounds."""
+ def _add_pml_to_bounds(num_layers: Tuple[int, int], bounds: Coords1D):
+ """Append absorber layers to the beginning and end of the simulation bounds
+ along one dimension.
+
+ Parameters
+ ----------
+ num_layers : Tuple[int, int]
+ number of layers in the absorber + and - direction along one dimension.
+ bound_coords : np.ndarray
+ coordinates specifying boundaries between cells along one dimension.
+
+ Returns
+ -------
+ np.ndarray
+ New bound coordinates along dimension taking abosrber into account.
+ """
if bounds.size < 2:
return bounds
@@ -519,7 +839,13 @@ def add_pml_to_bounds(num_layers: Tuple[int, int], bounds: Numpy):
@property
def wvl_mat_min(self) -> float:
- """minimum wavelength in the material"""
+ """Minimum wavelength in the material.
+
+ Returns
+ -------
+ float
+ Minimum wavelength in the material (microns).
+ """
freq_max = max(source.source_time.freq0 for source in self.sources)
wvl_min = C_0 / min(freq_max)
eps_max = max(abs(structure.medium.get_eps(freq_max)) for structure in self.structures)
@@ -527,7 +853,18 @@ def wvl_mat_min(self) -> float:
return wvl_min / n_max
def discretize(self, box: Box) -> Grid:
- """returns subgrid containing only cells that intersect with Box"""
+ """Grid containing only cells that intersect with a :class:`Box`.
+
+ Parameters
+ ----------
+ box : :class:`Box`
+ Rectangular geometry within simulation to discretize.
+
+ Returns
+ -------
+ :class:`Grid`
+ The FDTD subgrid containing simulation points that intersect with ``box``.
+ """
if not self.intersects(box):
log.error(f"Box {box} is outside simulation, cannot discretize")
@@ -558,7 +895,27 @@ def discretize(self, box: Box) -> Grid:
return Grid(boundaries=sub_boundaries)
def epsilon(self, box: Box, freq: float = None) -> Dict[str, xr.DataArray]:
- """get data of permittivity at volume specified by box and freq"""
+ """Get array of permittivity at volume specified by box and freq
+
+ Parameters
+ ----------
+ box : :class:`Box`
+ Rectangular geometry specifying where to measure the permittivity.
+ freq : float = None
+ The frequency to evaluate the mediums at.
+ If not specified, evaluates at infinite frequency.
+
+ Returns
+ -------
+ Dict[str, xarray.DataArray]
+ Mapping of coordinate type to xarray DataArray containing permittivity data.
+ keys of dict are ``{'centers', 'boundaries', 'Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz'}``.
+ ``'centers'`` contains the permittivity at the yee cell centers.
+ `'boundaries'`` contains the permittivity at the corner intersections between yee cells.
+ ``'Ex'`` and other field keys contain the permittivity
+ at the corresponding field position in the yee lattice.
+ For details on xarray datasets, refer to `xarray's Documentaton `_.
+ """
sub_grid = self.discretize(box)
eps_background = self.medium.eps_model(freq)
diff --git a/tidy3d/components/source.py b/tidy3d/components/source.py
index 50ac03933e..ff8ab27e98 100644
--- a/tidy3d/components/source.py
+++ b/tidy3d/components/source.py
@@ -1,44 +1,62 @@
-"""Defines current sources."""
+"""Defines electric current sources for injecting light into simulation."""
from abc import ABC, abstractmethod
-from typing import Tuple, Union, Literal
+from typing import Union, Literal
import pydantic
import numpy as np
from .base import Tidy3dBaseModel
-from .types import Direction, Polarization, Ax, FreqBound
+from .types import Direction, Polarization, Ax, FreqBound, Array
from .validators import assert_plane, validate_name_str
from .geometry import Box
from .mode import Mode
from .viz import add_ax_if_none, SourceParams
from ..log import ValidationError
+from ..constants import inf # pylint:disable=unused-import
+
+# TODO: change directional source to something signifying its intent is to create a specific field.
+
+# width of pulse frequency range defition in units of standard deviation.
+WIDTH_STD = 5
class SourceTime(ABC, Tidy3dBaseModel):
- """Base class describing the time dependence of a source"""
+ """Base class describing the time dependence of a source."""
amplitude: pydantic.NonNegativeFloat = 1.0
phase: float = 0.0
@abstractmethod
- def amp_time(self, time):
+ def amp_time(self, time: float) -> complex:
"""Complex-valued source amplitude as a function of time.
- Args:
- time (float): time in seconds.
+ Parameters
+ ----------
+ time : float
+ Time in seconds.
+
+ Returns
+ -------
+ complex
+ Complex-valued source amplitude at that time..
"""
@add_ax_if_none
- def plot(self, times: float, ax: Ax = None) -> Ax:
- """plot the time series
-
- Args:
- times (float): Description
- ax (Ax, optional): Description
-
- Returns:
- Ax: Description
+ def plot(self, times: Array[float], ax: Ax = None) -> Ax:
+ """Plot the complex-valued amplitude of the source time-dependence.
+
+ Parameters
+ ----------
+ times : np.ndarray
+ Array of times to plot source at in seconds.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
"""
times = np.array(times)
amp_complex = self.amp_time(times)
@@ -56,11 +74,11 @@ def plot(self, times: float, ax: Ax = None) -> Ax:
@property
@abstractmethod
def frequency_range(self) -> FreqBound:
- """frequency range for a source time"""
+ """Frequency range within 5 standard deviations of the central frequency."""
class Pulse(SourceTime, ABC):
- """Source ramps up and oscillates with freq0"""
+ """A source time that ramps up with some ``fwidth`` and oscillates at ``freq0``."""
freq0: pydantic.PositiveFloat
fwidth: pydantic.PositiveFloat # currently standard deviation
@@ -68,23 +86,53 @@ class Pulse(SourceTime, ABC):
@property
def frequency_range(self) -> FreqBound:
- """frequency range for a source time"""
- width_std = 5
- return (self.freq0 - width_std * self.fwidth, self.freq0 + width_std * self.fwidth)
+ """Frequency range within 5 standard deviations of the central frequency.
+
+ Returns
+ -------
+ Tuple[float, float]
+ Minimum and maximum frequencies of the
+ :class:`GaussianPulse` or :class:`ContinuousWave` power
+ within 5 standard deviations.
+ """
+ return (self.freq0 - WIDTH_STD * self.fwidth, self.freq0 + WIDTH_STD * self.fwidth)
class GaussianPulse(Pulse):
- """A gaussian pulse time dependence"""
-
- def amp_time(self, time):
- """complex amplitude as a function of time
+ """Source time dependence that describes a Gaussian pulse.
+
+ Parameters
+ ----------
+ freq0 : float
+ Central oscillating frequency in Hz.
+ Must be positive.
+ fwidth : float
+ Standard deviation width of the Gaussian pulse in Hz.
+ Must be positive.
+ offset : float = 5.0
+ Time of the maximum value of the pulse
+ in units of 1/fwidth.
+ Must be greater than 2.5.
+
+ Example
+ -------
+ >>> pulse = GaussianPulse(freq0=200e12, fwidth=20e12)
+ """
+
+ def amp_time(self, time: float) -> complex:
+ """Complex-valued source amplitude as a function of time.
- Args:
- time (TYPE): Description
+ Parameters
+ ----------
+ time : float
+ Time in seconds.
- Returns:
- TYPE: Description
+ Returns
+ -------
+ complex
+ Complex-valued source amplitude at supplied time.
"""
+
twidth = 1.0 / (2 * np.pi * self.fwidth)
omega0 = 2 * np.pi * self.freq0
time_shifted = time - self.offset * twidth
@@ -97,17 +145,40 @@ def amp_time(self, time):
return const * offset * oscillation * amp
-class CW(Pulse):
- """ramping up and holding steady"""
-
- def amp_time(self, time):
- """complex amplitude as a function of time
+class ContinuousWave(Pulse):
+ """Source time dependence that ramps up to continuous oscillation
+ and holds until end of simulation.
+
+ Parameters
+ ----------
+ freq0 : float
+ Central oscillating frequency in Hz.
+ Must be positive.
+ fwidth : float
+ Standard deviation width of the Gaussian pulse in Hz.
+ Must be positive.
+ offset : float = 5.0
+ Time of the maximum value of the pulse
+ in units of 1/fwidth.
+ Must be greater than 2.5.
+
+ Example
+ -------
+ >>> cw = ContinuousWave(freq0=200e12, fwidth=20e12)
+ """
+
+ def amp_time(self, time: float) -> complex:
+ """Complex-valued source amplitude as a function of time.
- Args:
- time (TYPE): Description
+ Parameters
+ ----------
+ time : float
+ Time in seconds.
- Returns:
- TYPE: Description
+ Returns
+ -------
+ complex
+ Complex-valued source amplitude at supplied time.
"""
twidth = 1.0 / (2 * np.pi * self.fwidth)
omega0 = 2 * np.pi * self.freq0
@@ -121,13 +192,26 @@ def amp_time(self, time):
return const * offset * oscillation * amp
-SourceTimeType = Union[GaussianPulse, CW]
+SourceTimeType = Union[GaussianPulse, ContinuousWave]
""" Source objects """
class Source(Box, ABC):
- """Template for all sources, all have Box geometry"""
+ """Abstract base class for all sources.
+
+ Parameters
+ ----------
+ center : Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ Center of source in x,y,z.
+ size : Tuple[float, float, float]
+ Size of source in x,y,z.
+ All elements must be non-negative.
+ source_time : :class:`GaussianPulse` or :class:`ContinuousWave`
+ Specification of time-dependence of source.
+ name : str = None
+ Optional name for source.
+ """
source_time: SourceTimeType
name: str = None
@@ -138,26 +222,101 @@ class Source(Box, ABC):
def plot(
self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **kwargs
) -> Ax:
- """plot source geometry"""
+ """Plot the source geometry on a cross section plane.
+
+ Parameters
+ ----------
+ x : float = None
+ Position of plane in x direction, only one of x,y,z can be specified to define plane.
+ y : float = None
+ Position of plane in y direction, only one of x,y,z can be specified to define plane.
+ z : float = None
+ Position of plane in z direction, only one of x,y,z can be specified to define plane.
+ ax : matplotlib.axes._subplots.Axes = None
+ Matplotlib axes to plot on, if not specified, one is created.
+ **patch_kwargs
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_. #pylint:disable=line-too-long
+
+ Returns
+ -------
+ matplotlib.axes._subplots.Axes
+ The supplied or created matplotlib axes.
+ """
kwargs = SourceParams().update_params(**kwargs)
ax = self.geometry.plot(x=x, y=y, z=z, ax=ax, **kwargs)
return ax
@property
def geometry(self):
- """box representation of self"""
+ """:class:`Box` representation of source.
+
+ Returns
+ -------
+ :class:`Box`
+ Representation of the source geometry as a :class:`Box`.
+ """
return Box(center=self.center, size=self.size)
class VolumeSource(Source):
- """Volume Source with time dependence and polarization"""
+ """Source spanning a rectangular volume with uniform time dependence.
+
+ Parameters
+ ----------
+ center : Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ Center of source in x,y,z.
+ size : Tuple[float, float, float]
+ Size of source in x,y,z.
+ All elements must be non-negative.
+ source_time : :class:`GaussianPulse` or :class:`ContinuousWave`
+ Specification of time-dependence of source.
+ polarization : str
+ Specifies the direction and type of current component.
+ Must be in ``{'Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz'}``.
+ For example, ``'Ez'`` specifies electric current source polarized along the z-axis.
+ name : str = None
+ Optional name for source.
+
+ Example
+ -------
+ >>> pulse = GaussianPulse(freq0=200e12, fwidth=20e12)
+ >>> pt_source = VolumeSource(size=(0,0,0), source_time=pulse, polarization='Ex')
+ """
polarization: Polarization
type: Literal["VolumeSource"] = "VolumeSource"
class ModeSource(Source):
- """Modal profile on finite extent plane"""
+ """Modal profile on finite extent plane
+
+ Parameters
+ ----------
+ center : Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ Center of source in x,y,z.
+ size : Tuple[float, float, float]
+ Size of source in x,y,z.
+ One component must be 0.0 to define plane.
+ All elements must be non-negative.
+ source_time : :class:`GaussianPulse` or :class:`ContinuousWave`
+ Specification of time-dependence of source.
+ direction : str
+ Specifies the sign of propagation.
+ Must be in ``{'+', '-'}``.
+ Note: propagation occurs along dimension normal to plane.
+ mode : :class:`Mode`
+ Specification of the mode being injected by source.
+ name : str = None
+ Optional name for source.
+
+ Example
+ -------
+ >>> pulse = GaussianPulse(freq0=200e12, fwidth=20e12)
+ >>> mode = Mode(mode_index=1, num_modes=3)
+ >>> mode_source = ModeSource(size=(10,10,0), source_time=pulse, mode=mode, direction='-')
+ """
direction: Direction
mode: Mode
@@ -166,7 +325,7 @@ class ModeSource(Source):
class DirectionalSource(Source, ABC):
- """A Planar Source with uni-directional propagation"""
+ """A Planar Source with uni-directional propagation."""
direction: Direction
polarization: Polarization
@@ -175,7 +334,7 @@ class DirectionalSource(Source, ABC):
@pydantic.root_validator(allow_reuse=True)
def polarization_is_orthogonal(cls, values):
- """ensure we dont allow a polarization parallel to the propagation direction"""
+ """Ensure the polarization is orthogonal to the propagation direction."""
size = values.get("size")
polarization = values.get("polarization")
assert size is not None
@@ -193,17 +352,90 @@ def polarization_is_orthogonal(cls, values):
class PlaneWave(DirectionalSource):
- """uniform distribution on infinite extent plane"""
+ """Uniform distribution on infinite extent plane.
+
+ Parameters
+ ----------
+ center : Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ Center of source in x,y,z.
+ size : Tuple[float, float, float]
+ Size of source in x,y,z.
+ One component must be 0.0 to define plane.
+ All elements must be non-negative.
+ source_time : :class:`GaussianPulse` or :class:`ContinuousWave`
+ Specification of time-dependence of source.
+ polarization : str
+ Specifies the direction and type of current component.
+ Must be in ``{'Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz'}``.
+ For example, ``'Ez'`` specifies electric current source polarized along the z-axis.
+ direction : str
+ Specifies the sign of propagation.
+ Must be in ``{'+', '-'}``.
+ Note: propagation occurs along dimension normal to plane.
+ mode : :class:`Mode`
+ Specification of the mode being injected by source.
+ name : str = None
+ Optional name for source.
+
+ Example
+ -------
+ >>> pulse = GaussianPulse(freq0=200e12, fwidth=20e12)
+ >>> pw_source = PlaneWave(size=(inf,0,inf), source_time=pulse, polarization='Ex', direction='+')
+ """
type: Literal["PlaneWave"] = "PlaneWave"
class GaussianBeam(DirectionalSource):
- """guassian distribution on finite extent plane"""
-
- waist_size: Tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat]
+ """guassian distribution on finite extent plane
+
+ Parameters
+ ----------
+ center : Tuple[float, float, float] = (0.0, 0.0, 0.0)
+ Center of source in x,y,z.
+ size : Tuple[float, float, float]
+ Size of source in x,y,z.
+ One component must be 0.0 to define plane.
+ All elements must be non-negative.
+ source_time : :class:`GaussianPulse` or :class:`ContinuousWave`
+ Specification of time-dependence of source.
+ polarization : str
+ Specifies the direction and type of current component.
+ Must be in ``{'Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz'}``.
+ For example, ``'Ez'`` specifies electric current source polarized along the z-axis.
+ direction : str
+ Specifies the sign of propagation.
+ Must be in ``{'+', '-'}``.
+ Note: propagation occurs along dimension normal to plane.
+ waist_radius: float
+ Radius of the beam at the waist (um).
+ Must be positive.
+ angle_theta: float = 0.0
+ Angle of propagation of the beam with respect to the normal axis (rad).
+ angle_phi: float = 0.0
+ Angle of propagation of the beam with respect to parallel axis (rad).
+ pol_angle: float = 0.0
+ Angle of the polarization with respect to the parallel axis (rad).
+ name : str = None
+ Optional name for source.
+
+ Example
+ -------
+ >>> pulse = GaussianPulse(freq0=200e12, fwidth=20e12)
+ >>> gauss = GaussianBeam(
+ ... size=(0,3,3),
+ ... source_time=pulse,
+ ... polarization='Hy',
+ ... direction='+',
+ ... waist_radius=1.0)
+ """
+
+ waist_radius: pydantic.PositiveFloat
+ angle_theta: float = 0.0
+ angle_phi: float = 0.0
+ pol_angle: float = 0.0
type: Literal["GaussianBeam"] = "GaussianBeam"
-# allowable sources to use in Simulation.sources
+# sources allowed in Simulation.sources
SourceType = Union[VolumeSource, PlaneWave, ModeSource, GaussianBeam]
diff --git a/tidy3d/components/structure.py b/tidy3d/components/structure.py
index 515b1c5ba5..1a7642dfa2 100644
--- a/tidy3d/components/structure.py
+++ b/tidy3d/components/structure.py
@@ -1,15 +1,33 @@
-""" defines Geometric objects with Medium properties """
+"""Defines Geometric objects with Medium properties."""
from .base import Tidy3dBaseModel
from .validators import validate_name_str
-from .geometry import GeometryType
-from .medium import MediumType
+from .geometry import GeometryType, Box # pylint: disable=unused-import
+from .medium import MediumType, Medium # pylint: disable=unused-import
from .types import Ax
from .viz import add_ax_if_none
class Structure(Tidy3dBaseModel):
- """An object that interacts with the electromagnetic fields"""
+ """Defines a physical object that interacts with the electromagnetic fields.
+ A :class:`Structure` is a combination of a material property (:class:`AbstractMedium`)
+ and a :class:`Geometry`.
+
+ Parameters
+ ----------
+ geometry : :class:`Geometry`
+ Defines spatial extent of the :class:`Structure`.
+ medium : :class:`AbstractMedium`
+ Defines the electromagnetic properties of the structure material.
+ name : str = None
+ Optional name for the structure, used for plotting and logging.
+
+ Example
+ -------
+ >>> box = Box(center=(0,0,1), size=(2, 2, 2))
+ >>> glass = Medium(permittivity=3.9)
+ >>> struct = Structure(geometry=box, medium=glass, name='glass_box')
+ """
geometry: GeometryType
medium: MediumType
@@ -21,24 +39,27 @@ class Structure(Tidy3dBaseModel):
def plot(
self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **patch_kwargs
) -> Ax:
- """Plot structure geometry cross section at single (x,y,z) coordinate.
+ """Plot structure geometry cross section.
+ Note: only one of x, y, or z must be specified to define cross section.
Parameters
----------
- x : ``float``, optional
+ x : float = None
Position of plane in x direction.
- y : ``float``, optional
+ y : float = None
Position of plane in y direction.
- z : ``float``, optional
+ z : float = None
Position of plane in z direction.
- ax : ``matplotlib.axes._subplots.Axes``, optional
+ ax : matplotlib.axes._subplots.Axes = None
matplotlib axes to plot on, if not specified, one is created.
**patch_kwargs
- Optional keyword arguments passed to ``add_artist(patch, **patch_kwargs)``.
+ Optional keyword arguments passed to the matplotlib patch plotting of structure.
+ For details on accepted values, refer to
+ `Matplotlib's documentation `_. #pylint:disable=line-too-long
Returns
-------
- ``matplotlib.axes._subplots.Axes``
+ matplotlib.axes._subplots.Axes
The supplied or created matplotlib axes.
"""
return self.geometry.plot(x=x, y=y, z=z, ax=ax, **patch_kwargs)
diff --git a/tidy3d/components/types.py b/tidy3d/components/types.py
index 20f2ad84d6..65a1edf7bd 100644
--- a/tidy3d/components/types.py
+++ b/tidy3d/components/types.py
@@ -31,13 +31,17 @@
Vertices = List[Coordinate2D]
Shapely = BaseGeometry
-""" grid """
+""" medium """
-""" medium """
+class ComplexNumber(pydantic.BaseModel):
+ """Holds real and imaginary parts of a complex number."""
+
+ real: float
+ imag: float
-Complex = Tuple[float, float]
-PoleAndResidue = Tuple[Complex, Complex]
+
+PoleAndResidue = Tuple[complex, complex]
FreqBound = Union[float, Inf, Literal[-inf]]
""" symmetries """
@@ -91,14 +95,20 @@ class Array(np.ndarray, metaclass=ArrayMeta):
"""type of numpy array with annotated type (Array[float], Array[complex])"""
+""" note:
+ ^ this is the best way to declare numpy types if you know dtype.
+ for example: ``field_amps: Array[float] = np.random.random(5)``.
+"""
+
# lists or np.ndarrays of certain type
FloatArrayLike = Union[List[float], Array[float]]
IntArrayLike = Union[List[int], Array[int]]
ComplexArrayLike = Union[List[complex], Array[complex]]
-# encoding for JSON in pydantic models
+# encoding for JSON in tidy3d data
+# technically not used yet since the tidy3d data has separate load and export methods.
def numpy_encoding(array):
- """json encoding of numpy array"""
+ """json encoding of a (maybe complex-valued) numpy array."""
if array.dtype == "complex":
return {"re": list(array.real), "im": list(array.imag)}
return list(array)
diff --git a/tidy3d/components/validators.py b/tidy3d/components/validators.py
index 6bcc2b4c21..25d001c896 100644
--- a/tidy3d/components/validators.py
+++ b/tidy3d/components/validators.py
@@ -7,6 +7,39 @@
from .geometry import Box
+""" Explanation of pydantic validators:
+
+ Validators are class methods that are added to the models to validate their fields (kwargs).
+ The functions on this page return validators based on config arguments
+ and are generally in multiple components of tidy3d.
+ The inner functions (validators) are decorated with @pydantic.validator, which is configured.
+ First argument is the string of the field being validated in the model.
+ ``allow_reuse`` lets us use the validator in more than one model.
+ ``always`` makes sure if the model is changed, the validator gets called again.
+
+ The function being decorated by @pydantic.validator generally takes
+ ``cls`` the class that the validator is added to.
+ ``val`` the value of the field being validated.
+ ``values`` a dictionary containing all of the other fields of the model.
+ It is important to note that the validator only has access to fields that are defined
+ before the field being validated.
+ Fields defined under the validated field will not be in ``values``.
+
+ All validators generally should throw an exception if the validation fails
+ and return val if it passes.
+ Sometimes, we can use validators to change ``val`` or ``values``,
+ but this should be done with caution as it can be hard to reason about.
+
+ To add a validator from this file to the pydantic model,
+ put it in the model's main body and assign it to a variable (class method).
+ For example ``_plane_validator = assert_plane()``.
+ Note, if the assigned name ``_plane_validator`` is used later on for another validator, say,
+ the original validator will be overwritten so be aware of this.
+
+ For more details: `Pydantic Validators `_
+"""
+
+
def assert_plane():
"""makes sure a field's `size` attribute has exactly 1 zero"""
diff --git a/tidy3d/components/viz.py b/tidy3d/components/viz.py
index 2898ccd833..4a5f3a6548 100644
--- a/tidy3d/components/viz.py
+++ b/tidy3d/components/viz.py
@@ -9,21 +9,23 @@
from pydantic import BaseModel
from .types import Ax
+from ..constants import pec_val
def make_ax() -> Ax:
- """makes an empty `ax`"""
+ """makes an empty `ax`."""
_, ax = plt.subplots(1, 1, tight_layout=True)
return ax
def add_ax_if_none(plot):
- """decorates `plot(ax=None)` function,
- if ax=None, creates ax and feeds it to `plot`.
+ """Decorates `plot(*args, **kwargs, ax=None)` function.
+ if ax=None in the function call, creates an ax and feeds it to rest of function.
"""
@wraps(plot)
def _plot(*args, **kwargs) -> Ax:
+ """New plot function using a generated ax if None."""
if kwargs.get("ax") is None:
ax = make_ax()
kwargs["ax"] = ax
@@ -32,101 +34,111 @@ def _plot(*args, **kwargs) -> Ax:
return _plot
+""" Utilities for default plotting parameters."""
+
+
class PatchParams(BaseModel):
- """holds parameters for matplotlib.patches
- https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Patch.html
+ """Datastructure holding default parameters for plotting matplotlib.patches.
+ See https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Patch.html for explanation.
"""
alpha: Any = None
edgecolor: Any = None
facecolor: Any = None
fill: bool = True
+ hatch: str = None
class PatchParamSwitcher(BaseModel):
- """base class for updating parameters based on default values"""
+ """Class used for updating plot kwargs based on :class:`PatchParams` values."""
def update_params(self, **plot_params):
- """return dictionary of plot params updated with fields user supplied **plot_params dict"""
+ """return dictionary of plot params updated with fields user supplied **plot_params dict."""
+
+ # update self's plot params with the the supplied plot parameters and return non none ones.
default_plot_params = self.get_plot_params()
default_plot_params_dict = default_plot_params.dict().copy()
default_plot_params_dict.update(plot_params)
- # get rid of pairs with value of None as they will mess up plots down the line
+ # get rid of pairs with value of None as they will mess up plots down the line.
return {key: val for key, val in default_plot_params_dict.items() if val is not None}
@abstractmethod
def get_plot_params(self) -> PatchParams:
- """returns PatchParams based on attributes of self"""
+ """Returns :class:`PatchParams` based on user-supplied args. Implement in subclasses."""
class GeoParams(PatchParamSwitcher):
- """Patch plotting parameters for td.Geometry"""
+ """Patch plotting parameters for :class:`Geometry`."""
def get_plot_params(self) -> PatchParams:
- """returns PatchParams based on attributes of self"""
- return PatchParams(edgecolor="black", facecolor="cornflowerblue")
+ """Returns :class:`PatchParams` based on user-supplied args."""
+ return PatchParams(edgecolor=None, facecolor="cornflowerblue")
class SourceParams(PatchParamSwitcher):
- """Patch plotting parameters for `td.Source`"""
+ """Patch plotting parameters for :class:`Source`."""
def get_plot_params(self) -> PatchParams:
- """returns PatchParams based on attributes of self"""
+ """Returns :class:`PatchParams` based on user-supplied args."""
return PatchParams(alpha=0.7, facecolor="blueviolet", edgecolor="blueviolet")
class MonitorParams(PatchParamSwitcher):
- """Patch plotting parameters for `td.Monitor`"""
+ """Patch plotting parameters for :class:`Monitor`."""
def get_plot_params(self) -> PatchParams:
- """returns PatchParams based on attributes of self"""
+ """Returns :class:`PatchParams` based on user-supplied args."""
return PatchParams(alpha=0.7, facecolor="crimson", edgecolor="crimson")
class StructMediumParams(PatchParamSwitcher):
- """Patch plotting parameters for `td.Structures in `td.Simulation.plot_structures`"""
+ """Patch plotting parameters for :class:`Structures` in ``Simulation.plot_structures``."""
medium: Any
medium_map: dict
def get_plot_params(self) -> PatchParams:
- """returns PatchParams based on attributes of self"""
+ """Returns :class:`PatchParams` based on user-supplied args."""
mat_index = self.medium_map[self.medium]
mat_cmap = cm.Set2 # pylint: disable=no-name-in-module, no-member
facecolor = mat_cmap(mat_index % len(mat_cmap.colors))
- return PatchParams(facecolor=facecolor)
+ if self.medium.name == "PEC":
+ return PatchParams(facecolor="black", edgecolor="black", lw=0)
+ return PatchParams(facecolor=facecolor, edgecolor=facecolor, lw=0)
class StructEpsParams(PatchParamSwitcher):
- """Patch plotting parameters for `td.Structures in `td.Simulation.plot_structures_eps`"""
+ """Patch plotting parameters for :class:`Structures` in `td.Simulation.plot_structures_eps`."""
eps: float
eps_max: float
def get_plot_params(self) -> PatchParams:
- """returns PatchParams based on attributes of self"""
+ """Returns :class:`PatchParams` based on user-supplied args."""
chi = self.eps - 1.0
chi_max = self.eps_max - 1.0
color = 1 - chi / chi_max
- return PatchParams(facecolor=str(color))
+ if self.eps == pec_val:
+ return PatchParams(facecolor="gold", edgecolor="k", lw=1)
+ return PatchParams(facecolor=str(color), edgecolor=str(color), lw=0)
class PMLParams(PatchParamSwitcher):
- """Patch plotting parameters for `td.Simulation.pml_layers`"""
+ """Patch plotting parameters for :class:`AbsorberSpec` (PML)."""
def get_plot_params(self) -> PatchParams:
- """returns PatchParams based on attributes of self"""
+ """Returns :class:`PatchParams` based on user-supplied args."""
return PatchParams(alpha=0.7, facecolor="sandybrown", edgecolor="sandybrown")
class SymParams(PatchParamSwitcher):
- """Patch plotting parameters for `td.Simulation.symmetry`"""
+ """Patch plotting parameters for `td.Simulation.symmetry`."""
sym_value: int
def get_plot_params(self) -> PatchParams:
- """returns PatchParams based on attributes of self"""
+ """Returns :class:`PatchParams` based on user-supplied args."""
if self.sym_value == 1:
return PatchParams(alpha=0.5, facecolor="lightsteelblue", edgecolor="lightsteelblue")
if self.sym_value == -1:
@@ -135,8 +147,8 @@ def get_plot_params(self) -> PatchParams:
class SimDataGeoParams(PatchParamSwitcher):
- """Patch plotting parameters for `td.Simulation.symmetry`"""
+ """Patch plotting parameters for `td.SimulationData`."""
def get_plot_params(self) -> PatchParams:
- """returns PatchParams based on attributes of self"""
+ """Returns :class:`PatchParams` based on user-supplied args."""
return PatchParams(alpha=0.4, edgecolor="black")
diff --git a/tidy3d/constants.py b/tidy3d/constants.py
index 6a7b99194d..6c9145c590 100644
--- a/tidy3d/constants.py
+++ b/tidy3d/constants.py
@@ -1,18 +1,32 @@
# pylint: disable=invalid-name
-""" defines constants used elsewhere in the package"""
+"""Defines importable constants.
+
+Attributes:
+ inf (float): Tidy3d representation of infinity.
+ C_0 (float): Speed of light in vacuum [um/s]
+ EPSILON_0 (float): Vacuum permittivity [F/um]
+ MU_0 (float): Vacuum permeability [H/um]
+ ETA_0 (float): Vacuum impedance
+ HBAR (float): reduced Planck constant [eV*s]
+ Q_e (float): funamental charge [C]
+"""
import numpy as np
-EPSILON_0 = np.float32(8.85418782e-18) # vacuum permittivity [F/um]
-MU_0 = np.float32(1.25663706e-12) # vacuum permeability [H/um]
-C_0 = 1 / np.sqrt(EPSILON_0 * MU_0) # speed of light in vacuum [um/s]
-ETA_0 = np.sqrt(MU_0 / EPSILON_0) # vacuum impedance
-Q_e = 1.602176634e-19 # funamental charge
-HBAR = 6.582119569e-16 # reduced Planck constant [eV*s]
+# fundamental constants
+EPSILON_0 = np.float32(8.85418782e-18)
+MU_0 = np.float32(1.25663706e-12)
+C_0 = 1 / np.sqrt(EPSILON_0 * MU_0)
+ETA_0 = np.sqrt(MU_0 / EPSILON_0)
+Q_e = 1.602176634e-19
+HBAR = 6.582119569e-16
-inf = 1e16
+# infinity (very large)
+inf = 1e20
+# floating point precisions
dp_eps = np.finfo(np.float64).eps
fp_eps = np.finfo(np.float32).eps
+# values of PEC for mode solver
pec_val = -1e8
diff --git a/tidy3d/convert.py b/tidy3d/convert.py
index 3a0b9b08a1..708af10e5e 100644
--- a/tidy3d/convert.py
+++ b/tidy3d/convert.py
@@ -6,7 +6,8 @@
from tidy3d import Simulation, SimulationData, FieldData, data_type_map
from tidy3d import Box, Sphere, Cylinder, PolySlab
-from tidy3d import Medium # , DispersiveMedium
+from tidy3d import Medium, AnisotropicMedium
+from tidy3d.components.medium import DispersiveMedium, PECMedium
from tidy3d import VolumeSource, ModeSource, PlaneWave
from tidy3d import GaussianPulse
from tidy3d import PML, Absorber, StablePML
@@ -41,7 +42,7 @@ def old_json_parameters(sim: Simulation) -> Dict:
profile = "standard"
pml_layers.append({"profile": profile, "Nlayers": pml.num_layers})
- sizes = sim.grid.cell_sizes
+ sizes = sim.grid.sizes
mesh_step_x = np.mean(sizes.x)
mesh_step_y = np.mean(sizes.y)
mesh_step_z = np.mean(sizes.z)
@@ -96,29 +97,42 @@ def old_json_structures(sim: Simulation) -> Tuple[List[Dict], List[Dict]]:
}
)
- """ TODO: Dispersive mediums need to eventually be defined as pole residue pairs for the
- solver. Seems like this will have to be done on the server side in the revamp. """
- # elif isinstance(medium, DispersiveMedium):
- # poles = []
- # for pole in medium.poles:
- # poles.append([pole[0].real, pole[0].imag, pole[1].real, pole[1].imag])
- # med.update(
- # {
- # "type": "Medium",
- # "permittivity": [medium.eps_inf] * 3,
- # "conductivity": [0, 0, 0],
- # "poles": poles,
- # }
- # )
-
- """ TODO: support PEC. Note that PMC is probably not needed (not supported currently)."""
- # elif isinstance(medium, PEC):
- # med.update({"type": "PEC"})
+ elif isinstance(medium, AnisotropicMedium):
+ """TODO: support diagonal anisotropy in non-dispersive media"""
+ med.update(
+ {
+ "type": "Medium",
+ "permittivity": [
+ medium.xx.permittivity,
+ medium.yy.permittivity,
+ medium.zz.permittivity,
+ ],
+ "conductivity": [
+ medium.xx.conductivity,
+ medium.yy.conductivity,
+ medium.zz.conductivity,
+ ],
+ "poles": [],
+ }
+ )
+ elif isinstance(medium, DispersiveMedium):
+ poles = []
+ for (a, c) in medium.pole_residue.poles:
+ poles.append([a[0], a[1], c[0], c[1]])
+ med.update(
+ {
+ "type": "PoleResidue",
+ "permittivity": [medium.pole_residue.eps_inf] * 3,
+ "conductivity": [0, 0, 0],
+ "poles": poles,
+ }
+ )
+ elif isinstance(medium, PECMedium):
+ med.update({"type": "PEC"})
medium_list.append(med)
struct_list = []
for structure in sim.structures:
- """TODO: Shouldn't structures also have custom names?"""
struct = {"name": structure.medium.name, "mat_index": medium_map[structure.medium]}
geom = structure.geometry
if isinstance(geom, Box):
diff --git a/tidy3d/log.py b/tidy3d/log.py
index 0bf7df6c8e..e827946b43 100644
--- a/tidy3d/log.py
+++ b/tidy3d/log.py
@@ -1,42 +1,74 @@
-""" logging for tidy3d"""
+"""Logging and error-handling for Tidy3d."""
import logging
from rich.logging import RichHandler
+# TODO: more logging features (to file, etc).
+
FORMAT = "%(message)s"
logging.basicConfig(level="INFO", format=FORMAT, datefmt="[%X]", handlers=[RichHandler()])
log = logging.getLogger("rich")
+level_map = {
+ "error": 40,
+ "warning": 30,
+ "info": 20,
+ "debug": 10,
+}
+
class Tidy3DError(Exception):
- """any error in tidy3d"""
+ """Any error in tidy3d"""
def __init__(self, message: str = None):
- """log the error message and then raise the Exception"""
+ """Log just the error message and then raise the Exception."""
log.error(message)
super().__init__(self, message)
+class ConfigError(Tidy3DError):
+ """Error when configuring Tidy3d."""
+
+
+class Tidy3dKeyError(Tidy3DError):
+ """Could not find a key in a Tidy3d dictionary."""
+
+
class ValidationError(Tidy3DError):
- """error when constructing tidy3d components"""
+ """eError when constructing Tidy3d components."""
class SetupError(Tidy3DError):
- """error regarding the setup of the components (outside of domains, etc)"""
+ """Error regarding the setup of the components (outside of domains, etc)."""
class FileError(Tidy3DError):
- """error reading or writing to file"""
+ """Error reading or writing to file."""
class WebError(Tidy3DError):
- """error with the webAPI"""
+ """Error with the webAPI."""
class AuthenticationError(Tidy3DError):
- """error authenticating a user through webapi webAPI"""
+ """Error authenticating a user through webapi webAPI."""
class DataError(Tidy3DError):
- """error accessing data"""
+ """Error accessing data."""
+
+
+def logging_level(level: str = "info") -> None:
+ """Set tidy3d logging level priority.
+
+ Parameters
+ ----------
+ level : str
+ One of ``{'debug', 'info', 'warning', 'error'}`` (listed in increasing priority).
+ the lowest priority level of logging messages to display.
+ """
+ if level not in level_map:
+ raise ConfigError(f"logging level {level} not supported, must be in {level_map.keys()}.")
+ level_int = level_map[level]
+ log.setLevel(level_int)
diff --git a/tidy3d/material_library.py b/tidy3d/material_library.py
index 117e614e4f..b7759fb3a6 100644
--- a/tidy3d/material_library.py
+++ b/tidy3d/material_library.py
@@ -20,9 +20,9 @@
References
----------
- * A. D. Rakic et al., Applied Optics, 37, 5271-5283 (1998)
+ * A. D. Rakic et al., Applied Optics + 1j*37 + 1j*5271-5283 (1998)
* P. B. Johnson and R. W. Christy. Optical constants of the noble metals,
- Phys. Rev. B 6, 4370-4379 (1972).
+ Phys. Rev. B 6 + 1j*4370-4379 (1972).
Aluminum ("Al")
@@ -43,7 +43,7 @@
* A. D. Rakic. Algorithm for the determination of intrinsic optical
constants of metal films: application to aluminum,
- Appl. Opt. 34, 4755-4767 (1995)
+ Appl. Opt. 34 + 1j*4755-4767 (1995)
Alumina ("Al2O3")
@@ -84,7 +84,7 @@
References
----------
- * R.E. Fern and A. Onton, J. Applied Physics, 42, 3499-500 (1971)
+ * R.E. Fern and A. Onton, J. Applied Physics + 1j*42 + 1j*3499-500 (1971)
* Horiba Technical Note 08: Lorentz Dispersion Model
`[pdf] `_.
@@ -185,7 +185,7 @@
References
----------
- * P. B. Johnson and R. W. Christy. Optical constants of the noble metals, Phys. Rev. B 6, 4370-4379 (1972)
+ * P. B. Johnson and R. W. Christy. Optical constants of the noble metals, Phys. Rev. B 6 + 1j*4370-4379 (1972)
N-BK7 borosilicate glass ("BK7")
@@ -220,7 +220,7 @@
* A. D. Rakic. Algorithm for the determination of intrinsic optical
constants of metal films: application to aluminum,
- Appl. Opt. 34, 4755-4767 (1995)
+ Appl. Opt. 34 + 1j*4755-4767 (1995)
Calcium fluoride ("CaF2")
@@ -261,7 +261,7 @@
* N. Sultanova, S. Kasarova and I. Nikolov.
Dispersion properties of optical polymers,
- Acta Physica Polonica A 116, 585-587 (2009)
+ Acta Physica Polonica A 116 + 1j*585-587 (2009)
Chromium ("Cr")
@@ -282,7 +282,7 @@
* A. D. Rakic. Algorithm for the determination of intrinsic optical
constants of metal films: application to aluminum,
- Appl. Opt. 34, 4755-4767 (1995)
+ Appl. Opt. 34 + 1j*4755-4767 (1995)
Copper ("Cu")
@@ -301,7 +301,7 @@
References
----------
- * P. B. Johnson and R. W. Christy. Optical constants of the noble metals, Phys. Rev. B 6, 4370-4379 (1972)
+ * P. B. Johnson and R. W. Christy. Optical constants of the noble metals, Phys. Rev. B 6 + 1j*4370-4379 (1972)
Fused silica ("FusedSilica")
@@ -321,10 +321,10 @@
----------
* I. H. Malitson. Interspecimen comparison of the refractive index of
- fused silica, J. Opt. Soc. Am. 55, 1205-1208 (1965)
+ fused silica, J. Opt. Soc. Am. 55 + 1j*1205-1208 (1965)
* C. Z. Tan. Determination of refractive index of silica glass for
infrared wavelengths by IR spectroscopy,
- J. Non-Cryst. Solids 223, 158-163 (1998)
+ J. Non-Cryst. Solids 223 + 1j*158-163 (1998)
Gallium arsenide ("GaAs")
@@ -346,7 +346,7 @@
* T. Skauli, P. S. Kuo, K. L. Vodopyanov, T. J. Pinguet, O. Levi,
L. A. Eyres, J. S. Harris, M. M. Fejer, B. Gerard, L. Becouarn,
and E. Lallier. Improved dispersion relations for GaAs and
- applications to nonlinear optics, J. Appl. Phys., 94, 6447-6455 (2003)
+ applications to nonlinear optics, J. Appl. Phys. + 946447-6455 (2003)
Germanium ("Ge")
@@ -488,12 +488,12 @@
References
----------
- * Handbook of Optics, 2nd edition, Vol. 2. McGraw-Hill 1994
+ * Handbook of Optics + 1j*2nd edition, Vol. 2. McGraw-Hill 1994
* G. D. Pettit and W. J. Turner. Refractive index of InP,
- J. Appl. Phys. 36, 2081 (1965)
+ J. Appl. Phys. 36 + 1j*2081 (1965)
* A. N. Pikhtin and A. D. Yaskov. Disperson of the refractive index of
semiconductors with diamond and zinc-blende structures,
- Sov. Phys. Semicond. 12, 622-626 (1978)
+ Sov. Phys. Semicond. 12 + 1j*622-626 (1978)
Magnesium fluoride ("MgF2")
@@ -552,7 +552,7 @@
References
----------
- * P. B. Johnson and R. W. Christy. Optical constants of the noble metals, Phys. Rev. B 6, 4370-4379 (1972)
+ * P. B. Johnson and R. W. Christy. Optical constants of the noble metals, Phys. Rev. B 6 + 1j*4370-4379 (1972)
Polyetherimide ("PEI")
@@ -634,7 +634,7 @@
`[pdf] `_.
* N. Sultanova, S. Kasarova and I. Nikolov.
Dispersion properties of optical polymers,
- Acta Physica Polonica A 116, 585-587 (2009)
+ Acta Physica Polonica A 116 + 1j*585-587 (2009)
Polytetrafluoroethylene, or Teflon ("PTFE")
@@ -693,7 +693,7 @@
References
----------
- * P. B. Johnson and R. W. Christy. Optical constants of the noble metals, Phys. Rev. B 6, 4370-4379 (1972)
+ * P. B. Johnson and R. W. Christy. Optical constants of the noble metals, Phys. Rev. B 6 + 1j*4370-4379 (1972)
Polycarbonate. ("Polycarbonate")
@@ -718,7 +718,7 @@
`[pdf] `_.
* N. Sultanova, S. Kasarova and I. Nikolov.
Dispersion properties of optical polymers,
- Acta Physica Polonica A 116, 585-587 (2009)
+ Acta Physica Polonica A 116 + 1j*585-587 (2009)
Polystyrene. ("Polystyrene")
@@ -739,7 +739,7 @@
* N. Sultanova, S. Kasarova and I. Nikolov.
Dispersion properties of optical polymers,
- Acta Physica Polonica A 116, 585-587 (2009)
+ Acta Physica Polonica A 116 + 1j*585-587 (2009)
Platinum ("Pt")
@@ -760,7 +760,7 @@
* W. S. M. Werner, K. Glantschnig, C. Ambrosch-Draxl.
Optical constants and inelastic electron-scattering data for 17
- elemental metals, J. Phys Chem Ref. Data 38, 1013-1092 (2009)
+ elemental metals, J. Phys Chem Ref. Data 38 + 1j*1013-1092 (2009)
Sapphire. ("Sapphire")
@@ -803,13 +803,13 @@
References
----------
- * T. Baak. Silicon oxynitride; a material for GRIN optics, Appl. Optics 21, 1069-1072 (1982)
+ * T. Baak. Silicon oxynitride; a material for GRIN optics, Appl. Optics 21 + 1j*1069-1072 (1982)
* Horiba Technical Note 08: Lorentz Dispersion Model
`[pdf] `_.
* K. Luke, Y. Okawachi, M. R. E. Lamont, A. L. Gaeta, M. Lipson.
Broadband mid-infrared frequency comb generation in a Si3N4 microresonator,
- Opt. Lett. 40, 4823-4826 (2015)
- * H. R. Philipp. Optical properties of silicon nitride, J. Electrochim. Soc. 120, 295-300 (1973)
+ Opt. Lett. 40 + 1j*4823-4826 (2015)
+ * H. R. Philipp. Optical properties of silicon nitride, J. Electrochim. Soc. 120 + 1j*295-300 (1973)
Silicon carbide ("SiC")
@@ -930,7 +930,7 @@
* W. S. M. Werner, K. Glantschnig, C. Ambrosch-Draxl.
Optical constants and inelastic electron-scattering data for 17
- elemental metals, J. Phys Chem Ref. Data 38, 1013-1092 (2009)
+ elemental metals, J. Phys Chem Ref. Data 38 + 1j*1013-1092 (2009)
Titanium oxide ("TiOx")
@@ -971,7 +971,7 @@
* W. S. M. Werner, K. Glantschnig, C. Ambrosch-Draxl.
Optical constants and inelastic electron-scattering data for 17
- elemental metals, J. Phys Chem Ref. Data 38, 1013-1092 (2009)
+ elemental metals, J. Phys Chem Ref. Data 38 + 1j*1013-1092 (2009)
Yttrium oxide ("Y2O3")
@@ -995,7 +995,7 @@
* Horiba Technical Note 08: Lorentz Dispersion Model
`[pdf] `_.
* Y. Nigara. Measurement of the optical constants of yttrium oxide,
- Jpn. J. Appl. Phys. 7, 404-408 (1968)
+ Jpn. J. Appl. Phys. 7 + 1j*404-408 (1968)
Yttrium aluminium garnet ("YAG")
@@ -1016,7 +1016,7 @@
* D. E. Zelmon, D. L. Small and R. Page.
Refractive-index measurements of undoped yttrium aluminum garnet
- from 0.4 to 5.0 um, Appl. Opt. 37, 4933-4935 (1998)
+ from 0.4 to 5.0 um, Appl. Opt. 37 + 1j*4933-4935 (1998)
Zirconium oxide ("ZrO2")
@@ -1083,14 +1083,14 @@
300K including temperature coefficients, Sol. Energ. Mat. Sol. Cells 92,
1305–1310 (2008).
* M. A. Green and M. Keevers, Optical properties of intrinsic silicon
- at 300 K, Progress in Photovoltaics, 3, 189-92 (1995).
+ at 300 K, Progress in Photovoltaics + 1j*3 + 1j*189-92 (1995).
* H. H. Li. Refractive index of silicon and germanium and its wavelength
- and temperature derivatives, J. Phys. Chem. Ref. Data 9, 561-658 (1993).
+ and temperature derivatives, J. Phys. Chem. Ref. Data 9 + 1j*561-658 (1993).
* C. D. Salzberg and J. J. Villa. Infrared Refractive Indexes of Silicon,
Germanium and Modified Selenium Glass,
- J. Opt. Soc. Am., 47, 244-246 (1957).
+ J. Opt. Soc. Am. + 1j*47 + 1j*244-246 (1957).
* B. Tatian. Fitting refractive-index data with the Sellmeier dispersion
- formula, Appl. Opt. 23, 4477-4485 (1984).
+ formula, Appl. Opt. 23 + 1j*4477-4485 (1984).
"""
@@ -1099,28 +1099,28 @@
eps_inf=1.0,
poles=[
(
- (-275580863813647.1, 312504541922578.7),
- (410592688830514.8, -1.3173437570517746e16),
+ (-275580863813647.1 + 1j * 312504541922578.7),
+ (410592688830514.8 - 1j * 1.3173437570517746e16),
),
(
- (-1148310840598705.2, 8055992835194972.0),
- (227736607453638.5, -1042414461766764.9),
+ (-1148310840598705.2 + 1j * 8055992835194972.0),
+ (227736607453638.5 - 1j * 1042414461766764.9),
),
(
- (-381116695232772.56, 6594145937912653.0),
- (161555291564323.06, -1397161265004318.2),
+ (-381116695232772.56 + 1j * 6594145937912653.0),
+ (161555291564323.06 - 1j * 1397161265004318.2),
),
(
- (-1.2755935758322332e16, 4213421975115564.5),
- (1.718968422861484e16, 2.293341935281984e16),
+ (-1.2755935758322332e16 + 1j * 4213421975115564.5),
+ (1.718968422861484e16 + 1j * 2.293341935281984e16),
),
(
- (-1037538194.0633082, -71105682833114.89),
- (117311511.37080565, 6.61015554492372e17),
+ (-1037538194.0633082 - 1j * 71105682833114.89),
+ (117311511.37080565 + 1j * 6.61015554492372e17),
),
(
- (-76642436669493.88, 123745349008080.44),
- (129838572187083.62, -2.1821880909947117e17),
+ (-76642436669493.88 + 1j * 123745349008080.44),
+ (129838572187083.62 - 1j * 2.1821880909947117e17),
),
],
frequency_range=(151926744799612.75, 7596337239980637.0),
@@ -1130,17 +1130,17 @@
eps_inf=1.0,
poles=[
(
- (-1.2332423729774158e16, -1157025502703526.8),
- (1.0800083435396464e16, -4.781815206914558e16),
+ (-1.2332423729774158e16 - 1j * 1157025502703526.8),
+ (1.0800083435396464e16 - 1j * 4.781815206914558e16),
),
(
- (-2229555965713773.2, -6952870039573486.0),
- (4439804688475990.0, 6272392738308416.0),
+ (-2229555965713773.2 - 1j * 6952870039573486.0),
+ (4439804688475990.0 + 1j * 6272392738308416.0),
),
- ((-7.482270443496804e-294, -528948840665300.7), (-0.0, -1.2076416298344678e17)),
+ ((-7.482270443496804e-294 - 1j * 528948840665300.7), (-0.0 - 1j * 1.2076416298344678e17)),
(
- (-3295983388.845004, 314479339729201.94),
- (7864861845440.258, -5.2524694748286035e17),
+ (-3295983388.845004 + 1j * 314479339729201.94),
+ (7864861845440.258 - 1j * 5.2524694748286035e17),
),
],
frequency_range=(154771532566312.25, 1595489401708072.2),
@@ -1150,21 +1150,21 @@
eps_inf=1.0,
poles=[
(
- (-38634980988505.31, -48273958812026.45),
- (4035140886647080.0, 2.835977690098632e18),
+ (-38634980988505.31 - 1j * 48273958812026.45),
+ (4035140886647080.0 + 1j * 2.835977690098632e18),
),
- ((-1373449221156.457, 0.0), (7.630343339215653e16, 2.252091523762478e17)),
+ ((-1373449221156.457 + 1j * 0.0), (7.630343339215653e16 + 1j * 2.252091523762478e17)),
(
- (-1.0762187388103686e16, -799978314126058.1),
- (-1.5289438747838848e16, 4.746731963865045e16),
+ (-1.0762187388103686e16 - 1j * 799978314126058.1),
+ (-1.5289438747838848e16 + 1j * 4.746731963865045e16),
),
(
- (-179338332256147.1, -243607346238054.5),
- (-4.625363670034073e16, 7.703073947098675e16),
+ (-179338332256147.1 - 1j * 243607346238054.5),
+ (-4.625363670034073e16 + 1j * 7.703073947098675e16),
),
(
- (-1.0180997365823526e16, -5542555481403632.0),
- (-1.6978040336362288e16, -1.4140848316870884e16),
+ (-1.0180997365823526e16 - 1j * 5542555481403632.0),
+ (-1.6978040336362288e16 - 1j * 1.4140848316870884e16),
),
],
frequency_range=(151926744799612.75, 1.5192674479961274e16),
@@ -1172,46 +1172,46 @@
Al2O3_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -1.856240967961668e16), (0.0, 1.4107431356508676e16))],
+ poles=[((-0.0 - 1j * 1.856240967961668e16), (0.0 + 1j * 1.4107431356508676e16))],
frequency_range=(145079354536315.6, 1450793545363156.0),
)
AlAs_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-287141547671268.06, -6859562349716031.0), (0.0, 2.4978200955702556e16))],
+ poles=[((-287141547671268.06 - 1j * 6859562349716031.0), (0.0 + 1j * 2.4978200955702556e16))],
frequency_range=(0.0, 725396772681578.0),
)
AlAs_FernOnton1971 = PoleResidue(
eps_inf=1,
poles=[
- ((0.0, 6674881541314847.0), (-0.0, -2.0304989648679764e16)),
- ((0.0, 68198825885555.74), (-0.0, -64788884591277.95)),
+ ((0.0 + 1j * 6674881541314847.0), (-0.0 - 1j * 2.0304989648679764e16)),
+ ((0.0 + 1j * 68198825885555.74), (-0.0 - 1j * 64788884591277.95)),
],
frequency_range=(136269299354975.81, 535343676037405.0),
)
AlGaN_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-96473482947754.08, -1.0968686723518324e16), (0.0, 1.974516343551917e16))],
+ poles=[((-96473482947754.08 - 1j * 1.0968686723518324e16), (0.0 + 1j * 1.974516343551917e16))],
frequency_range=(145079354536315.6, 967195696908770.8),
)
AlN_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -1.354578856633347e16), (0.0, 2.2391188500149228e16))],
+ poles=[((-0.0 - 1j * 1.354578856633347e16), (0.0 + 1j * 2.2391188500149228e16))],
frequency_range=(181349193170394.5, 1148544890079165.2),
)
AlxOy_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-654044636362332.8, -1.9535949662203744e16), (0.0, 2.123004231270711e16))],
+ poles=[((-654044636362332.8 - 1j * 1.9535949662203744e16), (0.0 + 1j * 2.123004231270711e16))],
frequency_range=(145079354536315.6, 1450793545363156.0),
)
Aminoacid_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -2.2518582114198596e16), (0.0, 5472015453750259.0))],
+ poles=[((-0.0 - 1j * 2.2518582114198596e16), (0.0 + 1j * 5472015453750259.0))],
frequency_range=(362698386340789.0, 1208994621135963.5),
)
@@ -1219,28 +1219,28 @@
eps_inf=1.0,
poles=[
(
- (-2734662976094585.0, -5109708411015428.0),
- (6336826024756207.0, 4435873101906770.0),
+ (-2734662976094585.0 - 1j * 5109708411015428.0),
+ (6336826024756207.0 + 1j * 4435873101906770.0),
),
(
- (-1350147983711818.5, -5489311548525578.0),
- (1313699470597296.0, 2519572763961442.0),
+ (-1350147983711818.5 - 1j * 5489311548525578.0),
+ (1313699470597296.0 + 1j * 2519572763961442.0),
),
(
- (-617052918383578.8, -4245316498596240.5),
- (577794256452581.6, 1959978954055246.2),
+ (-617052918383578.8 - 1j * 4245316498596240.5),
+ (577794256452581.6 + 1j * 1959978954055246.2),
),
(
- (-49323313828269.45, 357801380626459.0),
- (107506676273403.77, -1.4556042795341494e17),
+ (-49323313828269.45 + 1j * 357801380626459.0),
+ (107506676273403.77 - 1j * 1.4556042795341494e17),
),
(
- (-1443242886602454.5, 1.2515133019565118e16),
- (230166586216985.78, -3809468920144284.5),
+ (-1443242886602454.5 + 1j * 1.2515133019565118e16),
+ (230166586216985.78 - 1j * 3809468920144284.5),
),
(
- (-258129278193.38495, 126209156799910.83),
- (972898514880373.2, -2.6164309961808477e17),
+ (-258129278193.38495 + 1j * 126209156799910.83),
+ (972898514880373.2 - 1j * 2.6164309961808477e17),
),
],
frequency_range=(972331166717521.5, 1.002716515677444e16),
@@ -1249,9 +1249,9 @@
BK7_Zemax = PoleResidue(
eps_inf=1,
poles=[
- ((0.0, 2.431642149296798e16), (-0.0, -1.2639823249559002e16)),
- ((0.0, 1.3313466757556814e16), (-0.0, -1542979833250087.0)),
- ((0.0, 185098620483566.44), (-0.0, -93518250617894.06)),
+ ((0.0 + 1j * 2.431642149296798e16), (-0.0 - 1j * 1.2639823249559002e16)),
+ ((0.0 + 1j * 1.3313466757556814e16), (-0.0 - 1j * 1542979833250087.0)),
+ ((0.0 + 1j * 185098620483566.44), (-0.0 - 1j * 93518250617894.06)),
],
frequency_range=(119916983432378.72, 999308195269822.8),
)
@@ -1260,20 +1260,20 @@
eps_inf=1.0,
poles=[
(
- (-1895389650993988.8, 97908760254751.03),
- (40119229416830.445, -6.072472443146835e17),
+ (-1895389650993988.8 + 1j * 97908760254751.03),
+ (40119229416830.445 - 1j * 6.072472443146835e17),
),
(
- (-173563254483411.3, -39098441331858.36),
- (17327582796970.727, 2.1782706819526035e17),
+ (-173563254483411.3 - 1j * 39098441331858.36),
+ (17327582796970.727 + 1j * 2.1782706819526035e17),
),
(
- (-3894265931723855.5, 4182034916796805.5),
- (12304771601918.207, -7.207815056419813e16),
+ (-3894265931723855.5 + 1j * 4182034916796805.5),
+ (12304771601918.207 - 1j * 7.207815056419813e16),
),
(
- (-21593264136101.0, 15791763527.314959),
- (10898385976899.773, -1.844312751315413e21),
+ (-21593264136101.0 + 1j * 15791763527.314959),
+ (10898385976899.773 - 1j * 1.844312751315413e21),
),
],
frequency_range=(30385348959922.547, 7596337239980637.0),
@@ -1281,13 +1281,13 @@
CaF2_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -2.376134288665943e16), (0.0, 1.2308375615289586e16))],
+ poles=[((-0.0 - 1j * 2.376134288665943e16), (0.0 + 1j * 1.2308375615289586e16))],
frequency_range=(181349193170394.5, 1148544890079165.2),
)
Cellulose_Sultanova2009 = PoleResidue(
eps_inf=1,
- poles=[((0.0, 1.7889308287957964e16), (-0.0, -1.0053791257832376e16))],
+ poles=[((0.0 + 1j * 1.7889308287957964e16), (-0.0 - 1j * 1.0053791257832376e16))],
frequency_range=(284973819943865.75, 686338046201801.2),
)
@@ -1295,20 +1295,20 @@
eps_inf=1.0,
poles=[
(
- (-1986166383636938.8, -2164878977347264.2),
- (7556808013710.747, 7.049099034302554e16),
+ (-1986166383636938.8 - 1j * 2164878977347264.2),
+ (7556808013710.747 + 1j * 7.049099034302554e16),
),
(
- (-721541271079502.1, -373401161923.8366),
- (310196803320813.3, 3.9059060187608424e19),
+ (-721541271079502.1 - 1j * 373401161923.8366),
+ (310196803320813.3 + 1j * 3.9059060187608424e19),
),
(
- (-63813936856379.42, -74339943925.90295),
- (9692153948376.459, 1.677574997330204e20),
+ (-63813936856379.42 - 1j * 74339943925.90295),
+ (9692153948376.459 + 1j * 1.677574997330204e20),
),
(
- (-14969882528204.193, 2792246309026.462),
- (1365296575589394.2, -3.587733271017399e18),
+ (-14969882528204.193 + 1j * 2792246309026.462),
+ (1365296575589394.2 - 1j * 3.587733271017399e18),
),
],
frequency_range=(151926744799612.75, 1.5192674479961274e16),
@@ -1318,24 +1318,24 @@
eps_inf=1.0,
poles=[
(
- (-26648472832094.61, -138613399508745.61),
- (1569506577450794.8, 5.4114978936556614e17),
+ (-26648472832094.61 - 1j * 138613399508745.61),
+ (1569506577450794.8 + 1j * 5.4114978936556614e17),
),
(
- (-371759347003379.5, -246275957923571.7),
- (-3214099365675777.0, 6.815369975824028e16),
+ (-371759347003379.5 - 1j * 246275957923571.7),
+ (-3214099365675777.0 + 1j * 6.815369975824028e16),
),
(
- (-729831805397277.0, -3688510464653965.0),
- (1975278935189313.2, 3073498774961688.5),
+ (-729831805397277.0 - 1j * 3688510464653965.0),
+ (1975278935189313.2 + 1j * 3073498774961688.5),
),
(
- (-3181433040973120.0, -6135291322604277.0),
- (5089000024526812.0, 1.2704443456133342e16),
+ (-3181433040973120.0 - 1j * 6135291322604277.0),
+ (5089000024526812.0 + 1j * 1.2704443456133342e16),
),
(
- (-40088932206916.91, -2.91706942364891e16),
- (1249236469534085.0, 8344554643332125.0),
+ (-40088932206916.91 - 1j * 2.91706942364891e16),
+ (1249236469534085.0 + 1j * 8344554643332125.0),
),
],
frequency_range=(972331166717521.5, 1.002716515677444e16),
@@ -1344,9 +1344,9 @@
FusedSilica_Zemax = PoleResidue(
eps_inf=1,
poles=[
- ((0.0, 2.7537034527932452e16), (-0.0, -9585177720141492.0)),
- ((0.0, 1.620465316968868e16), (-0.0, -3305284173070520.5)),
- ((0.0, 190341645710801.38), (-0.0, -85413852993771.3)),
+ ((0.0 + 1j * 2.7537034527932452e16), (-0.0 - 1j * 9585177720141492.0)),
+ ((0.0 + 1j * 1.620465316968868e16), (-0.0 - 1j * 3305284173070520.5)),
+ ((0.0 + 1j * 190341645710801.38), (-0.0 - 1j * 85413852993771.3)),
],
frequency_range=(44745143071783.1, 1427583136099746.8),
)
@@ -1354,9 +1354,9 @@
GaAs_Skauli2003 = PoleResidue(
eps_inf=1,
poles=[
- ((0.0, 4250781024557878.5), (-0.0, -1.1618961579876792e16)),
- ((0.0, 2153617667595138.0), (-0.0, -26166023937747.41)),
- ((0.0, 51024513930292.87), (-0.0, -49940804278927.375)),
+ ((0.0 + 1j * 4250781024557878.5), (-0.0 - 1j * 1.1618961579876792e16)),
+ ((0.0 + 1j * 2153617667595138.0), (-0.0 - 1j * 26166023937747.41)),
+ ((0.0 + 1j * 51024513930292.87), (-0.0 - 1j * 49940804278927.375)),
],
frequency_range=(17634850504761.58, 309064390289635.9),
)
@@ -1364,54 +1364,56 @@
Ge_Icenogle1976 = PoleResidue(
eps_inf=1,
poles=[
- ((0.0, 2836329349380603.5), (-0.0, -9542546463056102.0)),
- ((0.0, 30278857121656.766), (-0.0, -3225758043455.7036)),
+ ((0.0 + 1j * 2836329349380603.5), (-0.0 - 1j * 9542546463056102.0)),
+ ((0.0 + 1j * 30278857121656.766), (-0.0 - 1j * 3225758043455.7036)),
],
frequency_range=(24982704881745.566, 119916983432378.72),
)
GeOx_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-351710414211103.44, -2.4646085673376252e16), (0.0, 2.02755336442934e16))],
+ poles=[((-351710414211103.44 - 1j * 2.4646085673376252e16), (0.0 + 1j * 2.02755336442934e16))],
frequency_range=(145079354536315.6, 967195696908770.8),
)
H2O_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -1.7289263558195928e16), (0.0, 5938862032240302.0))],
+ poles=[((-0.0 - 1j * 1.7289263558195928e16), (0.0 + 1j * 5938862032240302.0))],
frequency_range=(362698386340789.0, 1450793545363156.0),
)
HMDS_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-379816861999031.8, -1.8227252520914852e16), (0.0, 1.0029341899480378e16))],
+ poles=[((-379816861999031.8 - 1j * 1.8227252520914852e16), (0.0 + 1j * 1.0029341899480378e16))],
frequency_range=(362698386340789.0, 1571693007476752.5),
)
HfO2_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-2278901171994190.5, -1.4098114301144558e16), (0.0, 1.3743164680834702e16))],
+ poles=[
+ ((-2278901171994190.5 - 1j * 1.4098114301144558e16), (0.0 + 1j * 1.3743164680834702e16))
+ ],
frequency_range=(362698386340789.0, 1450793545363156.0),
)
ITO_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-483886682186766.56, -1.031968022520672e16), (0.0, 1.292796190658882e16))],
+ poles=[((-483886682186766.56 - 1j * 1.031968022520672e16), (0.0 + 1j * 1.292796190658882e16))],
frequency_range=(362698386340789.0, 1450793545363156.0),
)
InP_Pettit1965 = PoleResidue(
eps_inf=1,
poles=[
- ((0.0, 3007586733129570.0), (-0.0, -3482785436964042.0)),
- ((0.0, 57193003520845.59), (-0.0, -79069327367569.03)),
+ ((0.0 + 1j * 3007586733129570.0), (-0.0 - 1j * 3482785436964042.0)),
+ ((0.0 + 1j * 57193003520845.59), (-0.0 - 1j * 79069327367569.03)),
],
frequency_range=(29979245858094.68, 315571009032575.6),
)
MgF2_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -2.5358092974503356e16), (0.0, 1.1398462792039258e16))],
+ poles=[((-0.0 - 1j * 2.5358092974503356e16), (0.0 + 1j * 1.1398462792039258e16))],
frequency_range=(193439139381754.16, 918835912063332.1),
)
@@ -1419,16 +1421,16 @@
eps_inf=1.0,
poles=[
(
- (-56577071909034.84, 1.709097252165159e16),
- (104656337098134.19, -1.5807476741024398e16),
+ (-56577071909034.84 + 1j * 1.709097252165159e16),
+ (104656337098134.19 - 1j * 1.5807476741024398e16),
),
(
- (-1.4437966258192067e17, -2258757151354688.5),
- (1.5132011505098516e16, -4.810654072512032e17),
+ (-1.4437966258192067e17 - 1j * 2258757151354688.5),
+ (1.5132011505098516e16 - 1j * 4.810654072512032e17),
),
(
- (-982824644.4296285, -4252237346494.8228),
- (338287950556.00256, 4386571425642974.0),
+ (-982824644.4296285 - 1j * 4252237346494.8228),
+ (338287950556.00256 + 1j * 4386571425642974.0),
),
],
frequency_range=(55517121959434.59, 832756829391519.0),
@@ -1438,24 +1440,24 @@
eps_inf=1.0,
poles=[
(
- (-130147997.31788255, -149469760922412.1),
- (74748038596353.97, 3.01022049985022e17),
+ (-130147997.31788255 - 1j * 149469760922412.1),
+ (74748038596353.97 + 1j * 3.01022049985022e17),
),
(
- (-27561493423510.0, -165502078583657.34),
- (8080361635535756.0, -1.8948337145713684e16),
+ (-27561493423510.0 - 1j * 165502078583657.34),
+ (8080361635535756.0 - 1j * 1.8948337145713684e16),
),
(
- (-226806637902024.8, -346391867988.41425),
- (1.238514968044484e16, -1.3261156707711676e16),
+ (-226806637902024.8 - 1j * 346391867988.41425),
+ (1.238514968044484e16 - 1j * 1.3261156707711676e16),
),
(
- (-980995274941083.2, -912202488656228.9),
- (-898785384166810.4, 2.414339979079635e16),
+ (-980995274941083.2 - 1j * 912202488656228.9),
+ (-898785384166810.4 + 1j * 2.414339979079635e16),
),
(
- (-4687205371459777.0, -8976520568647726.0),
- (-5847989829468756.0, 8791690849762542.0),
+ (-4687205371459777.0 - 1j * 8976520568647726.0),
+ (-5847989829468756.0 + 1j * 8791690849762542.0),
),
],
frequency_range=(972331166717521.5, 1.002716515677444e16),
@@ -1463,42 +1465,42 @@
PEI_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -1.8231209375953524e16), (0.0, 9936009109894670.0))],
+ poles=[((-0.0 - 1j * 1.8231209375953524e16), (0.0 + 1j * 9936009109894670.0))],
frequency_range=(181349193170394.5, 1148544890079165.2),
)
PEN_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -6981033923542204.0), (0.0, 5117097865956436.0))],
+ poles=[((-0.0 - 1j * 6981033923542204.0), (0.0 + 1j * 5117097865956436.0))],
frequency_range=(362698386340789.0, 773756557527016.6),
)
PET_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -1.063487213597289e16), (0.0, 1.169835934957018e16))],
+ poles=[((-0.0 - 1j * 1.063487213597289e16), (0.0 + 1j * 1.169835934957018e16))],
)
PMMA_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -1.7360669128251744e16), (0.0, 1.015599144002727e16))],
+ poles=[((-0.0 - 1j * 1.7360669128251744e16), (0.0 + 1j * 1.015599144002727e16))],
frequency_range=(181349193170394.5, 1100185105233726.6),
)
PMMA_Sultanova2009 = PoleResidue(
eps_inf=1,
- poles=[((0.0, 1.7709719337156064e16), (-0.0, -1.0465558642292376e16))],
+ poles=[((0.0 + 1j * 1.7709719337156064e16), (-0.0 - 1j * 1.0465558642292376e16))],
frequency_range=(284973819943865.75, 686338046201801.2),
)
PTFE_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -2.5039046810424176e16), (0.0, 8763666383648461.0))],
+ poles=[((-0.0 - 1j * 2.5039046810424176e16), (0.0 + 1j * 8763666383648461.0))],
frequency_range=(362698386340789.0, 1571693007476752.5),
)
PVC_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -1.8551774807480708e16), (0.0, 1.209575717447742e16))],
+ poles=[((-0.0 - 1j * 1.8551774807480708e16), (0.0 + 1j * 1.209575717447742e16))],
frequency_range=(362698386340789.0, 1148544890079165.2),
)
@@ -1506,21 +1508,21 @@
eps_inf=1.0,
poles=[
(
- (-27947601188212.62, -88012749128378.45),
- (-116820857784644.19, 4.431305747926611e17),
+ (-27947601188212.62 - 1j * 88012749128378.45),
+ (-116820857784644.19 + 1j * 4.431305747926611e17),
),
- ((-42421241831450.59, 0.0), (2.0926917440899536e16, -2.322604734166214e17)),
+ ((-42421241831450.59 + 1j * 0.0), (2.0926917440899536e16 - 1j * 2.322604734166214e17)),
(
- (-1156114791888924.0, -459830394883492.75),
- (-2205692318269041.5, 5.882192811019071e16),
+ (-1156114791888924.0 - 1j * 459830394883492.75),
+ (-2205692318269041.5 + 1j * 5.882192811019071e16),
),
(
- (-16850504828430.291, -19945795950186.92),
- (-2244562993366961.8, 2.2399893428156035e17),
+ (-16850504828430.291 - 1j * 19945795950186.92),
+ (-2244562993366961.8 + 1j * 2.2399893428156035e17),
),
(
- (-1.0165311890218712e16, -6195195244753680.0),
- (-8682197716799510.0, -2496615613677907.5),
+ (-1.0165311890218712e16 - 1j * 6195195244753680.0),
+ (-8682197716799510.0 - 1j * 2496615613677907.5),
),
],
frequency_range=(972331166717521.5, 1.002716515677444e16),
@@ -1528,19 +1530,19 @@
Polycarbonate_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -1.8240324980641504e16), (0.0, 1.3716724385442412e16))],
+ poles=[((-0.0 - 1j * 1.8240324980641504e16), (0.0 + 1j * 1.3716724385442412e16))],
frequency_range=(362698386340789.0, 967195696908770.8),
)
Polycarbonate_Sultanova2009 = PoleResidue(
eps_inf=1,
- poles=[((0.0, 1.290535618305202e16), (-0.0, -9151188069402186.0))],
+ poles=[((0.0 + 1j * 1.290535618305202e16), (-0.0 - 1j * 9151188069402186.0))],
frequency_range=(284973819943865.75, 686338046201801.2),
)
Polystyrene_Sultanova2009 = PoleResidue(
eps_inf=1,
- poles=[((0.0, 1.3248080478547494e16), (-0.0, -9561802085391654.0))],
+ poles=[((0.0 + 1j * 1.3248080478547494e16), (-0.0 - 1j * 9561802085391654.0))],
frequency_range=(284973819943865.75, 686338046201801.2),
)
@@ -1548,24 +1550,24 @@
eps_inf=1.0,
poles=[
(
- (-101718046412896.23, -222407105780688.0),
- (4736075731111783.0, 7.146182537352074e17),
+ (-101718046412896.23 - 1j * 222407105780688.0),
+ (4736075731111783.0 + 1j * 7.146182537352074e17),
),
(
- (-78076341531946.67, -60477052937666.555),
- (5454987478240738.0, 4.413657205572709e17),
+ (-78076341531946.67 - 1j * 60477052937666.555),
+ (5454987478240738.0 + 1j * 4.413657205572709e17),
),
(
- (-6487635330201033.0, -155489439108998.5),
- (5343260155670645.0, 2.067963085430939e17),
+ (-6487635330201033.0 - 1j * 155489439108998.5),
+ (5343260155670645.0 + 1j * 2.067963085430939e17),
),
(
- (-2281398148570798.5, -64631536899092.15),
- (-1930595420879896.2, -4.8251418308161344e17),
+ (-2281398148570798.5 - 1j * 64631536899092.15),
+ (-1930595420879896.2 - 1j * 4.8251418308161344e17),
),
(
- (-9967323231923196.0, -4041974141709040.5),
- (-501748269346742.7, 6.883385112306915e16),
+ (-9967323231923196.0 - 1j * 4041974141709040.5),
+ (-501748269346742.7 + 1j * 6.883385112306915e16),
),
],
frequency_range=(120884055879414.03, 2997924585809468.0),
@@ -1573,76 +1575,76 @@
Sapphire_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -2.0143967092980652e16), (0.0, 2.105044561216478e16))],
+ poles=[((-0.0 - 1j * 2.0143967092980652e16), (0.0 + 1j * 2.105044561216478e16))],
frequency_range=(362698386340789.0, 1329894083249559.8),
)
Si3N4_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-1357465464784539.5, -4646140872332419.0), (0.0, 1.103606337254506e16))],
+ poles=[((-1357465464784539.5 - 1j * 4646140872332419.0), (0.0 + 1j * 1.103606337254506e16))],
frequency_range=(362698386340789.0, 1329894083249559.8),
)
Si3N4_Philipp1973 = PoleResidue(
eps_inf=1,
- poles=[((0.0, 1.348644355236665e16), (-0.0, -1.9514209498096924e16))],
+ poles=[((0.0 + 1j * 1.348644355236665e16), (-0.0 - 1j * 1.9514209498096924e16))],
frequency_range=(241768111758828.06, 1448272746767859.0),
)
Si3N4_Luke2015 = PoleResidue(
eps_inf=1,
poles=[
- ((0.0, 1.391786035350109e16), (-0.0, -2.1050067891652724e16)),
- ((0.0, 1519267431623.5857), (-0.0, -3.0623873619236616e16)),
+ ((0.0 + 1j * 1.391786035350109e16), (-0.0 - 1j * 2.1050067891652724e16)),
+ ((0.0 + 1j * 1519267431623.5857), (-0.0 - 1j * 3.0623873619236616e16)),
],
frequency_range=(54468106573573.19, 967072447035312.2),
)
SiC_Horiba = PoleResidue(
eps_inf=3.0,
- poles=[((-0.0, -1.2154139583969018e16), (0.0, 2.3092865209541132e16))],
+ poles=[((-0.0 - 1j * 1.2154139583969018e16), (0.0 + 1j * 2.3092865209541132e16))],
frequency_range=(145079354536315.6, 967195696908770.8),
)
SiN_Horiba = PoleResidue(
eps_inf=2.32,
- poles=[((-302334222151229.3, -9863009385232968.0), (0.0, 6244215164693547.0))],
+ poles=[((-302334222151229.3 - 1j * 9863009385232968.0), (0.0 + 1j * 6244215164693547.0))],
frequency_range=(145079354536315.6, 1450793545363156.0),
)
SiO2_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-75963372399806.36, -1.823105111824081e16), (0.0, 1.0209565875622414e16))],
+ poles=[((-75963372399806.36 - 1j * 1.823105111824081e16), (0.0 + 1j * 1.0209565875622414e16))],
frequency_range=(169259246959034.88, 1208994621135963.5),
)
SiON_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -1.651139862482191e16), (0.0, 1.1079148477255502e16))],
+ poles=[((-0.0 - 1j * 1.651139862482191e16), (0.0 + 1j * 1.1079148477255502e16))],
frequency_range=(181349193170394.5, 725396772681578.0),
)
Ta2O5_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-618341851334423.8, -1.205777404193952e16), (0.0, 1.8938176054079756e16))],
+ poles=[((-618341851334423.8 - 1j * 1.205777404193952e16), (0.0 + 1j * 1.8938176054079756e16))],
frequency_range=(181349193170394.5, 967195696908770.8),
)
Ti_Werner2009 = PoleResidue(
eps_inf=1.0,
poles=[
- ((-55002727357489.695, -103457301057900.64), (0.0, 1.4157836508658926e18)),
- ((-3889516074161299.0, -6.314261108475189e16), (0.0, 2192302508847248.2)),
- ((-2919746613155850.5, -7.211858151732786e16), (0.0, 744301222539582.0)),
- ((-4635394958195360.0, -5.622429893839941e16), (0.0, 2101343798471838.0)),
- ((-9774364062177540.0, -4844300045008988.0), (0.0, 7.377824793744533e16)),
+ ((-55002727357489.695 - 1j * 103457301057900.64), (0.0 + 1j * 1.4157836508658926e18)),
+ ((-3889516074161299.0 - 1j * 6.314261108475189e16), (0.0 + 1j * 2192302508847248.2)),
+ ((-2919746613155850.5 - 1j * 7.211858151732786e16), (0.0 + 1j * 744301222539582.0)),
+ ((-4635394958195360.0 - 1j * 5.622429893839941e16), (0.0 + 1j * 2101343798471838.0)),
+ ((-9774364062177540.0 - 1j * 4844300045008988.0), (0.0 + 1j * 7.377824793744533e16)),
],
frequency_range=(120884055879414.03, 2997924585809468.0),
)
TiOx_Horiba = PoleResidue(
eps_inf=0.29,
- poles=[((-0.0, -9875238411974826.0), (0.0, 1.7429795797135566e16))],
+ poles=[((-0.0 - 1j * 9875238411974826.0), (0.0 + 1j * 1.7429795797135566e16))],
frequency_range=(145079354536315.6, 725396772681578.0),
)
@@ -1650,24 +1652,24 @@
eps_inf=1.0,
poles=[
(
- (-6008545281436.0, -273822982315836.25),
- (2874701466157776.0, 6.354855141434104e17),
+ (-6008545281436.0 - 1j * 273822982315836.25),
+ (2874701466157776.0 + 1j * 6.354855141434104e17),
),
(
- (-18716635733325.97, -7984905262277.852),
- (2669048417776342.0, 1.4111869583971584e17),
+ (-18716635733325.97 - 1j * 7984905262277.852),
+ (2669048417776342.0 + 1j * 1.4111869583971584e17),
),
(
- (-7709052771634303.0, -64340875428723.28),
- (501889387931716.2, 5.510078120444142e16),
+ (-7709052771634303.0 - 1j * 64340875428723.28),
+ (501889387931716.2 + 1j * 5.510078120444142e16),
),
(
- (-330546522884264.1, -1422878310689065.0),
- (584859595267922.1, 3.664402566039364e16),
+ (-330546522884264.1 - 1j * 1422878310689065.0),
+ (584859595267922.1 + 1j * 3.664402566039364e16),
),
(
- (-3989296857299139.0, -3986090497375137.0),
- (-352374832782093.06, 6.323677441887342e16),
+ (-3989296857299139.0 - 1j * 3986090497375137.0),
+ (-352374832782093.06 + 1j * 6.323677441887342e16),
),
],
frequency_range=(120884055879414.03, 2997924585809468.0),
@@ -1675,15 +1677,15 @@
Y2O3_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-0.0, -1.3814698904628784e16), (0.0, 1.1846104310719182e16))],
+ poles=[((-0.0 - 1j * 1.3814698904628784e16), (0.0 + 1j * 1.1846104310719182e16))],
frequency_range=(374788332552148.7, 967195696908770.8),
)
Y2O3_Nigara1968 = PoleResidue(
eps_inf=1,
poles=[
- ((0.0, 1.3580761146063806e16), (-0.0, -1.7505601117276244e16)),
- ((0.0, 82126420080181.8), (-0.0, -161583731507757.7)),
+ ((0.0 + 1j * 1.3580761146063806e16), (-0.0 - 1j * 1.7505601117276244e16)),
+ ((0.0 + 1j * 82126420080181.8), (-0.0 - 1j * 161583731507757.7)),
],
frequency_range=(31228381102181.96, 1199169834323787.2),
)
@@ -1691,35 +1693,35 @@
YAG_Zelmon1998 = PoleResidue(
eps_inf=1,
poles=[
- ((0.0, 1.7303796419562446e16), (-0.0, -1.974363171472075e16)),
- ((0.0, 112024123195387.16), (-0.0, -183520159101147.16)),
+ ((0.0 + 1j * 1.7303796419562446e16), (-0.0 - 1j * 1.974363171472075e16)),
+ ((0.0 + 1j * 112024123195387.16), (-0.0 - 1j * 183520159101147.16)),
],
frequency_range=(59958491716189.36, 749481146452367.0),
)
ZrO2_Horiba = PoleResidue(
eps_inf=1.0,
- poles=[((-97233116671752.14, -1.446765717253359e16), (0.0, 2.0465425413547396e16))],
+ poles=[((-97233116671752.14 - 1j * 1.446765717253359e16), (0.0 + 1j * 2.0465425413547396e16))],
frequency_range=(362698386340789.0, 725396772681578.0),
)
aSi_Horiba = PoleResidue(
eps_inf=3.109,
- poles=[((-1458496750076282.0, -5789844327200831.0), (0.0, 4.485863370051096e16))],
+ poles=[((-1458496750076282.0 - 1j * 5789844327200831.0), (0.0 + 1j * 4.485863370051096e16))],
frequency_range=(362698386340789.0, 1450793545363156.0),
)
cSi_SalzbergVilla1957 = PoleResidue(
eps_inf=1.0,
- poles=[((0.0, 6206417594288582.0), (-0.0, -3.311074436985222e16))],
+ poles=[((0.0 + 1j * 6206417594288582.0), (-0.0 - 1j * 3.311074436985222e16))],
frequency_range=(27253859870995.164, 220435631309519.7),
)
cSi_Li1993_293K = PoleResidue(
eps_inf=1.0,
poles=[
- ((0.0, 4010819041318578.0), (0.0, 1.2156273362672036e16)),
- ((0.0, 5022626939326166.0), (-0.0, -4.1977794227247144e16)),
+ ((0.0 + 1j * 4010819041318578.0), (0.0 + 1j * 1.2156273362672036e16)),
+ ((0.0 + 1j * 5022626939326166.0), (-0.0 - 1j * 4.1977794227247144e16)),
],
frequency_range=(21413747041496.2, 249827048817455.7),
)
@@ -1728,20 +1730,20 @@
eps_inf=1.0,
poles=[
(
- (-516580533476358.94, -7988869406082532.0),
- (531784950915900.1, 4114144409090735.5),
+ (-516580533476358.94 - 1j * 7988869406082532.0),
+ (531784950915900.1 + 1j * 4114144409090735.5),
),
(
- (-422564506478804.25, -6388843514992565.0),
- (2212987364690094.5, 1.665883190033301e16),
+ (-422564506478804.25 - 1j * 6388843514992565.0),
+ (2212987364690094.5 + 1j * 1.665883190033301e16),
),
(
- (-169315596364414.94, 5194420450502291.0),
- (301374428182025.6, -4618167601749804.0),
+ (-169315596364414.94 + 1j * 5194420450502291.0),
+ (301374428182025.6 - 1j * 4618167601749804.0),
),
(
- (-379444981070553.4, 5656363945615038.0),
- (1105733518717537.1, -8204725853411607.0),
+ (-379444981070553.4 + 1j * 5656363945615038.0),
+ (1105733518717537.1 - 1j * 8204725853411607.0),
),
],
frequency_range=(206753419710997.8, 1199169834323787.2),
diff --git a/tidy3d/plugins/dispersion/fit.py b/tidy3d/plugins/dispersion/fit.py
index b895a2c20c..32df967660 100644
--- a/tidy3d/plugins/dispersion/fit.py
+++ b/tidy3d/plugins/dispersion/fit.py
@@ -253,8 +253,7 @@ def _make_medium(self, coeffs):
Dispersive medium corresponding to this set of ``coeffs``.
"""
poles_complex = _coeffs_to_poles(coeffs)
- poles_re_im = [(_unpack_complex(a), _unpack_complex(c)) for (a, c) in poles_complex]
- return PoleResidue(poles=poles_re_im, frequency_range=self.frequency_range)
+ return PoleResidue(poles=poles_complex, frequency_range=self.frequency_range)
def fit_single(
self,
diff --git a/tidy3d/web/auth.py b/tidy3d/web/auth.py
index 3d5084fdad..4a606dc8a5 100644
--- a/tidy3d/web/auth.py
+++ b/tidy3d/web/auth.py
@@ -39,9 +39,9 @@ def set_authentication_config(email: str, password: str) -> None:
def get_credentials() -> None:
- """what"""
+ """Tries to log user in from file, if not working, prompts user for login info and saves."""
- # if we find both email and password in the credential path
+ # if we find something in the credential path
if os.path.exists(credential_path):
# try to authenticate them
diff --git a/tidy3d/web/container.py b/tidy3d/web/container.py
index eb73f10e6f..e8ea3db1e2 100644
--- a/tidy3d/web/container.py
+++ b/tidy3d/web/container.py
@@ -19,11 +19,11 @@
class WebContainer(Tidy3dBaseModel, ABC):
- """base class for job and batch, technically not used"""
+ """Base class for :class:`Job` and :class:`Batch`, technically not used"""
class Job(WebContainer):
- """Interface for managing the running of a :class:`Simulation` on server."""
+ """Interface for managing the running of a :class:`.Simulation` on server."""
simulation: Simulation
task_name: TaskName
@@ -35,13 +35,13 @@ def run(self, path: str = DEFAULT_DATA_PATH) -> SimulationData:
Parameters
----------
- path_dir : str
+ path_dir : str = "./simulation_data.hdf5"
Base directory where data will be downloaded, by default current working directory.
Returns
-------
- ``{TaskName: SimulationData}``
- Dictionary mapping task name to :class:`SimulationData` for :class:`Job`.
+ Dict[str: :class:`.SimulationData`]
+ Dictionary mapping task name to :class:`.SimulationData` for :class:`Job`.
"""
self.upload()
@@ -50,7 +50,12 @@ def run(self, path: str = DEFAULT_DATA_PATH) -> SimulationData:
return self.load_data(path=path)
def upload(self) -> None:
- """Upload simulation to server without running."""
+ """Upload simulation to server without running.
+
+ Note
+ ----
+ To start the simulation running, call :meth:`Job.start` after uploaded.
+ """
task_id = web.upload(
simulation=self.simulation, task_name=self.task_name, folder_name=self.folder_name
)
@@ -61,34 +66,46 @@ def get_info(self) -> TaskInfo:
Returns
-------
- ``TaskInfo``
+ :class:`TaskInfo`
:class:`TaskInfo` object containing info about status, size, credits of task and others.
"""
+
task_info = web.get_info(task_id=self.task_id)
return task_info
@property
def status(self):
- """return current status"""
+ """Return current status of :class:`Job`."""
return self.get_info().status
def start(self) -> None:
- """start running a task"""
+ """Start running a :class:`Job`.
+
+ Note
+ ----
+ To monitor progress of the :class:`Job`, call :meth:`Job.monitor` after started.
+ """
web.start(self.task_id)
def get_run_info(self) -> RunInfo:
- """Return information about the running ``Job``.
+ """Return information about the running :class:`Job`.
Returns
-------
- RunInfo
+ :class:`RunInfo`
Task run information.
"""
run_info = web.get_run_info(task_id=self.task_id)
return run_info
def monitor(self) -> None:
- """monitor progress of running ``Job``."""
+ """Monitor progress of running :class:`Job`.
+
+ Note
+ ----
+ To load the output of completed simulation into :class:`.SimulationData`objets,
+ call :meth:`Job.load_data`.
+ """
status = self.status
console = Console()
@@ -107,8 +124,12 @@ def download(self, path: str = DEFAULT_DATA_PATH) -> None:
Parameters
----------
- path : ``str``
+ path : str = "./simulation_data.hdf5"
Path to download data as ``.hdf5`` file (including filename).
+
+ Note
+ ----
+ To load the data into :class:`.SimulationData`objets, can call :meth:`Job.load_data`.
"""
web.download(task_id=self.task_id, simulation=self.simulation, path=path)
@@ -118,12 +139,12 @@ def load_data(self, path: str = DEFAULT_DATA_PATH) -> SimulationData:
Parameters
----------
- path : str
+ path : str = "./simulation_data.hdf5"
Path to download data as ``.hdf5`` file (including filename).
Returns
-------
- :class:`SimulationData`
+ :class:`.SimulationData`
Object containing data about simulation.
"""
return web.load_data(task_id=self.task_id, simulation=self.simulation, path=path)
@@ -135,13 +156,13 @@ def delete(self):
class Batch(WebContainer):
- """Interface for submitting several :class:`Simulation` objects to sever.
+ """Interface for submitting several :class:`.Simulation` objects to sever.
Parameters
----------
- simulations : ``{str: :class:`Simulation`}``
- Mapping of task name to :class:`Simulation` objects.
- folder_name : ``str``, optional
+ simulations : Dict[str, :class:`.Simulation`]
+ Mapping of task name to :class:`.Simulation` objects.
+ folder_name : ``str`` = './'
Name of folder to store member of each batch on web UI.
"""
@@ -150,17 +171,30 @@ class Batch(WebContainer):
folder_name: str = "default"
def run(self, path_dir: str = DEFAULT_DATA_DIR):
- """Run each :class:`Job` in :class:`Batch` all the way through and return iterator for data.
+ """Upload and run each simulation in :class:`Batch`.
+ Returns generator that can be used to loop through data results.
Parameters
----------
- path_dir : ``str``
+ path_dir : str
Base directory where data will be downloaded, by default current working directory.
Yields
------
- ``(TaskName, SimulationData)``
- Task name and Simulation data, returned one by one if iterated over.
+ str, :class:`.SimulationData`
+ Yields the name of task
+ and its corresponding :class:`.SimulationData` at each iteration.
+
+ Note
+ ----
+ A typical usage might look like:
+
+ >>> batch_results = batch.run()
+ >>> for task_name, sim_data in batch_results:
+ ... # do something with data.
+
+ Note that because ``batch_results`` is a generator, only the current iteration of
+ :class:`.SimulationData` is stored in memory at a time.
"""
self.upload()
@@ -170,7 +204,12 @@ def run(self, path_dir: str = DEFAULT_DATA_DIR):
return self.items()
def upload(self) -> None:
- """create jobs and upload to server"""
+ """Create a series of tasks in the :class:`Batch` and upload them to server.
+
+ Note
+ ----
+ To start the simulations running, must call :meth:`Batch.start` after uploaded.
+ """
self.jobs = {}
for task_name, simulation in self.simulations.items():
job = Job(simulation=simulation, task_name=task_name, folder_name=self.folder_name)
@@ -178,12 +217,12 @@ def upload(self) -> None:
job.upload()
def get_info(self) -> Dict[TaskName, TaskInfo]:
- """get general information about all job's task
+ """Get information about each task in the :class:`Batch`.
Returns
-------
- ``{str: :class:`TaskInfo`}``
- Description
+ Dict[str, :class:`TaskInfo`]
+ Mapping of task name to data about task associated with each task.
"""
info_dict = {}
for task_name, job in self.jobs.items():
@@ -192,17 +231,22 @@ def get_info(self) -> Dict[TaskName, TaskInfo]:
return info_dict
def start(self) -> None:
- """start running a task"""
+ """Start running all tasks in the :class:`Batch`.
+
+ Note
+ ----
+ To monitor the running simulations, can call :meth:`Batch.monitor`.
+ """
for _, job in self.jobs.items():
job.start()
def get_run_info(self) -> Dict[TaskName, RunInfo]:
- """get information about a each of the tasks in batch.
+ """get information about a each of the tasks in the :class:`Batch`.
Returns
-------
- ``{str: RunInfo}``
- Dictionary of task name to dictionary of run info for each task.
+ Dict[str: :class:`RunInfo`]
+ Maps task names to run info for each task in the :class:`Batch`.
"""
run_info_dict = {}
for task_name, job in self.jobs.items():
@@ -211,7 +255,12 @@ def get_run_info(self) -> Dict[TaskName, RunInfo]:
return run_info_dict
def monitor(self) -> None: # pylint:disable=too-many-locals
- """monitor progress of each of the running tasks in batch."""
+ """Monitor progress of each of the running tasks.
+
+ Note
+ ----
+ To loop through the data of completed simulations, can call :meth:`Batch.items`.
+ """
def pbar_description(task_name: str, status: str) -> str:
return f"{task_name}: status = {status}"
@@ -248,50 +297,63 @@ def pbar_description(task_name: str, status: str) -> str:
@staticmethod
def _job_data_path(task_id: TaskId, path_dir: str = DEFAULT_DATA_DIR):
- """Default path to data of a single Job in Batch
+ """Default path to data of a single :class:`Job` in :class:`Batch`.
Parameters
----------
- task_id : ``TaskId``
- task_id corresponding to a :class:`Job`
- path_dir : ``str``, optional
- Base directory where data will be downloaded, by default current working directory.
+ task_id : str
+ task_id corresponding to a :class:`Job`.
+ path_dir : str = './'
+ Base directory where data will be downloaded, by default, the current working directory.
Returns
-------
str
- path of the data file
+ Full path to the data file.
"""
return os.path.join(path_dir, f"{str(task_id)}.hdf5")
def download(self, path_dir: str = DEFAULT_DATA_DIR) -> None:
- """download results.
+ """Download results of each task.
Parameters
----------
- path_dir : ``str``
- Base directory where data will be downloaded, by default current working directory.
+ path_dir : str = './'
+ Base directory where data will be downloaded, by default the current working directory.
+
+ Note
+ ----
+ To load the data into :class:`.SimulationData`objets, can call :meth:`Batch.items`.
+
+ The data for each task will be named as ``{path_dir}/{task_name}.hdf5``.
+
"""
+
for task_name, job in self.jobs.items():
job_path = self._job_data_path(task_name, path_dir)
job.download(path=job_path)
def load_data(self, path_dir: str = DEFAULT_DATA_DIR) -> Dict[TaskName, SimulationData]:
- """download results and load them into SimulationData object.
- Note: this will return a dictionary of :class:`SimulationData` objects, each of which can
- hold a large amount of data.
- Use `Batch.items()` to instead loop through :class:`SimulationData` objects and only store
- current iteration in memory if many simulations or large amounts of data.
+ """Download results and load them into :class:`.SimulationData` object.
Parameters
----------
- path_dir : str
+ path_dir : str = './'
Base directory where data will be downloaded, by default current working directory.
Returns
-------
- ``{TaskName: SimulationData}``
- Dictionary mapping task name to :class:`SimulationData` for :class:`Job`.
+ Dict[str, :class:`.SimulationData`]
+ Dictionary mapping task names to :class:`.SimulationData` for :class:`Batch`.
+
+ Note
+ ----
+ This will return a dictionary of :class:`.SimulationData` objects,
+ each of which can hold a large amount of data.
+ If many simulations or large amounts of data,
+ use ``for task_name, sim_data in Batch.items():``
+ to instead loop through :class:`.SimulationData` objects and only store
+ current iteration in memory.
"""
sim_data_dir = {}
self.download(path_dir=path_dir)
@@ -302,23 +364,25 @@ def load_data(self, path_dir: str = DEFAULT_DATA_DIR) -> Dict[TaskName, Simulati
return sim_data_dir
def delete(self):
- """delete server-side data associated with job"""
+ """Delete server-side data associated with each task in the batch."""
for _, job in self.jobs.items():
job.delete()
self.jobs = None
def items(self, path_dir: str = DEFAULT_DATA_DIR) -> Generator:
- """simple iterator, ``for task_name, sim_data in batch.items(): do something``
+ """Generates :class:`.SimulationData` for batch.
+ Used like: ``for task_name, sim_data in batch.items(): do something``.
Parameters
----------
- path_dir : ``str``
+ path_dir : str = './'
Base directory where data will be downloaded, by default current working directory.
Yields
------
- ``(TaskName, SimulationData)``
- Task name and Simulation data, returned one by one if iterated over.
+ str, :class:`.SimulationData`
+ Yields the name of task
+ and its corresponding :class:`.SimulationData` at each iteration.
"""
for task_name, job in self.jobs.items():
job_path = self._job_data_path(task_id=job.task_id, path_dir=path_dir)
diff --git a/tidy3d/web/webapi.py b/tidy3d/web/webapi.py
index 72a8311e41..4bd9f7dce3 100644
--- a/tidy3d/web/webapi.py
+++ b/tidy3d/web/webapi.py
@@ -19,11 +19,12 @@
from ..convert import export_old_json, load_old_monitor_data, load_solver_results
+# TODO: Original simulation still needed in download functions because we don't convert
+# old json files to new ones.
+
REFRESH_TIME = 0.3
TOTAL_DOTS = 3
-""" webapi functions """
-
def run(
simulation: Simulation,
@@ -31,23 +32,24 @@ def run(
folder_name: str = "default",
path: str = "simulation_data.hdf5",
) -> SimulationData:
- """submits simulation to server, starts running, monitors progress, downloads and loads results.
+ """Submits a :class:`.Simulation` to server, starts running, monitors progress, downloads,
+ and loads results as a :class:`.SimulationData` object.
Parameters
----------
- simulation : :class:`Simulation`
+ simulation : :class:`.Simulation`
Simulation to upload to server.
- task_name : ``str``
- Name of task
- path : ``str``
+ task_name : str
+ Name of task.
+ path : str = "simulation_data.hdf5"
Path to download results file (.hdf5), including filename.
- folder_name : ``str``
- Name of folder to store task on web UI
+ folder_name : str = "default"
+ Name of folder to store task on web UI.
Returns
-------
- :class:`SimulationData`
- Object containing solver results for the supplied :class:`Simulation`.
+ :class:`.SimulationData`
+ Object containing solver results for the supplied :class:`.Simulation`.
"""
task_id = upload(simulation=simulation, task_name=task_name, folder_name=folder_name)
start(task_id)
@@ -56,22 +58,25 @@ def run(
def upload(simulation: Simulation, task_name: str, folder_name: str = "default") -> TaskId:
- """upload simulation to server (as draft, dont run).
+ """Upload simulation to server, but do not start running :class:`.Simulation`.
Parameters
----------
- simulation : :class:`Simulation`
+ simulation : :class:`.Simulation`
Simulation to upload to server.
- task_name : ``str``
- name of task
- folder_name : ``str``
- name of folder to store task on web UI
-
+ task_name : str
+ Name of task.
+ folder_name : str
+ Name of folder to store task on web UI
Returns
-------
TaskId
Unique identifier of task on server.
+
+ Note
+ ----
+ To start the simulation running, must call :meth:`start` after uploaded.
"""
return _upload_task(simulation=simulation, task_name=task_name, folder_name=folder_name)
@@ -81,12 +86,12 @@ def get_info(task_id: TaskId) -> TaskInfo:
Parameters
----------
- task_id : TaskId
- Unique identifier of task on server.
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
Returns
-------
- TaskInfo
+ :class:`TaskInfo`
Object containing information about status, size, credits of task.
"""
method = os.path.join("fdtd/task", task_id)
@@ -101,8 +106,12 @@ def start(task_id: TaskId) -> None:
Parameters
----------
- task_id : TaskId
- Unique identifier of task on server.
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+
+ Note
+ ----
+ To monitor progress, can call :meth:`monitor` after starting simulation.
"""
task = get_info(task_id)
folder_name = task.folderId
@@ -112,12 +121,12 @@ def start(task_id: TaskId) -> None:
def get_run_info(task_id: TaskId):
- """gets the % done and field_decay for a running task
+ """Gets the % done and field_decay for a running task.
Parameters
----------
- task_id : TaskId
- Unique identifier of task on server.
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
Returns
-------
@@ -140,8 +149,12 @@ def monitor(task_id: TaskId) -> None:
Parameters
----------
- task_id : ``TaskId``
- Unique identifier of task on server.
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+
+ Note
+ ----
+ To load results when finished, may call :meth:`load_data`.
"""
task_info = get_info(task_id)
@@ -198,13 +211,15 @@ def monitor(task_id: TaskId) -> None:
def download(task_id: TaskId, simulation: Simulation, path: str = "simulation_data.hdf5") -> None:
- """Fownload results of task and log to file.
+ """Download results of task and log to file.
Parameters
----------
- task_id : TaskId
- Unique identifier of task on server.
- path : str
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ simulation : :class:`.Simulation`
+ Original simulation.
+ path : str = "simulation_data.hdf5"
Download path to .hdf5 data file (including filename).
"""
@@ -258,20 +273,22 @@ def load_data(
path: str = "simulation_data.hdf5",
replace_existing=True,
) -> SimulationData:
- """Download and Load simultion results into ``SimulationData`` object.
+ """Download and Load simultion results into :class:`.SimulationData` object.
Parameters
----------
- task_id : ``TaskId``
- Unique identifier of task on server.
- path : ``str``
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ simulation : :class:`.Simulation`
+ Original simulation.
+ path : str
Download path to .hdf5 data file (including filename).
- replace_existing: ``bool``
- Downloads even if file exists (overwriting).
+ replace_existing: bool = True
+ Downloads even if file exists (overwriting the existing).
Returns
-------
- :class:`SimulationData`
+ :class:`.SimulationData`
Object containing simulation data.
"""
if not os.path.exists(path) or replace_existing:
@@ -286,8 +303,8 @@ def delete(task_id: TaskId) -> TaskInfo:
Parameters
----------
- task_id : TaskId
- Unique identifier of task on server.
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
Returns
-------
@@ -313,7 +330,7 @@ def _upload_task( # pylint:disable=too-many-locals
json_string = json.dumps(sim_dict, indent=4)
# TODO: remove node size, time steps, compute weight, worker group
- node_size = int(np.prod([len(sizes) for sizes in simulation.grid.cell_sizes.dict().values()]))
+ node_size = int(np.prod([len(sizes) for sizes in simulation.grid.sizes.dict().values()]))
data = {
"status": "draft",
"solverVersion": solver_version,
@@ -357,15 +374,15 @@ def _upload_task( # pylint:disable=too-many-locals
def _download_file(task_id: TaskId, fname: str, path: str) -> None:
- """Download a specific file ``fname`` to ``path``.
+ """Download a specific file from server.
Parameters
----------
- task_id : ``TaskId``
- Unique identifier of task on server.
- fname : ``str``
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ fname : str
Name of the file on server (eg. ``monitor_data.hdf5``, ``tidy3d.log``, ``simulation.json``)
- path : ``str``
+ path : str
Path where the file will be downloaded to (including filename).
"""
log.info(f'downloading file "{fname}" to "{path}"')
@@ -397,7 +414,7 @@ def _download_file(task_id: TaskId, fname: str, path: str) -> None:
def _rm_file(path: str):
- """clear path if it exists"""
+ """Clear path if it exists."""
if os.path.exists(path) and not os.path.isdir(path):
log.info(f"removing file {path}")
os.remove(path)