diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index b2c06fb53d..54c508d0a8 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -122,7 +122,7 @@ jobs: fetch-depth: 1 - uses: astral-sh/ruff-action@v3 with: - version: 0.5.5 + version: 0.11.11 - name: Run ruff format run: ruff format --check --diff - name: Run ruff check diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8440b0412a..bd8a5dd904 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.5.5" + rev: "v0.11.11" hooks: - id: ruff args: [ --fix ] diff --git a/docs/_ext/custom-meta.py b/docs/_ext/custom-meta.py index ef4a70a614..1f5a9f9ef0 100644 --- a/docs/_ext/custom-meta.py +++ b/docs/_ext/custom-meta.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os diff --git a/docs/_ext/custom-robots.py b/docs/_ext/custom-robots.py index f796a6e509..c0a0002294 100644 --- a/docs/_ext/custom-robots.py +++ b/docs/_ext/custom-robots.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os diff --git a/docs/_ext/custom-sitemap.py b/docs/_ext/custom-sitemap.py index aec6a5d5cb..3af404160e 100644 --- a/docs/_ext/custom-sitemap.py +++ b/docs/_ext/custom-sitemap.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re import xml.etree.ElementTree as ET diff --git a/docs/conf.py b/docs/conf.py index 9a5b62352f..f3794f771c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,8 @@ # relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. # +from __future__ import annotations + import datetime import logging import os diff --git a/docs/generate_doc.py b/docs/generate_doc.py index 0917f94ac5..a02fb9bbeb 100644 --- a/docs/generate_doc.py +++ b/docs/generate_doc.py @@ -1,4 +1,5 @@ # Generate documentation for Material Library (Python) +from __future__ import annotations import numpy as np @@ -24,8 +25,7 @@ def generate_material_library_doc(): def num2str(num): if np.isinf(num): return " " - else: - return str(round(num, 2)) + return str(round(num, 2)) with open(fname, "w") as f: # Write file header @@ -238,8 +238,7 @@ def generate_rf_material_library_doc(): def num2str(num): if np.isinf(num): return " " - else: - return str(round(num, 2)) + return str(round(num, 2)) with open(fname, "w") as f: # Write file header diff --git a/poetry.lock b/poetry.lock index bc81447700..ab69f859a0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "absl-py" @@ -39,7 +39,7 @@ description = "A light, configurable Sphinx theme" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\" and (extra == \"dev\" or extra == \"docs\")" +markers = "python_version < \"3.10\" and (extra == \"dev\" or extra == \"docs\")" files = [ {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, @@ -440,8 +440,8 @@ files = [ jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = [ - {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, + {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, ] [package.extras] @@ -825,7 +825,7 @@ description = "Python library for calculating contours of 2D quadrilateral grids optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, @@ -1082,7 +1082,7 @@ description = "Parallel PyData with Task Scheduling" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "dask-2024.8.0-py3-none-any.whl", hash = "sha256:250ea3df30d4a25958290eec4f252850091c6cfaed82d098179c3b25bba18309"}, {file = "dask-2024.8.0.tar.gz", hash = "sha256:f1fec39373d2f101bc045529ad4e9b30e34e6eb33b7aa0fa7073aec7b1bf9eee"}, @@ -1265,7 +1265,7 @@ description = "Collection of common python utils" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "etils-1.5.2-py3-none-any.whl", hash = "sha256:6dc882d355e1e98a5d1a148d6323679dc47c9a5792939b9de72615aa4737eb0b"}, {file = "etils-1.5.2.tar.gz", hash = "sha256:ba6a3e1aff95c769130776aa176c11540637f5dd881f3b79172a5149b6b1c446"}, @@ -1313,7 +1313,10 @@ files = [ [package.dependencies] fsspec = {version = "*", optional = true, markers = "extra == \"epath\""} importlib_resources = {version = "*", optional = true, markers = "extra == \"epath\""} -typing_extensions = {version = "*", optional = true, markers = "extra == \"epath\" or extra == \"epy\""} +typing_extensions = [ + {version = "*", optional = true, markers = "extra == \"epy\""}, + {version = "*", optional = true, markers = "extra == \"epath\" or extra == \"epy\""}, +] zipp = {version = "*", optional = true, markers = "extra == \"epath\""} [package.extras] @@ -1343,7 +1346,7 @@ description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version < \"3.11\" and (extra == \"dev\" or extra == \"docs\")" +markers = "python_version <= \"3.10\" and (extra == \"dev\" or extra == \"docs\")" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -1426,7 +1429,7 @@ description = "A platform independent file lock." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"dev\" or extra == \"pytorch\" or extra == \"docs\"" +markers = "sys_platform == \"darwin\" and (extra == \"dev\" or extra == \"pytorch\" or extra == \"docs\") or extra == \"dev\" or extra == \"docs\" or extra == \"pytorch\"" files = [ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, @@ -1444,7 +1447,7 @@ description = "Flax: A neural network library for JAX designed for flexibility" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "flax-0.8.5-py3-none-any.whl", hash = "sha256:c96e46d1c48a300d010ebf5c4846f163bdd7acc6efff5ff2bfb1cb5b08aa65d8"}, {file = "flax-0.8.5.tar.gz", hash = "sha256:4a9cb7950ece54b0addaa73d77eba24e46138dbe783d01987be79d20ccb2b09b"}, @@ -1483,7 +1486,7 @@ jax = ">=0.4.27" msgpack = "*" numpy = [ {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, ] optax = "*" orbax-checkpoint = "*" @@ -1955,7 +1958,7 @@ type = ["pytest-mypy"] name = "importlib-resources" version = "6.5.2" description = "Read resources from Python packages" -optional = false +optional = true python-versions = ">=3.9" groups = ["main"] files = [ @@ -2029,7 +2032,7 @@ description = "IPython: Productive Interactive Computing" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\" and (extra == \"dev\" or extra == \"docs\")" +markers = "python_version < \"3.10\" and (extra == \"dev\" or extra == \"docs\")" files = [ {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, @@ -2214,7 +2217,7 @@ description = "Differentiate, compile, and transform Numpy code." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "jax-0.4.30-py3-none-any.whl", hash = "sha256:289b30ae03b52f7f4baf6ef082a9f4e3e29c1080e22d13512c5ecf02d5f1a55b"}, {file = "jax-0.4.30.tar.gz", hash = "sha256:94d74b5b2db0d80672b61d83f1f63ebf99d2ab7398ec12b2ca0c9d1e97afe577"}, @@ -2259,8 +2262,8 @@ files = [ jaxlib = "0.5.3" ml_dtypes = ">=0.4.0" numpy = [ - {version = ">=1.25"}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.25"}, ] opt_einsum = "*" scipy = ">=1.11.1" @@ -2288,7 +2291,7 @@ description = "XLA library for JAX" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "jaxlib-0.4.30-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:c40856e28f300938c6824ab1a615166193d6997dec946578823f6d402ad454e5"}, {file = "jaxlib-0.4.30-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bdfda6a3c7a2b0cc0a7131009eb279e98ca4a6f25679fabb5302dd135a5e349"}, @@ -2366,7 +2369,7 @@ description = "Type annotations and runtime checking for shape and dtype of JAX/ optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\" and (extra == \"dev\" or extra == \"docs\")" +markers = "python_version < \"3.10\" and (extra == \"dev\" or extra == \"docs\")" files = [ {file = "jaxtyping-0.2.36-py3-none-any.whl", hash = "sha256:b19bcbd4009df8734602203402483a4066ad2eb3382904432e370588e9c9707d"}, {file = "jaxtyping-0.2.36.tar.gz", hash = "sha256:781ac44a3cf8982063d7ee48b5008ccfad7b13793bf878eb3058d5319aa08f0f"}, @@ -2422,7 +2425,7 @@ description = "A very fast and expressive template engine." optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"dev\" or extra == \"pytorch\" or extra == \"docs\"" +markers = "sys_platform == \"darwin\" and (extra == \"dev\" or extra == \"pytorch\" or extra == \"docs\") or extra == \"dev\" or extra == \"docs\" or extra == \"pytorch\"" files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -2849,7 +2852,7 @@ description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, @@ -3065,7 +3068,7 @@ description = "a KLU solver for JAX" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "python_version == \"3.9\" and (extra == \"dev\" or extra == \"docs\")" +markers = "python_version < \"3.10\" and (extra == \"dev\" or extra == \"docs\")" files = [ {file = "klujax-0.2.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8fbbdca92bd18bff4f6cb5e8383e6709cf5f3112db5a9deddfa15cc3011cd3d5"}, {file = "klujax-0.2.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:257d48a068144ddf47d1cba1792432360b5c9cd3f2594fffe38dc3c2d56bb12d"}, @@ -3174,7 +3177,7 @@ description = "Safely add untrusted strings to HTML/XML markup." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"dev\" or extra == \"pytorch\" or extra == \"docs\"" +markers = "sys_platform == \"darwin\" and (extra == \"dev\" or extra == \"pytorch\" or extra == \"docs\") or extra == \"dev\" or extra == \"docs\" or extra == \"pytorch\"" files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -3246,7 +3249,7 @@ description = "Python plotting package" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "matplotlib-3.9.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fdd7abfb706dfa8d307af64a87f1a862879ec3cd8d0ec8637458f0885b9c50"}, {file = "matplotlib-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d89bc4e85e40a71d1477780366c27fb7c6494d293e1617788986f74e2a03d7ff"}, @@ -3495,11 +3498,11 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.21"}, - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, {version = ">=2.1.0", markers = "python_version >= \"3.13\""}, {version = ">=1.26.0", markers = "python_version == \"3.12\""}, {version = ">=1.23.3", markers = "python_version >= \"3.11\""}, + {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, + {version = ">=1.21", markers = "python_version < \"3.10\""}, ] [package.extras] @@ -3618,7 +3621,7 @@ description = "An extended [CommonMark](https://spec.commonmark.org/) compliant optional = true python-versions = ">=3.8" groups = ["main"] -markers = "python_version == \"3.9\" and (extra == \"dev\" or extra == \"docs\")" +markers = "python_version < \"3.10\" and (extra == \"dev\" or extra == \"docs\")" files = [ {file = "myst_parser-3.0.1-py3-none-any.whl", hash = "sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1"}, {file = "myst_parser-3.0.1.tar.gz", hash = "sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87"}, @@ -3838,7 +3841,7 @@ description = "Python package for creating and manipulating graphs and networks" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"dev\" or extra == \"pytorch\" or extra == \"trimesh\" or extra == \"docs\"" +markers = "sys_platform == \"darwin\" and (extra == \"dev\" or extra == \"pytorch\" or extra == \"trimesh\" or extra == \"docs\") or extra == \"dev\" or extra == \"trimesh\" or extra == \"docs\" or extra == \"pytorch\"" files = [ {file = "networkx-2.8.8-py3-none-any.whl", hash = "sha256:e435dfa75b1d7195c7b8378c3859f0445cd88c6b0375c181ed66823a9ceb7524"}, {file = "networkx-2.8.8.tar.gz", hash = "sha256:230d388117af870fce5647a3c52401fcf753e94720e6ea6b4197a5355648885e"}, @@ -3915,7 +3918,7 @@ description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, @@ -4268,7 +4271,7 @@ description = "Orbax Checkpoint" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "orbax_checkpoint-0.6.4-py3-none-any.whl", hash = "sha256:b4f2608ee4d1da67f7619fc35ff9c928ecdf4ccf7546eeb43ecf38c2608b6dea"}, {file = "orbax_checkpoint-0.6.4.tar.gz", hash = "sha256:366b4d528a7322e1b3d9ddcaed45c8515add0d2fc69c8975c30d98638543240f"}, @@ -4491,9 +4494,9 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -4593,7 +4596,7 @@ description = "Pexpect allows easy control of interactive console applications." optional = true python-versions = "*" groups = ["main"] -markers = "(extra == \"dev\" or extra == \"docs\") and sys_platform != \"win32\" and (python_version == \"3.9\" or sys_platform != \"win32\" and sys_platform != \"emscripten\")" +markers = "(extra == \"dev\" or extra == \"docs\") and sys_platform != \"win32\" and (python_version < \"3.10\" or sys_platform != \"win32\" and sys_platform != \"emscripten\")" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -4829,7 +4832,7 @@ description = "Run a subprocess in a pseudo terminal" optional = true python-versions = "*" groups = ["main"] -markers = "(extra == \"dev\" or extra == \"docs\") and (os_name != \"nt\" or python_version == \"3.9\" or sys_platform != \"win32\" and sys_platform != \"emscripten\") and (sys_platform != \"win32\" or os_name != \"nt\")" +markers = "(extra == \"dev\" or extra == \"docs\") and (sys_platform != \"win32\" or os_name != \"nt\") and (sys_platform != \"win32\" and sys_platform != \"emscripten\" or os_name != \"nt\" or python_version < \"3.10\")" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -4858,7 +4861,7 @@ description = "Seamless operability between C++11 and Python" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "python_version == \"3.9\" and (extra == \"dev\" or extra == \"docs\")" +markers = "python_version < \"3.10\" and (extra == \"dev\" or extra == \"docs\")" files = [ {file = "pybind11-2.13.6-py3-none-any.whl", hash = "sha256:237c41e29157b962835d356b370ededd57594a26d5894a795960f0047cb5caf5"}, {file = "pybind11-2.13.6.tar.gz", hash = "sha256:ba6af10348c12b24e92fa086b39cfba0eff619b61ac77c406167d813b096d39a"}, @@ -5134,9 +5137,9 @@ files = [ astroid = ">=3.3.8,<=3.4.0.dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ - {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, - {version = ">=0.3.6", markers = "python_version == \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, + {version = ">=0.2", markers = "python_version < \"3.11\""}, ] isort = ">=4.2.5,<5.13 || >5.13,<7" mccabe = ">=0.6,<0.8" @@ -5393,7 +5396,7 @@ description = "Python for Window Extensions" optional = true python-versions = "*" groups = ["main"] -markers = "platform_python_implementation != \"PyPy\" and (extra == \"dev\" or extra == \"docs\") and sys_platform == \"win32\"" +markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\" and (extra == \"dev\" or extra == \"docs\")" files = [ {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, @@ -5885,31 +5888,31 @@ files = [ [[package]] name = "ruff" -version = "0.5.5" +version = "0.11.11" description = "An extremely fast Python linter and code formatter, written in Rust." optional = true python-versions = ">=3.7" groups = ["main"] markers = "extra == \"dev\" or extra == \"ruff\"" files = [ - {file = "ruff-0.5.5-py3-none-linux_armv6l.whl", hash = "sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf"}, - {file = "ruff-0.5.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8"}, - {file = "ruff-0.5.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3"}, - {file = "ruff-0.5.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16"}, - {file = "ruff-0.5.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b"}, - {file = "ruff-0.5.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e"}, - {file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0"}, - {file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7"}, - {file = "ruff-0.5.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884"}, - {file = "ruff-0.5.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091"}, - {file = "ruff-0.5.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523"}, - {file = "ruff-0.5.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f"}, - {file = "ruff-0.5.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8"}, - {file = "ruff-0.5.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab"}, - {file = "ruff-0.5.5-py3-none-win32.whl", hash = "sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d"}, - {file = "ruff-0.5.5-py3-none-win_amd64.whl", hash = "sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445"}, - {file = "ruff-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6"}, - {file = "ruff-0.5.5.tar.gz", hash = "sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a"}, + {file = "ruff-0.11.11-py3-none-linux_armv6l.whl", hash = "sha256:9924e5ae54125ed8958a4f7de320dab7380f6e9fa3195e3dc3b137c6842a0092"}, + {file = "ruff-0.11.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8a93276393d91e952f790148eb226658dd275cddfde96c6ca304873f11d2ae4"}, + {file = "ruff-0.11.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6e333dbe2e6ae84cdedefa943dfd6434753ad321764fd937eef9d6b62022bcd"}, + {file = "ruff-0.11.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7885d9a5e4c77b24e8c88aba8c80be9255fa22ab326019dac2356cff42089fc6"}, + {file = "ruff-0.11.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b5ab797fcc09121ed82e9b12b6f27e34859e4227080a42d090881be888755d4"}, + {file = "ruff-0.11.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e231ff3132c1119ece836487a02785f099a43992b95c2f62847d29bace3c75ac"}, + {file = "ruff-0.11.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a97c9babe1d4081037a90289986925726b802d180cca784ac8da2bbbc335f709"}, + {file = "ruff-0.11.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8c4ddcbe8a19f59f57fd814b8b117d4fcea9bee7c0492e6cf5fdc22cfa563c8"}, + {file = "ruff-0.11.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6224076c344a7694c6fbbb70d4f2a7b730f6d47d2a9dc1e7f9d9bb583faf390b"}, + {file = "ruff-0.11.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:882821fcdf7ae8db7a951df1903d9cb032bbe838852e5fc3c2b6c3ab54e39875"}, + {file = "ruff-0.11.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dcec2d50756463d9df075a26a85a6affbc1b0148873da3997286caf1ce03cae1"}, + {file = "ruff-0.11.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99c28505ecbaeb6594701a74e395b187ee083ee26478c1a795d35084d53ebd81"}, + {file = "ruff-0.11.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9263f9e5aa4ff1dec765e99810f1cc53f0c868c5329b69f13845f699fe74f639"}, + {file = "ruff-0.11.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:64ac6f885e3ecb2fdbb71de2701d4e34526651f1e8503af8fb30d4915a3fe345"}, + {file = "ruff-0.11.11-py3-none-win32.whl", hash = "sha256:1adcb9a18802268aaa891ffb67b1c94cd70578f126637118e8099b8e4adcf112"}, + {file = "ruff-0.11.11-py3-none-win_amd64.whl", hash = "sha256:748b4bb245f11e91a04a4ff0f96e386711df0a30412b9fe0c74d5bdc0e4a531f"}, + {file = "ruff-0.11.11-py3-none-win_arm64.whl", hash = "sha256:6c51f136c0364ab1b774767aa8b86331bd8e9d414e2d107db7a2189f35ea1f7b"}, + {file = "ruff-0.11.11.tar.gz", hash = "sha256:7774173cc7c1980e6bf67569ebb7085989a78a103922fb83ef3dfe230cd0687d"}, ] [[package]] @@ -6054,7 +6057,7 @@ description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, @@ -6469,7 +6472,7 @@ description = "Python documentation generator" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\" and (extra == \"dev\" or extra == \"docs\")" +markers = "python_version < \"3.10\" and (extra == \"dev\" or extra == \"docs\")" files = [ {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, @@ -6875,7 +6878,7 @@ description = "Read and write large, multi-dimensional arrays" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "tensorstore-0.1.69-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:9b6bf6938a3a9ac1a415d06479496f0e3180d487be6e218d223e53203b12c208"}, {file = "tensorstore-0.1.69-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0a030706644c501aba8def8c453a26148d1c76e11a1020a3aa6a2737d0d8f982"}, @@ -7028,7 +7031,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version < \"3.11\" and (extra == \"dev\" or extra == \"docs\")" +markers = "python_version <= \"3.10\" and (extra == \"dev\" or extra == \"docs\")" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -7096,7 +7099,7 @@ description = "Tensors and Dynamic neural networks in Python with strong GPU acc optional = true python-versions = ">=3.9.0" groups = ["main"] -markers = "(extra == \"dev\" or extra == \"pytorch\") and sys_platform == \"darwin\"" +markers = "sys_platform == \"darwin\" and (extra == \"dev\" or extra == \"pytorch\")" files = [ {file = "torch-2.6.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:6860df13d9911ac158f4c44031609700e1eba07916fff62e21e6ffa0a9e01961"}, {file = "torch-2.6.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c4f103a49830ce4c7561ef4434cc7926e5a5fe4e5eb100c19ab36ea1e2b634ab"}, @@ -7154,7 +7157,7 @@ description = "Tensors and Dynamic neural networks in Python with strong GPU acc optional = true python-versions = ">=3.9.0" groups = ["main"] -markers = "(extra == \"dev\" or extra == \"pytorch\") and sys_platform != \"darwin\"" +markers = "sys_platform != \"darwin\" and (extra == \"dev\" or extra == \"pytorch\")" files = [ {file = "torch-2.6.0+cpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:35a9e78b7e4096968b54c1a198687b981569c50ae93e661aa430f9fd208da102"}, {file = "torch-2.6.0+cpu-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:90832f4d118c566b8652a2196ac695fc1f14cf420db27b5a1b41c7eaaf2141e9"}, @@ -7411,7 +7414,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, @@ -7614,7 +7617,7 @@ description = "N-D labeled arrays and datasets in Python" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.9\"" +markers = "python_version < \"3.10\"" files = [ {file = "xarray-2024.7.0-py3-none-any.whl", hash = "sha256:1b0fd51ec408474aa1f4a355d75c00cc1c02bd425d97b2c2e551fd21810e7f64"}, {file = "xarray-2024.7.0.tar.gz", hash = "sha256:4cae512d121a8522d41e66d942fb06c526bc1fd32c2c181d5fe62fe65b671638"}, @@ -7696,4 +7699,4 @@ vtk = ["vtk"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<3.14" -content-hash = "6fe0199df83cdcc05ad0f26206c1bc23a0bf79bb15111f1516ee56ceb3325c10" +content-hash = "2f16c1b854573000ec715dfa048a71a0819c627e999dad064a47af1fd74131c2" diff --git a/pyproject.toml b/pyproject.toml index 9dacdf9803..0491ff43f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ joblib = "*" ### Optional dependencies ### # development core bump-my-version = { version = "*", optional = true } -ruff = { version = "0.5.5", optional = true } +ruff = { version = "0.11.11", optional = true } coverage = { version = "*", optional = true } dill = { version = "*", optional = true } ipython = { version = "*", optional = true } @@ -245,31 +245,48 @@ line-length = 100 extend-exclude = ["docs/faq/", "docs/notebooks/"] [tool.ruff.lint] -typing-modules = [ - "tidy3d.components.types", -] # without this Literal["something fails"] -select = [ - "I", # isort +extend-select = [ "E", # pycodestyle errors - "W", # pycodestyle warnings "F", # pyflakes - "C", # flake8-comprehensions - "B", # flake8-bugbear - "UP", - "NPY201", # numpy 2.* compatibility check + "B", # bugbear + "I", # isort + "UP", # pyupgrade + "W", # pycodestyle + "C4", # flake8-comprehensions + "NPY", # numpy-specific rules + "RUF", # ruff builtins + "ISC", # implicit string concatenation + "PIE", # flake8-pie + "RSE", # unnecessary parantheses on raised exceptions + "TID", # no relative imports from parent modules + "PLE", # pylint errors + "PLC", # pylint conventions +] +extend-ignore = [ + "RUF001", # ambiguous unicode characters + "RUF002", # ambiguous unicode characters + "RUF003", # ambiguous unicode characters + "RUF012", # type hints for mutable defaults + "RUF015", # next(iter(...)) instead of list(...)[0] + "E501", # line too long + "B905", # `zip()` without an explicit `strict=` parameter + "UP007", # TODO: Remove once Python >= 3.10 + "NPY002", # TODO: Revisit RNG handling ] -ignore = [ - "E501", - "B008", # do not perform function calls in argument defaults - "C901", # too complex - "UP007", # use x | y instead of union[x,y] (does not work) - "B905", # `zip()` without an explicit `strict=` parameter - "C408", # C408 Unnecessary `dict` call (rewrite as a literal) - "B904", - "B028", # stacklevel - "UP006", # typy annotation with Tuple[float] messes up pydantic - "UP038", # TODO decide what to do here - "UP035", # TODO decide what to do here + +[tool.ruff.lint.isort] +required-imports = ["from __future__ import annotations"] + +[tool.ruff.lint.per-file-ignores] +"tests/**/*" = [ + "B015", # useless comparison + "B018", # useless expression + "E402", # module-level import not at top of file + "E731", # lambda assignment + "F841", # unused local variable + "S101", # asserts allowed in tests + "NPY201", # numpy 2.* compatibility check + "TID252", # allow relative imports in tests ] [tool.pytest.ini_options] diff --git a/scripts/benchmark_import.py b/scripts/benchmark_import.py index beb304096f..cb430999d8 100644 --- a/scripts/benchmark_import.py +++ b/scripts/benchmark_import.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def test_import(): import subprocess import time diff --git a/scripts/make_script.py b/scripts/make_script.py index aeacbf5c40..cbd061f095 100644 --- a/scripts/make_script.py +++ b/scripts/make_script.py @@ -8,6 +8,8 @@ """ +from __future__ import annotations + import argparse import os import re @@ -83,11 +85,11 @@ def main(args): # read the formatted content back with open(temp_file_path, encoding="utf-8") as temp_file: sim_string = temp_file.read() - except subprocess.CalledProcessError: + except subprocess.CalledProcessError as exc: raise RuntimeError( "Ruff formatting failed. Your script might not be compatible with make_script.py. " "This could be due to unsupported features like CustomMedium." - ) + ) from exc finally: # remove the temporary file os.remove(temp_file_path) diff --git a/scripts/sample.py b/scripts/sample.py index 2e679a9fe6..7c83263390 100644 --- a/scripts/sample.py +++ b/scripts/sample.py @@ -1,5 +1,7 @@ """Generates sample simulation json and h5 files in the tests/sims folder""" +from __future__ import annotations + import sys from os.path import join diff --git a/scripts/schema.py b/scripts/schema.py index b3e5fa962f..34d622a29c 100644 --- a/scripts/schema.py +++ b/scripts/schema.py @@ -1,5 +1,7 @@ """Generates schema for Simulation, saves it to file.""" +from __future__ import annotations + import json import tidy3d diff --git a/tests/__init__.py b/tests/__init__.py index 5f4adad8e9..0bee19ee71 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys sys.path.append("./") diff --git a/tests/_test_data/_test_datasets_no_vtk.py b/tests/_test_data/_test_datasets_no_vtk.py index 227c4a29c3..0e6866ac65 100644 --- a/tests/_test_data/_test_datasets_no_vtk.py +++ b/tests/_test_data/_test_datasets_no_vtk.py @@ -1,5 +1,7 @@ """Tests tidy3d/components/data/dataset.py""" +from __future__ import annotations + import builtins import pytest @@ -14,7 +16,7 @@ def hide_vtk(monkeypatch, request): def mocked_import(name, *args, **kwargs): if name in ["vtk", "vtkmodules.vtkCommonCore"]: - raise ImportError() + raise ImportError return import_orig(name, *args, **kwargs) monkeypatch.setattr(builtins, "__import__", mocked_import) diff --git a/tests/_test_local/_test_adjoint_performance.py b/tests/_test_local/_test_adjoint_performance.py index 616065460c..dfe9c18bf7 100644 --- a/tests/_test_local/_test_adjoint_performance.py +++ b/tests/_test_local/_test_adjoint_performance.py @@ -1,12 +1,15 @@ +from __future__ import annotations + import sys import time import matplotlib.pyplot as plt import numpy as np import pytest -import tidy3d as td from jax import grad from memory_profiler import profile + +import tidy3d as td from tidy3d.plugins.adjoint.components.data.data_array import JaxDataArray from tidy3d.plugins.adjoint.components.data.dataset import JaxPermittivityDataset from tidy3d.plugins.adjoint.components.geometry import JaxBox @@ -52,12 +55,12 @@ def make_sim(eps_values: np.ndarray) -> JaxSimulation: # custom medium (xmin, ymin, zmin), (xmax, ymax, zmax) = jax_box.bounds - coords = dict( - x=np.linspace(xmin, xmax, Nx).tolist(), - y=np.linspace(ymin, ymax, Ny).tolist(), - z=np.linspace(zmin, zmax, Nz).tolist(), - f=[FREQ0], - ) + coords = { + "x": np.linspace(xmin, xmax, Nx).tolist(), + "y": np.linspace(ymin, ymax, Ny).tolist(), + "z": np.linspace(zmin, zmax, Nz).tolist(), + "f": [FREQ0], + } eps_ii = JaxDataArray(values=eps_values, coords=coords) field_components = {f"eps_{dim}{dim}": eps_ii for dim in "xyz"} diff --git a/tests/_test_local/_test_adjoint_performance_multi.py b/tests/_test_local/_test_adjoint_performance_multi.py index 1ea12222e3..3b2ebc3f63 100644 --- a/tests/_test_local/_test_adjoint_performance_multi.py +++ b/tests/_test_local/_test_adjoint_performance_multi.py @@ -1,11 +1,14 @@ +from __future__ import annotations + import cProfile import jax import jax.numpy as jnp import pytest +from memory_profiler import profile + import tidy3d as td import tidy3d.plugins.adjoint as tda -from memory_profiler import profile from tidy3d.plugins.adjoint.web import run_local as run from ..utils import run_emulated diff --git a/tests/_test_local/_test_data_performance.py b/tests/_test_local/_test_data_performance.py index ceb272fabc..a4b86c668f 100644 --- a/tests/_test_local/_test_data_performance.py +++ b/tests/_test_local/_test_data_performance.py @@ -1,9 +1,12 @@ +from __future__ import annotations + import os import sys import numpy as np -import tidy3d as td from memory_profiler import profile + +import tidy3d as td from tidy3d.components.data.data_array import ScalarFieldDataArray from tidy3d.components.data.monitor_data import FieldData from tidy3d.components.data.sim_data import SimulationData @@ -48,7 +51,7 @@ def make_sim_data_1(file_size_gb=FILE_SIZE_GB): src = PointDipole( center=(0, 0, 0), source_time=GaussianPulse(freq0=3e14, fwidth=1e14), polarization="Ex" ) - coords = dict(x=x, y=y, z=z, f=f) + coords = {"x": x, "y": y, "z": z, "f": f} Ex = ScalarFieldDataArray(data, coords=coords) monitor = FieldMonitor(size=(2, 2, 2), freqs=f, name="test", fields=["Ex"]) field_data = FieldData(monitor=monitor, Ex=Ex) @@ -70,7 +73,7 @@ def make_sim_data_1(file_size_gb=FILE_SIZE_GB): @profile def test_memory_1_save(): - print(f'sim_data_size = {SIM_DATA_1.monitor_data["test"].Ex.nbytes:.2e} Bytes') + print(f"sim_data_size = {SIM_DATA_1.monitor_data['test'].Ex.nbytes:.2e} Bytes") SIM_DATA_1.to_file(PATH) print(f"file_size = {os.path.getsize(PATH):.2e} Bytes") @@ -88,7 +91,7 @@ def test_core_profile_small_1_save(): y = np.arange(Ny) z = np.arange(Nz) t = np.arange(Nt) - coords = dict(x=x, y=y, z=z, t=t) + coords = {"x": x, "y": y, "z": z, "t": t} scalar_field = td.ScalarFieldTimeDataArray(np.random.random((Nx, Ny, Nz, Nt)), coords=coords) monitor = td.FieldTimeMonitor(size=(2, 4, 6), interval=100, name="field", fields=["Ex", "Hz"]) data = td.FieldTimeData(monitor=monitor, Ex=scalar_field, Hz=scalar_field) @@ -123,7 +126,7 @@ def test_speed_many_datasets(): y = np.arange(Ny) z = np.arange(Nz) f = np.arange(Nf) - coords = dict(x=x, y=y, z=z, f=f) + coords = {"x": x, "y": y, "z": z, "f": f} scalar_field = td.ScalarFieldDataArray(np.random.random((Nx, Ny, Nz, Nf)), coords=coords) def make_field_data(num_index: int): @@ -133,7 +136,7 @@ def make_field_data(num_index: int): freqs=np.linspace(1e14, 2e14, Nf).tolist(), name=str(num_index), ) - scalar_fields = {fld: scalar_field for fld in monitor.fields} + scalar_fields = dict.fromkeys(monitor.fields, scalar_field) return td.FieldData(monitor=monitor, **scalar_fields) diff --git a/tests/_test_local/_test_fit_web.py b/tests/_test_local/_test_fit_web.py index 92c06b7963..b135514017 100644 --- a/tests/_test_local/_test_fit_web.py +++ b/tests/_test_local/_test_fit_web.py @@ -1,6 +1,9 @@ +from __future__ import annotations + from math import isclose import numpy as np + from tidy3d.plugins.fitter import AdvancedFitterParam, StableDispersionFitter np.random.seed(4) diff --git a/tests/_test_local/_test_plugins_web.py b/tests/_test_local/_test_plugins_web.py index cbf9b2d4ce..a475889cff 100644 --- a/tests/_test_local/_test_plugins_web.py +++ b/tests/_test_local/_test_plugins_web.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import numpy as np + from tidy3d.plugins.fitter import DispersionFitter, StableDispersionFitter diff --git a/tests/_test_local/_test_web.py b/tests/_test_local/_test_web.py index 75c93f61ca..5662a27853 100644 --- a/tests/_test_local/_test_web.py +++ b/tests/_test_local/_test_web.py @@ -1,5 +1,7 @@ """tests converted webapi""" +from __future__ import annotations + import os from unittest import TestCase, mock diff --git a/tests/_test_notebooks/full_test_notebooks.py b/tests/_test_notebooks/full_test_notebooks.py index 22eca50834..da8fbe0158 100644 --- a/tests/_test_notebooks/full_test_notebooks.py +++ b/tests/_test_notebooks/full_test_notebooks.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys @@ -66,7 +68,7 @@ ] # if any run only supplied, only add those -if len(run_only): +if run_only: notebook_filenames_all = [NOTEBOOK_DIR + base + ".ipynb" for base in run_only] # filter out the skip notebooks diff --git a/tests/conftest.py b/tests/conftest.py index a1c37d57d8..02cde5897b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from pathlib import Path @@ -7,9 +9,10 @@ import numpy as np import psutil import pytest -import tidy3d as td from autograd.test_util import check_grads from autograd.wrap_util import unary_to_nary + +import tidy3d as td from tidy3d.log import DEFAULT_LEVEL, set_logging_console, set_logging_level diff --git a/tests/ruff.toml b/tests/ruff.toml deleted file mode 100644 index 816bcd68ec..0000000000 --- a/tests/ruff.toml +++ /dev/null @@ -1,14 +0,0 @@ -# custom lint settings for testing - -extend = "../pyproject.toml" - -[lint] -extend-ignore = [ - "B015", # useless comparison - "B018", # useless expression - "E402", # module-level import not at top of file - "E731", # lambda assignment - "F841", # unused local variable - "S101", # asserts allowed in tests - "NPY201", # numpy 2.* compatibility check -] diff --git a/tests/test_cli/full_test_develop.py b/tests/test_cli/full_test_develop.py index d46ca78a4d..7cf8e1d5f4 100644 --- a/tests/test_cli/full_test_develop.py +++ b/tests/test_cli/full_test_develop.py @@ -2,11 +2,14 @@ These scripts just test the CLI commands for the develop command, and verify that they run properly. """ +from __future__ import annotations + import os from unittest.mock import patch import pytest from click.testing import CliRunner + from tidy3d.web.cli import tidy3d_cli diff --git a/tests/test_components/material/test_multi_physics.py b/tests/test_components/material/test_multi_physics.py index 0641b00a70..eea9354c48 100644 --- a/tests/test_components/material/test_multi_physics.py +++ b/tests/test_components/material/test_multi_physics.py @@ -1,6 +1,9 @@ +from __future__ import annotations + import copy import pytest + import tidy3d as td diff --git a/tests/test_components/test_IO.py b/tests/test_components/test_IO.py index b4fe3322df..973b60f127 100644 --- a/tests/test_components/test_IO.py +++ b/tests/test_components/test_IO.py @@ -1,5 +1,7 @@ """Tests file export and loading.""" +from __future__ import annotations + import json import os from time import time @@ -8,6 +10,7 @@ import h5py import numpy as np import pytest + import tidy3d as td from tidy3d import __version__ from tidy3d.components.base import DATA_ARRAY_MAP @@ -70,9 +73,9 @@ def test_simulation_load_export(split_string, tmp_path): SIM.to_hdf5(path_hdf5) SIM2 = td.Simulation.from_file(path) SIM_HDF5 = td.Simulation.from_hdf5(path_hdf5) - assert ( - set_datasets_to_none(SIM)._json_string == SIM2._json_string - ), "original and loaded simulations are not the same" + assert set_datasets_to_none(SIM)._json_string == SIM2._json_string, ( + "original and loaded simulations are not the same" + ) assert SIM == SIM_HDF5, "original and loaded from hdf5 simulations are not the same" @@ -80,9 +83,9 @@ def test_simulation_load_export_yaml(tmp_path): path = str(tmp_path / "simulation.yaml") SIM.to_file(path) SIM2 = td.Simulation.from_file(path) - assert ( - set_datasets_to_none(SIM)._json_string == SIM2._json_string - ), "original and loaded simulations are not the same" + assert set_datasets_to_none(SIM)._json_string == SIM2._json_string, ( + "original and loaded simulations are not the same" + ) def test_component_load_export(tmp_path): @@ -189,7 +192,7 @@ def test_validation_speed(tmp_path): for i in range(n): new_structure = SIM.structures[0].copy(update={"name": str(i)}) new_structures.append(new_structure) - S = SIM.copy(update=dict(structures=new_structures)) + S = SIM.copy(update={"structures": new_structures}) S.to_file(path) time_start = time() @@ -324,7 +327,7 @@ def test_monitor_data_from_file(): def test_data_array_to_hdf5(tmp_path): values = np.linspace(0, 1, 10) - coords = dict(f=values) + coords = {"f": values} flux = td.FluxDataArray(values, coords=coords) path = str(tmp_path / "flux.hdf5") diff --git a/tests/test_components/test_apodization.py b/tests/test_components/test_apodization.py index d1c3b14440..4fadd253d3 100644 --- a/tests/test_components/test_apodization.py +++ b/tests/test_components/test_apodization.py @@ -1,8 +1,11 @@ """Tests mode objects.""" +from __future__ import annotations + import matplotlib.pyplot as plt import pydantic.v1 as pydantic import pytest + import tidy3d as td diff --git a/tests/test_components/test_autograd.py b/tests/test_components/test_autograd.py index e035da0903..c46624648f 100644 --- a/tests/test_components/test_autograd.py +++ b/tests/test_components/test_autograd.py @@ -1,4 +1,5 @@ # test autograd integration into tidy3d +from __future__ import annotations import copy import cProfile @@ -13,10 +14,11 @@ import numpy as np import numpy.testing as npt import pytest -import tidy3d as td -import tidy3d.web as web import xarray as xr from autograd.test_util import check_grads + +import tidy3d as td +import tidy3d.web as web from tidy3d.components.autograd.derivative_utils import DerivativeInfo from tidy3d.components.autograd.utils import is_tidy_box from tidy3d.components.data.data_array import DataArray @@ -86,7 +88,7 @@ PML_X = True if IS_3D else False # shape of the custom medium -DA_SHAPE_X = 1 if IS_3D else 1 +DA_SHAPE_X = 1 DA_SHAPE = (DA_SHAPE_X, 1_000, 1_000) if TEST_CUSTOM_MEDIUM_SPEED else (DA_SHAPE_X, 12, 12) # number of vertices in the polyslab @@ -235,7 +237,7 @@ class EmulatedBatchData(web.BatchData): def load_sim_data(self, task_name): return batch_data_orig[task_name] - task_paths = {task_name: "" for task_name in simulations.keys()} + task_paths = dict.fromkeys(simulations.keys(), "") batch_data = EmulatedBatchData( task_paths=task_paths, @@ -319,11 +321,11 @@ def make_structures(params: anp.ndarray) -> dict[str, td.Structure]: medium=td.CustomMedium( permittivity=td.SpatialDataArray( eps_arr, - coords=dict( - x=np.linspace(-0.5, 0.5, nx), - y=np.linspace(-0.5, 0.5, ny), - z=np.linspace(-0.5, 0.5, nz), - ), + coords={ + "x": np.linspace(-0.5, 0.5, nx), + "y": np.linspace(-0.5, 0.5, ny), + "z": np.linspace(-0.5, 0.5, nz), + }, ), ), ) @@ -331,12 +333,12 @@ def make_structures(params: anp.ndarray) -> dict[str, td.Structure]: # custom medium with vector valued permittivity data eps_ii = td.ScalarFieldDataArray( eps_arr.reshape(nx, ny, nz, 1), - coords=dict( - x=np.linspace(-0.5, 0.5, nx), - y=np.linspace(-0.5, 0.5, ny), - z=np.linspace(-0.5, 0.5, nz), - f=[td.C_0], - ), + coords={ + "x": np.linspace(-0.5, 0.5, nx), + "y": np.linspace(-0.5, 0.5, ny), + "z": np.linspace(-0.5, 0.5, nz), + "f": [td.C_0], + }, ) custom_med_vec = td.Structure( @@ -452,7 +454,7 @@ def make_structures(params: anp.ndarray) -> dict[str, td.Structure]: x = np.linspace(-0.5, 0.5, nx) y = np.linspace(-0.5, 0.5, ny) z = np.linspace(-0.5, 0.5, nz) - coords = dict(x=x, y=y, z=z) + coords = {"x": x, "y": y, "z": z} eps_inf = td.SpatialDataArray(anp.real(custom_disp_values), coords=coords) a1 = td.SpatialDataArray(-custom_disp_values, coords=coords) @@ -473,20 +475,20 @@ def make_structures(params: anp.ndarray) -> dict[str, td.Structure]: ) cylinder = td.Structure(geometry=cylinder_geo, medium=polyslab.medium) - return dict( - medium=medium, - center_list=center_list, - size_element=size_element, - custom_med=custom_med, - custom_med_vec=custom_med_vec, - polyslab=polyslab, - polyslab_dispersive=polyslab_dispersive, - geo_group=geo_group, - complex_polyslab=complex_polyslab_geo_group, - pole_res=pole_res, - custom_pole_res=custom_pole_res, - cylinder=cylinder, - ) + return { + "medium": medium, + "center_list": center_list, + "size_element": size_element, + "custom_med": custom_med, + "custom_med_vec": custom_med_vec, + "polyslab": polyslab, + "polyslab_dispersive": polyslab_dispersive, + "geo_group": geo_group, + "complex_polyslab": complex_polyslab_geo_group, + "pole_res": pole_res, + "custom_pole_res": custom_pole_res, + "cylinder": cylinder, + } def make_monitors() -> dict[str, tuple[td.Monitor, typing.Callable[[td.SimulationData], float]]]: @@ -547,12 +549,12 @@ def field_point_postprocess_fn(sim_data, mnt_data): value += anp.sum(sim_data.get_intensity(mnt_data.monitor.name).values) return value - return dict( - mode=(mode_mnt, mode_postprocess_fn), - diff=(diff_mnt, diff_postprocess_fn), - field_vol=(field_vol, field_vol_postprocess_fn), - field_point=(field_point, field_point_postprocess_fn), - ) + return { + "mode": (mode_mnt, mode_postprocess_fn), + "diff": (diff_mnt, diff_postprocess_fn), + "field_vol": (field_vol, field_vol_postprocess_fn), + "field_point": (field_point, field_point_postprocess_fn), + } def plot_sim(sim: td.Simulation, plot_eps: bool = True) -> None: @@ -646,7 +648,7 @@ def postprocess(data: td.SimulationData) -> float: mnt_data = data[monitor_key] return monitor_pp_fn(data, mnt_data) - return dict(sim=make_sim, postprocess=postprocess) + return {"sim": make_sim, "postprocess": postprocess} @pytest.mark.parametrize("axis", (0, 1, 2)) @@ -1095,7 +1097,7 @@ def test_sim_full_ops(structure_key): def objective(*params): s = make_structures(*params)[structure_key] s = s.updated_copy(geometry=s.geometry.updated_copy(center=(2, 2, 2), size=(0, 0, 0))) - sim_full_traced = SIM_FULL.updated_copy(structures=list(SIM_FULL.structures) + [s]) + sim_full_traced = SIM_FULL.updated_copy(structures=[*list(SIM_FULL.structures), s]) sim_full_static = sim_full_traced.to_static() @@ -1134,7 +1136,7 @@ def test_sim_fields_io(structure_key, tmp_path): from file, and then converting back, returns the same object.""" s = make_structures(params0)[structure_key] s = s.updated_copy(geometry=s.geometry.updated_copy(center=(2, 2, 2), size=(0, 0, 0))) - sim_full_traced = SIM_FULL.updated_copy(structures=list(SIM_FULL.structures) + [s]) + sim_full_traced = SIM_FULL.updated_copy(structures=[*list(SIM_FULL.structures), s]) sim_fields = sim_full_traced._strip_traced_fields() field_map = FieldMap.from_autograd_field_map(sim_fields) @@ -1150,7 +1152,7 @@ def test_web_incompatible_inputs(monkeypatch): def catch(*args, **kwargs): """Just raise an exception.""" - raise AssertionError() + raise AssertionError monkeypatch.setattr(td.web.api.webapi, "run", catch) monkeypatch.setattr(td.web.api.container.Job, "run", catch) @@ -1245,18 +1247,18 @@ def test_adjoint_src_width(): adj_srcs_fwidth = td.SimulationData._adjoint_src_width_single(adj_srcs) for src in adj_srcs_fwidth: - assert np.isclose( - (src.source_time.freq0 - f0) / f0, 0.0 - ), "f0 of adjoint source should be centered on original f0" + assert np.isclose((src.source_time.freq0 - f0) / f0, 0.0), ( + "f0 of adjoint source should be centered on original f0" + ) check_fwidth = ( src.source_time.freq0 - td.components.data.sim_data.NUM_ADJOINT_FWIDTH_TO_ZERO * src.source_time.fwidth ) / src.source_time.freq0 - assert np.isclose(check_fwidth, 0.0) or ( - check_fwidth > 0.0 - ), "fwidth of adjoint source should decay sufficiently before f=0" + assert np.isclose(check_fwidth, 0.0) or (check_fwidth > 0.0), ( + "fwidth of adjoint source should decay sufficiently before f=0" + ) def test_broadband_adjoint_src_width(): @@ -1278,7 +1280,7 @@ def test_broadband_adjoint_src_width(): for f0 in f0_adj_all: f = np.array([f0]) - coords = dict(x=x, y=y, z=z, f=f) + coords = {"x": x, "y": y, "z": z, "f": f} dataset = td.FieldDataset(Ex=td.ScalarFieldDataArray(np.ones((1, 1, 1, 1)), coords=coords)) @@ -1303,12 +1305,12 @@ def test_broadband_adjoint_src_width(): f0_expected - np.min(f0_adj_all) ) / td.components.data.sim_data.NUM_ADJOINT_FWIDTH_TO_FMIN - assert np.isclose( - (f0_expected - broadband_f0) / f0_expected, 0.0 - ), "Expected freq0 not matching for broadband source" - assert np.isclose( - (fwidth_expected - broadband_fwidth) / fwidth_expected, 0.0 - ), "Expected fwidth not matching for broadband source" + assert np.isclose((f0_expected - broadband_f0) / f0_expected, 0.0), ( + "Expected freq0 not matching for broadband source" + ) + assert np.isclose((fwidth_expected - broadband_fwidth) / fwidth_expected, 0.0), ( + "Expected fwidth not matching for broadband source" + ) # Test the case where we need a wider pulse to cover all the adjoint frequencies than we would otherwise choose for # each individual adjoint source @@ -1331,12 +1333,12 @@ def test_broadband_adjoint_src_width(): f0_expected - np.min(f0_broadband) ) / td.components.data.sim_data.NUM_ADJOINT_FWIDTH_TO_FMIN - assert np.isclose( - (f0_expected - broadband_f0) / f0_expected, 0.0 - ), "Expected freq0 not matching for broadband source" - assert np.isclose( - (fwidth_expected - broadband_fwidth) / fwidth_expected, 0.0 - ), "Expected fwidth not matching for broadband source" + assert np.isclose((f0_expected - broadband_f0) / f0_expected, 0.0), ( + "Expected freq0 not matching for broadband source" + ) + assert np.isclose((fwidth_expected - broadband_fwidth) / fwidth_expected, 0.0), ( + "Expected fwidth not matching for broadband source" + ) # Test the case where we have a narrow set of frequencies for the adjoint sources and so we can # choose a wider overall source than is needed for covering those frequencies. This larger pulse width @@ -1358,12 +1360,12 @@ def test_broadband_adjoint_src_width(): f0_expected = 0.5 * (np.max(f0_broadband) + np.min(f0_broadband)) fwidth_expected = f0_expected / td.components.data.sim_data.NUM_ADJOINT_FWIDTH_TO_ZERO - assert np.isclose( - (f0_expected - broadband_f0) / f0_expected, 0.0 - ), "Expected freq0 not matching for broadband source" - assert np.isclose( - (fwidth_expected - broadband_fwidth) / fwidth_expected, 0.0 - ), "Expected fwidth not matching for broadband source" + assert np.isclose((f0_expected - broadband_f0) / f0_expected, 0.0), ( + "Expected freq0 not matching for broadband source" + ) + assert np.isclose((fwidth_expected - broadband_fwidth) / fwidth_expected, 0.0), ( + "Expected fwidth not matching for broadband source" + ) @pytest.mark.parametrize("colocate", [True, False]) @@ -1603,8 +1605,8 @@ def J(eps): eps_out=1.0, frequency=freq, bounds=((-1, -1, -1), (1, 1, 1)), - eps_no_structure=td.SpatialDataArray([[[1.0]]], coords=dict(x=[0], y=[0], z=[0])), - eps_inf_structure=td.SpatialDataArray([[[2.0]]], coords=dict(x=[0], y=[0], z=[0])), + eps_no_structure=td.SpatialDataArray([[[1.0]]], coords={"x": [0], "y": [0], "z": [0]}), + eps_inf_structure=td.SpatialDataArray([[[2.0]]], coords={"x": [0], "y": [0], "z": [0]}), bounds_intersect=((-1, -1, -1), (1, 1, 1)), ) @@ -1637,7 +1639,7 @@ def test_custom_pole_residue(monkeypatch): x = np.linspace(-0.5, 0.5, nx) y = np.linspace(-0.5, 0.5, ny) z = np.linspace(-0.5, 0.5, nz) - coords = dict(x=x, y=y, z=z) + coords = {"x": x, "y": y, "z": z} eps_inf = td.SpatialDataArray(anp.real(values), coords=coords) a1 = td.SpatialDataArray(-values, coords=coords) @@ -1685,8 +1687,8 @@ def J(eps): eps_out=1.0, frequency=freq, bounds=((-1, -1, -1), (1, 1, 1)), - eps_no_structure=td.SpatialDataArray([[[1.0]]], coords=dict(x=[0], y=[0], z=[0])), - eps_inf_structure=td.SpatialDataArray([[[2.0]]], coords=dict(x=[0], y=[0], z=[0])), + eps_no_structure=td.SpatialDataArray([[[1.0]]], coords={"x": [0], "y": [0], "z": [0]}), + eps_inf_structure=td.SpatialDataArray([[[2.0]]], coords={"x": [0], "y": [0], "z": [0]}), bounds_intersect=((-1, -1, -1), (1, 1, 1)), ) @@ -1802,7 +1804,7 @@ def objective(params): structure_traced = make_structures(params)[structure_key] sim = SIM_BASE.updated_copy( structures=[structure_traced], - monitors=list(SIM_BASE.monitors) + [mnt_single, mnt_multi], + monitors=[*list(SIM_BASE.monitors), mnt_single, mnt_multi], ) data = run(sim, task_name="multifreq_test") return postprocess_fn(data) @@ -1905,16 +1907,16 @@ def postprocess(sim_data: td.SimulationData) -> float: return postprocess -MULT_FREQ_TEST_CASES = dict( - src_1_freq_1=check_1_src_single, - src_2_freq_1=check_2_src_single, - src_1_freq_2=check_1_src_multi, - src_2_freq_1_mon_1=check_1_src_multi, - src_2_freq_1_mon_2=check_2_src_both, - src_2_freq_2_mon_1=check_1_multisrc, - src_2_freq_2_mon_2=check_2_multisrc, - src_1_freq_2_broadband=check_1_src_broadband, -) +MULT_FREQ_TEST_CASES = { + "src_1_freq_1": check_1_src_single, + "src_2_freq_1": check_2_src_single, + "src_1_freq_2": check_1_src_multi, + "src_2_freq_1_mon_1": check_1_src_multi, + "src_2_freq_1_mon_2": check_2_src_both, + "src_2_freq_2_mon_1": check_1_multisrc, + "src_2_freq_2_mon_2": check_2_multisrc, + "src_1_freq_2_broadband": check_1_src_broadband, +} checks = list(MULT_FREQ_TEST_CASES.items()) @@ -1935,7 +1937,7 @@ def objective(params): structure_traced = make_structures(params)[structure_key] sim = SIM_BASE.updated_copy( structures=[structure_traced], - monitors=list(SIM_BASE.monitors) + [mnt_single, mnt_multi], + monitors=[*list(SIM_BASE.monitors), mnt_single, mnt_multi], ) data = run(sim, task_name="multifreq_test") return postprocess_fn(data) @@ -1959,7 +1961,7 @@ def objective_indi(params, structure_key) -> float: structure_traced = make_structures(params)[structure_key] sim = SIM_BASE.updated_copy( structures=[structure_traced], - monitors=list(SIM_BASE.monitors) + [mnt_multi], + monitors=[*list(SIM_BASE.monitors), mnt_multi], ) sim_data = web.run(sim, task_name="multifreq_test") @@ -1973,7 +1975,7 @@ def objective_multi(params, structure_key) -> float: structure_traced = make_structures(params)[structure_key] sim = SIM_BASE.updated_copy( structures=[structure_traced], - monitors=list(SIM_BASE.monitors) + [mnt_multi], + monitors=[*list(SIM_BASE.monitors), mnt_multi], ) sim_data = web.run(sim, task_name="multifreq_test") amps = get_amps(sim_data, "multi").sel(mode_index=0, direction="+") diff --git a/tests/test_components/test_autograd_mode_polyslab_numerical.py b/tests/test_components/test_autograd_mode_polyslab_numerical.py index 3c2540aa51..a5224dbf79 100644 --- a/tests/test_components/test_autograd_mode_polyslab_numerical.py +++ b/tests/test_components/test_autograd_mode_polyslab_numerical.py @@ -1,4 +1,5 @@ # test autograd and compares to numerically computed finite difference gradients +from __future__ import annotations import operator import sys @@ -7,9 +8,10 @@ import matplotlib.pylab as plt import numpy as np import pytest +from scipy.ndimage import gaussian_filter + import tidy3d as td import tidy3d.web as web -from scipy.ndimage import gaussian_filter PLOT_FD_ADJ_COMPARISON = False NUM_FINITE_DIFFERENCE = 10 @@ -167,7 +169,7 @@ def make_base_sim( monitor_index_block = td.Box( center=(0, 0, 0.25 * sim_size_um[2] + mesh_wvl_um), - size=tuple(2 * size for size in sim_size_um[0:2]) + (mesh_wvl_um + 0.5 * sim_size_um[2],), + size=(*tuple(2 * size for size in sim_size_um[0:2]), mesh_wvl_um + 0.5 * sim_size_um[2]), ) sim_base = td.Simulation( @@ -204,7 +206,7 @@ def objective(vertices): sim_base = create_sim_base() simulation_dict = {} - for idx in range(0, len(vertices)): + for idx in range(len(vertices)): vertices_x = vertices[idx][0:NUM_VERTICES] vertices_y = vertices[idx][NUM_VERTICES:] @@ -232,7 +234,7 @@ def objective(vertices): ) sim_with_polyslab = sim_base.updated_copy( - structures=sim_base.structures + (polyslab_structure,) + structures=(*sim_base.structures, polyslab_structure) ) simulation_dict[f"numerical_mode_polyslab_testing_{idx}"] = sim_with_polyslab.copy() @@ -242,7 +244,7 @@ def objective(vertices): ) objective_vals = [] - for idx in range(0, len(vertices)): + for idx in range(len(vertices)): objective_vals.append(eval_fn(sim_data[f"numerical_mode_polyslab_testing_{idx}"])) if len(vertices) == 1: @@ -277,7 +279,7 @@ def objective(vertices): mode_data_test_parameters = [] test_number = 0 -for idx in range(0, len(mesh_wvls_um)): +for idx in range(len(mesh_wvls_um)): mesh_wvl_um = mesh_wvls_um[idx] adj_wvl_um = adj_wvls_um[idx] @@ -346,7 +348,7 @@ def test_finite_difference_mode_data_polyslab( box_for_override = td.Box( center=(0, 0, 0), - size=(np.inf, np.inf) + (MODE_LAYER_HEIGHT_WVL * mesh_wvl_um + mesh_wvl_um,), + size=(np.inf, np.inf, MODE_LAYER_HEIGHT_WVL * mesh_wvl_um + mesh_wvl_um), ) sim_path_dir = tmp_path / f"test{test_number}" @@ -405,7 +407,7 @@ def eval_fn(sim_data): obj, adj_grad = obj_val_and_grad([list(vertex_centers_x) + list(vertex_centers_y)]) - for fd_idx in range(0, NUM_FINITE_DIFFERENCE): + for fd_idx in range(NUM_FINITE_DIFFERENCE): # Create random perturbation of vertices to check against the computed adjoint gradient. random_pattern = rng.random(2 * NUM_VERTICES) - 0.5 random_pattern = gaussian_filter(random_pattern, sigma=1) @@ -425,7 +427,7 @@ def eval_fn(sim_data): all_obj = objective(all_vertex) fd_grad = np.zeros(NUM_FINITE_DIFFERENCE) - for fd_idx in range(0, NUM_FINITE_DIFFERENCE): + for fd_idx in range(NUM_FINITE_DIFFERENCE): obj_up_location = 2 * fd_idx obj_down_location = 2 * fd_idx + 1 diff --git a/tests/test_components/test_autograd_numerical.py b/tests/test_components/test_autograd_numerical.py index 3f1e827038..6def75ec10 100644 --- a/tests/test_components/test_autograd_numerical.py +++ b/tests/test_components/test_autograd_numerical.py @@ -1,4 +1,5 @@ # test autograd and compares to numerically computed finite difference gradients +from __future__ import annotations import operator import sys @@ -7,9 +8,10 @@ import matplotlib.pylab as plt import numpy as np import pytest +from scipy.ndimage import gaussian_filter + import tidy3d as td import tidy3d.web as web -from scipy.ndimage import gaussian_filter PLOT_FD_ADJ_COMPARISON = False NUM_FINITE_DIFFERENCE = 10 @@ -95,7 +97,7 @@ def make_base_sim( monitor_index_block = td.Box( center=(0, 0, 0.25 * sim_size_um[2] + mesh_wvl_um), - size=tuple(2 * size for size in sim_size_um[0:2]) + (mesh_wvl_um + 0.5 * sim_size_um[2],), + size=(*tuple(2 * size for size in sim_size_um[0:2]), mesh_wvl_um + 0.5 * sim_size_um[2]), ) monitor_index_block_structure = td.Structure( geometry=monitor_index_block, medium=td.Medium(permittivity=monitor_bg_index**2) @@ -125,14 +127,14 @@ def objective(perm_arrays): sim_base = create_sim_base() simulation_dict = {} - for idx in range(0, len(perm_arrays)): + for idx in range(len(perm_arrays)): block_structure = td.Structure.from_permittivity_array( eps_data=perm_arrays[idx], geometry=geometry, ) sim_with_block = sim_base.updated_copy( - structures=sim_base.structures + (block_structure,) + structures=(*sim_base.structures, block_structure) ) simulation_dict[f"numerical_field_testing_{idx}"] = sim_with_block.copy() @@ -142,7 +144,7 @@ def objective(perm_arrays): ) objective_vals = [] - for idx in range(0, len(perm_arrays)): + for idx in range(len(perm_arrays)): objective_vals.append(eval_fn(sim_data[f"numerical_field_testing_{idx}"])) if len(perm_arrays) == 1: @@ -191,7 +193,7 @@ def flux(sim_data): field_data_test_parameters = [] test_number = 0 -for idx in range(0, len(mesh_wvls_um)): +for idx in range(len(mesh_wvls_um)): mesh_wvl_um = mesh_wvls_um[idx] adj_wvl_um = adj_wvls_um[idx] @@ -305,7 +307,7 @@ def test_finite_difference_field_data(field_data_test_parameters, rng, tmp_path, all_perm = [] pattern_dot_adj_gradient = np.zeros(NUM_FINITE_DIFFERENCE) - for fd_idx in range(0, NUM_FINITE_DIFFERENCE): + for fd_idx in range(NUM_FINITE_DIFFERENCE): random_pattern = rng.random((dim, dim, Nz)) - 0.5 random_pattern = gaussian_filter(random_pattern, sigma=3) random_pattern /= np.linalg.norm(random_pattern) @@ -321,7 +323,7 @@ def test_finite_difference_field_data(field_data_test_parameters, rng, tmp_path, all_obj = objective(all_perm) fd_grad = np.zeros(NUM_FINITE_DIFFERENCE) - for fd_idx in range(0, NUM_FINITE_DIFFERENCE): + for fd_idx in range(NUM_FINITE_DIFFERENCE): obj_up_location = 2 * fd_idx obj_down_location = 2 * fd_idx + 1 diff --git a/tests/test_components/test_base.py b/tests/test_components/test_base.py index 9058ec0897..1a82febfe3 100644 --- a/tests/test_components/test_base.py +++ b/tests/test_components/test_base.py @@ -1,7 +1,10 @@ """Tests the base model.""" +from __future__ import annotations + import numpy as np import pytest + import tidy3d as td from tidy3d.components.base import Tidy3dBaseModel @@ -79,15 +82,15 @@ def test_deep_copy(): # assert id(s.geometry) != id(s_kwargs.geometry) # behavior of modifying attributes - s_default = s.copy(update=dict(geometry=td.Sphere(radius=1.0))) + s_default = s.copy(update={"geometry": td.Sphere(radius=1.0)}) assert id(s.geometry) != id(s_default.geometry) # s_shallow = s.copy(deep=False, update=dict(geometry=Sphere(radius=1.0))) # assert id(s.geometry) != id(s_shallow.geometry) # behavior of modifying attributes of attributes - new_geometry = s.geometry.copy(update=dict(size=(2, 2, 2))) - s_default = s.copy(update=dict(geometry=new_geometry)) + new_geometry = s.geometry.copy(update={"size": (2, 2, 2)}) + s_default = s.copy(update={"geometry": new_geometry}) assert id(s.geometry) != id(s_default.geometry) # s_shallow = s.copy(deep=False) @@ -111,7 +114,7 @@ def test_updated_copy(): s2 = s.updated_copy(medium=m2, geometry=b2) assert s2.geometry == b2 assert s2.medium == m2 - s3 = s.updated_copy(**{"medium": m2, "geometry": b2}) + s3 = s.updated_copy(medium=m2, geometry=b2) assert s3 == s2 diff --git a/tests/test_components/test_bc_placement.py b/tests/test_components/test_bc_placement.py index 6c1a1fcf70..f11bc82756 100644 --- a/tests/test_components/test_bc_placement.py +++ b/tests/test_components/test_bc_placement.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from tidy3d.components.bc_placement import ( MediumMediumInterface, SimulationBoundary, diff --git a/tests/test_components/test_beam.py b/tests/test_components/test_beam.py index 457ad28292..a5686ed2d5 100644 --- a/tests/test_components/test_beam.py +++ b/tests/test_components/test_beam.py @@ -1,8 +1,11 @@ """Tests for the various BeamProfile components.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pd import pytest + from tidy3d.components.beam import ( AstigmaticGaussianBeamProfile, GaussianBeamProfile, diff --git a/tests/test_components/test_boundaries.py b/tests/test_components/test_boundaries.py index 9ca0f7d915..1142b64362 100644 --- a/tests/test_components/test_boundaries.py +++ b/tests/test_components/test_boundaries.py @@ -1,7 +1,10 @@ """Tests boundary conditions.""" +from __future__ import annotations + import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.components.boundary import ( PML, diff --git a/tests/test_components/test_custom.py b/tests/test_components/test_custom.py index 4ba744f054..0c9dbd4662 100644 --- a/tests/test_components/test_custom.py +++ b/tests/test_components/test_custom.py @@ -1,13 +1,14 @@ """Tests custom sources and mediums.""" -from typing import Tuple +from __future__ import annotations import dill as pickle import numpy as np import pydantic.v1 as pydantic import pytest -import tidy3d as td import xarray as xr + +import tidy3d as td from tidy3d.components.data.dataset import PermittivityDataset from tidy3d.components.data.utils import UnstructuredGridDataset, _get_numpy_array from tidy3d.components.medium import ( @@ -40,7 +41,7 @@ def make_scalar_data(): """Makes a scalar field data array.""" data = np.random.random((Nx, Ny, Nz, 1)) + 1 - return td.ScalarFieldDataArray(data, coords=dict(x=X, y=Y, z=Z, f=freqs)) + return td.ScalarFieldDataArray(data, coords={"x": X, "y": Y, "z": Z, "f": freqs}) def make_scalar_data_multifreqs(): @@ -48,7 +49,7 @@ def make_scalar_data_multifreqs(): Nfreq = 2 freqs_mul = [2e14, 3e14] data = np.random.random((Nx, Ny, Nz, Nfreq)) - return td.ScalarFieldDataArray(data, coords=dict(x=X, y=Y, z=Z, f=freqs_mul)) + return td.ScalarFieldDataArray(data, coords={"x": X, "y": Y, "z": Z, "f": freqs_mul}) def make_custom_field_source(): @@ -77,7 +78,7 @@ def make_spatial_data(value=0, dx=0, unstructured=False, seed=None, uniform=Fals data = value * np.ones((Nx, Ny, Nz)) else: data = np.random.random((Nx, Ny, Nz)) + value - arr = td.SpatialDataArray(data, coords=dict(x=X + dx, y=Y, z=Z)) + arr = td.SpatialDataArray(data, coords={"x": X + dx, "y": Y, "z": Z}) if unstructured: method = "direct" if uniform else "linear" return cartesian_to_unstructured(arr, seed=seed, method=method) @@ -89,7 +90,7 @@ def make_spatial_data(value=0, dx=0, unstructured=False, seed=None, uniform=Fals CURRENT_SRC = make_custom_current_source() -def get_dataset(custom_source_obj) -> Tuple[str, td.FieldDataset]: +def get_dataset(custom_source_obj) -> tuple[str, td.FieldDataset]: """Get a dict containing dataset depending on type and its key.""" if isinstance(custom_source_obj, td.CustomFieldSource): return "field_dataset", custom_source_obj.field_dataset @@ -115,7 +116,7 @@ def test_custom_source_simulation(source): def test_validator_tangential_field(): """Test that it errors if no tangential field defined.""" field_dataset = FIELD_SRC.field_dataset - field_dataset = field_dataset.copy(update=dict(Ex=None, Ez=None, Hx=None, Hz=None)) + field_dataset = field_dataset.copy(update={"Ex": None, "Ez": None, "Hx": None, "Hz": None}) with pytest.raises(pydantic.ValidationError): _ = td.CustomFieldSource(size=SIZE, source_time=ST, field_dataset=field_dataset) @@ -123,7 +124,7 @@ def test_validator_tangential_field(): def test_validator_non_planar(): """Test that it errors if the source geometry has a volume.""" field_dataset = FIELD_SRC.field_dataset - field_dataset = field_dataset.copy(update=dict(Ex=None, Ez=None, Hx=None, Hz=None)) + field_dataset = field_dataset.copy(update={"Ex": None, "Ez": None, "Hx": None, "Hz": None}) with pytest.raises(pydantic.ValidationError): _ = td.CustomFieldSource(size=(1, 1, 1), source_time=ST, field_dataset=field_dataset) @@ -132,8 +133,8 @@ def test_validator_non_planar(): def test_validator_freq_out_of_range_src(source): """Test that it errors if field_dataset frequency out of range of source_time.""" key, dataset = get_dataset(source) - Ex_new = td.ScalarFieldDataArray(dataset.Ex.data, coords=dict(x=X, y=Y, z=Z, f=[0])) - dataset_fail = dataset.copy(update=dict(Ex=Ex_new)) + Ex_new = td.ScalarFieldDataArray(dataset.Ex.data, coords={"x": X, "y": Y, "z": Z, "f": [0]}) + dataset_fail = dataset.copy(update={"Ex": Ex_new}) with pytest.raises(pydantic.ValidationError): _ = source.updated_copy(size=SIZE, source_time=ST, **{key: dataset_fail}) @@ -143,8 +144,8 @@ def test_validator_freq_multiple(source): """Test that it errors more than 1 frequency given.""" key, dataset = get_dataset(source) new_data = np.concatenate((dataset.Ex.data, dataset.Ex.data), axis=-1) - Ex_new = td.ScalarFieldDataArray(new_data, coords=dict(x=X, y=Y, z=Z, f=[1, 2])) - dataset_fail = dataset.copy(update=dict(Ex=Ex_new)) + Ex_new = td.ScalarFieldDataArray(new_data, coords={"x": X, "y": Y, "z": Z, "f": [1, 2]}) + dataset_fail = dataset.copy(update={"Ex": Ex_new}) with pytest.raises(pydantic.ValidationError): _ = source.copy(update={key: dataset_fail}) @@ -321,7 +322,7 @@ def test_medium_raw(): # lossy data = np.random.random((Nx, Ny, Nz, 1)) + 1 + 1e-2 * 1j - eps_raw = td.ScalarFieldDataArray(data, coords=dict(x=X, y=Y, z=Z, f=freqs)) + eps_raw = td.ScalarFieldDataArray(data, coords={"x": X, "y": Y, "z": Z, "f": freqs}) eps_raw_s = td.SpatialDataArray(eps_raw.squeeze(dim="f", drop=True)) eps_raw_u = cartesian_to_unstructured(eps_raw_s, pert=0.01, method="nearest") med = CustomMedium.from_eps_raw(eps_raw) @@ -374,7 +375,7 @@ def test_medium_interp(unstructured): Nx = 1 X = [1.1] data = np.random.random((Nx, Ny, Nz, 1)) - orig_data = td.ScalarFieldDataArray(data, coords=dict(x=X, y=Y, z=Z, f=freqs)) + orig_data = td.ScalarFieldDataArray(data, coords={"x": X, "y": Y, "z": Z, "f": freqs}) if unstructured: orig_data = cartesian_to_unstructured(orig_data.isel(f=0), pert=0.2, method="linear") @@ -414,7 +415,7 @@ def test_medium_smaller_than_one_positive_sigma(unstructured): # eps_inf < 1 n_data = 1 + np.random.random((Nx, Ny, Nz, 1)) n_data[0, 0, 0, 0] = 0.5 - n_dataarray = td.ScalarFieldDataArray(n_data, coords=dict(x=X, y=Y, z=Z, f=freqs)) + n_dataarray = td.ScalarFieldDataArray(n_data, coords={"x": X, "y": Y, "z": Z, "f": freqs}) if unstructured: n_dataarray = cartesian_to_unstructured(n_dataarray.isel(f=0)) @@ -426,8 +427,8 @@ def test_medium_smaller_than_one_positive_sigma(unstructured): n_data = 1 + np.random.random((Nx, Ny, Nz, 1)) k_data = np.random.random((Nx, Ny, Nz, 1)) k_data[0, 0, 0, 0] = -0.1 - n_dataarray = td.ScalarFieldDataArray(n_data, coords=dict(x=X, y=Y, z=Z, f=freqs)) - k_dataarray = td.ScalarFieldDataArray(k_data, coords=dict(x=X, y=Y, z=Z, f=freqs)) + n_dataarray = td.ScalarFieldDataArray(n_data, coords={"x": X, "y": Y, "z": Z, "f": freqs}) + k_dataarray = td.ScalarFieldDataArray(k_data, coords={"x": X, "y": Y, "z": Z, "f": freqs}) if unstructured: n_dataarray = cartesian_to_unstructured(n_dataarray.isel(f=0), seed=1) @@ -622,7 +623,7 @@ def make_scalar_data_f(): """Makes a scalar field data array with random f.""" data = np.random.random((Nx, Ny, Nz, 1)) + 1 return td.ScalarFieldDataArray( - data, coords=dict(x=X, y=Y, z=Z, f=[freqs[0] * np.random.random(1)[0]]) + data, coords={"x": X, "y": Y, "z": Z, "f": [freqs[0] * np.random.random(1)[0]]} ) # same f and different f diff --git a/tests/test_components/test_eme.py b/tests/test_components/test_eme.py index 0e9c617607..95b79f34ac 100644 --- a/tests/test_components/test_eme.py +++ b/tests/test_components/test_eme.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import numpy as np import pydantic.v1 as pd import pytest -import tidy3d as td from matplotlib import pyplot as plt + +import tidy3d as td from tidy3d.exceptions import SetupError, ValidationError from ..utils import AssertLogLevel @@ -311,10 +314,12 @@ def test_eme_simulation(): with AssertLogLevel("INFO", contains_str="wavelength"): sim = sim.updated_copy(grid_spec=grid_spec) # multiple freqs are ok, but not for autogrid - _ = sim.updated_copy(grid_spec=td.GridSpec.uniform(dl=0.2), freqs=[1e10] + list(sim.freqs)) + _ = sim.updated_copy( + grid_spec=td.GridSpec.uniform(dl=0.2), freqs=[10000000000.0, *list(sim.freqs)] + ) with AssertLogLevel("INFO", contains_str="wavelength"): _ = sim.updated_copy( - freqs=list(sim.freqs) + [1e10], + freqs=[*list(sim.freqs), 10000000000.0], grid_spec=grid_spec, ) @@ -610,15 +615,15 @@ def _get_eme_scalar_mode_field_data_array(num_sweep=0): sweep_index = np.arange(num_sweep) else: sweep_index = [0] - coords = dict( - x=x, - y=y, - z=z, - f=f, - sweep_index=sweep_index, - eme_cell_index=eme_cell_index, - mode_index=mode_index, - ) + coords = { + "x": x, + "y": y, + "z": z, + "f": f, + "sweep_index": sweep_index, + "eme_cell_index": eme_cell_index, + "mode_index": mode_index, + } data = td.EMEScalarModeFieldDataArray( (1 + 1j) * np.random.random( @@ -647,15 +652,15 @@ def _get_eme_scalar_field_data_array(num_sweep=0): sweep_index = np.arange(num_sweep) else: sweep_index = [0] - coords = dict( - x=x, - y=y, - z=z, - f=f, - sweep_index=sweep_index, - eme_port_index=eme_port_index, - mode_index=mode_index, - ) + coords = { + "x": x, + "y": y, + "z": z, + "f": f, + "sweep_index": sweep_index, + "eme_port_index": eme_port_index, + "mode_index": mode_index, + } data = td.EMEScalarFieldDataArray( (1 + 1j) * np.random.random((len(x), len(y), len(z), 2, len(sweep_index), 2, 5)), coords=coords, @@ -689,9 +694,12 @@ def _get_eme_smatrix_data_array(num_modes_in=2, num_modes_out=3, num_freqs=2, nu data = (1 + 1j) * np.random.random( (len(f), len(mode_index_out), len(mode_index_in), len(sweep_index)) ) - coords = dict( - f=f, mode_index_out=mode_index_out, mode_index_in=mode_index_in, sweep_index=sweep_index - ) + coords = { + "f": f, + "mode_index_out": mode_index_out, + "mode_index_in": mode_index_in, + "sweep_index": sweep_index, + } smatrix_entry = td.EMESMatrixDataArray(data, coords=coords) if num_modes_in == 0: @@ -730,14 +738,14 @@ def _get_eme_coeff_data_array(num_sweep=0): sweep_index = np.arange(num_sweep) else: sweep_index = [0] - coords = dict( - f=f, - sweep_index=sweep_index, - eme_port_index=eme_port_index, - eme_cell_index=eme_cell_index, - mode_index_out=mode_index_out, - mode_index_in=mode_index_in, - ) + coords = { + "f": f, + "sweep_index": sweep_index, + "eme_port_index": eme_port_index, + "eme_cell_index": eme_cell_index, + "mode_index_out": mode_index_out, + "mode_index_in": mode_index_in, + } data = td.EMECoefficientDataArray( (1 + 1j) * np.random.random( @@ -776,9 +784,12 @@ def _get_eme_mode_index_data_array(num_sweep=0): sweep_index = np.arange(num_sweep) else: sweep_index = [0] - coords = dict( - f=f, sweep_index=sweep_index, eme_cell_index=eme_cell_index, mode_index=mode_index - ) + coords = { + "f": f, + "sweep_index": sweep_index, + "eme_cell_index": eme_cell_index, + "mode_index": mode_index, + } data = td.EMEModeIndexDataArray( (1 + 1j) * np.random.random((len(f), len(sweep_index), len(eme_cell_index), len(mode_index))), @@ -800,14 +811,14 @@ def test_eme_smatrix_data_array(): def _get_eme_mode_solver_dataset(num_sweep=0): n_complex = _get_eme_mode_index_data_array(num_sweep=num_sweep) field = _get_eme_scalar_mode_field_data_array(num_sweep=num_sweep) - fields = {key: field for key in ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"]} + fields = dict.fromkeys(["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"], field) return td.EMEModeSolverDataset(n_complex=n_complex, **fields) def _get_eme_field_dataset(num_sweep=0): field = _get_eme_scalar_field_data_array(num_sweep=num_sweep) - fields = {key: field for key in ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"]} + fields = dict.fromkeys(["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"], field) return td.EMEFieldDataset(**fields) @@ -851,12 +862,12 @@ def _get_eme_mode_solver_data(num_sweep=0): ) ) grid_dual_correction_data = grid_primal_correction_data - grid_correction_coords = dict( - f=n_complex.f, - sweep_index=sweep_index, - eme_cell_index=n_complex.eme_cell_index, - mode_index=n_complex.mode_index, - ) + grid_correction_coords = { + "f": n_complex.f, + "sweep_index": sweep_index, + "eme_cell_index": n_complex.eme_cell_index, + "mode_index": n_complex.mode_index, + } grid_primal_correction = td.components.data.data_array.EMEFreqModeDataArray( grid_primal_correction_data, coords=grid_correction_coords ) diff --git a/tests/test_components/test_field_projection.py b/tests/test_components/test_field_projection.py index 94d1e0d552..d6047d4727 100644 --- a/tests/test_components/test_field_projection.py +++ b/tests/test_components/test_field_projection.py @@ -1,8 +1,11 @@ """Test near field to far field transformations.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.components.field_projection import FieldProjector from tidy3d.exceptions import DataError @@ -165,7 +168,7 @@ def test_proj_data(tmp_path): r = np.atleast_1d(5) theta = np.linspace(0, np.pi, 10) phi = np.linspace(0, 2 * np.pi, 20) - coords_tp = dict(r=r, theta=theta, phi=phi, f=f) + coords_tp = {"r": r, "theta": theta, "phi": phi, "f": f} values_tp = (1 + 1j) * np.random.random((len(r), len(theta), len(phi), len(f))) scalar_field_tp = td.FieldProjectionAngleDataArray(values_tp, coords=coords_tp) monitor_tp = td.FieldProjectionAngleMonitor( @@ -185,7 +188,7 @@ def test_proj_data(tmp_path): x = np.linspace(0, 5, 10) y = np.linspace(0, 10, 20) z = np.atleast_1d(5) - coords_xy = dict(x=x, y=y, z=z, f=f) + coords_xy = {"x": x, "y": y, "z": z, "f": f} values_xy = (1 + 1j) * np.random.random((len(x), len(y), len(z), len(f))) scalar_field_xy = td.FieldProjectionCartesianDataArray(values_xy, coords=coords_xy) monitor_xy = td.FieldProjectionCartesianMonitor( @@ -212,7 +215,7 @@ def test_proj_data(tmp_path): ux = np.linspace(0, 0.4, 10) uy = np.linspace(0, 0.6, 20) r = np.atleast_1d(5) - coords_u = dict(ux=ux, uy=uy, r=r, f=f) + coords_u = {"ux": ux, "uy": uy, "r": r, "f": f} values_u = (1 + 1j) * np.random.random((len(ux), len(uy), len(r), len(f))) scalar_field_u = td.FieldProjectionKSpaceDataArray(values_u, coords=coords_u) monitor_u = td.FieldProjectionKSpaceMonitor( @@ -244,7 +247,7 @@ def test_proj_data(tmp_path): x = np.linspace(0, 5, 10) y = np.linspace(0, 10, 20) z = np.atleast_1d(5) - coords_xy = dict(x=x, y=y, z=z, f=f) + coords_xy = {"x": x, "y": y, "z": z, "f": f} values_xy = (1 + 1j) * np.random.random((len(x), len(y), len(z), len(f))) scalar_field_xy = td.FieldProjectionCartesianDataArray(values_xy, coords=coords_xy) _ = td.FieldProjectionCartesianMonitor( @@ -290,7 +293,7 @@ def test_proj_clientside(): y = np.linspace(-1, 1, 10) z = np.array([0.0]) f = [f0] - coords = dict(x=x, y=y, z=z, f=f) + coords = {"x": x, "y": y, "z": z, "f": f} scalar_field = td.ScalarFieldDataArray( (1 + 1j) * np.random.random((10, 10, 1, 1)), coords=coords ) @@ -469,7 +472,7 @@ def make_2d_proj(plane): x = np.array([0.0]) y = np.linspace(-1, 1, 10) z = np.array([0.0]) - coords = dict(x=x, y=y, z=z, f=[f0]) + coords = {"x": x, "y": y, "z": z, "f": [f0]} scalar_field = td.ScalarFieldDataArray( (1 + 1j) * np.random.random((1, 10, 1, 1)), coords=coords ) @@ -486,7 +489,7 @@ def make_2d_proj(plane): x = np.array([0.0]) y = np.array([0.0]) z = np.linspace(-1, 1, 10) - coords = dict(x=x, y=y, z=z, f=[f0]) + coords = {"x": x, "y": y, "z": z, "f": [f0]} scalar_field = td.ScalarFieldDataArray( (1 + 1j) * np.random.random((1, 1, 10, 1)), coords=coords ) @@ -503,7 +506,7 @@ def make_2d_proj(plane): x = np.array([0.0]) y = np.array([0.0]) z = np.linspace(-1, 1, 10) - coords = dict(x=x, y=y, z=z, f=[f0]) + coords = {"x": x, "y": y, "z": z, "f": [f0]} scalar_field = td.ScalarFieldDataArray( (1 + 1j) * np.random.random((1, 1, 10, 1)), coords=coords ) diff --git a/tests/test_components/test_frequencies.py b/tests/test_components/test_frequencies.py index 30c8f25716..a066f0c395 100644 --- a/tests/test_components/test_frequencies.py +++ b/tests/test_components/test_frequencies.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import numpy as np import pytest + import tidy3d as td diff --git a/tests/test_components/test_geometry.py b/tests/test_components/test_geometry.py index 5618ed35bb..5237abdacb 100644 --- a/tests/test_components/test_geometry.py +++ b/tests/test_components/test_geometry.py @@ -1,5 +1,7 @@ """Tests Geometry objects.""" +from __future__ import annotations + import math import warnings @@ -9,8 +11,9 @@ import pydantic.v1 as pydantic import pytest import shapely -import tidy3d as td import trimesh + +import tidy3d as td from tidy3d.components.geometry.mesh import AREA_SIZE_THRESHOLD from tidy3d.components.geometry.utils import ( SnapBehavior, @@ -182,7 +185,7 @@ def test_zero_dims(): def test_inside_polyslab_sidewall(): - ps = POLYSLAB.copy(update=dict(sidewall_angle=0.1)) + ps = POLYSLAB.copy(update={"sidewall_angle": 0.1}) ps.inside(x=0, y=0, z=0) @@ -283,7 +286,7 @@ def test_box_from_bounds(): def test_polyslab_center_axis(): """Test the handling of center_axis in a polyslab having (-td.inf, td.inf) bounds.""" - ps = POLYSLAB.copy(update=dict(slab_bounds=(-td.inf, td.inf))) + ps = POLYSLAB.copy(update={"slab_bounds": (-td.inf, td.inf)}) assert ps.center_axis == 0 @@ -292,7 +295,7 @@ def test_polyslab_center_axis(): ) def test_polyslab_inf_bounds(lower_bound, upper_bound): """Test the handling of various operations in a polyslab having inf bounds.""" - ps = POLYSLAB.copy(update=dict(slab_bounds=(lower_bound, upper_bound))) + ps = POLYSLAB.copy(update={"slab_bounds": (lower_bound, upper_bound)}) # catch any runtime warning related to inf operations with warnings.catch_warnings(): warnings.simplefilter("error") @@ -326,28 +329,28 @@ def test_polyslab_inf_to_finite_bounds(axis): vertices=[[0, 0], [2.5, 1], [2, 3], [0.5, 4], [-1.5, 2.5]], ) - assert ps_low_inf.finite_length_axis == ( - LARGE_NUMBER + axis_bound - ), "Unexpected finite length for polyslab axis with -inf bound" - assert ps_high_inf.finite_length_axis == ( - LARGE_NUMBER + axis_bound - ), "Unexpected finite length for polyslab axis with inf bound" - assert ( - ps_inf.finite_length_axis == 2 * LARGE_NUMBER - ), "Unexpected finite length for polyslab axis with two inf bounds" + assert ps_low_inf.finite_length_axis == (LARGE_NUMBER + axis_bound), ( + "Unexpected finite length for polyslab axis with -inf bound" + ) + assert ps_high_inf.finite_length_axis == (LARGE_NUMBER + axis_bound), ( + "Unexpected finite length for polyslab axis with inf bound" + ) + assert ps_inf.finite_length_axis == 2 * LARGE_NUMBER, ( + "Unexpected finite length for polyslab axis with two inf bounds" + ) def test_validate_polyslab_vertices_valid(): with pytest.raises(pydantic.ValidationError): - POLYSLAB.copy(update=dict(vertices=(1, 2, 3))) + POLYSLAB.copy(update={"vertices": (1, 2, 3)}) with pytest.raises(pydantic.ValidationError): crossing_verts = ((0, 0), (1, 1), (0, 1), (1, 0)) - POLYSLAB.copy(update=dict(vertices=crossing_verts)) + POLYSLAB.copy(update={"vertices": crossing_verts}) def test_sidewall_failed_validation(): with pytest.raises(pydantic.ValidationError): - POLYSLAB.copy(update=dict(sidewall_angle=1000)) + POLYSLAB.copy(update={"sidewall_angle": 1000}) def test_surfaces(): @@ -428,16 +431,16 @@ def test_geometryoperations(): assert UNION + CYLINDER == td.GeometryGroup( geometries=(UNION.geometry_a, UNION.geometry_b, CYLINDER) ) - assert BOX + GROUP == td.GeometryGroup(geometries=(BOX,) + GROUP.geometries) - assert GROUP + CYLINDER == td.GeometryGroup(geometries=GROUP.geometries + (CYLINDER,)) + assert BOX + GROUP == td.GeometryGroup(geometries=(BOX, *GROUP.geometries)) + assert GROUP + CYLINDER == td.GeometryGroup(geometries=(*GROUP.geometries, CYLINDER)) assert BOX | CYLINDER == td.GeometryGroup(geometries=(BOX, CYLINDER)) assert BOX | UNION == td.GeometryGroup(geometries=(BOX, UNION.geometry_a, UNION.geometry_b)) assert UNION | CYLINDER == td.GeometryGroup( geometries=(UNION.geometry_a, UNION.geometry_b, CYLINDER) ) - assert BOX | GROUP == td.GeometryGroup(geometries=(BOX,) + GROUP.geometries) - assert GROUP | CYLINDER == td.GeometryGroup(geometries=GROUP.geometries + (CYLINDER,)) + assert BOX | GROUP == td.GeometryGroup(geometries=(BOX, *GROUP.geometries)) + assert GROUP | CYLINDER == td.GeometryGroup(geometries=(*GROUP.geometries, CYLINDER)) assert BOX * SPHERE == td.ClipOperation( operation="intersection", geometry_a=BOX, geometry_b=SPHERE @@ -1040,7 +1043,7 @@ def test_custom_surface_geometry(tmp_path): def test_geo_group_sim(): geo_grp = td.TriangleMesh.from_stl("tests/data/two_boxes_separate.stl") geos_orig = list(geo_grp.geometries) - geo_grp_full = geo_grp.updated_copy(geometries=geos_orig + [td.Box(size=(1, 1, 1))]) + geo_grp_full = geo_grp.updated_copy(geometries=[*geos_orig, td.Box(size=(1, 1, 1))]) sim = td.Simulation( size=(10, 10, 10), diff --git a/tests/test_components/test_grid.py b/tests/test_components/test_grid.py index 587a09ddbd..e063c38b5a 100644 --- a/tests/test_components/test_grid.py +++ b/tests/test_components/test_grid.py @@ -1,7 +1,10 @@ """Tests grid operations.""" +from __future__ import annotations + import numpy as np import pytest + import tidy3d as td from tidy3d.components.grid.grid import Coords, FieldGrid, Grid from tidy3d.exceptions import SetupError diff --git a/tests/test_components/test_grid_spec.py b/tests/test_components/test_grid_spec.py index 2fa831e717..8dcbc92694 100644 --- a/tests/test_components/test_grid_spec.py +++ b/tests/test_components/test_grid_spec.py @@ -1,8 +1,11 @@ """Tests GridSpec.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.exceptions import SetupError @@ -41,17 +44,17 @@ def test_make_coords(): def test_make_coords_with_snapping_points(): """Test the behavior of snapping points""" gs = make_grid_spec() - make_coords_args = dict( - structures=[ + make_coords_args = { + "structures": [ td.Structure(geometry=td.Box(size=(2, 2, 1)), medium=td.Medium()), td.Structure(geometry=td.Box(size=(1, 1, 1)), medium=td.Medium(permittivity=4)), ], - symmetry=(0, 0, 0), - periodic=(False, False, False), - wavelength=1.0, - num_pml_layers=(0, 0), - axis=0, - ) + "symmetry": (0, 0, 0), + "periodic": (False, False, False), + "wavelength": 1.0, + "num_pml_layers": (0, 0), + "axis": 0, + } # 1) no snapping points, 0.85 is not on any grid boundary coord_original = gs.grid_x.make_coords( diff --git a/tests/test_components/test_heat.py b/tests/test_components/test_heat.py index edcf26112e..ee04c07c87 100644 --- a/tests/test_components/test_heat.py +++ b/tests/test_components/test_heat.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import numpy as np import pydantic.v1 as pd import pytest -import tidy3d as td from matplotlib import pyplot as plt + +import tidy3d as td from tidy3d import ( ConvectionBC, DistanceUnstructuredGrid, @@ -156,7 +159,7 @@ def make_heat_mnt_data(): y = np.linspace(0, 2, ny) z = np.linspace(0, 3, nz) T = np.random.default_rng().uniform(300, 350, (nx, ny, nz)) - coords = dict(x=x, y=y, z=z) + coords = {"x": x, "y": y, "z": z} temperature_field = td.SpatialDataArray(T, coords=coords) mnt_data1 = TemperatureData(monitor=temp_mnt1, temperature=temperature_field) @@ -218,7 +221,7 @@ def make_heat_mnt_data(): y = np.linspace(0, 2, ny) z = np.linspace(0, 3, nz) T = np.random.default_rng().uniform(300, 350, (nx, ny, nz)) - coords = dict(x=x, y=y, z=z) + coords = {"x": x, "y": y, "z": z} temperature_field = td.SpatialDataArray(T, coords=coords) mnt_data5 = TemperatureData(monitor=temp_mnt5, temperature=temperature_field) @@ -228,7 +231,7 @@ def make_heat_mnt_data(): y = np.linspace(0, 2, ny) z = np.linspace(0, 3, nz) T = np.random.default_rng().uniform(300, 350, (nx, ny, nz)) - coords = dict(x=x, y=y, z=z) + coords = {"x": x, "y": y, "z": z} temperature_field = td.SpatialDataArray(T, coords=coords) mnt_data6 = TemperatureData(monitor=temp_mnt6, temperature=temperature_field) @@ -278,7 +281,9 @@ def make_heat_source(): def make_custom_heat_source(): return HeatSource( structures=["solid_structure"], - rate=td.SpatialDataArray(np.ones((1, 2, 3)), coords=dict(x=[0], y=[1, 2], z=[3, 4, 5])), + rate=td.SpatialDataArray( + np.ones((1, 2, 3)), coords={"x": [0], "y": [1, 2], "z": [3, 4, 5]} + ), ) @@ -410,10 +415,10 @@ def test_heat_sim(): medium=heat_sim.medium, ) with pytest.raises(pd.ValidationError): - _ = heat_sim.updated_copy(structures=list(heat_sim.structures) + [struct_1d]) + _ = heat_sim.updated_copy(structures=[*list(heat_sim.structures), struct_1d]) with pytest.raises(pd.ValidationError): - _ = heat_sim.updated_copy(structures=list(heat_sim.structures) + [struct_2d]) + _ = heat_sim.updated_copy(structures=[*list(heat_sim.structures), struct_2d]) # no data expected inside a monitor for mnt_size in [(0.2, 0.2, 0.2), (0, 1, 1), (0, 2, 0), (0, 0, 0)]: @@ -624,7 +629,7 @@ def test_symmetry_expanded(zero_dim_axis): z = np.linspace(*data_span_z, num_points[2]) v = np.sin(x[:, None, None]) * np.cos(y[None, :, None]) * np.exp(z[None, None, :]) - data_cart = td.SpatialDataArray(v, coords=dict(x=x, y=y, z=z)) + data_cart = td.SpatialDataArray(v, coords={"x": x, "y": y, "z": z}) data_ugrid = cartesian_to_unstructured(data_cart, seed=33342) mnt_cart = td.TemperatureMonitor( diff --git a/tests/test_components/test_heat_charge.py b/tests/test_components/test_heat_charge.py index 756aa21e30..ecf38b4fcb 100644 --- a/tests/test_components/test_heat_charge.py +++ b/tests/test_components/test_heat_charge.py @@ -1,10 +1,13 @@ """Test suite for heat-charge simulation objects and data using pytest fixtures.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pd import pytest -import tidy3d as td from matplotlib import pyplot as plt + +import tidy3d as td from tidy3d.components.tcad.types import ( AugerRecombination, CaugheyThomasMobility, @@ -411,7 +414,7 @@ def temperature_monitor_data(monitors): y = np.linspace(0, 2, ny) z = np.linspace(0, 3, nz) T = np.random.default_rng().uniform(300, 350, (nx, ny, nz)) - coords = dict(x=x, y=y, z=z) + coords = {"x": x, "y": y, "z": z} temperature_field = td.SpatialDataArray(T, coords=coords) mnt_data1 = td.TemperatureData(monitor=temp_mnt1, temperature=temperature_field) @@ -489,7 +492,7 @@ def voltage_monitor_data(monitors): y = np.linspace(0, 2, ny) z = np.linspace(0, 3, nz) T = np.random.default_rng().uniform(-5, 5, (nx, ny, nz)) - coords = dict(x=x, y=y, z=z) + coords = {"x": x, "y": y, "z": z} voltage_field = td.SpatialDataArray(T, coords=coords) mnt_data1 = td.SteadyPotentialData(monitor=volt_mnt1, potential=voltage_field) @@ -832,14 +835,14 @@ def test_heat_charge_simulation(simulation_data): assert cond_sim is not None, "Conduction simulation should be created successfully." voltage_capacitance_sim = voltage_capacitance_sim_data.simulation - assert ( - voltage_capacitance_sim is not None - ), "Voltage-Capacitance simulation should be created successfully." + assert voltage_capacitance_sim is not None, ( + "Voltage-Capacitance simulation should be created successfully." + ) current_voltage_sim = current_voltage_simulation_data.simulation - assert ( - current_voltage_sim is not None - ), "Current-Voltage simulation should be created successfully." + assert current_voltage_sim is not None, ( + "Current-Voltage simulation should be created successfully." + ) def test_sim_data_plotting(simulation_data): @@ -1087,7 +1090,6 @@ def test_doping_distributions(self): """Test doping distributions.""" # Implementation needed # This test was empty in the original code. - pass # -------------------------- @@ -1361,7 +1363,7 @@ def test_dynamic_simulation_updates(heat_simulation): # Add a new monitor new_monitor = td.TemperatureMonitor(size=(1, 1, 1), name="new_temp_mnt") updated_sim = heat_simulation.updated_copy( - monitors=tuple(list(heat_simulation.monitors) + [new_monitor]) + monitors=(*list(heat_simulation.monitors), new_monitor) ) assert len(updated_sim.monitors) == len(heat_simulation.monitors) + 1 assert updated_sim.monitors[-1].name == "new_temp_mnt" @@ -1402,13 +1404,13 @@ def test_bandgap_monitor(): tri_grid_values_single_voltage = td.IndexedVoltageDataArray( [[0.0], [0], [3], [3]], - coords=dict(index=np.arange(4), voltage=[1]), + coords={"index": np.arange(4), "voltage": [1]}, name="test", ) tri_grid_values_multi_voltage = td.IndexedVoltageDataArray( [[0.0, 0.0], [0, 0], [3, -3], [3, -3]], - coords=dict(index=np.arange(4), voltage=[-1, 1]), + coords={"index": np.arange(4), "voltage": [-1, 1]}, name="test", ) @@ -1450,7 +1452,7 @@ def test_bandgap_monitor(): tet_grid_values_single_voltage = td.IndexedVoltageDataArray( [[0.0], [0.0], [0.0], [0.0], [3.0], [3.0], [3.0], [3.0]], - coords=dict(index=np.arange(8), voltage=[1]), + coords={"index": np.arange(8), "voltage": [1]}, name="test_tet", ) @@ -1465,7 +1467,7 @@ def test_bandgap_monitor(): [3.0, 3.5], [3.0, 3.5], ], - coords=dict(index=np.arange(8), voltage=[-1, 1]), + coords={"index": np.arange(8), "voltage": [-1, 1]}, name="test_tet", ) diff --git a/tests/test_components/test_layerrefinement.py b/tests/test_components/test_layerrefinement.py index 91dc1fdf87..506234ee01 100644 --- a/tests/test_components/test_layerrefinement.py +++ b/tests/test_components/test_layerrefinement.py @@ -1,8 +1,11 @@ """Tests 2d corner finder.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.components.grid.corner_finder import CornerFinderSpec from tidy3d.components.grid.grid_spec import GridRefinement, LayerRefinementSpec diff --git a/tests/test_components/test_lumped_element.py b/tests/test_components/test_lumped_element.py index f91ece5b0c..fb15221366 100644 --- a/tests/test_components/test_lumped_element.py +++ b/tests/test_components/test_lumped_element.py @@ -1,8 +1,11 @@ """Tests lumped elements.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.components.lumped_element import NetworkConversions diff --git a/tests/test_components/test_medium.py b/tests/test_components/test_medium.py index 6d1467474e..b3ffd19844 100644 --- a/tests/test_components/test_medium.py +++ b/tests/test_components/test_medium.py @@ -1,11 +1,12 @@ """Tests mediums.""" -from typing import Dict +from __future__ import annotations import matplotlib.pyplot as plt import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.exceptions import SetupError, ValidationError @@ -292,7 +293,7 @@ def test_sellmeier_from_dispersion(): assert np.allclose(-dn_df * td.C_0 / wvl**2, dn_dwvl) -def eps_compare(medium: td.Medium, expected: Dict, tol: float = 1e-5): +def eps_compare(medium: td.Medium, expected: dict, tol: float = 1e-5): for freq, val in expected.items(): assert np.abs(medium.eps_model(freq) - val) < tol @@ -833,7 +834,7 @@ def test_custom_medium(): Z = [0] freqs = [2e14] n_data = np.ones((Nx, Ny, Nz, Nf)) - n_dataset = td.ScalarFieldDataArray(n_data, coords=dict(x=X, y=Y, z=Z, f=freqs)) + n_dataset = td.ScalarFieldDataArray(n_data, coords={"x": X, "y": Y, "z": Z, "f": freqs}) def create_mediums(n_dataset): ## Three equivalent ways of defining custom medium for the lens @@ -842,7 +843,9 @@ def create_mediums(n_dataset): _ = td.CustomMedium.from_nk(n_dataset, interp_method="nearest") # define custom medium with permittivity data - eps_dataset = td.ScalarFieldDataArray(n_dataset**2, coords=dict(x=X, y=Y, z=Z, f=freqs)) + eps_dataset = td.ScalarFieldDataArray( + n_dataset**2, coords={"x": X, "y": Y, "z": Z, "f": freqs} + ) _ = td.CustomMedium.from_eps_raw(eps_dataset, interp_method="nearest") # define each component of permittivity via "PermittivityDataset" @@ -856,9 +859,9 @@ def create_mediums(n_dataset): with pytest.raises(pydantic.ValidationError): # repeat some entries so data cannot be interpolated - X2 = [X[0]] + list(X) + X2 = [X[0], *list(X)] n_data2 = np.vstack((n_data[0, :, :, :].reshape(1, Ny, Nz, Nf), n_data)) - n_dataset2 = td.ScalarFieldDataArray(n_data2, coords=dict(x=X2, y=Y, z=Z, f=freqs)) + n_dataset2 = td.ScalarFieldDataArray(n_data2, coords={"x": X2, "y": Y, "z": Z, "f": freqs}) create_mediums(n_dataset=n_dataset2) diff --git a/tests/test_components/test_meshgenerate.py b/tests/test_components/test_meshgenerate.py index 6b10e11a9a..2a5ec3d59f 100644 --- a/tests/test_components/test_meshgenerate.py +++ b/tests/test_components/test_meshgenerate.py @@ -1,9 +1,12 @@ """Tests generating meshes.""" +from __future__ import annotations + import warnings import numpy as np import pytest + import tidy3d as td from tidy3d.components.grid.mesher import GradedMesher from tidy3d.constants import fp_eps @@ -735,7 +738,7 @@ def test_anisotropic_material_meshing(unstructured, z): ), ) - coords = dict(x=[0, 1], y=[0, 1], z=z) + coords = {"x": [0, 1], "y": [0, 1], "z": z} ones = td.SpatialDataArray(np.ones((2, 2, len(z))), coords=coords) if unstructured: ones = cartesian_to_unstructured(ones, seed=951) @@ -815,9 +818,9 @@ def test_override_are_box(): dl=[1, 2, 3], ) - assert isinstance( - override_not_box.geometry, td.Box - ), "Sphere override structure was not converted to Box" + assert isinstance(override_not_box.geometry, td.Box), ( + "Sphere override structure was not converted to Box" + ) def test_override_unshadowed(): diff --git a/tests/test_components/test_microwave.py b/tests/test_components/test_microwave.py index a0cd365791..5fb08815e9 100644 --- a/tests/test_components/test_microwave.py +++ b/tests/test_components/test_microwave.py @@ -1,10 +1,13 @@ """Tests microwave tools.""" +from __future__ import annotations + from math import isclose import numpy as np import pytest import xarray as xr + from tidy3d.components.data.monitor_data import FreqDataArray from tidy3d.components.microwave.data.monitor_data import AntennaMetricsData from tidy3d.components.microwave.formulas.circuit_parameters import ( diff --git a/tests/test_components/test_mode.py b/tests/test_components/test_mode.py index df08f470a3..dd61e36fff 100644 --- a/tests/test_components/test_mode.py +++ b/tests/test_components/test_mode.py @@ -1,10 +1,13 @@ """Tests mode objects.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pydantic import pytest -import tidy3d as td from matplotlib import pyplot as plt + +import tidy3d as td from tidy3d.exceptions import SetupError, ValidationError from ..test_data.test_data_arrays import ( @@ -125,9 +128,14 @@ def test_mode_sim(): with AssertLogLevel("INFO"): _ = sim.updated_copy(freqs=FS[0], grid_spec=grid_spec) # multiple freqs are ok - _ = sim.updated_copy(grid_spec=td.GridSpec.uniform(dl=0.2), freqs=[1e10] + list(sim.freqs)) _ = sim.updated_copy( - size=sim.size, freqs=list(sim.freqs) + [1e10], grid_spec=grid_spec, mode_spec=MODE_SPEC + grid_spec=td.GridSpec.uniform(dl=0.2), freqs=[10000000000.0, *list(sim.freqs)] + ) + _ = sim.updated_copy( + size=sim.size, + freqs=[*list(sim.freqs), 10000000000.0], + grid_spec=grid_spec, + mode_spec=MODE_SPEC, ) # size limit diff --git a/tests/test_components/test_monitor.py b/tests/test_components/test_monitor.py index 825949aef9..346681985e 100644 --- a/tests/test_components/test_monitor.py +++ b/tests/test_components/test_monitor.py @@ -1,8 +1,11 @@ """Tests monitors.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.exceptions import SetupError, ValidationError diff --git a/tests/test_components/test_packaging.py b/tests/test_components/test_packaging.py index 71d9893224..86de3ae4c9 100644 --- a/tests/test_components/test_packaging.py +++ b/tests/test_components/test_packaging.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import pytest + from tidy3d.packaging import Tidy3dImportError, check_import, verify_packages_import assert check_import("tidy3d") is True diff --git a/tests/test_components/test_parameter_perturbation.py b/tests/test_components/test_parameter_perturbation.py index b66cd996b2..4fbd5d2091 100644 --- a/tests/test_components/test_parameter_perturbation.py +++ b/tests/test_components/test_parameter_perturbation.py @@ -1,16 +1,23 @@ """Tests parameter perturbations.""" +from __future__ import annotations + import matplotlib.pyplot as plt import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from ..utils import AssertLogLevel, cartesian_to_unstructured -sp_arr = td.SpatialDataArray(300 * np.ones((2, 2, 2)), coords=dict(x=[1, 2], y=[3, 4], z=[5, 6])) +sp_arr = td.SpatialDataArray( + 300 * np.ones((2, 2, 2)), coords={"x": [1, 2], "y": [3, 4], "z": [5, 6]} +) sp_arr_u = cartesian_to_unstructured(sp_arr) -sp_arr_2d = td.SpatialDataArray(300 * np.ones((2, 1, 2)), coords=dict(x=[1, 2], y=[3.5], z=[5, 6])) +sp_arr_2d = td.SpatialDataArray( + 300 * np.ones((2, 1, 2)), coords={"x": [1, 2], "y": [3.5], "z": [5, 6]} +) sp_arr_2d_u = cartesian_to_unstructured(sp_arr_2d) sp_arrs = [sp_arr, sp_arr_u, sp_arr_2d, sp_arr_2d_u] @@ -77,7 +84,7 @@ def test_heat_perturbation(): _ = perturb.plot(temperature=np.linspace(200, 400, 10), val="angle") # test custom heat perturbation - perturb_data = td.HeatDataArray([1 + 1j, 3 + 1j, 1j], coords=dict(T=[200, 300, 400])) + perturb_data = td.HeatDataArray([1 + 1j, 3 + 1j, 1j], coords={"T": [200, 300, 400]}) for interp_method in ["linear", "nearest"]: perturb = td.CustomHeatPerturbation( @@ -257,7 +264,7 @@ def test_sample(perturb): # test custom charge perturbation perturb_data = td.ChargeDataArray( [[1 + 1j, 3 + 1j, 1j], [2 + 2j, 2j, 2 + 2j]], - coords=dict(n=[2e17, 2e18], p=[1e16, 1e17, 1e18]), + coords={"n": [2e17, 2e18], "p": [1e16, 1e17, 1e18]}, ) for interp_method in ["linear", "nearest"]: @@ -369,13 +376,13 @@ def test_parameter_perturbation(unstructured): perturb_data = td.ChargeDataArray( [[1 + 1j, 3 + 1j, 1j], [2 + 2j, 2j, 2 + 2j]], - coords=dict(n=[2e17, 2e18], p=[1e16, 1e17, 1e18]), + coords={"n": [2e17, 2e18], "p": [1e16, 1e17, 1e18]}, ) charge = td.CustomChargePerturbation(perturbation_values=perturb_data, interp_method="linear") - coords = dict(x=[1, 2], y=[3, 4], z=[5, 6]) - coords2 = dict(x=[1, 2], y=[3, 4], z=[5]) + coords = {"x": [1, 2], "y": [3, 4], "z": [5, 6]} + coords2 = {"x": [1, 2], "y": [3, 4], "z": [5]} temperature = td.SpatialDataArray(300 * np.random.random((2, 2, 2)), coords=coords) electron_density = td.SpatialDataArray(1e18 * np.random.random((2, 2, 2)), coords=coords) hole_density = td.SpatialDataArray(2e18 * np.random.random((2, 2, 2)), coords=coords) @@ -440,9 +447,9 @@ def test_permittivity_perturbation(): hole_range=[0, 2e19], ) - t_arr = td.SpatialDataArray([[[350]]], coords=dict(x=[0], y=[0], z=[0])) - n_arr = td.SpatialDataArray([[[1e18]]], coords=dict(x=[0], y=[0], z=[0])) - p_arr = td.SpatialDataArray([[[2e18]]], coords=dict(x=[0], y=[0], z=[0])) + t_arr = td.SpatialDataArray([[[350]]], coords={"x": [0], "y": [0], "z": [0]}) + n_arr = td.SpatialDataArray([[[1e18]]], coords={"x": [0], "y": [0], "z": [0]}) + p_arr = td.SpatialDataArray([[[2e18]]], coords={"x": [0], "y": [0], "z": [0]}) # basic make perm_pb = td.PermittivityPerturbation(delta_eps=td.ParameterPerturbation(heat=heat_pb)) @@ -533,9 +540,9 @@ def test_index_perturbation(): freq0 = td.C_0 - t_arr = td.SpatialDataArray([[[350]]], coords=dict(x=[0], y=[0], z=[0])) - n_arr = td.SpatialDataArray([[[1e18]]], coords=dict(x=[0], y=[0], z=[0])) - p_arr = td.SpatialDataArray([[[2e18]]], coords=dict(x=[0], y=[0], z=[0])) + t_arr = td.SpatialDataArray([[[350]]], coords={"x": [0], "y": [0], "z": [0]}) + n_arr = td.SpatialDataArray([[[1e18]]], coords={"x": [0], "y": [0], "z": [0]}) + p_arr = td.SpatialDataArray([[[2e18]]], coords={"x": [0], "y": [0], "z": [0]}) # basic make index_pb = td.IndexPerturbation(delta_n=td.ParameterPerturbation(heat=heat_pb), freq=freq0) diff --git a/tests/test_components/test_perturbation_medium.py b/tests/test_components/test_perturbation_medium.py index eff3063b06..2eec5b4bad 100644 --- a/tests/test_components/test_perturbation_medium.py +++ b/tests/test_components/test_perturbation_medium.py @@ -1,8 +1,11 @@ """Tests mediums.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from ..utils import AssertLogLevel, cartesian_to_unstructured @@ -11,7 +14,7 @@ @pytest.mark.parametrize("unstructured", [False, True]) def test_perturbation_medium(unstructured): # fields to sample at - coords = dict(x=[1, 2], y=[3, 4], z=[5, 6]) + coords = {"x": [1, 2], "y": [3, 4], "z": [5, 6]} temperature = td.SpatialDataArray(300 * np.ones((2, 2, 2)), coords=coords) electron_density = td.SpatialDataArray(1e18 * np.ones((2, 2, 2)), coords=coords) hole_density = td.SpatialDataArray(2e18 * np.ones((2, 2, 2)), coords=coords) @@ -297,7 +300,7 @@ def test_correct_values(dispersive): ), ) - t_arr = td.SpatialDataArray([[[333]]], coords=dict(x=[0], y=[0], z=[0])) + t_arr = td.SpatialDataArray([[[333]]], coords={"x": [0], "y": [0], "z": [0]}) pp_large_sampled = pp_large.apply_data(temperature=t_arr).values[0, 0, 0] pp_small_sampled = pp_small.apply_data(temperature=t_arr).values[0, 0, 0] diff --git a/tests/test_components/test_scene.py b/tests/test_components/test_scene.py index 3f209f716e..0704b9cdc6 100644 --- a/tests/test_components/test_scene.py +++ b/tests/test_components/test_scene.py @@ -1,9 +1,12 @@ """Tests the scene and its validators.""" +from __future__ import annotations + import matplotlib.pyplot as plt import numpy as np import pydantic.v1 as pd import pytest + import tidy3d as td from tidy3d.components.scene import MAX_GEOMETRY_COUNT, MAX_NUM_MEDIUMS @@ -113,7 +116,7 @@ def test_structure_alpha(): new_structs = [ td.Structure(geometry=s.geometry, medium=SCENE_FULL.medium) for s in SCENE_FULL.structures ] - S2 = SCENE_FULL.copy(update=dict(structures=new_structs)) + S2 = SCENE_FULL.copy(update={"structures": new_structs}) _ = S2.plot_structures_eps(x=0, alpha=0.5) plt.close() @@ -236,7 +239,7 @@ def test_perturbed_mediums_copy(unstructured, z): ), ) - coords = dict(x=[1, 2], y=[3, 4], z=z) + coords = {"x": [1, 2], "y": [3, 4], "z": z} temperature = td.SpatialDataArray(300 * np.ones((2, 2, len(z))), coords=coords) electron_density = td.SpatialDataArray(1e18 * np.ones((2, 2, len(z))), coords=coords) hole_density = td.SpatialDataArray(2e18 * np.ones((2, 2, len(z))), coords=coords) diff --git a/tests/test_components/test_sidewall.py b/tests/test_components/test_sidewall.py index f6f735eded..31252a603e 100644 --- a/tests/test_components/test_sidewall.py +++ b/tests/test_components/test_sidewall.py @@ -1,10 +1,13 @@ """test slanted polyslab can be correctly setup and visualized.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pydantic import pytest -import tidy3d as td from shapely import Point, Polygon + +import tidy3d as td from tidy3d.constants import fp_eps np.random.seed(4) diff --git a/tests/test_components/test_simulation.py b/tests/test_components/test_simulation.py index 3020328ae5..44b54323df 100644 --- a/tests/test_components/test_simulation.py +++ b/tests/test_components/test_simulation.py @@ -1,5 +1,7 @@ """Tests the simulation and its validators.""" +from __future__ import annotations + import uuid import gdstk @@ -7,8 +9,9 @@ import numpy as np import pydantic.v1 as pydantic import pytest -import tidy3d as td from matplotlib.testing.compare import compare_images + +import tidy3d as td from tidy3d.components import simulation from tidy3d.components.scene import MAX_GEOMETRY_COUNT, MAX_NUM_MEDIUMS from tidy3d.components.simulation import MAX_NUM_SOURCES @@ -631,21 +634,21 @@ def test_sources_edge_case_validation(): def test_validate_size_run_time(monkeypatch): monkeypatch.setattr(simulation, "MAX_TIME_STEPS", 1) with pytest.raises(SetupError): - s = SIM.copy(update=dict(run_time=1e-12)) + s = SIM.copy(update={"run_time": 1e-12}) s._validate_size() def test_validate_size_spatial_and_time(monkeypatch): monkeypatch.setattr(simulation, "MAX_CELLS_TIMES_STEPS", 1) with pytest.raises(SetupError): - s = SIM.copy(update=dict(run_time=1e-12)) + s = SIM.copy(update={"run_time": 1e-12}) s._validate_size() def test_validate_mnt_size(monkeypatch): # warning for monitor size monkeypatch.setattr(simulation, "WARN_MONITOR_DATA_SIZE_GB", 1 / 2**30) - s = SIM.copy(update=dict(monitors=(td.FieldMonitor(name="f", freqs=[1e12], size=(1, 1, 1)),))) + s = SIM.copy(update={"monitors": (td.FieldMonitor(name="f", freqs=[1e12], size=(1, 1, 1)),)}) with AssertLogLevel("WARNING"): s._validate_monitor_size() @@ -653,7 +656,7 @@ def test_validate_mnt_size(monkeypatch): monkeypatch.setattr(simulation, "MAX_SIMULATION_DATA_SIZE_GB", 1 / 2**30) with pytest.raises(SetupError): s = SIM.copy( - update=dict(monitors=(td.FieldMonitor(name="f", freqs=[1e12], size=(1, 1, 1)),)) + update={"monitors": (td.FieldMonitor(name="f", freqs=[1e12], size=(1, 1, 1)),)} ) s._validate_monitor_size() @@ -746,7 +749,7 @@ def medium_customani(self): x = np.linspace(-1, 1, Nx) y = np.linspace(-1, 1, Ny) z = np.linspace(-1, 1, Nz) - coords = dict(x=x, y=y, z=z) + coords = {"x": x, "y": y, "z": z} permittivity = td.SpatialDataArray(2 * np.ones((Nx, Ny, Nz)), coords=coords) conductivity = td.SpatialDataArray(np.ones((Nx, Ny, Nz)), coords=coords) medium_xx = td.CustomMedium(permittivity=permittivity, conductivity=conductivity) @@ -765,7 +768,7 @@ def medium_customani(self): r = 1 n_data[r_mesh <= r] = n0 * (1 - A * r_mesh[r_mesh <= r] ** 2) # convert to dataset array - n_dataset = td.SpatialDataArray(n_data, coords=dict(x=x, y=y, z=z)) + n_dataset = td.SpatialDataArray(n_data, coords={"x": x, "y": y, "z": z}) medium_zz = td.CustomMedium.from_nk(n_dataset, interp_method="nearest") return td.CustomAnisotropicMedium(xx=medium_xx, yy=medium_yy, zz=medium_zz) @@ -819,10 +822,7 @@ def test_bad_eps_arg(self, eps_comp): @pytest.mark.parametrize( "eps_comp", - [ - None, - ] - + diag_comps, + [None, *diag_comps], ) def test_plot_anisotropic_medium(self, eps_comp): """Test plotting diagonal components of a diagonally anisotropic medium succeeds or not. @@ -853,11 +853,7 @@ def test_plot_anisotropic_medium_diff(self, tmp_path, eps_comp1, eps_comp2, expe @pytest.mark.parametrize( "eps_comp", - [ - None, - ] - + diag_comps - + offdiag_comps, + [None, *diag_comps, *offdiag_comps], ) def test_plot_fully_anisotropic_medium(self, eps_comp): """Test plotting all components of a fully anisotropic medium. @@ -884,10 +880,7 @@ def test_plot_fully_anisotropic_medium_diff(self, tmp_path, eps_comp1, eps_comp2 @pytest.mark.parametrize( "eps_comp", - [ - None, - ] - + diag_comps, + [None, *diag_comps], ) def test_plot_customanisotropic_medium(self, eps_comp, medium_customani): """Test plotting diagonal components of a diagonally anisotropic custom medium. @@ -970,7 +963,7 @@ def test_structure_alpha(): new_structs = [ td.Structure(geometry=s.geometry, medium=SIM_FULL.medium) for s in SIM_FULL.structures ] - S2 = SIM_FULL.copy(update=dict(structures=new_structs)) + S2 = SIM_FULL.copy(update={"structures": new_structs}) _ = S2.plot_structures_eps(x=0, alpha=0.5) plt.close() @@ -1012,7 +1005,7 @@ def test_plot_eps_with_default_frequency(): def test_plot_symmetries(): - S2 = SIM.copy(update=dict(symmetry=(1, 0, -1))) + S2 = SIM.copy(update={"symmetry": (1, 0, -1)}) S2.plot_symmetries(x=0) plt.close() @@ -1020,7 +1013,7 @@ def test_plot_symmetries(): def test_plot_grid(): override = td.Structure(geometry=td.Box(size=(1, 1, 1)), medium=td.Medium()) S2 = SIM_FULL.copy( - update=dict(grid_spec=td.GridSpec(wavelength=1.0, override_structures=[override])) + update={"grid_spec": td.GridSpec(wavelength=1.0, override_structures=[override])} ) S2.plot_grid(x=0) plt.close() @@ -1035,7 +1028,7 @@ def test_plot_boundaries(): ), z=td.Boundary(plus=td.Periodic(), minus=td.Periodic()), ) - S2 = SIM_FULL.copy(update=dict(boundary_spec=bound_spec)) + S2 = SIM_FULL.copy(update={"boundary_spec": bound_spec}) S2.plot_boundaries(z=0) plt.close() @@ -1063,25 +1056,25 @@ def test_complex_fields(): ), z=td.Boundary(plus=td.Periodic(), minus=td.Periodic()), ) - S2 = SIM_FULL.copy(update=dict(boundary_spec=bound_spec)) + S2 = SIM_FULL.copy(update={"boundary_spec": bound_spec}) assert S2.complex_fields def test_nyquist(): S = SIM.copy( - update=dict( - sources=( + update={ + "sources": ( td.PointDipole( polarization="Ex", source_time=td.GaussianPulse(freq0=2e14, fwidth=1e11) ), ), - ) + } ) assert S.nyquist_step > 1 # nyquist step decreses to 1 when the frequency-domain monitor is at high frequency S_MONITOR = S.copy( - update=dict(monitors=[td.FluxMonitor(size=(1, 1, 0), freqs=[1e14, 1e20], name="flux")]) + update={"monitors": [td.FluxMonitor(size=(1, 1, 0), freqs=[1e14, 1e20], name="flux")]} ) assert S_MONITOR.nyquist_step == 1 @@ -1104,15 +1097,15 @@ def test_discretize_non_intersect(): def test_warn_sim_background_medium_freq_range(): with AssertLogLevel("WARNING"): _ = SIM.copy( - update=dict( - sources=( + update={ + "sources": ( td.PointDipole( polarization="Ex", source_time=td.GaussianPulse(freq0=2e14, fwidth=1e11) ), ), - monitors=(td.FluxMonitor(name="test", freqs=[2e12], size=(1, 1, 0)),), - medium=td.Medium(frequency_range=(0, 1e12)), - ) + "monitors": (td.FluxMonitor(name="test", freqs=[2e12], size=(1, 1, 0)),), + "medium": td.Medium(frequency_range=(0, 1e12)), + } ) @@ -1415,49 +1408,49 @@ def test_proj_monitor_distance(): # Cartesian monitor projecting backwards ( td.FieldProjectionCartesianMonitor, - dict(x=[4], y=[5], proj_distance=-1e5, proj_axis=2), + {"x": [4], "y": [5], "proj_distance": -1e5, "proj_axis": 2}, None, "+", ), # Cartesian monitor with custom origin projecting backwards ( td.FieldProjectionCartesianMonitor, - dict(x=[4], y=[5], proj_distance=39, proj_axis=2), + {"x": [4], "y": [5], "proj_distance": 39, "proj_axis": 2}, (1, 2, -40), "+", ), # Cartesian monitor with custom origin projecting backwards with normal_dir '-' ( td.FieldProjectionCartesianMonitor, - dict(x=[4], y=[5], proj_distance=41, proj_axis=2), + {"x": [4], "y": [5], "proj_distance": 41, "proj_axis": 2}, (1, 2, -40), "-", ), # Angle monitor projecting backwards ( td.FieldProjectionAngleMonitor, - dict(theta=[np.pi / 2 + 1e-2], phi=[0], proj_distance=1e3), + {"theta": [np.pi / 2 + 1e-2], "phi": [0], "proj_distance": 1e3}, None, "+", ), # Angle monitor projecting backwards with custom origin ( td.FieldProjectionAngleMonitor, - dict(theta=[np.pi / 2 - 0.02], phi=[0], proj_distance=10), + {"theta": [np.pi / 2 - 0.02], "phi": [0], "proj_distance": 10}, (0, 0, -0.5), "+", ), # Angle monitor projecting backwards with custom origin and normal_dir '-' ( td.FieldProjectionAngleMonitor, - dict(theta=[np.pi / 2 + 0.02], phi=[0], proj_distance=10), + {"theta": [np.pi / 2 + 0.02], "phi": [0], "proj_distance": 10}, (0, 0, 0.5), "-", ), # Cartesian monitor using approximations but too short proj_distance ( td.FieldProjectionCartesianMonitor, - dict(x=[4], y=[5], proj_distance=9, proj_axis=2), + {"x": [4], "y": [5], "proj_distance": 9, "proj_axis": 2}, None, "+", ), @@ -2104,7 +2097,9 @@ def test_tfsf_structures_grid(): Y = np.linspace(-1, 1, Ny) Z = np.linspace(-1, 1, Nz) data = np.ones((Nx, Ny, Nz, 1)) - eps_diagonal_data = td.ScalarFieldDataArray(data, coords=dict(x=X, y=Y, z=Z, f=[td.C_0])) + eps_diagonal_data = td.ScalarFieldDataArray( + data, coords={"x": X, "y": Y, "z": Z, "f": [td.C_0]} + ) eps_components = {f"eps_{d}{d}": eps_diagonal_data for d in "xyz"} eps_dataset = td.PermittivityDataset(**eps_components) custom_medium = td.CustomMedium(eps_dataset=eps_dataset, name="my_medium") @@ -2353,7 +2348,7 @@ def test_dt(): geometry=td.Box(size=(1, 1, 1), center=(-1, 0, 0)), medium=td.PoleResidue(eps_inf=0.16, poles=[(-1 + 1j, 2 + 2j)]), ) - sim_new = sim.copy(update=dict(structures=[structure])) + sim_new = sim.copy(update={"structures": [structure]}) assert sim_new.dt == 0.4 * dt @@ -2670,7 +2665,7 @@ def test_perturbed_mediums_copy(unstructured, z): ), ) - coords = dict(x=[1, 2], y=[3, 4], z=z) + coords = {"x": [1, 2], "y": [3, 4], "z": z} temperature = td.SpatialDataArray(300 * np.ones((2, 2, len(z))), coords=coords) electron_density = td.SpatialDataArray(1e18 * np.ones((2, 2, len(z))), coords=coords) hole_density = td.SpatialDataArray(2e18 * np.ones((2, 2, len(z))), coords=coords) @@ -2838,11 +2833,11 @@ def test_sim_subsection(unstructured, nz): perm = td.SpatialDataArray( 1 + np.random.random((11, 12, nz)), - coords=dict( - x=np.linspace(-0.51, 0.52, 11), - y=np.linspace(-1.02, 1.04, 12), - z=np.linspace(-1.51, 1.51, nz), - ), + coords={ + "x": np.linspace(-0.51, 0.52, 11), + "y": np.linspace(-1.02, 1.04, 12), + "z": np.linspace(-1.51, 1.51, nz), + }, ) if unstructured: @@ -3094,7 +3089,9 @@ def test_advanced_material_intersection(): Y = np.linspace(-1, 1, Ny) Z = np.linspace(-1, 1, Nz) data = np.ones((Nx, Ny, Nz, 1)) - eps_diagonal_data = td.ScalarFieldDataArray(data, coords=dict(x=X, y=Y, z=Z, f=[td.C_0])) + eps_diagonal_data = td.ScalarFieldDataArray( + data, coords={"x": X, "y": Y, "z": Z, "f": [td.C_0]} + ) eps_components = {f"eps_{d}{d}": eps_diagonal_data for d in "xyz"} eps_dataset = td.PermittivityDataset(**eps_components) custom_medium = td.CustomMedium(eps_dataset=eps_dataset, name="my_medium") diff --git a/tests/test_components/test_source.py b/tests/test_components/test_source.py index b3817d57dc..e4e4cc6db1 100644 --- a/tests/test_components/test_source.py +++ b/tests/test_components/test_source.py @@ -1,9 +1,12 @@ """Tests sources.""" +from __future__ import annotations + import matplotlib.pyplot as plt import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.components.source.field import CHEB_GRID_WIDTH, DirectionalSource from tidy3d.exceptions import SetupError @@ -369,7 +372,7 @@ def test_custom_source_time(): _ = cst.amp_time(-1) assert np.allclose(cst.amp_time([2]), np.exp(-1j * 2 * np.pi * 2 * freq0), rtol=0, atol=ATOL) - vals = td.components.data.data_array.TimeDataArray([1, 2], coords=dict(t=[-1, -0.5])) + vals = td.components.data.data_array.TimeDataArray([1, 2], coords={"t": [-1, -0.5]}) dataset = td.components.data.dataset.TimeDataset(values=vals) cst = td.CustomSourceTime(source_time_dataset=dataset, freq0=freq0, fwidth=0.1e12) source = td.PointDipole(center=(0, 0, 0), source_time=cst, polarization="Ex") @@ -386,7 +389,7 @@ def test_custom_source_time(): # test single value validation error with pytest.raises(pydantic.ValidationError): - vals = td.components.data.data_array.TimeDataArray([1], coords=dict(t=[0])) + vals = td.components.data.data_array.TimeDataArray([1], coords={"t": [0]}) dataset = td.components.data.dataset.TimeDataset(values=vals) cst = td.CustomSourceTime(source_time_dataset=dataset, freq0=freq0, fwidth=0.1e12) assert np.allclose(cst.amp_time([0]), [1], rtol=0, atol=ATOL) @@ -399,7 +402,7 @@ def test_custom_field_source(): Z = [0] freqs = [2e14] n_data = np.ones((Nx, Ny, Nz, Nf)) - n_dataset = td.ScalarFieldDataArray(n_data, coords=dict(x=X, y=Y, z=Z, f=freqs)) + n_dataset = td.ScalarFieldDataArray(n_data, coords={"x": X, "y": Y, "z": Z, "f": freqs}) def make_custom_field_source(field_ds): custom_source = td.CustomFieldSource( @@ -413,9 +416,9 @@ def make_custom_field_source(field_ds): with pytest.raises(pydantic.ValidationError): # repeat some entries so data cannot be interpolated - X2 = [X[0]] + list(X) + X2 = [X[0], *list(X)] n_data2 = np.vstack((n_data[0, :, :, :].reshape(1, Ny, Nz, Nf), n_data)) - n_dataset2 = td.ScalarFieldDataArray(n_data2, coords=dict(x=X2, y=Y, z=Z, f=freqs)) + n_dataset2 = td.ScalarFieldDataArray(n_data2, coords={"x": X2, "y": Y, "z": Z, "f": freqs}) field_dataset = td.FieldDataset(Ex=n_dataset, Hy=n_dataset2) make_custom_field_source(field_dataset) diff --git a/tests/test_components/test_structure.py b/tests/test_components/test_structure.py index 5deea90b42..04553e69d8 100644 --- a/tests/test_components/test_structure.py +++ b/tests/test_components/test_structure.py @@ -1,9 +1,12 @@ +from __future__ import annotations + import autograd as ag import autograd.numpy as anp import gdstk import numpy as np import pydantic.v1 as pd import pytest + import tidy3d as td @@ -30,7 +33,7 @@ def test_custom_medium_to_gds(tmp_path): f = np.array([td.C_0]) mx, my, mz, _ = np.meshgrid(x, y, z, f, indexing="ij", sparse=True) data = 1 + 1 / (1 + (mx - 1) ** 2 + my**2 + mz**2) - eps_diagonal_data = td.ScalarFieldDataArray(data, coords=dict(x=x, y=y, z=z, f=f)) + eps_diagonal_data = td.ScalarFieldDataArray(data, coords={"x": x, "y": y, "z": z, "f": f}) eps_components = {f"eps_{d}{d}": eps_diagonal_data for d in "xyz"} eps_dataset = td.PermittivityDataset(**eps_components) medium = td.CustomMedium(eps_dataset=eps_dataset, name="my_medium") @@ -62,7 +65,7 @@ def test_lower_dimension_custom_medium_to_gds(tmp_path): f = np.array([td.C_0]) mx, my, mz, _ = np.meshgrid(x, y, z, f, indexing="ij", sparse=True) data = 1 + 1 / (1 + (mx - 1) ** 2 + mz**2) - eps_diagonal_data = td.ScalarFieldDataArray(data, coords=dict(x=x, y=y, z=z, f=f)) + eps_diagonal_data = td.ScalarFieldDataArray(data, coords={"x": x, "y": y, "z": z, "f": f}) eps_components = {f"eps_{d}{d}": eps_diagonal_data for d in "xyz"} eps_dataset = td.PermittivityDataset(**eps_components) medium = td.CustomMedium(eps_dataset=eps_dataset, name="my_medium") @@ -86,7 +89,7 @@ def test_non_symmetric_custom_medium_to_gds(tmp_path): data = 1 + mx + 0 * my + (mz - 2) ** 2 print(data.min(), data.max()) - eps_diagonal_data = td.ScalarFieldDataArray(data, coords=dict(x=x, y=y, z=z, f=f)) + eps_diagonal_data = td.ScalarFieldDataArray(data, coords={"x": x, "y": y, "z": z, "f": f}) eps_components = {f"eps_{d}{d}": eps_diagonal_data for d in "xyz"} eps_dataset = td.PermittivityDataset(**eps_components) medium = td.CustomMedium(eps_dataset=eps_dataset, name="my_medium") diff --git a/tests/test_components/test_time_modulation.py b/tests/test_components/test_time_modulation.py index 96b03a4d70..a4060ea77e 100644 --- a/tests/test_components/test_time_modulation.py +++ b/tests/test_components/test_time_modulation.py @@ -1,10 +1,13 @@ """Tests space time modulation.""" +from __future__ import annotations + from math import isclose import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from ..utils import cartesian_to_unstructured @@ -16,7 +19,7 @@ X = np.linspace(-1, 1, NX) Y = np.linspace(-1, 1, NY) Z = np.linspace(-1, 1, NZ) -COORDS = dict(x=X, y=Y, z=Z) +COORDS = {"x": X, "y": Y, "z": Z} ARRAY_CMP = td.SpatialDataArray(np.random.random((NX, NY, NZ)) + 0.1j, coords=COORDS) ARRAY = td.SpatialDataArray(np.random.random((NX, NY, NZ)), coords=COORDS) @@ -261,7 +264,7 @@ def test_supported_modulated_medium_types(unstructured, z): # custom permittivity = td.SpatialDataArray( - np.ones((2, 2, len(z))) * 2, coords=dict(x=[1, 2], y=[1, 3], z=z) + np.ones((2, 2, len(z))) * 2, coords={"x": [1, 2], "y": [1, 3], "z": z} ) if unstructured: permittivity = cartesian_to_unstructured(permittivity, seed=345) diff --git a/tests/test_components/test_types.py b/tests/test_components/test_types.py index f992bbec8d..dbf93c88f9 100644 --- a/tests/test_components/test_types.py +++ b/tests/test_components/test_types.py @@ -1,10 +1,13 @@ """Tests type definitions.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pydantic import pytest + from tidy3d.components.base import Tidy3dBaseModel -from tidy3d.components.types import ArrayLike, Complex, Tuple, constrained_array +from tidy3d.components.types import ArrayLike, Complex, constrained_array def _test_validate_array_like(): @@ -89,7 +92,7 @@ def test_hash(): class MyClass(Tidy3dBaseModel): a: ArrayLike b: constrained_array(ndim=1) - c: Tuple[ArrayLike, ...] + c: tuple[ArrayLike, ...] c = MyClass(a=[1.0], b=[2.0, 1.0], c=([2.0, 1.0])) hash(c.json()) diff --git a/tests/test_components/test_viz.py b/tests/test_components/test_viz.py index ec7a6fcfe2..2d23b9f8c3 100644 --- a/tests/test_components/test_viz.py +++ b/tests/test_components/test_viz.py @@ -1,9 +1,12 @@ """Tests visualization operations.""" +from __future__ import annotations + import matplotlib as mpl import matplotlib.pyplot as plt import pydantic.v1 as pd import pytest + import tidy3d as td from tidy3d import Box, Medium, Simulation, Structure from tidy3d.components.viz import Polygon, restore_matplotlib_rcparams, set_default_labels_and_title @@ -192,18 +195,17 @@ def plot_with_multi_viz_spec(alphas, facecolors, edgecolors, rng, use_viz_spec=T td.VisualizationSpec( facecolor=facecolors[idx], edgecolor=edgecolors[idx], alpha=alphas[idx] ) - for idx in range(0, len(alphas)) + for idx in range(len(alphas)) ] - media = [td.Medium(permittivity=2.25) for idx in range(0, len(viz_specs))] + media = [td.Medium(permittivity=2.25) for idx in range(len(viz_specs))] if use_viz_spec: media = [ - td.Medium(permittivity=2.25, viz_spec=viz_specs[idx]) - for idx in range(0, len(viz_specs)) + td.Medium(permittivity=2.25, viz_spec=viz_specs[idx]) for idx in range(len(viz_specs)) ] structures = [] - for idx in range(0, len(viz_specs)): - center = tuple(list(rng.uniform(-3, 3, 2)) + [0]) + for idx in range(len(viz_specs)): + center = (*list(rng.uniform(-3, 3, 2)), 0) size = tuple(rng.uniform(1, 2, 3)) box = td.Box(center=center, size=size) diff --git a/tests/test_data/test_data_arrays.py b/tests/test_data/test_data_arrays.py index f61123fb0d..f38f42572e 100644 --- a/tests/test_data/test_data_arrays.py +++ b/tests/test_data/test_data_arrays.py @@ -1,11 +1,14 @@ """Tests tidy3d/components/data/data_array.py""" -from typing import List, Tuple +from __future__ import annotations + +from typing import Optional import numpy as np import pytest -import tidy3d as td import xarray.testing as xrt + +import tidy3d as td from tidy3d.exceptions import DataError np.random.seed(4) @@ -124,7 +127,7 @@ def get_xyz( monitor: td.components.monitor.MonitorType, grid_key: str, symmetry: bool -) -> Tuple[List[float], List[float], List[float]]: +) -> tuple[list[float], list[float], list[float]]: sim = SIM_SYM if symmetry else SIM grid = sim.discretize_monitor(monitor) if monitor.colocate: @@ -138,22 +141,24 @@ def get_xyz( return x, y, z -def make_scalar_field_data_array(grid_key: str, symmetry=True, colocate: bool = None): +def make_scalar_field_data_array(grid_key: str, symmetry=True, colocate: Optional[bool] = None): monitor = FIELD_MONITOR if colocate is not None: monitor = monitor.updated_copy(colocate=colocate) XS, YS, ZS = get_xyz(monitor, grid_key, symmetry) values = (1 + 1j) * np.random.random((len(XS), len(YS), len(ZS), len(FS))) - return td.ScalarFieldDataArray(values, coords=dict(x=XS, y=YS, z=ZS, f=FS)) + return td.ScalarFieldDataArray(values, coords={"x": XS, "y": YS, "z": ZS, "f": FS}) def make_scalar_field_time_data_array(grid_key: str, symmetry=True): XS, YS, ZS = get_xyz(FIELD_TIME_MONITOR, grid_key, symmetry) values = np.random.random((len(XS), len(YS), len(ZS), len(TS))) - return td.ScalarFieldTimeDataArray(values, coords=dict(x=XS, y=YS, z=ZS, t=TS)) + return td.ScalarFieldTimeDataArray(values, coords={"x": XS, "y": YS, "z": ZS, "t": TS}) -def make_scalar_mode_field_data_array(grid_key: str, symmetry=True, colocate: bool = None): +def make_scalar_mode_field_data_array( + grid_key: str, symmetry=True, colocate: Optional[bool] = None +): monitor = MODE_MONITOR_WITH_FIELDS if colocate is not None: monitor = monitor.updated_copy(colocate=colocate) @@ -161,7 +166,7 @@ def make_scalar_mode_field_data_array(grid_key: str, symmetry=True, colocate: bo values = (1 + 0.1j) * np.random.random((len(XS), 1, len(ZS), len(FS), len(MODE_INDICES))) return td.ScalarModeFieldDataArray( - values, coords=dict(x=XS, y=[0.0], z=ZS, f=FS, mode_index=MODE_INDICES) + values, coords={"x": XS, "y": [0.0], "z": ZS, "f": FS, "mode_index": MODE_INDICES} ) @@ -180,35 +185,37 @@ def make_scalar_mode_field_data_array_smooth(grid_key: str, symmetry=True, rot: ) return td.ScalarModeFieldDataArray( - values, coords=dict(x=XS, y=[0.0], z=ZS, f=FS, mode_index=MODE_INDICES) + values, coords={"x": XS, "y": [0.0], "z": ZS, "f": FS, "mode_index": MODE_INDICES} ) def make_mode_amps_data_array(): values = (1 + 1j) * np.random.random((len(DIRECTIONS), len(MODE_INDICES), len(FS))) return td.ModeAmpsDataArray( - values, coords=dict(direction=DIRECTIONS, mode_index=MODE_INDICES, f=FS) + values, coords={"direction": DIRECTIONS, "mode_index": MODE_INDICES, "f": FS} ) def make_mode_index_data_array(): values = (1 + 0.1j) * np.random.random((len(FS), len(MODE_INDICES))) - return td.ModeIndexDataArray(values, coords=dict(f=FS, mode_index=MODE_INDICES)) + return td.ModeIndexDataArray(values, coords={"f": FS, "mode_index": MODE_INDICES}) def make_far_field_data_array(): values = (1 + 1j) * np.random.random((len(PD), len(THETAS), len(PHIS), len(FS))) - return td.FieldProjectionAngleDataArray(values, coords=dict(r=PD, theta=THETAS, phi=PHIS, f=FS)) + return td.FieldProjectionAngleDataArray( + values, coords={"r": PD, "theta": THETAS, "phi": PHIS, "f": FS} + ) def make_flux_data_array(): values = np.random.random(len(FS)) - return td.FluxDataArray(values, coords=dict(f=FS)) + return td.FluxDataArray(values, coords={"f": FS}) def make_flux_time_data_array(): values = np.random.random(len(TS)) - return td.FluxTimeDataArray(values, coords=dict(t=TS)) + return td.FluxTimeDataArray(values, coords={"t": TS}) def make_diffraction_data_array(): @@ -216,7 +223,9 @@ def make_diffraction_data_array(): return ( [SIZE_2D[0], SIZE_2D[2]], [1.0, 2.0], - td.DiffractionDataArray(values, coords=dict(orders_x=ORDERS_X, orders_y=ORDERS_Y, f=FS)), + td.DiffractionDataArray( + values, coords={"orders_x": ORDERS_X, "orders_y": ORDERS_Y, "f": FS} + ), ) @@ -293,11 +302,11 @@ def test_ops(): def test_empty_field_time(): _ = td.ScalarFieldTimeDataArray( np.random.rand(5, 5, 5, 0), - coords=dict(x=np.arange(5), y=np.arange(5), z=np.arange(5), t=[]), + coords={"x": np.arange(5), "y": np.arange(5), "z": np.arange(5), "t": []}, ) _ = td.ScalarFieldTimeDataArray( np.random.rand(5, 5, 5, 0), - coords=dict(x=np.arange(5), y=np.arange(5), z=np.arange(5), t=[]), + coords={"x": np.arange(5), "y": np.arange(5), "z": np.arange(5), "t": []}, ) @@ -308,7 +317,7 @@ def test_abs(): def test_heat_data_array(): T = [0, 1e-12, 2e-12] - _ = td.HeatDataArray((1 + 1j) * np.random.random((3,)), coords=dict(T=T)) + _ = td.HeatDataArray((1 + 1j) * np.random.random((3,)), coords={"T": T}) def test_steady_voltage_data_array(): @@ -320,34 +329,34 @@ def test_steady_voltage_data_array(): def test_charge_data_array(): n = [0, 1e-12, 2e-12] p = [0, 3e-12, 4e-12] - _ = td.ChargeDataArray((1 + 1j) * np.random.random((3, 3)), coords=dict(n=n, p=p)) + _ = td.ChargeDataArray((1 + 1j) * np.random.random((3, 3)), coords={"n": n, "p": p}) def test_point_data_array(): _ = td.PointDataArray( np.random.rand(2, 3), - coords=dict(index=np.arange(2), axis=np.arange(3)), + coords={"index": np.arange(2), "axis": np.arange(3)}, ) def test_cell_data_array(): _ = td.CellDataArray( [[0, 1, 2], [1, 2, 3]], - coords=dict(cell_index=np.arange(2), vertex_index=np.arange(3)), + coords={"cell_index": np.arange(2), "vertex_index": np.arange(3)}, ) def test_indexed_data_array(): _ = td.IndexedDataArray( np.random.rand(10), - coords=dict(index=np.arange(10)), + coords={"index": np.arange(10)}, ) def test_spatial_data_array(): arr = td.SpatialDataArray( [[[0, 1], [2, 3]], [[4, 5], [6, 7]]], - coords=dict(x=[0, 1], y=[1, 2], z=[2, 3]), + coords={"x": [0, 1], "y": [1, 2], "z": [2, 3]}, ) # make it non sorted @@ -358,7 +367,7 @@ def test_spatial_data_array(): reflected_expected = td.SpatialDataArray( [[[4, 5], [6, 7]], [[0, 1], [2, 3]], [[0, 1], [2, 3]], [[4, 5], [6, 7]]], - coords=dict(x=[-2, -1, 0, 1], y=[1, 2], z=[2, 3]), + coords={"x": [-2, -1, 0, 1], "y": [1, 2], "z": [2, 3]}, ) assert reflected == reflected_expected @@ -368,7 +377,7 @@ def test_spatial_data_array(): reflected_expected = td.SpatialDataArray( [[[4, 5], [6, 7]], [[0, 1], [2, 3]]], - coords=dict(x=[-2, -1], y=[1, 2], z=[2, 3]), + coords={"x": [-2, -1], "y": [1, 2], "z": [2, 3]}, ) assert reflected == reflected_expected @@ -378,7 +387,7 @@ def test_spatial_data_array(): reflected_expected = td.SpatialDataArray( [[[2, 3], [0, 1], [2, 3]], [[6, 7], [4, 5], [6, 7]]], - coords=dict(x=[0, 1], y=[0, 1, 2], z=[2, 3]), + coords={"x": [0, 1], "y": [0, 1, 2], "z": [2, 3]}, ) assert reflected == reflected_expected @@ -388,7 +397,7 @@ def test_spatial_data_array(): reflected_expected = td.SpatialDataArray( [[[2, 3], [0, 1]], [[6, 7], [4, 5]]], - coords=dict(x=[0, 1], y=[0, 1], z=[2, 3]), + coords={"x": [0, 1], "y": [0, 1], "z": [2, 3]}, ) assert reflected == reflected_expected @@ -403,11 +412,11 @@ def test_sel_inside(nx): nz = 12 arr = td.SpatialDataArray( np.random.random((nx, ny, nz)), - coords=dict( - x=np.linspace(0, 1, nx), - y=np.linspace(2, 3, ny), - z=np.linspace(0, 2, nz), - ), + coords={ + "x": np.linspace(0, 1, nx), + "y": np.linspace(2, 3, ny), + "z": np.linspace(0, 2, nz), + }, ) bounds_small = [[0.1, 2, 2], [1, 2.5, 2]] @@ -429,20 +438,20 @@ def test_uniform_check(): """check if each element in the array is of equal value.""" arr = td.SpatialDataArray( np.ones((2, 2, 2), dtype=np.complex128), - coords=dict(x=[0, 1], y=[1, 2], z=[2, 3]), + coords={"x": [0, 1], "y": [1, 2], "z": [2, 3]}, ) assert arr.is_uniform # small variation is still considered as uniform arr = td.SpatialDataArray( np.ones((2, 2, 2)) + np.random.random((2, 2, 2)) * 1e-6, - coords=dict(x=[0, 1], y=[1, 2], z=[2, 3]), + coords={"x": [0, 1], "y": [1, 2], "z": [2, 3]}, ) assert arr.is_uniform arr = td.SpatialDataArray( np.ones((2, 2, 2)) + np.random.random((2, 2, 2)) * 1e-4, - coords=dict(x=[0, 1], y=[1, 2], z=[2, 3]), + coords={"x": [0, 1], "y": [1, 2], "z": [2, 3]}, ) assert not arr.is_uniform diff --git a/tests/test_data/test_datasets.py b/tests/test_data/test_datasets.py index 527b5f599e..844b398f39 100644 --- a/tests/test_data/test_datasets.py +++ b/tests/test_data/test_datasets.py @@ -1,5 +1,7 @@ """Tests tidy3d/components/data/dataset.py""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pd import pytest @@ -63,7 +65,7 @@ def test_triangular_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False): with pytest.raises(pd.ValidationError): tri_grid_points_bad = td.PointDataArray( np.random.random((4, 3)), - coords=dict(index=np.arange(4), axis=np.arange(3)), + coords={"index": np.arange(4), "axis": np.arange(3)}, ) _ = dataset_type( @@ -77,7 +79,7 @@ def test_triangular_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False): # grid with degenerate cells tri_grid_cells_bad = td.CellDataArray( [[0, 1, 1], [1, 2, 3]], - coords=dict(cell_index=np.arange(2), vertex_index=np.arange(3)), + coords={"cell_index": np.arange(2), "vertex_index": np.arange(3)}, ) with AssertLogLevel("WARNING"): @@ -113,7 +115,7 @@ def test_triangular_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False): # invalid cell connections tri_grid_cells_bad = td.CellDataArray( [[0, 1, 2, 3]], - coords=dict(cell_index=np.arange(1), vertex_index=np.arange(4)), + coords={"cell_index": np.arange(1), "vertex_index": np.arange(4)}, ) with pytest.raises(pd.ValidationError): _ = dataset_type( @@ -126,7 +128,7 @@ def test_triangular_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False): tri_grid_cells_bad = td.CellDataArray( [[0, 1, 5], [1, 2, 3]], - coords=dict(cell_index=np.arange(2), vertex_index=np.arange(3)), + coords={"cell_index": np.arange(2), "vertex_index": np.arange(3)}, ) with pytest.raises(pd.ValidationError): _ = dataset_type( @@ -259,7 +261,7 @@ def test_triangular_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False): _ = tri_grid_one_field.plot(vmin=-20, vmax=100) plt.close() - _ = tri_grid_one_field.plot(cbar_kwargs=dict(label="test")) + _ = tri_grid_one_field.plot(cbar_kwargs={"label": "test"}) plt.close() _ = tri_grid_one_field.plot(cmap="RdBu") @@ -383,7 +385,7 @@ def test_tetrahedral_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False): # wrong points dimensionality tet_grid_points_bad = td.PointDataArray( np.random.random((8, 2)), - coords=dict(index=np.arange(8), axis=np.arange(2)), + coords={"index": np.arange(8), "axis": np.arange(2)}, ) with pytest.raises(pd.ValidationError): _ = dataset_type( @@ -395,7 +397,7 @@ def test_tetrahedral_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False): # grid with degenerate cells tet_grid_cells_bad = td.CellDataArray( [[0, 1, 1, 7], [0, 2, 3, 7], [0, 2, 2, 7], [0, 4, 6, 7], [0, 4, 5, 7], [0, 5, 5, 7]], - coords=dict(cell_index=np.arange(6), vertex_index=np.arange(4)), + coords={"cell_index": np.arange(6), "vertex_index": np.arange(4)}, ) with AssertLogLevel("WARNING"): @@ -429,7 +431,7 @@ def test_tetrahedral_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False): # invalid cell connections tet_grid_cells_bad = td.CellDataArray( [[0, 1, 3], [0, 2, 3], [0, 2, 6], [0, 4, 6], [0, 4, 5], [0, 1, 5]], - coords=dict(cell_index=np.arange(6), vertex_index=np.arange(3)), + coords={"cell_index": np.arange(6), "vertex_index": np.arange(3)}, ) with pytest.raises(pd.ValidationError): _ = dataset_type( @@ -440,7 +442,7 @@ def test_tetrahedral_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False): tet_grid_cells_bad = td.CellDataArray( [[0, 1, 3, 17], [0, 2, 3, 7], [0, 2, 6, 7], [0, 4, 6, 7], [0, 4, 5, 7], [0, 1, 5, 7]], - coords=dict(cell_index=np.arange(6), vertex_index=np.arange(4)), + coords={"cell_index": np.arange(6), "vertex_index": np.arange(4)}, ) with pytest.raises(pd.ValidationError): _ = dataset_type( @@ -615,7 +617,7 @@ def test_cartesian_to_unstructured(nz, use_vtk, fill_value): z = np.linspace(-0.2, 0.15, nz) values = np.sin(x[:, None, None]) * np.cos(y[None, :, None]) * np.exp(z[None, None, :]) - arr_c = td.SpatialDataArray(values, coords=dict(x=x, y=y, z=z)) + arr_c = td.SpatialDataArray(values, coords={"x": x, "y": y, "z": z}) arr_u_linear = cartesian_to_unstructured(arr_c, pert=0.1, method="linear", seed=123) arr_c_linear = arr_u_linear.interp( @@ -696,7 +698,7 @@ def test_cell_values(): tri_grid_values = td.IndexedVoltageDataArray( [[0.0, 0.0], [0, 0], [3, -3], [3, -3]], - coords=dict(index=np.arange(4), voltage=[-1, 1]), + coords={"index": np.arange(4), "voltage": [-1, 1]}, name="test", ) @@ -733,7 +735,7 @@ def test_cell_values(): ) tet_grid_values = td.IndexedDataArray( - [0.0, 0.0, 0.0, 0.0, 3.0, 3.0, 3.0, 3.0], coords=dict(index=np.arange(8)), name="test_tet" + [0.0, 0.0, 0.0, 0.0, 3.0, 3.0, 3.0, 3.0], coords={"index": np.arange(8)}, name="test_tet" ) tet_grid = td.TetrahedralGridDataset( diff --git a/tests/test_data/test_monitor_data.py b/tests/test_data/test_monitor_data.py index 5ea9d6a6cc..c0f70d6454 100644 --- a/tests/test_data/test_monitor_data.py +++ b/tests/test_data/test_monitor_data.py @@ -1,11 +1,14 @@ """Tests tidy3d/components/data/monitor_data.py""" +from __future__ import annotations + import matplotlib.pyplot as plt import numpy as np import pydantic.v1 as pydantic import pytest -import tidy3d as td import xarray as xr + +import tidy3d as td from tidy3d.components.data.data_array import ( FreqDataArray, FreqModeDataArray, @@ -239,17 +242,17 @@ def make_field_dataset_using_power_density( phi=phi, ) - coords = dict(r=r_proj, theta=theta, phi=phi, f=freqs) + coords = {"r": r_proj, "theta": theta, "phi": phi, "f": freqs} field = td.FieldProjectionAngleDataArray(values, coords=coords) - field_components = dict( - Er=field, - Etheta=field, - Ephi=field, - Hr=field, - Htheta=-1.0 * field, - Hphi=field, - ) + field_components = { + "Er": field, + "Etheta": field, + "Ephi": field, + "Hr": field, + "Htheta": -1.0 * field, + "Hphi": field, + } field_dataset = xr.Dataset(field_components) return monitor, field_dataset @@ -398,7 +401,7 @@ def test_directivity_data(planar_monitor): _ = data.flux f = data.flux.f.values # make some dummy data to represent power supplied to antenna - power_in = FreqDataArray(np.abs(np.random.random(size=np.shape(f))), coords=dict(f=f)) + power_in = FreqDataArray(np.abs(np.random.random(size=np.shape(f))), coords={"f": f}) assert isinstance(data.partial_radiation_intensity(), xr.Dataset) assert isinstance(data.radiation_intensity, xr.DataArray) assert isinstance(data.partial_directivity(), xr.Dataset) @@ -661,7 +664,7 @@ def test_data_array_hdf5_no_warnings(tmp_path): def test_diffraction_data_use_medium(): data = make_diffraction_data() - data = data.copy(update=dict(medium=td.Medium(permittivity=4))) + data = data.copy(update={"medium": td.Medium(permittivity=4)}) assert np.allclose(data.eta, np.real(td.ETA_0 / 2.0)) @@ -863,7 +866,7 @@ def test_no_nans(): eps_nan = eps_data.eps_xx.isel(f=[0]) eps_nan[:] = np.nan eps_dataset_nan = td.PermittivityDataset( - **{key: eps_nan for key in ["eps_xx", "eps_yy", "eps_zz"]} + **dict.fromkeys(["eps_xx", "eps_yy", "eps_zz"], eps_nan) ) with pytest.raises(pydantic.ValidationError): td.CustomMedium(eps_dataset=eps_dataset_nan) @@ -917,7 +920,7 @@ def mode_data(self) -> td.ModeData: return self.simdata(monitor)["modes"] @pytest.mark.parametrize("background_index", [1, 2, 3]) - @pytest.mark.parametrize("freq", list(freqs) + [None]) + @pytest.mark.parametrize("freq", [*list(freqs), None]) @pytest.mark.parametrize("n_x", [2**5, 2**6]) @pytest.mark.parametrize("n_y", [2**5, 2**6]) @pytest.mark.parametrize("units", ["mm", "cm", "in", "m"]) diff --git a/tests/test_data/test_sim_data.py b/tests/test_data/test_sim_data.py index d2569fb20a..2ea3da663d 100644 --- a/tests/test_data/test_sim_data.py +++ b/tests/test_data/test_sim_data.py @@ -1,9 +1,12 @@ """Tests SimulationData""" +from __future__ import annotations + import matplotlib.pyplot as plt import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.components.data.data_array import ScalarFieldTimeDataArray from tidy3d.components.data.monitor_data import FieldTimeData @@ -388,12 +391,12 @@ def test_run_time_lt_start(tmp_path): normalize_index=None, ) - coords = dict( - x=np.linspace(-0.6, 0.6, 10), - y=np.linspace(-0.6, 0.6, 10), - z=[0.1], - t=[], - ) + coords = { + "x": np.linspace(-0.6, 0.6, 10), + "y": np.linspace(-0.6, 0.6, 10), + "z": [0.1], + "t": [], + } field_components = { field_name: ScalarFieldTimeDataArray(np.zeros((10, 10, 1, 0)), coords=coords) @@ -429,9 +432,9 @@ def test_plot_field_title(): def test_missing_monitor(): sim_data = make_sim_data() new_monitors = list(sim_data.simulation.monitors)[:-1] - new_sim = sim_data.simulation.copy(update=dict(monitors=new_monitors)) + new_sim = sim_data.simulation.copy(update={"monitors": new_monitors}) with pytest.raises(pydantic.ValidationError): - _ = sim_data.copy(update=dict(simulation=new_sim)) + _ = sim_data.copy(update={"simulation": new_sim}) def test_loading_non_field_data(): diff --git a/tests/test_material_library/test_material_library.py b/tests/test_material_library/test_material_library.py index 8fa25d22f2..681bf86c29 100644 --- a/tests/test_material_library/test_material_library.py +++ b/tests/test_material_library/test_material_library.py @@ -1,7 +1,10 @@ """Tests material library functions and pretty printing""" -import tidy3d as td +from __future__ import annotations + from rich.console import Console + +import tidy3d as td from tidy3d.material_library.material_library import MaterialItemUniaxial @@ -68,20 +71,20 @@ def test_medium_repr(): repr_noname_medium = test_media[0].__repr__() str_noname_medium_dict = str(noname_medium_in_dict) - assert ( - "type='Medium' permittivity=2.25 conductivity=0.0" in str_noname_medium - ), "Expected medium information in string" - assert ( - "Medium(attrs={}, name=None, frequency_range=None" in repr_noname_medium - ), "Expcted medium information in repr" + assert "type='Medium' permittivity=2.25 conductivity=0.0" in str_noname_medium, ( + "Expected medium information in string" + ) + assert "Medium(attrs={}, name=None, frequency_range=None" in repr_noname_medium, ( + "Expcted medium information in repr" + ) assert repr_noname_medium in str_noname_medium_dict, "Expected repr in dictionary string" for medium in test_media: repr_str = medium.__repr__() - assert ( - test_media[1].__repr__() == material_name - ), "Expected repr to return just the material name." + assert test_media[1].__repr__() == material_name, ( + "Expected repr to return just the material name." + ) def test_variant_str(): @@ -94,16 +97,16 @@ def test_variant_str(): printed_SiO2_Palik_lossless = str(td.material_library["SiO2"].variants["Palik_Lossless"]) - assert ( - "eps_inf: 1.5385442336875639" in printed_SiO2_Palik_lossless - ), "Expected eps_inf in SiO2 printed string" + assert "eps_inf: 1.5385442336875639" in printed_SiO2_Palik_lossless, ( + "Expected eps_inf in SiO2 printed string" + ) assert "poles: 2" in printed_SiO2_Palik_lossless, "Expected 1 pole in SiO2 printed string" printed_SiO2_Palik_lossy = str(td.material_library["SiO2"].variants["Palik_Lossy"]) - assert ( - "eps_inf: 2.1560362571240765" in printed_SiO2_Palik_lossy - ), "Expected eps_inf in SiO2 printed string" + assert "eps_inf: 2.1560362571240765" in printed_SiO2_Palik_lossy, ( + "Expected eps_inf in SiO2 printed string" + ) assert "poles: 5" in printed_SiO2_Palik_lossy, "Expected 1 pole in SiO2 printed string" @@ -112,9 +115,9 @@ def test_material_str(): printed_Ag = str(td.material_library["Ag"]) - assert ( - "Default Variant: Rakic1998BB" in printed_Ag - ), "Expected default variant in printed string" + assert "Default Variant: Rakic1998BB" in printed_Ag, ( + "Expected default variant in printed string" + ) assert "RakicLorentzDrude1998" in printed_Ag, "Expected variant in printed string" printed_Au = str(td.material_library["Au"]) diff --git a/tests/test_package/test_compat.py b/tests/test_package/test_compat.py index 17005c9046..e079bbe509 100644 --- a/tests/test_package/test_compat.py +++ b/tests/test_package/test_compat.py @@ -1,4 +1,5 @@ # test_compat.py +from __future__ import annotations import importlib import sys diff --git a/tests/test_package/test_config.py b/tests/test_package/test_config.py index 63b31e86a7..f589494ac6 100644 --- a/tests/test_package/test_config.py +++ b/tests/test_package/test_config.py @@ -1,7 +1,10 @@ """test the grid operations""" +from __future__ import annotations + import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.log import DEFAULT_LEVEL, _level_value diff --git a/tests/test_package/test_convert.py b/tests/test_package/test_convert.py index fb7d5e741f..06e8e6bf1a 100644 --- a/tests/test_package/test_convert.py +++ b/tests/test_package/test_convert.py @@ -1,6 +1,9 @@ """Test converting .lsf files to Tidy3D python files.""" +from __future__ import annotations + import pytest + from tidy3d.web.cli.app import convert diff --git a/tests/test_package/test_log.py b/tests/test_package/test_log.py index bc72d5b316..1198c540ea 100644 --- a/tests/test_package/test_log.py +++ b/tests/test_package/test_log.py @@ -1,10 +1,13 @@ """Test the logging.""" +from __future__ import annotations + import json import numpy as np import pydantic.v1 as pd import pytest + import tidy3d as td from tidy3d.exceptions import Tidy3dError from tidy3d.log import DEFAULT_LEVEL, _get_level_int, set_logging_level @@ -77,7 +80,7 @@ def test_logging_warning_capture(): center=(0, 0, 0), size=(domain_size, 0, domain_size), # additional frequency is outside the source range, but is inside the allowed validator range - freqs=list(freqs) + [0.1 * f0], + freqs=[*list(freqs), 0.1 * f0], mode_spec=td.ModeSpec(num_modes=3), name="mode", ) diff --git a/tests/test_package/test_main.py b/tests/test_package/test_main.py index e55b39d622..120ae7fcb1 100644 --- a/tests/test_package/test_main.py +++ b/tests/test_package/test_main.py @@ -1,6 +1,9 @@ """Test running tidy3d as command line application.""" +from __future__ import annotations + import pytest + import tidy3d as td from tidy3d.__main__ import main diff --git a/tests/test_package/test_make_script.py b/tests/test_package/test_make_script.py index 235d75396f..904e40db5f 100644 --- a/tests/test_package/test_make_script.py +++ b/tests/test_package/test_make_script.py @@ -1,5 +1,7 @@ """Tests generation of pythons script from simulation file.""" +from __future__ import annotations + import tidy3d as td from scripts.make_script import main diff --git a/tests/test_package/test_material_library.py b/tests/test_package/test_material_library.py index 649182de87..1b988c43f4 100644 --- a/tests/test_package/test_material_library.py +++ b/tests/test_package/test_material_library.py @@ -1,6 +1,9 @@ +from __future__ import annotations + import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.components.material.multi_physics import MultiPhysicsMedium from tidy3d.material_library.material_library import ( @@ -47,12 +50,14 @@ def test_MaterialItem(): medium=td.PoleResidue(), reference=[ReferenceData(doi="etc2.com", journal="paper2", url="www2")], ) - material = MaterialItem(name="material", variants=dict(v1=variant1, v2=variant2), default="v1") + material = MaterialItem( + name="material", variants={"v1": variant1, "v2": variant2}, default="v1" + ) assert material["v1"] == material.medium with pytest.raises(pydantic.ValidationError): material = MaterialItem( - name="material", variants=dict(v1=variant1, v2=variant2), default="v3" + name="material", variants={"v1": variant1, "v2": variant2}, default="v3" ) @@ -132,7 +137,7 @@ def test_uniaxial_material(): extraordinary=td.PoleResidue(eps_inf=6), ) material = MaterialItemUniaxial( - name="material", variants=dict(v1=variant1, v2=variant2), default="v1" + name="material", variants={"v1": variant1, "v2": variant2}, default="v1" ) for optical_axis in range(3): diff --git a/tests/test_package/test_parametric_variants.py b/tests/test_package/test_parametric_variants.py index 46aa58c6fc..ddd0d94bbc 100644 --- a/tests/test_package/test_parametric_variants.py +++ b/tests/test_package/test_parametric_variants.py @@ -1,6 +1,9 @@ +from __future__ import annotations + import numpy as np import pytest from numpy.random import default_rng + from tidy3d.material_library.parametric_materials import ( GRAPHENE_FIT_ATOL, GRAPHENE_FIT_FREQ_MAX, diff --git a/tests/test_plugins/autograd/invdes/test_filters.py b/tests/test_plugins/autograd/invdes/test_filters.py index 2be94fedd7..ddab528e81 100644 --- a/tests/test_plugins/autograd/invdes/test_filters.py +++ b/tests/test_plugins/autograd/invdes/test_filters.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import pytest + from tidy3d.plugins.autograd.invdes.filters import ( _get_kernel_size, make_circular_filter, diff --git a/tests/test_plugins/autograd/invdes/test_parametrizations.py b/tests/test_plugins/autograd/invdes/test_parametrizations.py index aa31dec92d..31d8a8dc18 100644 --- a/tests/test_plugins/autograd/invdes/test_parametrizations.py +++ b/tests/test_plugins/autograd/invdes/test_parametrizations.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import autograd.numpy as np import pytest + from tidy3d.plugins.autograd.invdes.parametrizations import make_filter_and_project from tidy3d.plugins.autograd.types import PaddingType diff --git a/tests/test_plugins/autograd/invdes/test_penalties.py b/tests/test_plugins/autograd/invdes/test_penalties.py index 8ddad52675..7590c92c3c 100644 --- a/tests/test_plugins/autograd/invdes/test_penalties.py +++ b/tests/test_plugins/autograd/invdes/test_penalties.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import pytest + from tidy3d.plugins.autograd.invdes.penalties import make_erosion_dilation_penalty from tidy3d.plugins.autograd.types import PaddingType diff --git a/tests/test_plugins/autograd/primitives/test_interpolate.py b/tests/test_plugins/autograd/primitives/test_interpolate.py index 475339b07f..e30b7a720d 100644 --- a/tests/test_plugins/autograd/primitives/test_interpolate.py +++ b/tests/test_plugins/autograd/primitives/test_interpolate.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import autograd.numpy as np import numpy.testing as npt import pytest from autograd import grad from autograd.test_util import check_grads + from tidy3d.plugins.autograd import interpolate_spline from ....utils import AssertLogLevel diff --git a/tests/test_plugins/autograd/primitives/test_misc.py b/tests/test_plugins/autograd/primitives/test_misc.py index ce38b7a42d..22ae8251e2 100644 --- a/tests/test_plugins/autograd/primitives/test_misc.py +++ b/tests/test_plugins/autograd/primitives/test_misc.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import pytest from autograd.test_util import check_grads + from tidy3d.plugins.autograd.primitives import gaussian_filter diff --git a/tests/test_plugins/autograd/test_differential_operators.py b/tests/test_plugins/autograd/test_differential_operators.py index 75879ea98c..296a218892 100644 --- a/tests/test_plugins/autograd/test_differential_operators.py +++ b/tests/test_plugins/autograd/test_differential_operators.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import autograd.numpy as np import pytest from autograd import grad as grad_ag from autograd import value_and_grad as value_and_grad_ag from numpy.testing import assert_allclose + from tidy3d.components.data.data_array import DataArray from tidy3d.plugins.autograd import grad, value_and_grad diff --git a/tests/test_plugins/autograd/test_functions.py b/tests/test_plugins/autograd/test_functions.py index dc93f43573..a29b9129b6 100644 --- a/tests/test_plugins/autograd/test_functions.py +++ b/tests/test_plugins/autograd/test_functions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import numpy as np import numpy.testing as npt import pytest @@ -6,6 +8,7 @@ from autograd import grad from autograd.test_util import check_grads from scipy.signal import convolve as convolve_sp + from tidy3d.plugins.autograd import ( add_at, convolve, diff --git a/tests/test_plugins/autograd/test_utilities.py b/tests/test_plugins/autograd/test_utilities.py index 68cf7a9706..c0aaecc4ee 100644 --- a/tests/test_plugins/autograd/test_utilities.py +++ b/tests/test_plugins/autograd/test_utilities.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import numpy as np import numpy.testing as npt import pytest import xarray as xr + from tidy3d.exceptions import Tidy3dError from tidy3d.plugins.autograd import ( chain, diff --git a/tests/test_plugins/expressions/test_functions.py b/tests/test_plugins/expressions/test_functions.py index d7942b545c..c4f039ca79 100644 --- a/tests/test_plugins/expressions/test_functions.py +++ b/tests/test_plugins/expressions/test_functions.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import numpy as np import pytest + from tidy3d.plugins.expressions.functions import Cos, Exp, Log, Log10, Sin, Sqrt, Tan from tidy3d.plugins.expressions.variables import Constant diff --git a/tests/test_plugins/expressions/test_operators.py b/tests/test_plugins/expressions/test_operators.py index d794a928fc..f9b80e25d3 100644 --- a/tests/test_plugins/expressions/test_operators.py +++ b/tests/test_plugins/expressions/test_operators.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import operator import numpy as np import pytest + from tidy3d.plugins.expressions.operators import ( Abs, Add, diff --git a/tests/test_plugins/expressions/test_variables.py b/tests/test_plugins/expressions/test_variables.py index 0d4818c9a8..da74b03dc0 100644 --- a/tests/test_plugins/expressions/test_variables.py +++ b/tests/test_plugins/expressions/test_variables.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import numpy as np import pytest + from tidy3d.plugins.expressions.variables import Constant, Variable diff --git a/tests/test_plugins/pytorch/test_wrapper.py b/tests/test_plugins/pytorch/test_wrapper.py index 2e80996c0c..09756d1b39 100644 --- a/tests/test_plugins/pytorch/test_wrapper.py +++ b/tests/test_plugins/pytorch/test_wrapper.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import autograd.numpy as anp import torch from autograd import elementwise_grad from numpy.testing import assert_allclose + from tidy3d.plugins.pytorch.wrapper import to_torch diff --git a/tests/test_plugins/smatrix/terminal_component_modeler_def.py b/tests/test_plugins/smatrix/terminal_component_modeler_def.py index e8b5eca779..1b8801c731 100644 --- a/tests/test_plugins/smatrix/terminal_component_modeler_def.py +++ b/tests/test_plugins/smatrix/terminal_component_modeler_def.py @@ -1,6 +1,9 @@ -from typing import Union +from __future__ import annotations + +from typing import Optional, Union import numpy as np + import tidy3d as td import tidy3d.plugins.microwave as microwave from tidy3d.plugins.smatrix import ( @@ -33,7 +36,9 @@ Router = 1.0 * mm -def make_simulation(planar_pec: bool, length: float = None, grid_spec: td.GridSpec = None): +def make_simulation( + planar_pec: bool, length: Optional[float] = None, grid_spec: td.GridSpec = None +): if length: strip_length = length else: @@ -109,7 +114,7 @@ def make_simulation(planar_pec: bool, length: float = None, grid_spec: td.GridSp def make_component_modeler( planar_pec: bool, reference_impedance: complex = 50, - length: float = None, + length: Optional[float] = None, port_refinement: bool = True, port_snapping: bool = True, grid_spec: td.GridSpec = None, @@ -167,7 +172,7 @@ def make_component_modeler( return modeler -def make_coaxial_simulation(length: float = None, grid_spec: td.GridSpec = None): +def make_coaxial_simulation(length: Optional[float] = None, grid_spec: td.GridSpec = None): if not length: length = default_strip_length @@ -244,7 +249,7 @@ def make_coaxial_simulation(length: float = None, grid_spec: td.GridSpec = None) def make_coaxial_component_modeler( reference_impedance: complex = 50, - length: float = None, + length: Optional[float] = None, port_refinement: bool = True, grid_spec: td.GridSpec = None, port_types: tuple[Union[CoaxialLumpedPort, WavePort], Union[CoaxialLumpedPort, WavePort]] = ( diff --git a/tests/test_plugins/smatrix/test_component_modeler.py b/tests/test_plugins/smatrix/test_component_modeler.py index 9fa2477a3b..74064826d9 100644 --- a/tests/test_plugins/smatrix/test_component_modeler.py +++ b/tests/test_plugins/smatrix/test_component_modeler.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import gdstk import matplotlib.pyplot as plt import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td from tidy3d.exceptions import SetupError, Tidy3dKeyError from tidy3d.plugins.smatrix import ( @@ -203,9 +206,9 @@ def test_validate_no_sources(): source = td.PointDipole( source_time=td.GaussianPulse(freq0=2e14, fwidth=1e14), polarization="Ex" ) - sim_w_source = modeler.simulation.copy(update=dict(sources=(source,))) + sim_w_source = modeler.simulation.copy(update={"sources": (source,)}) with pytest.raises(pydantic.ValidationError): - _ = modeler.copy(update=dict(simulation=sim_w_source)) + _ = modeler.copy(update={"simulation": sim_w_source}) def test_element_mappings_none(): @@ -232,7 +235,7 @@ def test_ports_too_close_boundary(): port_center_at_edge = list(port_at_edge.center) port_center_at_edge[0] = edge_val port_at_edge = port_at_edge.copy( - update=dict(center=port_center_at_edge, direction=port_dir) + update={"center": port_center_at_edge, "direction": port_dir} ) with pytest.raises(SetupError): modeler._shift_value_signed(port=port_at_edge) @@ -275,15 +278,15 @@ def test_run_component_modeler(monkeypatch): for mode_index_in in range(port_in.mode_spec.num_modes): for port_out in modeler.ports: for mode_index_out in range(port_out.mode_spec.num_modes): - coords_in = dict(port_in=port_in.name, mode_index_in=mode_index_in) - coords_out = dict(port_out=port_out.name, mode_index_out=mode_index_out) + coords_in = {"port_in": port_in.name, "mode_index_in": mode_index_in} + coords_out = {"port_out": port_out.name, "mode_index_out": mode_index_out} - assert np.all( - s_matrix.sel(**coords_in) != 0 - ), "source index not present in S matrix" - assert np.all( - s_matrix.sel(**coords_in).sel(**coords_out) != 0 - ), "monitor index not present in S matrix" + assert np.all(s_matrix.sel(**coords_in) != 0), ( + "source index not present in S matrix" + ) + assert np.all(s_matrix.sel(**coords_in).sel(**coords_out) != 0), ( + "monitor index not present in S matrix" + ) def test_component_modeler_run_only(monkeypatch): @@ -294,7 +297,7 @@ def test_component_modeler_run_only(monkeypatch): modeler = make_component_modeler(run_only=run_only) s_matrix = run_component_modeler(monkeypatch, modeler) - coords_in_run_only = dict(port_in=port_run_only, mode_index_in=mode_index_run_only) + coords_in_run_only = {"port_in": port_run_only, "mode_index_in": mode_index_run_only} # make sure the run only mappings are non-zero assert np.all(s_matrix.sel(**coords_in_run_only) != 0) @@ -312,19 +315,19 @@ def _test_mappings(element_mappings, s_matrix): (port_out_to, mode_index_out_to) = k (port_in_to, mode_index_in_to) = L - coords_from = dict( - port_in=port_in_from, - port_out=port_out_from, - mode_index_in=mode_index_in_from, - mode_index_out=mode_index_out_from, - ) + coords_from = { + "port_in": port_in_from, + "port_out": port_out_from, + "mode_index_in": mode_index_in_from, + "mode_index_out": mode_index_out_from, + } - coords_to = dict( - port_in=port_in_to, - port_out=port_out_to, - mode_index_in=mode_index_in_to, - mode_index_out=mode_index_out_to, - ) + coords_to = { + "port_in": port_in_to, + "port_out": port_out_to, + "mode_index_in": mode_index_in_to, + "mode_index_out": mode_index_out_to, + } assert np.all( s_matrix.sel(**coords_to).values == mult_by * s_matrix.sel(**coords_from).values @@ -396,7 +399,7 @@ def test_to_from_file_batch(tmp_path, monkeypatch): modeler = make_component_modeler() _ = run_component_modeler(monkeypatch, modeler) - batch = td.web.Batch(simulations=dict()) + batch = td.web.Batch(simulations={}) modeler._cached_properties["batch"] = batch diff --git a/tests/test_plugins/smatrix/test_terminal_component_modeler.py b/tests/test_plugins/smatrix/test_terminal_component_modeler.py index 22be14fed0..72802417a2 100644 --- a/tests/test_plugins/smatrix/test_terminal_component_modeler.py +++ b/tests/test_plugins/smatrix/test_terminal_component_modeler.py @@ -1,9 +1,12 @@ +from __future__ import annotations + import matplotlib.pyplot as plt import numpy as np import pydantic.v1 as pd import pytest -import tidy3d as td import xarray as xr + +import tidy3d as td from tidy3d.components.data.data_array import FreqDataArray from tidy3d.exceptions import SetupError, Tidy3dError, Tidy3dKeyError from tidy3d.plugins.microwave import CustomCurrentIntegral2D, VoltageIntegralAxisAligned @@ -73,9 +76,9 @@ def test_validate_no_sources(tmp_path): source = td.PointDipole( source_time=td.GaussianPulse(freq0=2e14, fwidth=1e14), polarization="Ex" ) - sim_w_source = modeler.simulation.copy(update=dict(sources=(source,))) + sim_w_source = modeler.simulation.copy(update={"sources": (source,)}) with pytest.raises(pd.ValidationError): - _ = modeler.copy(update=dict(simulation=sim_w_source)) + _ = modeler.copy(update={"simulation": sim_w_source}) def test_validate_3D_sim(tmp_path): @@ -137,13 +140,13 @@ def test_run_component_modeler(monkeypatch, tmp_path): for port_in in modeler.ports: for port_out in modeler.ports: - coords_in = dict(port_in=port_in.name) - coords_out = dict(port_out=port_out.name) + coords_in = {"port_in": port_in.name} + coords_out = {"port_out": port_out.name} assert np.all(s_matrix.sel(**coords_in) != 0), "source index not present in S matrix" - assert np.all( - s_matrix.sel(**coords_in).sel(**coords_out) != 0 - ), "monitor index not present in S matrix" + assert np.all(s_matrix.sel(**coords_in).sel(**coords_out) != 0), ( + "monitor index not present in S matrix" + ) def test_s_to_z_component_modeler(): @@ -173,11 +176,11 @@ def test_s_to_z_component_modeler(): dtype=complex, ) # Put coords in opposite order to check reordering - coords = dict( - f=np.array(freqs), - port_out=port_names, - port_in=port_names, - ) + coords = { + "f": np.array(freqs), + "port_out": port_names, + "port_in": port_names, + } s_matrix = TerminalPortDataArray(data=values, coords=coords) z_matrix = TerminalComponentModeler.s_to_z(s_matrix, reference=Z0) @@ -189,10 +192,10 @@ def test_s_to_z_component_modeler(): # test version with different port reference impedances values = np.full((len(freqs), len(port_names)), Z0) - coords = dict( - f=np.array(freqs), - port=port_names, - ) + coords = { + "f": np.array(freqs), + "port": port_names, + } z_port_matrix = PortDataArray(data=values, coords=coords) z_matrix = TerminalComponentModeler.s_to_z(s_matrix, reference=z_port_matrix) z_matrix_at_f = z_matrix.sel(f=1e8) @@ -203,11 +206,11 @@ def test_s_to_z_component_modeler(): def test_ab_to_s_component_modeler(): - coords = dict( - f=np.array([1e8]), - port_out=["lumped_port_1", "lumped_port_2"], - port_in=["lumped_port_1", "lumped_port_2"], - ) + coords = { + "f": np.array([1e8]), + "port_out": ["lumped_port_1", "lumped_port_2"], + "port_in": ["lumped_port_1", "lumped_port_2"], + } # Common case is reference impedance matched to loads, which means ideally # the a matrix would be an identity matrix, and as a result the s matrix will be # given directly by the b_matrix @@ -273,13 +276,13 @@ def test_run_coaxial_component_modeler(monkeypatch, tmp_path): for port_in in modeler.ports: for port_out in modeler.ports: - coords_in = dict(port_in=port_in.name) - coords_out = dict(port_out=port_out.name) + coords_in = {"port_in": port_in.name} + coords_out = {"port_out": port_out.name} assert np.all(s_matrix.sel(**coords_in) != 0), "source index not present in S matrix" - assert np.all( - s_matrix.sel(**coords_in).sel(**coords_out) != 0 - ), "monitor index not present in S matrix" + assert np.all(s_matrix.sel(**coords_in).sel(**coords_out) != 0), ( + "monitor index not present in S matrix" + ) def test_coarse_grid_at_coaxial_port(monkeypatch, tmp_path): @@ -376,10 +379,10 @@ def test_power_delivered_helper(monkeypatch, tmp_path): current = np.ones_like(freqs) * current_amplitude def compute_voltage_patch(self, sim_data): - return FreqDataArray(voltage, coords=dict(f=freqs)) + return FreqDataArray(voltage, coords={"f": freqs}) def compute_current_patch(self, sim_data): - return FreqDataArray(current, coords=dict(f=freqs)) + return FreqDataArray(current, coords={"f": freqs}) monkeypatch.setattr(CoaxialLumpedPort, "compute_voltage", compute_voltage_patch) monkeypatch.setattr(CoaxialLumpedPort, "compute_current", compute_current_patch) @@ -452,12 +455,12 @@ def test_run_coaxial_component_modeler_with_wave_ports( shape_both_ports = (len(modeler.freqs),) for port_in in modeler.ports: for port_out in modeler.ports: - coords_in = dict(port_in=port_in.name) - coords_out = dict(port_out=port_out.name) + coords_in = {"port_in": port_in.name} + coords_out = {"port_out": port_out.name} - assert np.all( - s_matrix.sel(**coords_in).values.shape == shape_one_port - ), "source index not present in S matrix" + assert np.all(s_matrix.sel(**coords_in).values.shape == shape_one_port), ( + "source index not present in S matrix" + ) assert np.all( s_matrix.sel(**coords_in).sel(**coords_out).values.shape == shape_both_ports ), "monitor index not present in S matrix" @@ -477,12 +480,12 @@ def test_run_mixed_component_modeler_with_wave_ports(monkeypatch, tmp_path): shape_both_ports = (len(modeler.freqs),) for port_in in modeler.ports: for port_out in modeler.ports: - coords_in = dict(port_in=port_in.name) - coords_out = dict(port_out=port_out.name) + coords_in = {"port_in": port_in.name} + coords_out = {"port_out": port_out.name} - assert np.all( - s_matrix.sel(**coords_in).values.shape == shape_one_port - ), "source index not present in S matrix" + assert np.all(s_matrix.sel(**coords_in).values.shape == shape_one_port), ( + "source index not present in S matrix" + ) assert np.all( s_matrix.sel(**coords_in).sel(**coords_out).values.shape == shape_both_ports ), "monitor index not present in S matrix" @@ -683,7 +686,7 @@ def test_antenna_helpers(monkeypatch, tmp_path): modeler.get_radiation_monitor_by_name("invalid") # Test monitor data normalization with different amplitude types - a_array = FreqDataArray(np.ones(len(modeler.freqs)), dict(f=modeler.freqs)) + a_array = FreqDataArray(np.ones(len(modeler.freqs)), {"f": modeler.freqs}) normalized_data_array = modeler._monitor_data_at_port_amplitude( modeler.ports[0], sim_data, rad_mon_data, a_array ) diff --git a/tests/test_plugins/test_adjoint.py b/tests/test_plugins/test_adjoint.py index 007faf6809..1c89c6bbc6 100644 --- a/tests/test_plugins/test_adjoint.py +++ b/tests/test_plugins/test_adjoint.py @@ -1,9 +1,11 @@ """Tests adjoint plugin.""" +from __future__ import annotations + import builtins import time from pathlib import Path -from typing import Dict, List, Tuple +from typing import Optional import gdstk import h5py @@ -13,11 +15,13 @@ import numpy as np import pydantic.v1 as pydantic import pytest -import tidy3d as td import trimesh from jax import grad from jax.test_util import check_grads from numpy.testing import assert_allclose +from xarray import DataArray + +import tidy3d as td from tidy3d.exceptions import AdjointError, DataError, Tidy3dKeyError from tidy3d.plugins.adjoint.components import simulation from tidy3d.plugins.adjoint.components.data.data_array import ( @@ -56,7 +60,6 @@ from tidy3d.plugins.adjoint.web import run, run_async, run_async_local, run_local from tidy3d.plugins.polyslab import ComplexPolySlab from tidy3d.web.api.container import BatchData -from xarray import DataArray from ..test_components.test_custom import CUSTOM_MEDIUM from ..utils import AssertLogLevel, run_async_emulated, run_emulated @@ -157,8 +160,8 @@ def run_emulated_bwd( folder_name: str, callback_url: str, verbose: bool, - num_proc: int = None, - path_dir: str = None, + num_proc: Optional[int] = None, + path_dir: Optional[str] = None, ) -> JaxSimulation: """Runs adjoint simulation on our servers, grabs the gradient data from fwd for processing.""" @@ -200,13 +203,13 @@ def run_emulated_bwd( # Emulated forward and backward run functions def run_async_emulated_fwd( - simulations: Tuple[td.Simulation, ...], - jax_infos: Tuple[JaxInfo, ...], + simulations: tuple[td.Simulation, ...], + jax_infos: tuple[JaxInfo, ...], folder_name: str, path_dir: str, callback_url: str, verbose: bool, -) -> Tuple[BatchData, Dict[str, str]]: +) -> tuple[BatchData, dict[str, str]]: """Runs the forward simulation on our servers, stores the gradient data for later.""" sim_datas_orig = {} @@ -229,14 +232,14 @@ def run_async_emulated_fwd( def run_async_emulated_bwd( - simulations: Tuple[td.Simulation, ...], - jax_infos: Tuple[JaxInfo, ...], + simulations: tuple[td.Simulation, ...], + jax_infos: tuple[JaxInfo, ...], folder_name: str, path_dir: str, callback_url: str, verbose: bool, - parent_tasks: List[List[str]], -) -> List[JaxSimulation]: + parent_tasks: list[list[str]], +) -> list[JaxSimulation]: """Runs adjoint simulation on our servers, grabs the gradient data from fwd for processing.""" sim_vjps_orig = [] @@ -259,7 +262,7 @@ def run_async_emulated_bwd( def make_sim( permittivity: float, - size: Tuple[float, float, float], + size: tuple[float, float, float], vertices: tuple, base_eps_val: float, custom_medium: bool = True, @@ -298,12 +301,12 @@ def make_sim( # custom medium Nx, Ny, Nz = 10, 1, 10 (xmin, ymin, zmin), (xmax, ymax, zmax) = jax_box1.bounds - coords = dict( - x=np.linspace(xmin, xmax, Nx).tolist(), - y=np.linspace(ymin, ymax, Ny).tolist(), - z=np.linspace(zmin, zmax, Nz).tolist(), - f=(FREQ0,), - ) + coords = { + "x": np.linspace(xmin, xmax, Nx).tolist(), + "y": np.linspace(ymin, ymax, Ny).tolist(), + "z": np.linspace(zmin, zmax, Nz).tolist(), + "f": (FREQ0,), + } jax_box_custom = JaxBox(size=size, center=(1, 0, 2)) values = base_eps_val + np.random.random((Nx, Ny, Nz, 1)) @@ -628,10 +631,10 @@ def _test_adjoint_setup_adj(use_emulated_run, tmp_path): for mode_data in sim_data_vjp.output_data: new_values = 0 * np.array(mode_data.amps.values) new_values[0, 0, 0] = 1 + 1j - amps_vjp = mode_data.amps.copy(update=dict(values=new_values.tolist())) - mode_data_vjp = mode_data.copy(update=dict(amps=amps_vjp)) + amps_vjp = mode_data.amps.copy(update={"values": new_values.tolist()}) + mode_data_vjp = mode_data.copy(update={"amps": amps_vjp}) output_data_vjp.append(mode_data_vjp) - sim_data_vjp = sim_data_vjp.copy(update=dict(output_data=output_data_vjp)) + sim_data_vjp = sim_data_vjp.copy(update={"output_data": output_data_vjp}) (sim_vjp,) = run.bwd( task_name="test", folder_name="default", @@ -796,7 +799,7 @@ def test_jax_data_array(): b = [2, 3] c = [4] values = np.random.random((len(a), len(b), len(c))) - coords = dict(a=a, b=b, c=c) + coords = {"a": a, "b": b, "c": c} # validate missing coord # with pytest.raises(AdjointError): @@ -850,7 +853,7 @@ def test_jax_data_array(): with pytest.raises(Tidy3dKeyError): da.interp(d=3) - da1d = JaxDataArray(values=[0.0, 1.0, 2.0, 3.0], coords=dict(x=[0, 1, 2, 3])) + da1d = JaxDataArray(values=[0.0, 1.0, 2.0, 3.0], coords={"x": [0, 1, 2, 3]}) assert np.isclose(da1d.interp(x=0.5), 0.5) # duplicate coordinates @@ -864,7 +867,7 @@ def test_jax_data_array(): c = [4, 6] shape = (len(a), len(b), len(c)) values = np.random.random(shape) - coords = dict(a=a, b=b, c=c) + coords = {"a": a, "b": b, "c": c} da = JaxDataArray(values=values, coords=coords) da2 = da.sel(b=[3, 4]) assert da2.shape == (3, 2, 2) @@ -877,7 +880,7 @@ def test_jax_data_array(): n = 11 cs = list(range(n)) vals = np.random.uniform(0, 1, (n, n, 1, 1, 2)) - coords = dict(x=cs, y=cs, z=[0], f=[0], direction=["+", "-"]) + coords = {"x": cs, "y": cs, "z": [0], "f": [0], "direction": ["+", "-"]} jda = JaxDataArray(values=vals, coords=coords) xda = DataArray(data=vals, coords=coords) @@ -1305,7 +1308,7 @@ def test_diff_data_angles(axis): values = (1 + 1j) * np.random.random((len(ORDERS_X), len(ORDERS_Y), len(FS))) sim_size = [SIZE_2D, SIZE_2D] bloch_vecs = [0, 0] - data = JaxDataArray(values=values, coords=dict(orders_x=ORDERS_X, orders_y=ORDERS_Y, f=FS)) + data = JaxDataArray(values=values, coords={"orders_x": ORDERS_X, "orders_y": ORDERS_Y, "f": FS}) diff_data = JaxDiffractionData( monitor=DIFFRACTION_MONITOR, @@ -1339,7 +1342,7 @@ def test_value_filter(): """Ensure value filter works as expected.""" values = np.array([1, 0.5 * VALUE_FILTER_THRESHOLD, 2 * VALUE_FILTER_THRESHOLD, 0]) - coords = dict(x=list(range(4))) + coords = {"x": list(range(4))} data = JaxDataArray(values=values, coords=coords) values_after, _ = data.nonzero_val_coords @@ -1489,12 +1492,12 @@ def _test_custom_medium_3D(use_emulated_run): def make_custom_medium(Nx: int, Ny: int, Nz: int) -> JaxCustomMedium: # custom medium (xmin, ymin, zmin), (xmax, ymax, zmax) = jax_box.bounds - coords = dict( - x=np.linspace(xmin, xmax, Nx).tolist(), - y=np.linspace(ymin, ymax, Ny).tolist(), - z=np.linspace(zmin, zmax, Nz).tolist(), - f=[FREQ0], - ) + coords = { + "x": np.linspace(xmin, xmax, Nx).tolist(), + "y": np.linspace(ymin, ymax, Ny).tolist(), + "z": np.linspace(zmin, zmax, Nz).tolist(), + "f": [FREQ0], + } values = np.random.random((Nx, Ny, Nz, 1)) eps_ii = JaxDataArray(values=values, coords=coords) @@ -1524,12 +1527,12 @@ def make_custom_medium(num_cells: int) -> JaxCustomMedium: # custom medium (xmin, ymin, zmin), (xmax, ymax, zmax) = jax_box.bounds - coords = dict( - x=np.linspace(xmin, xmax, Nx).tolist(), - y=np.linspace(ymin, ymax, Ny).tolist(), - z=np.linspace(zmin, zmax, Nz).tolist(), - f=[FREQ0], - ) + coords = { + "x": np.linspace(xmin, xmax, Nx).tolist(), + "y": np.linspace(ymin, ymax, Ny).tolist(), + "z": np.linspace(zmin, zmax, Nz).tolist(), + "f": [FREQ0], + } values = np.random.random((Nx, Ny, Nz, 1)) eps_ii = JaxDataArray(values=values, coords=coords) @@ -1559,12 +1562,12 @@ def make_custom_medium(num_cells: int) -> JaxCustomMedium: # custom medium (xmin, ymin, zmin), (xmax, ymax, zmax) = jax_box.bounds - coords = dict( - x=np.linspace(xmin, xmax, Nx).tolist(), - y=np.linspace(ymin, ymax, Ny).tolist(), - z=np.linspace(zmin, zmax, Nz).tolist(), - f=[FREQ0], - ) + coords = { + "x": np.linspace(xmin, xmax, Nx).tolist(), + "y": np.linspace(ymin, ymax, Ny).tolist(), + "z": np.linspace(zmin, zmax, Nz).tolist(), + "f": [FREQ0], + } values = np.random.random((Nx, Ny, Nz, 1)) + 1.0 eps_ii = JaxDataArray(values=values, coords=coords) @@ -1894,7 +1897,7 @@ def hide_jax(monkeypatch, request): def mocked_import(name, *args, **kwargs): if name in ["jax", "jax.interpreters.ad", "jax.interpreters.ad.JVPTracer"]: - raise ImportError() + raise ImportError return import_orig(name, *args, **kwargs) monkeypatch.setattr(builtins, "__import__", mocked_import) @@ -1951,11 +1954,11 @@ def test_package_flux(): """Test handling of packaging flux data for single and multi-freq.""" value = 1.0 - da_single = JaxDataArray(values=[value], coords=dict(f=[1.0])) + da_single = JaxDataArray(values=[value], coords={"f": [1.0]}) res_single = JaxFieldData.package_flux_results(None, da_single) assert res_single == value - da_multi = JaxDataArray(values=[1.0, 2.0], coords=dict(f=[1.0, 2.0])) + da_multi = JaxDataArray(values=[1.0, 2.0], coords={"f": [1.0, 2.0]}) res_multi = JaxFieldData.package_flux_results(None, da_multi) assert res_multi == da_multi @@ -2073,13 +2076,13 @@ def sidewall_angle(self, sidewall_angle_deg): return np.deg2rad(sidewall_angle_deg) def test_matches_complexpolyslab(self, vertices, sidewall_angle, dilation): - kwargs = dict( - vertices=vertices, - sidewall_angle=sidewall_angle, - slab_bounds=self.slab_bounds, - dilation=dilation, - axis=POLYSLAB_AXIS, - ) + kwargs = { + "vertices": vertices, + "sidewall_angle": sidewall_angle, + "slab_bounds": self.slab_bounds, + "dilation": dilation, + "axis": POLYSLAB_AXIS, + } cp = ComplexPolySlab(**kwargs) jcp = JaxComplexPolySlab(**kwargs) diff --git a/tests/test_plugins/test_array_factor.py b/tests/test_plugins/test_array_factor.py index fab3ac6d0c..0ca543ea98 100644 --- a/tests/test_plugins/test_array_factor.py +++ b/tests/test_plugins/test_array_factor.py @@ -1,8 +1,11 @@ """Test the array factor functions.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pydantic import pytest + import tidy3d as td import tidy3d.plugins.microwave as mw import tidy3d.plugins.smatrix as smatrix @@ -462,7 +465,7 @@ def test_rectangular_array_calculator_array_make_antenna_array(): ) sim_unit_with_sphere = sim_unit.updated_copy( - structures=[background_sphere] + list(sim_unit.structures) + structures=[background_sphere, *list(sim_unit.structures)] ) # check correctness of the antenna bounds detection antenna_bounds_with_sphere = array_calculator._detect_antenna_bounds(sim_unit_with_sphere) @@ -540,12 +543,12 @@ def test_rectangular_array_calculator_monitor_data_from_array_factor(): far_field_approx=False, ) - coords = dict( - r=[monitor.proj_distance], - theta=list(monitor.theta), - phi=list(monitor.phi), - f=list(monitor.freqs), - ) + coords = { + "r": [monitor.proj_distance], + "theta": list(monitor.theta), + "phi": list(monitor.phi), + "f": list(monitor.freqs), + } values = (1 + 1j) * np.ones( (len(coords["r"]), len(coords["theta"]), len(coords["phi"]), len(coords["f"])) ) @@ -628,12 +631,12 @@ def test_rectangular_array_calculator_monitor_data_from_array_factor(): theta=list(np.linspace(0, np.pi, 10)), far_field_approx=False, ) - coords_under_sampled = dict( - r=[monitor_directivity_under_sampled.proj_distance], - theta=list(monitor_directivity_under_sampled.theta), - phi=list(monitor_directivity_under_sampled.phi), - f=list(monitor_directivity_under_sampled.freqs), - ) + coords_under_sampled = { + "r": [monitor_directivity_under_sampled.proj_distance], + "theta": list(monitor_directivity_under_sampled.theta), + "phi": list(monitor_directivity_under_sampled.phi), + "f": list(monitor_directivity_under_sampled.freqs), + } values_under_sampled = (1 + 1j) * np.random.random( ( len(coords_under_sampled["r"]), @@ -678,12 +681,12 @@ def test_rectangular_array_calculator_simulation_data_from_array_factor(): monitor = sim_unit.monitors[0] monitor_directivity = sim_unit.monitors[2] - coords = dict( - r=[monitor.proj_distance], - theta=list(monitor.theta), - phi=list(monitor.phi), - f=list(monitor.freqs), - ) + coords = { + "r": [monitor.proj_distance], + "theta": list(monitor.theta), + "phi": list(monitor.phi), + "f": list(monitor.freqs), + } values = (1 + 1j) * np.ones( (len(coords["r"]), len(coords["theta"]), len(coords["phi"]), len(coords["f"])) ) diff --git a/tests/test_plugins/test_design.py b/tests/test_plugins/test_design.py index 3e7083cc3b..b35ed5bb84 100644 --- a/tests/test_plugins/test_design.py +++ b/tests/test_plugins/test_design.py @@ -1,21 +1,25 @@ """Test the parameter sweep plugin.""" +from __future__ import annotations + import sys +from typing import Optional import matplotlib.pyplot as plt import numpy as np import pytest + import tidy3d as td import tidy3d.web as web from tidy3d.plugins import design as tdd from ..utils import run_emulated -SWEEP_METHODS = dict( - grid=tdd.MethodGrid(), - monte_carlo=tdd.MethodMonteCarlo(num_points=5, seed=1), - bay_opt=tdd.MethodBayOpt(initial_iter=5, n_iter=2, seed=1), - gen_alg=tdd.MethodGenAlg( +SWEEP_METHODS = { + "grid": tdd.MethodGrid(), + "monte_carlo": tdd.MethodMonteCarlo(num_points=5, seed=1), + "bay_opt": tdd.MethodBayOpt(initial_iter=5, n_iter=2, seed=1), + "gen_alg": tdd.MethodGenAlg( solutions_per_pop=6, n_generations=2, n_parents_mating=4, @@ -23,8 +27,8 @@ mutation_prob=0, keep_parents=0, ), - part_swarm=tdd.MethodParticleSwarm(n_particles=3, n_iter=2, seed=1), -) + "part_swarm": tdd.MethodParticleSwarm(n_particles=3, n_iter=2, seed=1), +} # Task names that should be produced for the different methods expected_task_names = { @@ -36,7 +40,7 @@ } -def emulated_batch_run(simulations, path_dir: str = None, **kwargs): +def emulated_batch_run(simulations, path_dir: Optional[str] = None, **kwargs): data_dict = {task_name: run_emulated(sim) for task_name, sim in simulations.simulations.items()} task_ids = dict(zip(simulations.simulations.keys(), data_dict.keys())) task_paths = {key: f"/path/to/{key}" for key in simulations.simulations.keys()} @@ -800,7 +804,7 @@ def test_result_accessor_and_len(dims, coords, values, multi_val): result = tdd.Result(dims=dims, values=values, coords=coords) - for idx in range(0, len(values)): + for idx in range(len(values)): coord, value = result[idx] assert all(coord == coords[idx]) diff --git a/tests/test_plugins/test_dispersion_fitter.py b/tests/test_plugins/test_dispersion_fitter.py index 0330637bc0..5debd94d51 100644 --- a/tests/test_plugins/test_dispersion_fitter.py +++ b/tests/test_plugins/test_dispersion_fitter.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import matplotlib.pyplot as plt import numpy as np import pydantic.v1 as pydantic import pytest import responses + import tidy3d as td from tidy3d.exceptions import SetupError, ValidationError from tidy3d.plugins.dispersion import ( diff --git a/tests/test_plugins/test_invdes.py b/tests/test_plugins/test_invdes.py index f7c2cc02e0..0cb2a399fb 100644 --- a/tests/test_plugins/test_invdes.py +++ b/tests/test_plugins/test_invdes.py @@ -1,9 +1,11 @@ # Test the inverse design plugin +from __future__ import annotations import autograd.numpy as anp import numpy as np import numpy.testing as npt import pytest + import tidy3d as td import tidy3d.plugins.invdes as tdi from tidy3d.plugins.expressions import ModeAmp, ModePower @@ -361,9 +363,9 @@ def test_continue_run_fns(use_emulated_run): # noqa: F811 num_steps_orig = len(result_orig.history["params"]) num_steps_full = len(result_full.history["params"]) - assert ( - num_steps_full == num_steps_orig + num_steps_continue - ), "wrong number of elements in the combined run history." + assert num_steps_full == num_steps_orig + num_steps_continue, ( + "wrong number of elements in the combined run history." + ) def test_continue_run_from_file(use_emulated_run): # noqa: F811 @@ -377,17 +379,17 @@ def test_continue_run_from_file(use_emulated_run): # noqa: F811 ) num_steps_orig = len(result_orig.history["params"]) num_steps_new = len(result_full.history["params"]) - assert ( - num_steps_new == num_steps_orig + num_steps_continue - ), "wrong number of elements in the combined run history." + assert num_steps_new == num_steps_orig + num_steps_continue, ( + "wrong number of elements in the combined run history." + ) # test the convenience function to load it from file result_full = optimizer.continue_run_from_history(num_steps=2, post_process_fn=post_process_fn) num_steps_orig = num_steps_new num_steps_new = len(result_full.history["params"]) - assert ( - num_steps_new == num_steps_orig + num_steps_continue - ), "wrong number of elements in the combined run history." + assert num_steps_new == num_steps_orig + num_steps_continue, ( + "wrong number of elements in the combined run history." + ) def test_result( @@ -419,7 +421,7 @@ def test_result_data(use_emulated_run, use_emulated_to_sim_data): # noqa: F811 def test_result_data_multi( - use_emulated_to_sim_data, # noqa: F811 + use_emulated_to_sim_data, use_emulated_run, # noqa: F811 tmp_path, ): diff --git a/tests/test_plugins/test_microwave.py b/tests/test_plugins/test_microwave.py index bed13e9698..0cea9b17f0 100644 --- a/tests/test_plugins/test_microwave.py +++ b/tests/test_plugins/test_microwave.py @@ -1,15 +1,18 @@ """Test the microwave plugin.""" +from __future__ import annotations + from math import isclose import matplotlib.pyplot as plt import numpy as np import pydantic.v1 as pd import pytest -import tidy3d as td -import tidy3d.plugins.microwave as mw from skrf import Frequency from skrf.media import MLine + +import tidy3d as td +import tidy3d.plugins.microwave as mw from tidy3d import FieldData from tidy3d.constants import ETA_0 from tidy3d.exceptions import DataError @@ -96,7 +99,7 @@ def make_stripline_scalar_field_data_array(grid_key: str): values = np.where(above_and_within, -ones / ETA_0, values) values = np.where(below_and_within, ones / ETA_0, values) - return td.ScalarFieldDataArray(values, coords=dict(x=XS, y=YS, z=ZS, f=FS)) + return td.ScalarFieldDataArray(values, coords={"x": XS, "y": YS, "z": ZS, "f": FS}) def make_coaxial_field_data_array(grid_key: str): @@ -141,7 +144,7 @@ def compute_coax_radial_electric(rin, rout, x, y, is_x): else: field /= ETA_0 - return td.ScalarFieldDataArray(field, coords=dict(x=XS, y=YS, z=ZS, f=FS)) + return td.ScalarFieldDataArray(field, coords={"x": XS, "y": YS, "z": ZS, "f": FS}) def make_field_data(): diff --git a/tests/test_plugins/test_mode_solver.py b/tests/test_plugins/test_mode_solver.py index 911916d4b3..0166a2dc47 100644 --- a/tests/test_plugins/test_mode_solver.py +++ b/tests/test_plugins/test_mode_solver.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import matplotlib.pyplot as plt import numpy as np import pydantic.v1 as pydantic import pytest import responses + import tidy3d as td import tidy3d.plugins.mode.web as msweb from tidy3d import ScalarFieldDataArray @@ -457,7 +460,9 @@ def test_mode_solver_custom_medium(mock_remote_api, local, tmp_path): freq0 = td.C_0 / 1.0 n = np.array([1.5, 5]) n = n[:, None, None, None] - n_data = ScalarFieldDataArray(n, coords=dict(x=x_custom, y=y_custom, z=z_custom, f=[freq0])) + n_data = ScalarFieldDataArray( + n, coords={"x": x_custom, "y": y_custom, "z": z_custom, "f": [freq0]} + ) mat_custom = td.CustomMedium.from_nk(n_data, interp_method="nearest") waveguide = td.Structure(geometry=td.Box(size=(100, 0.5, 0.5)), medium=mat_custom) @@ -520,7 +525,7 @@ def test_mode_solver_unstructured_custom_medium(nx, cond_factor, interp, tol, tm n = 2.5 + (x_custom[:, None, None] + 0.6) / 1.2 * np.sin(y_custom[None, :, None]) * np.cos( z_custom[None, None, :] ) - n_data = td.SpatialDataArray(n, coords=dict(x=x_custom, y=y_custom, z=z_custom)) + n_data = td.SpatialDataArray(n, coords={"x": x_custom, "y": y_custom, "z": z_custom}) # unperturbed unstructured grid n_data_u = cartesian_to_unstructured(n_data, pert=0, seed=987, method="direct") diff --git a/tests/test_plugins/test_polyslab.py b/tests/test_plugins/test_polyslab.py index f62ce47735..267020d36b 100644 --- a/tests/test_plugins/test_polyslab.py +++ b/tests/test_plugins/test_polyslab.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import gdstk import numpy as np + import tidy3d as td from tidy3d.plugins.polyslab import ComplexPolySlab @@ -37,7 +40,7 @@ def test_many_sub_polyslabs(): num_subpoly = 200 dl_list = np.linspace(0, 0.1, num_subpoly) vertices = [(sum(dl_list[: i + 1]), 0) for i in range(num_subpoly)] - vertices = vertices + [(5, 20)] + vertices = [*vertices, (5, 20)] s = ComplexPolySlab( vertices=vertices, diff --git a/tests/test_plugins/test_resonance_finder.py b/tests/test_plugins/test_resonance_finder.py index 3a6f22a87a..2fabe2d3b4 100644 --- a/tests/test_plugins/test_resonance_finder.py +++ b/tests/test_plugins/test_resonance_finder.py @@ -1,6 +1,9 @@ +from __future__ import annotations + import numpy as np import pytest from numpy.random import default_rng + from tidy3d import FieldTimeData, FieldTimeMonitor, ScalarFieldTimeDataArray from tidy3d.plugins.resonance import ResonanceFinder @@ -95,7 +98,7 @@ def test_scalar_field_time(): t = np.arange(NTIME) / time_step signal = generate_signal(freqs, decays, amplitudes, phases, time_step) - coords = dict(x=[0], y=[0], z=[0], t=t) + coords = {"x": [0], "y": [0], "z": [0], "t": t} fd = ScalarFieldTimeDataArray(np.reshape(signal, (1, 1, 1, len(signal))), coords=coords) resonance_finder = ResonanceFinder(freq_window=(0.2, 0.5), init_num_freqs=100) resonances = resonance_finder.run_scalar_field_time(fd) @@ -112,7 +115,7 @@ def test_field_time_single(): t = np.arange(NTIME) / time_step signal = generate_signal(freqs, decays, amplitudes, phases, time_step) - coords = dict(x=[0], y=[0], z=[0], t=t) + coords = {"x": [0], "y": [0], "z": [0], "t": t} fd = ScalarFieldTimeDataArray(np.reshape(signal, (1, 1, 1, len(signal))), coords=coords) fd2 = ScalarFieldTimeDataArray(np.reshape(signal * 2, (1, 1, 1, len(signal))), coords=coords) monitor = FieldTimeMonitor(size=(0, 0, 0), interval=1, name="field", fields=["Hx", "Hy"]) @@ -133,7 +136,7 @@ def test_field_time_mult(): t = np.arange(NTIME) / time_step signal = generate_signal(freqs, decays, amplitudes, phases, time_step) - coords = dict(x=[0], y=[0], z=[0], t=t) + coords = {"x": [0], "y": [0], "z": [0], "t": t} fd = ScalarFieldTimeDataArray(np.reshape(signal, (1, 1, 1, len(signal))), coords=coords) fd2 = ScalarFieldTimeDataArray(np.reshape(signal * 2, (1, 1, 1, len(signal))), coords=coords) monitor = FieldTimeMonitor(size=(0, 0, 0), interval=1, name="field", fields=["Hx", "Hy"]) @@ -155,7 +158,7 @@ def test_field_time_e_and_m(): t = np.arange(NTIME) / time_step signal = generate_signal(freqs, decays, amplitudes, phases, time_step) - coords = dict(x=[0], y=[0], z=[0], t=t) + coords = {"x": [0], "y": [0], "z": [0], "t": t} fd = ScalarFieldTimeDataArray(np.reshape(signal, (1, 1, 1, len(signal))), coords=coords) fd2 = ScalarFieldTimeDataArray(np.reshape(signal * 2, (1, 1, 1, len(signal))), coords=coords) monitor = FieldTimeMonitor(size=(0, 0, 0), interval=1, name="field", fields=["Ex", "Hy"]) @@ -177,7 +180,7 @@ def test_field_time_use_e_only(): t = np.arange(NTIME) / time_step signal = generate_signal(freqs, decays, amplitudes, phases, time_step) - coords = dict(x=[0], y=[0], z=[0], t=t) + coords = {"x": [0], "y": [0], "z": [0], "t": t} fd = ScalarFieldTimeDataArray(np.reshape(signal, (1, 1, 1, len(signal))), coords=coords) fd2 = ScalarFieldTimeDataArray(np.reshape(signal * 2, (1, 1, 1, len(signal))), coords=coords) monitor = FieldTimeMonitor(size=(0, 0, 0), interval=1, name="field", fields=["Hy"]) diff --git a/tests/test_plugins/test_waveguide.py b/tests/test_plugins/test_waveguide.py index 3fcf37ff94..1c84807241 100644 --- a/tests/test_plugins/test_waveguide.py +++ b/tests/test_plugins/test_waveguide.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import numpy as np import pytest -import tidy3d as td from pydantic.v1 import ValidationError + +import tidy3d as td from tidy3d.plugins import waveguide diff --git a/tests/test_web/mock_web.py b/tests/test_web/mock_web.py index 956f00b117..1951088600 100644 --- a/tests/test_web/mock_web.py +++ b/tests/test_web/mock_web.py @@ -1,5 +1,8 @@ # custom class to be the mock return value # will override the requests.Response returned from requests.get +from __future__ import annotations + + class MockResponse: def __init__(self, code, json_data): self.status_code = code diff --git a/tests/test_web/test_cli.py b/tests/test_web/test_cli.py index 6ed6d16d2c..e7cbff4e3d 100644 --- a/tests/test_web/test_cli.py +++ b/tests/test_web/test_cli.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def test_tidy3d_cli(): pass # if os.path.exists(CONFIG_FILE): diff --git a/tests/test_web/test_env.py b/tests/test_web/test_env.py index 13152cd50b..62e8566b29 100644 --- a/tests/test_web/test_env.py +++ b/tests/test_web/test_env.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import ssl from tidy3d.web.core.environment import Env diff --git a/tests/test_web/test_material_fitter.py b/tests/test_web/test_material_fitter.py index 8f86c001d6..dd52fb3658 100644 --- a/tests/test_web/test_material_fitter.py +++ b/tests/test_web/test_material_fitter.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import pytest import responses + import tidy3d as td from tidy3d.plugins.dispersion import DispersionFitter from tidy3d.web.api.material_fitter import FitterOptions, MaterialFitterTask diff --git a/tests/test_web/test_task.py b/tests/test_web/test_task.py index cd2811c36f..dc5c051440 100644 --- a/tests/test_web/test_task.py +++ b/tests/test_web/test_task.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from tidy3d.web.core.task_info import RunInfo diff --git a/tests/test_web/test_tidy3d_folder.py b/tests/test_web/test_tidy3d_folder.py index 280854200b..1846ea8baa 100644 --- a/tests/test_web/test_tidy3d_folder.py +++ b/tests/test_web/test_tidy3d_folder.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import pytest import responses -import tidy3d as td from responses import matchers + +import tidy3d as td from tidy3d.web.core.environment import Env from tidy3d.web.core.task_core import Folder diff --git a/tests/test_web/test_tidy3d_material_library.py b/tests/test_web/test_tidy3d_material_library.py index b897d92962..25790b96dc 100644 --- a/tests/test_web/test_tidy3d_material_library.py +++ b/tests/test_web/test_tidy3d_material_library.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import pytest import responses + import tidy3d as td from tidy3d.web.api.material_libray import MaterialLibray from tidy3d.web.core.environment import Env diff --git a/tests/test_web/test_tidy3d_task.py b/tests/test_web/test_tidy3d_task.py index 6ae348be01..ba5b44dfdc 100644 --- a/tests/test_web/test_tidy3d_task.py +++ b/tests/test_web/test_tidy3d_task.py @@ -1,9 +1,12 @@ +from __future__ import annotations + import tempfile import pytest import responses -import tidy3d as td from responses import matchers + +import tidy3d as td from tidy3d.web.core import http_util from tidy3d.web.core.environment import Env, EnvironmentConfig from tidy3d.web.core.task_core import Folder, SimulationTask diff --git a/tests/test_web/test_webapi.py b/tests/test_web/test_webapi.py index bb81492716..442ef5d4f3 100644 --- a/tests/test_web/test_webapi.py +++ b/tests/test_web/test_webapi.py @@ -1,12 +1,13 @@ # Tests webapi and things that depend on it - +from __future__ import annotations import numpy as np import pytest import responses -import tidy3d as td from _pytest import monkeypatch from responses import matchers + +import tidy3d as td from tidy3d import Simulation from tidy3d.__main__ import main from tidy3d.components.data.data_array import ScalarFieldDataArray @@ -85,7 +86,7 @@ def make_sim_data(file_size_gb=FILE_SIZE_GB): src = PointDipole( center=(0, 0, 0), source_time=GaussianPulse(freq0=3e14, fwidth=1e14), polarization="Ex" ) - coords = dict(x=x, y=y, z=z, f=f) + coords = {"x": x, "y": y, "z": z, "f": f} Ex = ScalarFieldDataArray(data, coords=coords) monitor = FieldMonitor(size=(2, 2, 2), freqs=f, name="test", fields=["Ex"]) field_data = FieldData(monitor=monitor, Ex=Ex) diff --git a/tests/test_web/test_webapi_account.py b/tests/test_web/test_webapi_account.py index fb00d13e92..bb31a54010 100644 --- a/tests/test_web/test_webapi_account.py +++ b/tests/test_web/test_webapi_account.py @@ -1,7 +1,9 @@ # Tests webapi and things that depend on it +from __future__ import annotations import pytest import responses + import tidy3d as td from tidy3d.web.api.webapi import ( account, diff --git a/tests/test_web/test_webapi_eme.py b/tests/test_web/test_webapi_eme.py index e2053fb58c..47d0616382 100644 --- a/tests/test_web/test_webapi_eme.py +++ b/tests/test_web/test_webapi_eme.py @@ -1,10 +1,12 @@ # Tests webapi and things that depend on it +from __future__ import annotations import pytest import responses -import tidy3d as td from botocore.exceptions import ClientError from responses import matchers + +import tidy3d as td from tidy3d import EMESimulation from tidy3d.exceptions import SetupError from tidy3d.web.api.asynchronous import run_async diff --git a/tests/test_web/test_webapi_heat.py b/tests/test_web/test_webapi_heat.py index c7a1c2ebc6..fe3af8075f 100644 --- a/tests/test_web/test_webapi_heat.py +++ b/tests/test_web/test_webapi_heat.py @@ -1,10 +1,12 @@ # Tests webapi and things that depend on it +from __future__ import annotations import pytest import responses -import tidy3d as td from botocore.exceptions import ClientError from responses import matchers + +import tidy3d as td from tidy3d import HeatSimulation from tidy3d.web.api.asynchronous import run_async from tidy3d.web.api.container import Batch, Job diff --git a/tests/test_web/test_webapi_mode.py b/tests/test_web/test_webapi_mode.py index 432a3fd986..36355caa9f 100644 --- a/tests/test_web/test_webapi_mode.py +++ b/tests/test_web/test_webapi_mode.py @@ -1,11 +1,13 @@ # Tests webapi and things that depend on it +from __future__ import annotations import matplotlib.pyplot as plt import pytest import responses -import tidy3d as td from botocore.exceptions import ClientError from responses import matchers + +import tidy3d as td from tidy3d.components.data.dataset import ModeIndexDataArray from tidy3d.plugins.mode import ModeSolver from tidy3d.web.api.asynchronous import run_async diff --git a/tests/test_web/test_webapi_mode_sim.py b/tests/test_web/test_webapi_mode_sim.py index 381f409287..413b2b33f3 100644 --- a/tests/test_web/test_webapi_mode_sim.py +++ b/tests/test_web/test_webapi_mode_sim.py @@ -1,10 +1,12 @@ # Tests webapi and things that depend on it +from __future__ import annotations import pytest import responses -import tidy3d as td from botocore.exceptions import ClientError from responses import matchers + +import tidy3d as td from tidy3d.plugins.mode import ModeSolver from tidy3d.web.api.asynchronous import run_async from tidy3d.web.api.container import Batch, Job diff --git a/tests/utils.py b/tests/utils.py index 03bc23cfb4..a0dcca9046 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,14 +1,17 @@ +from __future__ import annotations + import dataclasses from pathlib import Path -from typing import Any, Dict, List, Tuple, Union +from typing import Any, Optional, Union import numpy as np import pydantic.v1 as pd -import tidy3d as td import trimesh import xarray as xr from autograd.core import VJPNode from autograd.tracer import new_box + +import tidy3d as td from tidy3d import ModeIndexDataArray from tidy3d.components.base import Tidy3dBaseModel from tidy3d.log import _get_level_int @@ -54,7 +57,7 @@ def cartesian_to_unstructured( array: td.SpatialDataArray, pert: float = 0.1, method: str = "linear", - seed: int = None, + seed: Optional[int] = None, same_bounds: bool = True, ) -> Union[td.TriangularGridDataset, td.TetrahedralGridDataset]: """Convert a SpatialDataArray into TriangularGridDataset/TetrahedralGridDataset with @@ -262,18 +265,18 @@ def make_spatial_data( data = lims[0] + (lims[1] - lims[0]) * rng.random(size) arr = td.SpatialDataArray( data, - coords=dict( - x=np.linspace(bounds[0][0], bounds[1][0], size[0]), - y=np.linspace(bounds[0][1], bounds[1][1], size[1]), - z=np.linspace(bounds[0][2], bounds[1][2], size[2]), - ), + coords={ + "x": np.linspace(bounds[0][0], bounds[1][0], size[0]), + "y": np.linspace(bounds[0][1], bounds[1][1], size[1]), + "z": np.linspace(bounds[0][2], bounds[1][2], size[2]), + }, ) if unstructured: return cartesian_to_unstructured(arr, pert=perturbation, method=method, seed=seed_grid) return arr -COORDS = dict(x=[-1.5, -0.5], y=[0, 1], z=[0, 1]) +COORDS = {"x": [-1.5, -0.5], "y": [0, 1], "z": [0, 1]} CUSTOM_SIZE = (2, 2, 2) CUSTOM_BOUNDS = [[-1.5, 0, 0], [-0.5, 1, 1]] CUSTOM_GRID_SEED = 12345 @@ -420,7 +423,7 @@ def make_custom_data(lims, unstructured): slab_bounds=(-0.1, 0.1), ), medium=td.CustomMedium( - permittivity=td.SpatialDataArray(tracer_arr, coords=dict(x=[-1], y=[0], z=[0])) + permittivity=td.SpatialDataArray(tracer_arr, coords={"x": [-1], "y": [0], "z": [0]}) ), name="traced custom polyslab", ), @@ -741,12 +744,12 @@ def make_custom_data(lims, unstructured): field_dataset=td.FieldDataset( Ex=td.ScalarFieldDataArray( np.ones((101, 101, 1, 1)), - coords=dict( - x=np.linspace(-1, 1, 101), - y=np.linspace(-1, 1, 101), - z=np.array([0]), - f=[2e14], - ), + coords={ + "x": np.linspace(-1, 1, 101), + "y": np.linspace(-1, 1, 101), + "z": np.array([0]), + "f": [2e14], + }, ) ), ), @@ -760,12 +763,12 @@ def make_custom_data(lims, unstructured): current_dataset=td.FieldDataset( Ex=td.ScalarFieldDataArray( np.ones((101, 101, 1, 1)), - coords=dict( - x=np.linspace(-1, 1, 101), - y=np.linspace(-1, 1, 101), - z=np.array([0]), - f=[2e14], - ), + coords={ + "x": np.linspace(-1, 1, 101), + "y": np.linspace(-1, 1, 101), + "z": np.array([0]), + "f": [2e14], + }, ) ), ), @@ -1033,10 +1036,10 @@ def make_diff_data(monitor: td.DiffractionMonitor) -> td.DiffractionData: f = list(monitor.freqs) orders_x = np.linspace(-1, 1, 3) orders_y = np.linspace(-2, 2, 5) - coords = dict(orders_x=orders_x, orders_y=orders_y, f=f) + coords = {"orders_x": orders_x, "orders_y": orders_y, "f": f} values = DATA_GEN_FN((len(orders_x), len(orders_y), len(f))) data = td.DiffractionDataArray(values, coords=coords) - field_data = {field: data for field in ("Er", "Etheta", "Ephi", "Hr", "Htheta", "Hphi")} + field_data = dict.fromkeys(("Er", "Etheta", "Ephi", "Hr", "Htheta", "Hphi"), data) return td.DiffractionData(monitor=monitor, sim_size=(1, 1), bloch_vecs=(0, 0), **field_data) def make_mode_data(monitor: td.ModeMonitor) -> td.ModeData: @@ -1048,7 +1051,7 @@ def make_mode_data(monitor: td.ModeMonitor) -> td.ModeData: n_complex = make_data( coords=index_coords, data_array_type=td.ModeIndexDataArray, is_complex=True ) - coords_amps = dict(direction=["+", "-"]) + coords_amps = {"direction": ["+", "-"]} coords_amps.update(index_coords) amps = make_data(coords=coords_amps, data_array_type=td.ModeAmpsDataArray, is_complex=True) field_cmps = {} @@ -1071,7 +1074,7 @@ def make_mode_data(monitor: td.ModeMonitor) -> td.ModeData: def make_flux_data(monitor: td.FluxMonitor) -> td.FluxData: """make a random ModeData from a ModeMonitor.""" - coords = dict(f=list(monitor.freqs)) + coords = {"f": list(monitor.freqs)} flux = make_data(coords=coords, data_array_type=td.FluxDataArray, is_complex=False) return td.FluxData(monitor=monitor, flux=flux) @@ -1082,9 +1085,9 @@ def make_directivity_data(monitor: td.DirectivityMonitor) -> td.DirectivityData: r = np.atleast_1d(monitor.proj_distance) theta = list(monitor.theta) phi = list(monitor.phi) - fluxcoords = dict(f=f) + fluxcoords = {"f": f} fluxdata = make_data(coords=fluxcoords, data_array_type=td.FluxDataArray, is_complex=False) - coords = dict(r=r, theta=theta, phi=phi, f=f) + coords = {"r": r, "theta": theta, "phi": phi, "f": f} scalar_field = make_data( coords=coords, data_array_type=td.FieldProjectionAngleDataArray, is_complex=True ) @@ -1109,7 +1112,7 @@ def make_field_projection_angle_data( theta = list(monitor.theta) phi = list(monitor.phi) - coords = dict(r=r, theta=theta, phi=phi, f=f) + coords = {"r": r, "theta": theta, "phi": phi, "f": f} scalar_field = make_data( coords=coords, data_array_type=td.FieldProjectionAngleDataArray, @@ -1141,26 +1144,26 @@ def make_field_projection_cartesian_data( # map the two planes to global (x, y, z) depending on the normal axis if monitor.proj_axis == 0: # (y, z) - coords = dict( - x=np.atleast_1d(proj_distance), - y=x_plane, - z=y_plane, - f=f, - ) + coords = { + "x": np.atleast_1d(proj_distance), + "y": x_plane, + "z": y_plane, + "f": f, + } elif monitor.proj_axis == 1: # (x, z) - coords = dict( - x=x_plane, - y=np.atleast_1d(proj_distance), - z=y_plane, - f=f, - ) + coords = { + "x": x_plane, + "y": np.atleast_1d(proj_distance), + "z": y_plane, + "f": f, + } else: # (x, y) - coords = dict( - x=x_plane, - y=y_plane, - z=np.atleast_1d(proj_distance), - f=f, - ) + coords = { + "x": x_plane, + "y": y_plane, + "z": np.atleast_1d(proj_distance), + "f": f, + } scalar_field = make_data( coords=coords, @@ -1188,7 +1191,7 @@ def make_field_projection_kspace_data( ux = list(monitor.ux) uy = list(monitor.uy) - coords = dict(ux=ux, uy=uy, r=r, f=f) + coords = {"ux": ux, "uy": uy, "r": r, "f": f} scalar_field = make_data( coords=coords, data_array_type=td.FieldProjectionKSpaceDataArray, @@ -1232,17 +1235,17 @@ def make_field_projection_kspace_data( class BatchDataTest(Tidy3dBaseModel): """Holds a collection of :class:`.SimulationData` returned by :class:`.Batch`.""" - task_paths: Dict[str, str] = pd.Field( + task_paths: dict[str, str] = pd.Field( ..., title="Data Paths", description="Mapping of task_name to path to corresponding data for each task in batch.", ) - task_ids: Dict[str, str] = pd.Field( + task_ids: dict[str, str] = pd.Field( ..., title="Task IDs", description="Mapping of task_name to task_id for each task in batch." ) - sim_data: Dict[str, td.SimulationData] + sim_data: dict[str, td.SimulationData] def load_sim_data(self, task_name: str) -> td.SimulationData: """Load a :class:`.SimulationData` from file by task name.""" @@ -1250,7 +1253,7 @@ def load_sim_data(self, task_name: str) -> td.SimulationData: _ = self.task_ids[task_name] return self.sim_data[task_name] - def items(self) -> Tuple[str, td.SimulationData]: + def items(self) -> tuple[str, td.SimulationData]: """Iterate through the :class:`.SimulationData` for each task_name.""" for task_name in self.task_paths.keys(): yield task_name, self.load_sim_data(task_name) @@ -1260,17 +1263,17 @@ def __getitem__(self, task_name: str) -> td.SimulationData: return self.load_sim_data(task_name) -def run_async_emulated(simulations: Dict[str, td.Simulation], **kwargs) -> BatchData: +def run_async_emulated(simulations: dict[str, td.Simulation], **kwargs) -> BatchData: """Emulate an async run function.""" task_ids = {task_name: f"task_id={i}" for i, task_name in enumerate(simulations.keys())} - task_paths = {task_name: "NONE" for task_name in simulations.keys()} + task_paths = dict.fromkeys(simulations.keys(), "NONE") sim_data = {task_name: run_emulated(sim) for task_name, sim in simulations.items()} return BatchDataTest(task_paths=task_paths, task_ids=task_ids, sim_data=sim_data) def assert_log_level( - records: List[Tuple[int, str]], log_level_expected: str, contains_str: str = None + records: list[tuple[int, str]], log_level_expected: str, contains_str: Optional[str] = None ) -> None: """Testing tool: Raises error if a log was not recorded as expected. diff --git a/tidy3d/__init__.py b/tidy3d/__init__.py index e0c51742d8..6bc0bd31ab 100644 --- a/tidy3d/__init__.py +++ b/tidy3d/__init__.py @@ -1,5 +1,7 @@ """Tidy3d package imports""" +from __future__ import annotations + from tidy3d.components.material.multi_physics import MultiPhysicsMedium from tidy3d.components.material.tcad.charge import ( ChargeConductorMedium, @@ -398,300 +400,300 @@ def set_logging_level(level: str) -> None: GeometryGroup.update_forward_refs() __all__ = [ - "Grid", - "Coords", - "GridSpec", - "UniformGrid", - "QuasiUniformGrid", - "CustomGrid", + "C_0", + "DATA_TYPE_MAP", + "EPSILON_0", + "ETA_0", + "HBAR", + "K_B", + "MU_0", + "PEC", + "PEC2D", + "PML", + "TFSF", + "Absorber", + "AbsorberParams", + "AbstractFieldProjectionData", + "AbstractMedium", + "AdmittanceNetwork", + "AnisotropicMedium", + "AntennaMetricsData", + "ApodizationSpec", + "AstigmaticGaussianBeam", + "AstigmaticGaussianBeamProfile", + "AugerRecombination", "AutoGrid", - "CustomGridBoundaries", - "LayerRefinementSpec", - "GridRefinement", - "CornerFinderSpec", + "AuxFieldTimeData", + "AuxFieldTimeMonitor", + "BlochBoundary", + "Boundary", + "BoundaryEdge", + "BoundaryEdgeType", + "BoundarySpec", "Box", - "Sphere", - "Cylinder", - "PolySlab", - "GeometryGroup", + "CaugheyThomasMobility", + "CellDataArray", + "ChargeConductorMedium", + "ChargeDataArray", + "ChargeInsulatorMedium", + "ChargeToleranceSpec", "ClipOperation", - "Transformed", - "TriangleMesh", - "Medium", - "PoleResidue", - "AnisotropicMedium", - "PEC", - "PECMedium", - "Medium2D", - "PEC2D", - "Sellmeier", - "Debye", - "Drude", - "Lorentz", + "CoaxialLumpedResistor", + "ConstantDoping", + "ConstantMobilityModel", + "ContinuousWave", + "ContinuousWaveTimeModulation", + "ContourPathAveraging", + "ConvectionBC", + "Coords", + "Coords1D", + "CornerFinderSpec", + "CurrentBC", + "CustomAnisotropicMedium", + "CustomChargePerturbation", + "CustomCurrentSource", + "CustomDebye", + "CustomDrude", + "CustomFieldSource", + "CustomGrid", + "CustomGridBoundaries", + "CustomHeatPerturbation", + "CustomLorentz", "CustomMedium", "CustomPoleResidue", "CustomSellmeier", - "FullyAnisotropicMedium", - "CustomLorentz", - "CustomDrude", - "CustomDebye", - "CustomAnisotropicMedium", - "LossyMetalMedium", - "SurfaceImpedanceFitterParam", - "HammerstadSurfaceRoughness", - "HuraySurfaceRoughness", - "RotationAroundAxis", - "PerturbationMedium", - "PerturbationPoleResidue", - "NedeljkovicSorefMashanovich", - "ParameterPerturbation", - "LinearHeatPerturbation", - "CustomHeatPerturbation", - "LinearChargePerturbation", - "CustomChargePerturbation", - "PermittivityPerturbation", - "IndexPerturbation", - "NonlinearSpec", - "NonlinearModel", - "NonlinearSusceptibility", - "TwoPhotonAbsorption", - "KerrNonlinearity", - "Structure", - "MeshOverrideStructure", - "ModeSpec", - "ApodizationSpec", - "GaussianPulse", - "ContinuousWave", "CustomSourceTime", - "UniformCurrentSource", - "PlaneWave", - "ModeSource", - "PointDipole", - "GaussianBeam", - "AstigmaticGaussianBeam", - "CustomFieldSource", - "TFSF", - "CustomCurrentSource", - "GaussianBeamProfile", - "AstigmaticGaussianBeamProfile", - "PlaneWaveBeamProfile", + "Cylinder", + "DCCurrentSource", + "DCVoltageSource", + "Debye", + "DefaultAbsorberParameters", + "DefaultPMLParameters", + "DefaultStablePMLParameters", + "DeviceCharacteristics", + "DiffractionData", + "DiffractionDataArray", + "DiffractionMonitor", + "DirectivityData", + "DirectivityMonitor", + "DistanceUnstructuredGrid", + "Drude", + "EMECoefficientData", + "EMECoefficientDataArray", + "EMECoefficientDataset", + "EMECoefficientMonitor", + "EMECompositeGrid", + "EMEExplicitGrid", + "EMEFieldData", + "EMEFieldDataset", + "EMEFieldMonitor", + "EMEFreqSweep", + "EMEGrid", + "EMELengthSweep", + "EMEModeIndexDataArray", + "EMEModeSolverData", + "EMEModeSolverDataset", + "EMEModeSolverMonitor", + "EMEModeSpec", + "EMEModeSweep", + "EMEMonitor", + "EMEPeriodicitySweep", + "EMESMatrixDataArray", + "EMESMatrixDataset", + "EMEScalarFieldDataArray", + "EMEScalarModeFieldDataArray", + "EMESimulation", + "EMESimulationData", + "EMESweepSpec", + "EMEUniformGrid", + "FieldData", + "FieldDataset", + "FieldGrid", "FieldMonitor", - "FieldTimeMonitor", - "AuxFieldTimeMonitor", - "FluxMonitor", - "FluxTimeMonitor", - "ModeMonitor", - "ModeSolverMonitor", - "PermittivityMonitor", + "FieldProjectionAngleData", + "FieldProjectionAngleDataArray", "FieldProjectionAngleMonitor", + "FieldProjectionCartesianData", + "FieldProjectionCartesianDataArray", "FieldProjectionCartesianMonitor", + "FieldProjectionKSpaceData", + "FieldProjectionKSpaceDataArray", "FieldProjectionKSpaceMonitor", "FieldProjectionSurface", - "DiffractionMonitor", - "DirectivityMonitor", - "RunTimeSpec", - "Simulation", "FieldProjector", - "ScalarFieldDataArray", - "ScalarModeFieldDataArray", - "ScalarModeFieldCylindricalDataArray", - "ScalarFieldTimeDataArray", - "SpatialDataArray", - "SpatialVoltageDataArray", - "ModeAmpsDataArray", - "ModeIndexDataArray", - "FluxDataArray", - "FluxTimeDataArray", - "FieldProjectionAngleDataArray", - "FieldProjectionCartesianDataArray", - "FieldProjectionKSpaceDataArray", - "DiffractionDataArray", - "HeatDataArray", - "ChargeDataArray", - "FieldDataset", - "FieldTimeDataset", - "PermittivityDataset", - "ModeSolverDataset", - "FieldData", "FieldTimeData", - "AuxFieldTimeData", - "PermittivityData", + "FieldTimeDataset", + "FieldTimeMonitor", + "FixedAngleSpec", + "FixedInPlaneKSpec", + "FluidMedium", + "FluidSpec", "FluxData", + "FluxDataArray", + "FluxMonitor", "FluxTimeData", - "ModeData", - "ModeSolverData", - "AbstractFieldProjectionData", - "FieldProjectionAngleData", - "FieldProjectionCartesianData", - "FieldProjectionKSpaceData", - "DiffractionData", - "DirectivityData", - "SimulationData", - "DATA_TYPE_MAP", - "BoundarySpec", - "Boundary", - "BoundaryEdge", - "BoundaryEdgeType", - "BlochBoundary", - "Periodic", - "PECBoundary", - "PMCBoundary", - "PML", - "StablePML", - "Absorber", - "PMLParams", - "AbsorberParams", - "PMLTypes", - "DefaultPMLParameters", - "DefaultStablePMLParameters", - "DefaultAbsorberParameters", - "C_0", - "ETA_0", - "HBAR", - "EPSILON_0", - "MU_0", - "Q_e", - "K_B", - "inf", - "frequencies", - "wavelengths", - "material_library", - "Graphene", - "AbstractMedium", + "FluxTimeDataArray", + "FluxTimeMonitor", + "FossumCarrierLifetime", + "FullyAnisotropicMedium", + "GaussianBeam", + "GaussianBeamProfile", + "GaussianDoping", + "GaussianPulse", "Geometry", - "Source", - "SourceTime", - "Monitor", - "YeeGrid", - "FieldGrid", - "Coords1D", - "log", - "set_logging_file", - "set_logging_console", - "config", - "__version__", - "Updater", - "AdmittanceNetwork", - "CoaxialLumpedResistor", + "GeometryGroup", + "Graphene", + "Grid", + "GridRefinement", + "GridRefinementLine", + "GridRefinementRegion", + "GridSpec", + "HammerstadSurfaceRoughness", + "HeatBoundarySpec", + "HeatChargeBoundarySpec", + "HeatChargeSimulation", + "HeatChargeSimulationData", + "HeatDataArray", + "HeatFluxBC", + "HeatFromElectricSource", + "HeatSimulation", + "HeatSimulationData", + "HeatSource", + "HeuristicPECStaircasing", + "HuraySurfaceRoughness", + "IndexPerturbation", + "IndexedDataArray", + "IndexedTimeDataArray", + "IndexedVoltageDataArray", + "InsulatingBC", + "IsothermalSteadyChargeDCAnalysis", + "KerrNonlinearity", + "LayerRefinementSpec", + "LinearChargePerturbation", + "LinearHeatPerturbation", "LinearLumpedElement", + "Lorentz", + "LossyMetalMedium", "LumpedElement", "LumpedResistor", - "RectangularLumpedElement", + "Medium", + "Medium2D", + "MediumMediumInterface", + "MeshOverrideStructure", + "ModeAmpsDataArray", + "ModeData", + "ModeIndexDataArray", + "ModeMonitor", + "ModeSimulation", + "ModeSimulationData", + "ModeSolverData", + "ModeSolverDataset", + "ModeSolverMonitor", + "ModeSource", + "ModeSpec", + "ModulationSpec", + "Monitor", + "MultiPhysicsMedium", + "NedeljkovicSorefMashanovich", + "NonlinearModel", + "NonlinearSpec", + "NonlinearSusceptibility", + "PECBoundary", + "PECConformal", + "PECMedium", + "PMCBoundary", + "PMLParams", + "PMLTypes", + "ParameterPerturbation", + "Periodic", + "PermittivityData", + "PermittivityDataset", + "PermittivityMonitor", + "PermittivityPerturbation", + "PerturbationMedium", + "PerturbationPoleResidue", + "PlaneWave", + "PlaneWaveBeamProfile", + "PointDataArray", + "PointDipole", + "PolarizedAveraging", + "PoleResidue", + "PolySlab", + "Q_e", + "QuasiUniformGrid", "RLCNetwork", + "RadiativeRecombination", + "RectangularLumpedElement", + "RotationAroundAxis", + "RunTimeSpec", + "ScalarFieldDataArray", + "ScalarFieldTimeDataArray", + "ScalarModeFieldCylindricalDataArray", + "ScalarModeFieldDataArray", "Scene", - "StructureStructureInterface", - "StructureBoundary", - "MediumMediumInterface", - "StructureSimulationBoundary", + "Sellmeier", + "SemiconductorMedium", + "ShockleyReedHallRecombination", + "Simulation", "SimulationBoundary", - "FluidMedium", - "FluidSpec", + "SimulationData", + "SlotboomBandGapNarrowing", "SolidMedium", "SolidSpec", - "ChargeConductorMedium", - "SemiconductorMedium", - "ChargeInsulatorMedium", - "HeatSimulation", - "HeatSimulationData", - "HeatChargeSimulationData", - "DeviceCharacteristics", - "TemperatureBC", - "ConvectionBC", - "HeatFluxBC", - "HeatBoundarySpec", - "VoltageBC", - "CurrentBC", - "InsulatingBC", - "UniformHeatSource", - "HeatSource", - "HeatFromElectricSource", - "UnsteadyHeatAnalysis", - "UnsteadySpec", - "UniformUnstructuredGrid", - "DistanceUnstructuredGrid", - "GridRefinementRegion", - "GridRefinementLine", - "TemperatureData", - "TemperatureMonitor", - "HeatChargeSimulation", - "SteadyPotentialData", - "SteadyFreeCarrierData", - "SteadyEnergyBandData", + "Source", + "SourceTime", + "SpaceModulation", + "SpaceTimeModulation", + "SpatialDataArray", + "SpatialVoltageDataArray", + "Sphere", + "StablePML", + "Staircasing", "SteadyCapacitanceData", - "CaugheyThomasMobility", - "ConstantMobilityModel", - "SlotboomBandGapNarrowing", - "ShockleyReedHallRecombination", - "FossumCarrierLifetime", - "AugerRecombination", - "RadiativeRecombination", - "ConstantDoping", - "GaussianDoping", - "HeatChargeBoundarySpec", - "SteadyPotentialMonitor", - "SteadyFreeCarrierMonitor", - "SteadyEnergyBandMonitor", "SteadyCapacitanceMonitor", - "SpaceTimeModulation", - "SpaceModulation", - "ContinuousWaveTimeModulation", - "ModulationSpec", - "PointDataArray", - "CellDataArray", - "IndexedDataArray", - "IndexedTimeDataArray", - "IndexedVoltageDataArray", + "SteadyEnergyBandData", + "SteadyEnergyBandMonitor", + "SteadyFreeCarrierData", + "SteadyFreeCarrierMonitor", + "SteadyPotentialData", + "SteadyPotentialMonitor", "SteadyVoltageDataArray", - "TriangularGridDataset", - "TetrahedralGridDataset", - "medium_from_nk", + "Structure", + "StructureBoundary", + "StructureSimulationBoundary", + "StructureStructureInterface", "SubpixelSpec", - "Staircasing", - "VolumetricAveraging", - "PolarizedAveraging", - "ContourPathAveraging", - "HeuristicPECStaircasing", - "PECConformal", "SurfaceImpedance", + "SurfaceImpedanceFitterParam", + "TemperatureBC", + "TemperatureData", + "TemperatureMonitor", + "TetrahedralGridDataset", + "Transformed", + "TriangleMesh", + "TriangularGridDataset", + "TwoPhotonAbsorption", + "UniformCurrentSource", + "UniformGrid", + "UniformHeatSource", + "UniformUnstructuredGrid", + "UnsteadyHeatAnalysis", + "UnsteadySpec", + "Updater", "VisualizationSpec", - "restore_matplotlib_rcparams", - "EMESimulation", - "EMESimulationData", - "EMEMonitor", - "EMEModeSolverMonitor", - "EMEFieldMonitor", - "EMESMatrixDataArray", - "EMEFieldDataset", - "EMECoefficientDataset", - "EMESMatrixDataset", - "EMEModeSolverData", - "EMEFieldData", - "EMECoefficientData", - "EMECoefficientMonitor", - "EMEModeSpec", - "EMEGrid", - "EMEUniformGrid", - "EMECompositeGrid", - "EMEExplicitGrid", - "EMEScalarFieldDataArray", - "EMEScalarModeFieldDataArray", - "EMEModeIndexDataArray", - "EMECoefficientDataArray", - "EMEModeSolverDataset", - "EMESweepSpec", - "EMELengthSweep", - "EMEModeSweep", - "EMEFreqSweep", - "EMEPeriodicitySweep", - "ModeSimulation", - "ModeSimulationData", - "FixedAngleSpec", - "FixedInPlaneKSpec", - "MultiPhysicsMedium", - "DCVoltageSource", - "DCCurrentSource", + "VoltageBC", "VoltageSourceType", - "IsothermalSteadyChargeDCAnalysis", - "ChargeToleranceSpec", - "AntennaMetricsData", + "VolumetricAveraging", + "YeeGrid", + "__version__", + "config", + "frequencies", + "inf", + "log", + "material_library", + "medium_from_nk", + "restore_matplotlib_rcparams", + "set_logging_console", + "set_logging_file", + "wavelengths", ] diff --git a/tidy3d/__main__.py b/tidy3d/__main__.py index da4f1a994d..6021d3367f 100644 --- a/tidy3d/__main__.py +++ b/tidy3d/__main__.py @@ -1,5 +1,7 @@ """command-line interface. For instructions run `python -m tidy3d --help`""" +from __future__ import annotations + import argparse import sys diff --git a/tidy3d/compat.py b/tidy3d/compat.py index 6060407fe8..e137448620 100644 --- a/tidy3d/compat.py +++ b/tidy3d/compat.py @@ -1,5 +1,7 @@ """Compatibility layer for handling differences between package versions.""" +from __future__ import annotations + try: from xarray.structure import alignment except ImportError: diff --git a/tidy3d/components/apodization.py b/tidy3d/components/apodization.py index 9dd8614094..6b7567fd64 100644 --- a/tidy3d/components/apodization.py +++ b/tidy3d/components/apodization.py @@ -1,10 +1,13 @@ """Defines specification for apodization.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pd -from ..constants import SECOND -from ..exceptions import SetupError +from tidy3d.constants import SECOND +from tidy3d.exceptions import SetupError + from .base import Tidy3dBaseModel, skip_if_fields_missing from .types import ArrayFloat1D, Ax from .viz import add_ax_if_none diff --git a/tidy3d/components/autograd/__init__.py b/tidy3d/components/autograd/__init__.py index e80b43fd42..a2e9eea893 100644 --- a/tidy3d/components/autograd/__init__.py +++ b/tidy3d/components/autograd/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .boxes import TidyArrayBox from .functions import interpn from .types import ( @@ -12,18 +14,18 @@ from .utils import get_static, is_tidy_box, split_list __all__ = [ + "AutogradFieldMap", + "AutogradTraced", "TidyArrayBox", + "TracedCoordinate", "TracedFloat", - "TracedSize1D", "TracedSize", - "TracedCoordinate", + "TracedSize1D", "TracedVertices", - "AutogradTraced", - "AutogradFieldMap", + "add_at", "get_static", "interpn", - "split_list", "is_tidy_box", + "split_list", "trapz", - "add_at", ] diff --git a/tidy3d/components/autograd/boxes.py b/tidy3d/components/autograd/boxes.py index 437005d9d5..78aa52289a 100644 --- a/tidy3d/components/autograd/boxes.py +++ b/tidy3d/components/autograd/boxes.py @@ -1,8 +1,9 @@ # Adds some functionality to the autograd arraybox and related autograd patches # NOTE: we do not subclass ArrayBox since that would break autograd's internal checks +from __future__ import annotations import importlib -from typing import Any, Callable, Dict, List, Tuple +from typing import Any, Callable import autograd.numpy as anp from autograd.extend import VJPNode, defjvp, register_notrace @@ -33,9 +34,9 @@ def from_arraybox(cls, box: ArrayBox) -> TidyArrayBox: def __array_function__( self: Any, func: Callable, - types: List[Any], - args: Tuple[Any, ...], - kwargs: Dict[str, Any], + types: list[Any], + args: tuple[Any, ...], + kwargs: dict[str, Any], ) -> Any: """ Handle the dispatch of NumPy functions to autograd's numpy implementation. @@ -102,7 +103,7 @@ def __array_ufunc__( ufunc: Callable, method: str, *inputs: Any, - **kwargs: Dict[str, Any], + **kwargs: dict[str, Any], ) -> Any: """ Handle the dispatch of NumPy ufuncs to autograd's numpy implementation. diff --git a/tidy3d/components/autograd/derivative_utils.py b/tidy3d/components/autograd/derivative_utils.py index e13b2abcf0..e0e981b857 100644 --- a/tidy3d/components/autograd/derivative_utils.py +++ b/tidy3d/components/autograd/derivative_utils.py @@ -5,10 +5,11 @@ import pydantic.v1 as pd import xarray as xr -from ...constants import LARGE_NUMBER -from ..base import Tidy3dBaseModel -from ..data.data_array import ScalarFieldDataArray, SpatialDataArray -from ..types import ArrayLike, Bound, tidycomplex +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.components.data.data_array import ScalarFieldDataArray, SpatialDataArray +from tidy3d.components.types import ArrayLike, Bound, tidycomplex +from tidy3d.constants import LARGE_NUMBER + from .types import PathType from .utils import get_static @@ -390,6 +391,6 @@ def integrate_within_bounds(arr: xr.DataArray, dims: list[str], bounds: Bound) - __all__ = [ - "integrate_within_bounds", "DerivativeInfo", + "integrate_within_bounds", ] diff --git a/tidy3d/components/autograd/functions.py b/tidy3d/components/autograd/functions.py index be48fe43ff..96d10f1547 100644 --- a/tidy3d/components/autograd/functions.py +++ b/tidy3d/components/autograd/functions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import itertools import autograd.numpy as anp @@ -251,7 +253,7 @@ def add_at(x: NDArray, indices_x: tuple, y: NDArray) -> NDArray: __all__ = [ + "add_at", "interpn", "trapz", - "add_at", ] diff --git a/tidy3d/components/autograd/types.py b/tidy3d/components/autograd/types.py index e8be92bb74..52ac58c8ab 100644 --- a/tidy3d/components/autograd/types.py +++ b/tidy3d/components/autograd/types.py @@ -1,6 +1,7 @@ # type information for autograd # utilities for working with autograd +from __future__ import annotations import copy import typing @@ -10,8 +11,7 @@ from autograd.extend import Box, defvjp, primitive from tidy3d.components.type_util import _add_schema - -from ..types import ArrayFloat2D, ArrayLike, Complex, Size1D +from tidy3d.components.types import ArrayFloat2D, ArrayLike, Complex, Size1D # add schema to the Box _add_schema(Box, title="AutogradBox", field_type_str="autograd.tracer.Box") @@ -36,7 +36,7 @@ # poles TracedComplex = typing.Union[Complex, Box] -TracedPoleAndResidue = typing.Tuple[TracedComplex, TracedComplex] +TracedPoleAndResidue = tuple[TracedComplex, TracedComplex] # The data type that we pass in and out of the web.run() @autograd.primitive AutogradTraced = typing.Union[Box, ArrayLike] @@ -46,11 +46,11 @@ InterpolationType = typing.Literal["nearest", "linear"] __all__ = [ + "AutogradFieldMap", + "AutogradTraced", + "TracedCoordinate", "TracedFloat", - "TracedSize1D", "TracedSize", - "TracedCoordinate", + "TracedSize1D", "TracedVertices", - "AutogradTraced", - "AutogradFieldMap", ] diff --git a/tidy3d/components/autograd/utils.py b/tidy3d/components/autograd/utils.py index 0a1fbfd43d..14da7b493f 100644 --- a/tidy3d/components/autograd/utils.py +++ b/tidy3d/components/autograd/utils.py @@ -1,4 +1,5 @@ # utilities for working with autograd +from __future__ import annotations import typing @@ -23,6 +24,6 @@ def is_tidy_box(x: typing.Any) -> bool: __all__ = [ "get_static", - "split_list", "is_tidy_box", + "split_list", ] diff --git a/tidy3d/components/base.py b/tidy3d/components/base.py index 997769acbf..36eb671a08 100644 --- a/tidy3d/components/base.py +++ b/tidy3d/components/base.py @@ -11,7 +11,7 @@ import tempfile from functools import wraps from math import ceil -from typing import Any, Callable, Dict, List, Tuple, Union +from typing import Any, Callable, Optional, Union import h5py import numpy as np @@ -23,8 +23,9 @@ from autograd.tracer import isbox from pydantic.v1.fields import ModelField -from ..exceptions import FileError -from ..log import log +from tidy3d.exceptions import FileError +from tidy3d.log import log + from .autograd.types import AutogradFieldMap, Box from .autograd.utils import get_static from .data.data_array import DATA_ARRAY_MAP, DataArray @@ -70,7 +71,7 @@ def cached_property(cached_property_getter): def ndarray_encoder(val): """How a ``np.ndarray`` gets handled before saving to json.""" if np.any(np.iscomplex(val)): - return dict(real=val.real.tolist(), imag=val.imag.tolist()) + return {"real": val.real.tolist(), "imag": val.imag.tolist()} return val.real.tolist() @@ -92,7 +93,7 @@ def _get_valid_extension(fname: str) -> str: ) -def skip_if_fields_missing(fields: List[str], root=False): +def skip_if_fields_missing(fields: list[str], root=False): """Decorate ``validator`` to check that other fields have passed validation.""" def actual_decorator(validator): @@ -110,8 +111,7 @@ def _validator(cls, *args, **kwargs): ) if root: return values - else: - return kwargs.get("val") if "val" in kwargs.keys() else args[0] + return kwargs.get("val") if "val" in kwargs else args[0] return validator(cls, *args, **kwargs) @@ -229,7 +229,7 @@ def copy(self, deep: bool = True, validate: bool = True, **kwargs) -> Tidy3dBase return new_copy def updated_copy( - self, path: str = None, deep: bool = True, validate: bool = True, **kwargs + self, path: Optional[str] = None, deep: bool = True, validate: bool = True, **kwargs ) -> Tidy3dBaseModel: """Make copy of a component instance with ``**kwargs`` indicating updated field values. @@ -270,7 +270,7 @@ def updated_copy( f"Could not grab integer index from path '{path}'. " f"Please correct the sub path containing '{integer_index_path}' to be an " f"integer index into '{field_name}' (containing {len(sub_component)} elements)." - ) + ) from None sub_component_list = list(sub_component) sub_component = sub_component_list[index] @@ -307,7 +307,9 @@ def help(self, methods: bool = False) -> None: rich.inspect(self, methods=methods) @classmethod - def from_file(cls, fname: str, group_path: str = None, **parse_obj_kwargs) -> Tidy3dBaseModel: + def from_file( + cls, fname: str, group_path: Optional[str] = None, **parse_obj_kwargs + ) -> Tidy3dBaseModel: """Loads a :class:`Tidy3dBaseModel` from .yaml, .json, .hdf5, or .hdf5.gz file. Parameters @@ -333,7 +335,7 @@ def from_file(cls, fname: str, group_path: str = None, **parse_obj_kwargs) -> Ti return cls.parse_obj(model_dict, **parse_obj_kwargs) @classmethod - def dict_from_file(cls, fname: str, group_path: str = None) -> dict: + def dict_from_file(cls, fname: str, group_path: Optional[str] = None) -> dict: """Loads a dictionary containing the model from a .yaml, .json, .hdf5, or .hdf5.gz file. Parameters @@ -590,7 +592,7 @@ def _json_string_from_hdf5(cls, fname: str) -> str: @classmethod def dict_from_hdf5( - cls, fname: str, group_path: str = "", custom_decoders: List[Callable] = None + cls, fname: str, group_path: str = "", custom_decoders: Optional[list[Callable]] = None ) -> dict: """Loads a dictionary containing the model contents from a .hdf5 file. @@ -668,7 +670,7 @@ def from_hdf5( cls, fname: str, group_path: str = "", - custom_decoders: List[Callable] = None, + custom_decoders: Optional[list[Callable]] = None, **parse_obj_kwargs, ) -> Tidy3dBaseModel: """Loads :class:`Tidy3dBaseModel` instance to .hdf5 file. @@ -698,7 +700,7 @@ def from_hdf5( ) return cls.parse_obj(model_dict, **parse_obj_kwargs) - def to_hdf5(self, fname: str, custom_encoders: List[Callable] = None) -> None: + def to_hdf5(self, fname: str, custom_encoders: Optional[list[Callable]] = None) -> None: """Exports :class:`Tidy3dBaseModel` instance to .hdf5 file. Parameters @@ -749,7 +751,7 @@ def add_data_to_file(data_dict: dict, group_path: str = "") -> None: @classmethod def dict_from_hdf5_gz( - cls, fname: str, group_path: str = "", custom_decoders: List[Callable] = None + cls, fname: str, group_path: str = "", custom_decoders: Optional[list[Callable]] = None ) -> dict: """Loads a dictionary containing the model contents from a .hdf5.gz file. @@ -790,7 +792,7 @@ def from_hdf5_gz( cls, fname: str, group_path: str = "", - custom_decoders: List[Callable] = None, + custom_decoders: Optional[list[Callable]] = None, **parse_obj_kwargs, ) -> Tidy3dBaseModel: """Loads :class:`Tidy3dBaseModel` instance to .hdf5.gz file. @@ -820,7 +822,7 @@ def from_hdf5_gz( ) return cls.parse_obj(model_dict, **parse_obj_kwargs) - def to_hdf5_gz(self, fname: str, custom_encoders: List[Callable] = None) -> None: + def to_hdf5_gz(self, fname: str, custom_encoders: Optional[list[Callable]] = None) -> None: """Exports :class:`Tidy3dBaseModel` instance to .hdf5.gz file. Parameters @@ -873,7 +875,7 @@ def check_equal(dict1: dict, dict2: dict) -> bool: return False # loop through elements in each dict - for key in dict1.keys(): + for key in dict1: # noqa: PLC0206 val1 = dict1[key] val2 = dict2[key] @@ -984,12 +986,12 @@ def handle_value(x: Any, path: tuple[str, ...]) -> None: # for sequences, add (i,) to the path and handle each value individually elif isinstance(x, (list, tuple)): for i, val in enumerate(x): - handle_value(val, path=path + (i,)) + handle_value(val, path=(*path, i)) # for dictionaries, add the (key,) to the path and handle each value individually elif isinstance(x, dict): for key, val in x.items(): - handle_value(val, path=path + (key,)) + handle_value(val, path=(*path, key)) # recursively parse the dictionary of this object self_dict = self.dict() @@ -1132,7 +1134,7 @@ def generate_docstring(cls) -> str: doc += "\n" cls.__doc__ = doc - def get_submodels_by_hash(self) -> Dict[int, List[Union[str, Tuple[str, int]]]]: + def get_submodels_by_hash(self) -> dict[int, list[Union[str, tuple[str, int]]]]: """Return a dictionary of this object's sub-models indexed by their hash values.""" fields = {} for key in self.__fields__: @@ -1166,7 +1168,7 @@ def get_submodels_by_hash(self) -> Dict[int, List[Union[str, Tuple[str, int]]]]: @staticmethod def _scientific_notation( min_val: float, max_val: float, min_digits: int = 4 - ) -> Tuple[str, str]: + ) -> tuple[str, str]: """ Convert numbers to scientific notation, displaying only digits up to the point of difference, with a minimum number of significant digits specified by `min_digits`. diff --git a/tidy3d/components/base_sim/data/monitor_data.py b/tidy3d/components/base_sim/data/monitor_data.py index 86d2f0717b..18fb82728a 100644 --- a/tidy3d/components/base_sim/data/monitor_data.py +++ b/tidy3d/components/base_sim/data/monitor_data.py @@ -6,8 +6,8 @@ import pydantic.v1 as pd -from ...data.dataset import Dataset -from ..monitor import AbstractMonitor +from tidy3d.components.base_sim.monitor import AbstractMonitor +from tidy3d.components.data.dataset import Dataset class AbstractMonitorData(Dataset, ABC): diff --git a/tidy3d/components/base_sim/data/sim_data.py b/tidy3d/components/base_sim/data/sim_data.py index 88644a26c6..0f64e80461 100644 --- a/tidy3d/components/base_sim/data/sim_data.py +++ b/tidy3d/components/base_sim/data/sim_data.py @@ -3,17 +3,18 @@ from __future__ import annotations from abc import ABC -from typing import Dict, Tuple, Union +from typing import Union import numpy as np import pydantic.v1 as pd import xarray as xr -from ....exceptions import DataError, Tidy3dKeyError, ValidationError -from ...base import Tidy3dBaseModel, skip_if_fields_missing -from ...data.utils import UnstructuredGridDatasetType -from ...types import FieldVal -from ..simulation import AbstractSimulation +from tidy3d.components.base import Tidy3dBaseModel, skip_if_fields_missing +from tidy3d.components.base_sim.simulation import AbstractSimulation +from tidy3d.components.data.utils import UnstructuredGridDatasetType +from tidy3d.components.types import FieldVal +from tidy3d.exceptions import DataError, Tidy3dKeyError, ValidationError + from .monitor_data import AbstractMonitorData @@ -28,7 +29,7 @@ class AbstractSimulationData(Tidy3dBaseModel, ABC): description="Original :class:`AbstractSimulation` associated with the data.", ) - data: Tuple[AbstractMonitorData, ...] = pd.Field( + data: tuple[AbstractMonitorData, ...] = pd.Field( ..., title="Monitor Data", description="List of :class:`AbstractMonitorData` instances " @@ -47,7 +48,7 @@ def __getitem__(self, monitor_name: str) -> AbstractMonitorData: return monitor_data.symmetry_expanded_copy @property - def monitor_data(self) -> Dict[str, AbstractMonitorData]: + def monitor_data(self) -> dict[str, AbstractMonitorData]: """Dictionary mapping monitor name to its associated :class:`AbstractMonitorData`.""" return {monitor_data.monitor.name: monitor_data for monitor_data in self.data} diff --git a/tidy3d/components/base_sim/monitor.py b/tidy3d/components/base_sim/monitor.py index e5355e5679..e29f707adc 100644 --- a/tidy3d/components/base_sim/monitor.py +++ b/tidy3d/components/base_sim/monitor.py @@ -1,16 +1,17 @@ """Abstract bases for classes that define how data is recorded from simulation.""" +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Tuple import numpy as np import pydantic.v1 as pd -from ..base import cached_property -from ..geometry.base import Box -from ..types import ArrayFloat1D, Axis, Numpy -from ..validators import _warn_unsupported_traced_argument -from ..viz import PlotParams, plot_params_monitor +from tidy3d.components.base import cached_property +from tidy3d.components.geometry.base import Box +from tidy3d.components.types import ArrayFloat1D, Axis, Numpy +from tidy3d.components.validators import _warn_unsupported_traced_argument +from tidy3d.components.viz import PlotParams, plot_params_monitor class AbstractMonitor(Box, ABC): @@ -88,7 +89,7 @@ def downsample(self, arr: Numpy, axis: Axis) -> Numpy: inds = np.append(inds, size - 1) return arr[inds] - def downsampled_num_cells(self, num_cells: Tuple[int, int, int]) -> Tuple[int, int, int]: + def downsampled_num_cells(self, num_cells: tuple[int, int, int]) -> tuple[int, int, int]: """Given a tuple of the number of cells spanned by the monitor along each dimension, return the number of cells one would have after downsampling based on ``interval_space``. """ diff --git a/tidy3d/components/base_sim/simulation.py b/tidy3d/components/base_sim/simulation.py index fd1a412f1d..f6f0e8c903 100644 --- a/tidy3d/components/base_sim/simulation.py +++ b/tidy3d/components/base_sim/simulation.py @@ -3,31 +3,35 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Optional, Tuple +from typing import Optional import autograd.numpy as anp import pydantic.v1 as pd -from ...exceptions import Tidy3dKeyError -from ...log import log -from ...version import __version__ -from ..base import cached_property, skip_if_fields_missing -from ..geometry.base import Box -from ..medium import Medium, MediumType3D -from ..scene import Scene -from ..structure import Structure -from ..types import TYPE_TAG_STR, Ax, Axis, Bound, LengthUnit, PriorityMode, Symmetry -from ..validators import ( +from tidy3d.components.base import cached_property, skip_if_fields_missing +from tidy3d.components.geometry.base import Box +from tidy3d.components.medium import Medium, MediumType3D +from tidy3d.components.scene import Scene +from tidy3d.components.structure import Structure +from tidy3d.components.types import ( + TYPE_TAG_STR, + Ax, + Axis, + Bound, + LengthUnit, + PriorityMode, + Symmetry, +) +from tidy3d.components.validators import ( _warn_unsupported_traced_argument, assert_objects_in_sim_bounds, assert_unique_names, ) -from ..viz import ( - PlotParams, - add_ax_if_none, - equal_aspect, - plot_params_symmetry, -) +from tidy3d.components.viz import PlotParams, add_ax_if_none, equal_aspect, plot_params_symmetry +from tidy3d.exceptions import Tidy3dKeyError +from tidy3d.log import log +from tidy3d.version import __version__ + from .monitor import AbstractMonitor @@ -44,7 +48,7 @@ class AbstractSimulation(Box, ABC): Background medium of simulation, defaults to vacuum if not specified. """ - structures: Tuple[Structure, ...] = pd.Field( + structures: tuple[Structure, ...] = pd.Field( (), title="Structures", description="Tuple of structures present in simulation. " @@ -73,7 +77,7 @@ class AbstractSimulation(Box, ABC): ) """ - symmetry: Tuple[Symmetry, Symmetry, Symmetry] = pd.Field( + symmetry: tuple[Symmetry, Symmetry, Symmetry] = pd.Field( (0, 0, 0), title="Symmetries", description="Tuple of integers defining reflection symmetry across a plane " @@ -81,7 +85,7 @@ class AbstractSimulation(Box, ABC): "at the simulation center of each axis, respectively. ", ) - sources: Tuple[None, ...] = pd.Field( + sources: tuple[None, ...] = pd.Field( (), title="Sources", description="Sources in the simulation.", @@ -93,7 +97,7 @@ class AbstractSimulation(Box, ABC): description="Specification of boundary conditions.", ) - monitors: Tuple[None, ...] = pd.Field( + monitors: tuple[None, ...] = pd.Field( (), title="Monitors", description="Monitors in the simulation. ", @@ -191,7 +195,6 @@ def _post_init_validators(self) -> None: def validate_pre_upload(self) -> None: """Validate the fully initialized simulation is ok for upload to our servers.""" - pass """ Accounting """ @@ -239,14 +242,14 @@ def simulation_structure(self) -> Structure: @add_ax_if_none def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - source_alpha: float = None, - monitor_alpha: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + source_alpha: Optional[float] = None, + monitor_alpha: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, fill_structures: bool = True, **patch_kwargs, ) -> Ax: @@ -304,12 +307,12 @@ def plot( @add_ax_if_none def plot_sources( self, - x: float = None, - y: float = None, - z: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, + alpha: Optional[float] = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's sources on a plane defined by one nonzero x,y,z coordinate. @@ -352,12 +355,12 @@ def plot_sources( @add_ax_if_none def plot_monitors( self, - x: float = None, - y: float = None, - z: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, + alpha: Optional[float] = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's monitors on a plane defined by one nonzero x,y,z coordinate. @@ -400,11 +403,11 @@ def plot_monitors( @add_ax_if_none def plot_symmetries( self, - x: float = None, - y: float = None, - z: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's symmetries on a plane defined by one nonzero x,y,z coordinate. @@ -475,9 +478,9 @@ def _make_symmetry_box(self, sym_axis: Axis) -> Box: @add_ax_if_none def plot_boundaries( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, **kwargs, ) -> Ax: @@ -509,12 +512,12 @@ def plot_boundaries( @add_ax_if_none def plot_structures( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, fill: bool = True, ) -> Ax: """Plot each of simulation's structures on a plane defined by one nonzero x,y,z coordinate. @@ -553,16 +556,16 @@ def plot_structures( @add_ax_if_none def plot_structures_eps( self, - x: float = None, - y: float = None, - z: float = None, - freq: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ) -> Ax: """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. @@ -620,15 +623,15 @@ def plot_structures_eps( @add_ax_if_none def plot_structures_heat_conductivity( self, - x: float = None, - y: float = None, - z: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ) -> Ax: """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. diff --git a/tidy3d/components/base_sim/source.py b/tidy3d/components/base_sim/source.py index cdaff53d66..f1630c41c0 100644 --- a/tidy3d/components/base_sim/source.py +++ b/tidy3d/components/base_sim/source.py @@ -6,9 +6,9 @@ import pydantic.v1 as pydantic -from ..base import Tidy3dBaseModel -from ..validators import validate_name_str -from ..viz import PlotParams +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.components.validators import validate_name_str +from tidy3d.components.viz import PlotParams class AbstractSource(Tidy3dBaseModel, ABC): diff --git a/tidy3d/components/bc_placement.py b/tidy3d/components/bc_placement.py index c59c11962e..1f48a94fbf 100644 --- a/tidy3d/components/bc_placement.py +++ b/tidy3d/components/bc_placement.py @@ -3,11 +3,12 @@ from __future__ import annotations from abc import ABC -from typing import Tuple, Union +from typing import Union import pydantic.v1 as pd -from ..exceptions import SetupError +from tidy3d.exceptions import SetupError + from .base import Tidy3dBaseModel from .types import BoxSurface @@ -38,7 +39,7 @@ class StructureStructureInterface(AbstractBCPlacement): >>> bc_placement = StructureStructureInterface(structures=["box", "sphere"]) """ - structures: Tuple[str, str] = pd.Field( + structures: tuple[str, str] = pd.Field( title="Structures", description="Names of two structures.", ) @@ -61,7 +62,7 @@ class MediumMediumInterface(AbstractBCPlacement): >>> bc_placement = MediumMediumInterface(mediums=["dieletric", "metal"]) """ - mediums: Tuple[str, str] = pd.Field( + mediums: tuple[str, str] = pd.Field( title="Mediums", description="Names of two mediums.", ) @@ -82,7 +83,7 @@ class SimulationBoundary(AbstractBCPlacement): >>> bc_placement = SimulationBoundary(surfaces=["x-", "x+"]) """ - surfaces: Tuple[BoxSurface, ...] = pd.Field( + surfaces: tuple[BoxSurface, ...] = pd.Field( ("x-", "x+", "y-", "y+", "z-", "z+"), title="Surfaces", description="Surfaces of simulation domain where to apply boundary conditions.", @@ -102,7 +103,7 @@ class StructureSimulationBoundary(AbstractBCPlacement): description="Name of the structure.", ) - surfaces: Tuple[BoxSurface, ...] = pd.Field( + surfaces: tuple[BoxSurface, ...] = pd.Field( ("x-", "x+", "y-", "y+", "z-", "z+"), title="Surfaces", description="Surfaces of simulation domain where to apply boundary conditions.", diff --git a/tidy3d/components/beam.py b/tidy3d/components/beam.py index 4a6f632466..a7ccedf10c 100644 --- a/tidy3d/components/beam.py +++ b/tidy3d/components/beam.py @@ -1,13 +1,16 @@ """Classes for creating data based on analytic beams like plane wave, Gaussian beam, and astigmatic Gaussian beam.""" +from __future__ import annotations + from abc import abstractmethod -from typing import Optional, Tuple, Union +from typing import Literal, Optional, Union import autograd.numpy as np import pydantic.v1 as pd -from ..constants import C_0, ETA_0, HERTZ, MICROMETER, RADIAN +from tidy3d.constants import C_0, ETA_0, HERTZ, MICROMETER, RADIAN + from .base import cached_property from .data.data_array import ScalarFieldDataArray from .data.monitor_data import FieldData @@ -16,7 +19,7 @@ from .medium import Medium, MediumType from .monitor import FieldMonitor from .source.field import FixedAngleSpec, FixedInPlaneKSpec -from .types import TYPE_TAG_STR, Direction, FreqArray, Literal, Numpy +from .types import TYPE_TAG_STR, Direction, FreqArray, Numpy from .validators import assert_plane DEFAULT_RESOLUTION = 200 @@ -156,7 +159,7 @@ def _field_data_on_grid(self, grid: Grid, background_n: Numpy, colocate=True) -> # Get the current field component field_vals = field_vals[comp % 3] # Make the ScalarFieldDataArray for the current component - coords = dict(x=x, y=y, z=z, f=np.array(self.freqs)) + coords = {"x": x, "y": y, "z": z, "f": np.array(self.freqs)} field_data = ScalarFieldDataArray(field_vals, coords=coords) scalar_fields[field] = field_data @@ -167,7 +170,6 @@ def scalar_field(self, points: Numpy, background_n: float) -> Numpy: """Scalar field corresponding to the analytic beam in coordinate system such that the propagation direction is z and the ``E``-field is entirely ``x``-polarized. The field is computed on an unstructured array ``points`` of shape ``(3, ...)``.""" - pass def analytic_beam_z_normal( self, points: Numpy, background_n: float, field: Literal["E", "H"] @@ -323,7 +325,7 @@ def _rotate_points_z(self, points: Numpy, background_n: Numpy) -> Numpy: if self.as_fixed_angle_source: # For fixed-angle, we do not rotate the points return points - elif isinstance(self.angular_spec, FixedInPlaneKSpec): + if isinstance(self.angular_spec, FixedInPlaneKSpec): # For fixed in-plane k, the rotation is angle-dependent points = self.rotate_points(points, [0, 0, 1], -self.angle_phi) angle_theta_actual = self._angle_theta_actual(background_n=background_n) @@ -373,7 +375,7 @@ class GaussianBeamProfile(BeamProfile): units=MICROMETER, ) - def beam_params(self, z: Numpy, k0: Numpy) -> Tuple[Numpy, Numpy, Numpy]: + def beam_params(self, z: Numpy, k0: Numpy) -> tuple[Numpy, Numpy, Numpy]: """Compute the parameters needed to evaluate a Gaussian beam at z. Parameters @@ -420,14 +422,14 @@ class AstigmaticGaussianBeamProfile(BeamProfile): See also :class:`.AstigmaticGaussianBeam`. """ - waist_sizes: Tuple[pd.PositiveFloat, pd.PositiveFloat] = pd.Field( + waist_sizes: tuple[pd.PositiveFloat, pd.PositiveFloat] = pd.Field( (1.0, 1.0), title="Waist sizes", description="Size of the beam at the waist in the local x and y directions.", units=MICROMETER, ) - waist_distances: Tuple[float, float] = pd.Field( + waist_distances: tuple[float, float] = pd.Field( (0.0, 0.0), title="Waist distances", description="Distance to the beam waist along the propagation direction " @@ -439,7 +441,7 @@ class AstigmaticGaussianBeamProfile(BeamProfile): units=MICROMETER, ) - def beam_params(self, z: Numpy, k0: Numpy) -> Tuple[Numpy, Numpy, Numpy, Numpy]: + def beam_params(self, z: Numpy, k0: Numpy) -> tuple[Numpy, Numpy, Numpy, Numpy]: """Compute the parameters needed to evaluate an astigmatic Gaussian beam at z. Parameters diff --git a/tidy3d/components/boundary.py b/tidy3d/components/boundary.py index 2e0acc62fa..e19eef59bd 100644 --- a/tidy3d/components/boundary.py +++ b/tidy3d/components/boundary.py @@ -3,14 +3,15 @@ from __future__ import annotations from abc import ABC -from typing import List, Tuple, Union +from typing import Union import numpy as np import pydantic.v1 as pd -from ..constants import EPSILON_0, MU_0, PML_SIGMA -from ..exceptions import DataError, SetupError -from ..log import log +from tidy3d.constants import EPSILON_0, MU_0, PML_SIGMA +from tidy3d.exceptions import DataError, SetupError +from tidy3d.log import log + from .base import Tidy3dBaseModel, cached_property from .medium import Medium from .source.field import TFSF, GaussianBeam, ModeSource, PlaneWave @@ -906,7 +907,7 @@ def all_sides(cls, boundary: BoundaryEdge): ) @cached_property - def to_list(self) -> List[Tuple[BoundaryEdgeType, BoundaryEdgeType]]: + def to_list(self) -> list[tuple[BoundaryEdgeType, BoundaryEdgeType]]: """Returns edge-wise boundary conditions along each dimension for internal use.""" return [ (self.x.minus, self.x.plus), @@ -917,12 +918,12 @@ def to_list(self) -> List[Tuple[BoundaryEdgeType, BoundaryEdgeType]]: @cached_property def flipped_bloch_vecs(self) -> BoundarySpec: """Return a copy of the instance where all Bloch vectors are multiplied by -1.""" - bound_dims = dict(x=self.x.copy(), y=self.y.copy(), z=self.z.copy()) + bound_dims = {"x": self.x.copy(), "y": self.y.copy(), "z": self.z.copy()} for dim_key, bound_dim in bound_dims.items(): - bound_edges = dict(plus=bound_dim.plus.copy(), minus=bound_dim.minus.copy()) + bound_edges = {"plus": bound_dim.plus.copy(), "minus": bound_dim.minus.copy()} for edge_key, bound_edge in bound_edges.items(): if isinstance(bound_edge, BlochBoundary): new_bloch_vec = -1 * bound_edge.bloch_vec - bound_edges[edge_key] = bound_edge.copy(update=dict(bloch_vec=new_bloch_vec)) + bound_edges[edge_key] = bound_edge.copy(update={"bloch_vec": new_bloch_vec}) bound_dims[dim_key] = bound_edges return self.copy(update=bound_dims) diff --git a/tidy3d/components/data/data_array.py b/tidy3d/components/data/data_array.py index 2397830f0e..caa053449d 100644 --- a/tidy3d/components/data/data_array.py +++ b/tidy3d/components/data/data_array.py @@ -3,7 +3,8 @@ from __future__ import annotations from abc import ABC -from typing import Any, Dict, List, Mapping, Union +from collections.abc import Mapping +from typing import Any, Optional, Union import autograd.numpy as anp import h5py @@ -17,8 +18,11 @@ from xarray.core.utils import OrderedSet, either_dict_or_kwargs from xarray.core.variable import as_variable -from ...compat import alignment -from ...constants import ( +from tidy3d.compat import alignment +from tidy3d.components.autograd import TidyArrayBox, get_static, interpn, is_tidy_box +from tidy3d.components.geometry.bound_ops import bounds_contains +from tidy3d.components.types import Axis, Bound +from tidy3d.constants import ( HERTZ, MICROMETER, PICOSECOND_PER_NANOMETER_PER_KILOMETER, @@ -26,10 +30,7 @@ SECOND, WATT, ) -from ...exceptions import DataError, FileError -from ..autograd import TidyArrayBox, get_static, interpn, is_tidy_box -from ..geometry.bound_ops import bounds_contains -from ..types import Axis, Bound +from tidy3d.exceptions import DataError, FileError # maps the dimension names to their attributes DIM_ATTRS = { @@ -69,7 +70,7 @@ class DataArray(xr.DataArray): # stores an ordered tuple of strings corresponding to the data dimensions _dims = () # stores a dictionary of attributes corresponding to the data values - _data_attrs: Dict[str, str] = {} + _data_attrs: dict[str, str] = {} def __init__(self, data, *args, **kwargs): # if data is a vanilla autograd box, convert to our box @@ -118,7 +119,7 @@ def assign_data_attrs(cls, val): val.attrs[attr_name] = attr return val - def _interp_validator(self, field_name: str = None) -> None: + def _interp_validator(self, field_name: Optional[str] = None) -> None: """Ensure the data can be interpolated or selected by checking for duplicate coordinates. NOTE @@ -152,17 +153,17 @@ def assign_coord_attrs(cls, val): def __modify_schema__(cls, field_schema): """Sets the schema of DataArray object.""" - schema = dict( - title="DataArray", - type="xr.DataArray", - properties=dict( - _dims=dict( - title="_dims", - type="Tuple[str, ...]", - ), - ), - required=["_dims"], - ) + schema = { + "title": "DataArray", + "type": "xr.DataArray", + "properties": { + "_dims": { + "title": "_dims", + "type": "Tuple[str, ...]", + }, + }, + "required": ["_dims"], + } field_schema.update(schema) @classmethod @@ -261,7 +262,7 @@ def __hash__(self) -> int: token_str = dask.base.tokenize(self) return hash(token_str) - def multiply_at(self, value: complex, coord_name: str, indices: List[int]) -> Self: + def multiply_at(self, value: complex, coord_name: str, indices: list[int]) -> Self: """Multiply self by value at indices.""" if isbox(self.data) or isbox(value): return self._ag_multiply_at(value, coord_name, indices) @@ -270,7 +271,7 @@ def multiply_at(self, value: complex, coord_name: str, indices: List[int]) -> Se self_mult[{coord_name: indices}] *= value return self_mult - def _ag_multiply_at(self, value: complex, coord_name: str, indices: List[int]) -> Self: + def _ag_multiply_at(self, value: complex, coord_name: str, indices: list[int]) -> Self: """Autograd multiply_at override when tracing.""" key = {coord_name: indices} _, index_tuple, _ = self.variable._broadcast_indexes(key) diff --git a/tidy3d/components/data/dataset.py b/tidy3d/components/data/dataset.py index de64f657f4..697a5d4805 100644 --- a/tidy3d/components/data/dataset.py +++ b/tidy3d/components/data/dataset.py @@ -3,17 +3,18 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Callable, Dict, Optional, Union, get_args +from typing import Any, Callable, Optional, Union, get_args import numpy as np import pydantic.v1 as pd import xarray as xr -from ...constants import C_0, PICOSECOND_PER_NANOMETER_PER_KILOMETER, UnitScaling -from ...exceptions import DataError -from ...log import log -from ..base import Tidy3dBaseModel -from ..types import Axis, xyz +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.components.types import Axis, xyz +from tidy3d.constants import C_0, PICOSECOND_PER_NANOMETER_PER_KILOMETER, UnitScaling +from tidy3d.exceptions import DataError +from tidy3d.log import log + from .data_array import ( DataArray, EMEScalarFieldDataArray, @@ -44,7 +45,7 @@ class AbstractFieldDataset(Dataset, ABC): @property @abstractmethod - def field_components(self) -> Dict[str, DataArray]: + def field_components(self) -> dict[str, DataArray]: """Maps the field components to their associated data.""" def apply_phase(self, phase: float) -> AbstractFieldDataset: @@ -60,15 +61,15 @@ def apply_phase(self, phase: float) -> AbstractFieldDataset: @property @abstractmethod - def grid_locations(self) -> Dict[str, str]: + def grid_locations(self) -> dict[str, str]: """Maps field components to the string key of their grid locations on the yee lattice.""" @property @abstractmethod - def symmetry_eigenvalues(self) -> Dict[str, Callable[[Axis], float]]: + def symmetry_eigenvalues(self) -> dict[str, Callable[[Axis], float]]: """Maps field components to their (positive) symmetry eigenvalues.""" - def package_colocate_results(self, centered_fields: Dict[str, ScalarFieldDataArray]) -> Any: + def package_colocate_results(self, centered_fields: dict[str, ScalarFieldDataArray]) -> Any: """How to package the dictionary of fields computed via self.colocate().""" return xr.Dataset(centered_fields) @@ -182,7 +183,7 @@ class ElectromagneticFieldDataset(AbstractFieldDataset, ABC): ) @property - def field_components(self) -> Dict[str, DataArray]: + def field_components(self) -> dict[str, DataArray]: """Maps the field components to their associated data.""" fields = { "Ex": self.Ex, @@ -195,22 +196,22 @@ def field_components(self) -> Dict[str, DataArray]: return {field_name: field for field_name, field in fields.items() if field is not None} @property - def grid_locations(self) -> Dict[str, str]: + def grid_locations(self) -> dict[str, str]: """Maps field components to the string key of their grid locations on the yee lattice.""" - return dict(Ex="Ex", Ey="Ey", Ez="Ez", Hx="Hx", Hy="Hy", Hz="Hz") + return {"Ex": "Ex", "Ey": "Ey", "Ez": "Ez", "Hx": "Hx", "Hy": "Hy", "Hz": "Hz"} @property - def symmetry_eigenvalues(self) -> Dict[str, Callable[[Axis], float]]: + def symmetry_eigenvalues(self) -> dict[str, Callable[[Axis], float]]: """Maps field components to their (positive) symmetry eigenvalues.""" - return dict( - Ex=lambda dim: -1 if (dim == 0) else +1, - Ey=lambda dim: -1 if (dim == 1) else +1, - Ez=lambda dim: -1 if (dim == 2) else +1, - Hx=lambda dim: +1 if (dim == 0) else -1, - Hy=lambda dim: +1 if (dim == 1) else -1, - Hz=lambda dim: +1 if (dim == 2) else -1, - ) + return { + "Ex": lambda dim: -1 if (dim == 0) else +1, + "Ey": lambda dim: -1 if (dim == 1) else +1, + "Ez": lambda dim: -1 if (dim == 2) else +1, + "Hx": lambda dim: +1 if (dim == 0) else -1, + "Hy": lambda dim: +1 if (dim == 1) else -1, + "Hz": lambda dim: +1 if (dim == 2) else -1, + } class FieldDataset(ElectromagneticFieldDataset): @@ -422,7 +423,7 @@ class AuxFieldDataset(AbstractFieldDataset, ABC): ) @property - def field_components(self) -> Dict[str, DataArray]: + def field_components(self) -> dict[str, DataArray]: """Maps the field components to their associated data.""" fields = { "Nfx": self.Nfx, @@ -432,19 +433,19 @@ def field_components(self) -> Dict[str, DataArray]: return {field_name: field for field_name, field in fields.items() if field is not None} @property - def grid_locations(self) -> Dict[str, str]: + def grid_locations(self) -> dict[str, str]: """Maps field components to the string key of their grid locations on the yee lattice.""" - return dict(Nfx="Ex", Nfy="Ey", Nfz="Ez") + return {"Nfx": "Ex", "Nfy": "Ey", "Nfz": "Ez"} @property - def symmetry_eigenvalues(self) -> Dict[str, Callable[[Axis], float]]: + def symmetry_eigenvalues(self) -> dict[str, Callable[[Axis], float]]: """Maps field components to their (positive) symmetry eigenvalues.""" - return dict( - Nfx=lambda dim: +1, - Nfy=lambda dim: +1, - Nfz=lambda dim: +1, - ) + return { + "Nfx": lambda dim: +1, + "Nfy": lambda dim: +1, + "Nfz": lambda dim: +1, + } class AuxFieldTimeDataset(AuxFieldDataset): @@ -559,7 +560,7 @@ class ModeSolverDataset(ElectromagneticFieldDataset): ) @property - def field_components(self) -> Dict[str, DataArray]: + def field_components(self) -> dict[str, DataArray]: """Maps the field components to their associated data.""" fields = { "Ex": self.Ex, @@ -632,19 +633,19 @@ class PermittivityDataset(AbstractFieldDataset): """ @property - def field_components(self) -> Dict[str, ScalarFieldDataArray]: + def field_components(self) -> dict[str, ScalarFieldDataArray]: """Maps the field components to their associated data.""" - return dict(eps_xx=self.eps_xx, eps_yy=self.eps_yy, eps_zz=self.eps_zz) + return {"eps_xx": self.eps_xx, "eps_yy": self.eps_yy, "eps_zz": self.eps_zz} @property - def grid_locations(self) -> Dict[str, str]: + def grid_locations(self) -> dict[str, str]: """Maps field components to the string key of their grid locations on the yee lattice.""" - return dict(eps_xx="Ex", eps_yy="Ey", eps_zz="Ez") + return {"eps_xx": "Ex", "eps_yy": "Ey", "eps_zz": "Ez"} @property - def symmetry_eigenvalues(self) -> Dict[str, Callable[[Axis], float]]: + def symmetry_eigenvalues(self) -> dict[str, Callable[[Axis], float]]: """Maps field components to their (positive) symmetry eigenvalues.""" - return dict(eps_xx=None, eps_yy=None, eps_zz=None) + return {"eps_xx": None, "eps_yy": None, "eps_zz": None} eps_xx: ScalarFieldDataArray = pd.Field( ..., diff --git a/tidy3d/components/data/monitor_data.py b/tidy3d/components/data/monitor_data.py index bb73fc7d10..59ebb647f7 100644 --- a/tidy3d/components/data/monitor_data.py +++ b/tidy3d/components/data/monitor_data.py @@ -6,7 +6,7 @@ import warnings from abc import ABC from math import isclose -from typing import Any, Callable, Dict, List, Optional, Tuple, Union, get_args +from typing import Any, Callable, Literal, Optional, Union, get_args import autograd.numpy as np import pydantic.v1 as pd @@ -14,14 +14,11 @@ from pandas import DataFrame from xarray.core.types import Self -from ...constants import C_0, EPSILON_0, ETA_0, MICROMETER, UnitScaling -from ...exceptions import DataError, SetupError, Tidy3dNotImplementedError, ValidationError -from ...log import log -from ..base import TYPE_TAG_STR, cached_property, skip_if_fields_missing -from ..base_sim.data.monitor_data import AbstractMonitorData -from ..grid.grid import Coords, Grid -from ..medium import Medium, MediumType -from ..monitor import ( +from tidy3d.components.base import TYPE_TAG_STR, cached_property, skip_if_fields_missing +from tidy3d.components.base_sim.data.monitor_data import AbstractMonitorData +from tidy3d.components.grid.grid import Coords, Grid +from tidy3d.components.medium import Medium, MediumType +from tidy3d.components.monitor import ( AuxFieldTimeMonitor, DiffractionMonitor, DirectivityMonitor, @@ -38,27 +35,16 @@ MonitorType, PermittivityMonitor, ) -from ..source.base import Source -from ..source.current import ( - CustomCurrentSource, - PointDipole, -) -from ..source.field import ( - CustomFieldSource, - ModeSource, - PlaneWave, -) -from ..source.time import ( - GaussianPulse, - SourceTimeType, -) -from ..types import ( +from tidy3d.components.source.base import Source +from tidy3d.components.source.current import CustomCurrentSource, PointDipole +from tidy3d.components.source.field import CustomFieldSource, ModeSource, PlaneWave +from tidy3d.components.source.time import GaussianPulse, SourceTimeType +from tidy3d.components.types import ( ArrayFloat1D, ArrayFloat2D, Coordinate, EMField, EpsSpecType, - Literal, Numpy, PolarizationBasis, Size, @@ -66,7 +52,14 @@ TrackFreq, UnitsZBF, ) -from ..validators import enforce_monitor_fields_present, required_if_symmetry_present +from tidy3d.components.validators import ( + enforce_monitor_fields_present, + required_if_symmetry_present, +) +from tidy3d.constants import C_0, EPSILON_0, ETA_0, MICROMETER, UnitScaling +from tidy3d.exceptions import DataError, SetupError, Tidy3dNotImplementedError, ValidationError +from tidy3d.log import log + from .data_array import ( DataArray, DiffractionDataArray, @@ -128,7 +121,7 @@ def normalize(self, source_spectrum_fn: Callable[[float], complex]) -> Dataset: return self.copy() def scale_fields_by_freq_array( - self, freq_array: FreqDataArray, method: str = None + self, freq_array: FreqDataArray, method: Optional[str] = None ) -> MonitorData: """Scale fields in :class:`.MonitorData` by an array of values stored in a :class:`.FreqDataArray`. @@ -152,7 +145,7 @@ def amplitude_fn(freq: list[float]) -> complex: return self.normalize(amplitude_fn) - def _updated(self, update: Dict) -> MonitorData: + def _updated(self, update: dict) -> MonitorData: """Similar to ``updated_copy``, but does not actually copy components, for speed. Note @@ -204,7 +197,7 @@ class AbstractFieldData(MonitorData, AbstractFieldDataset, ABC): FieldMonitor, FieldTimeMonitor, AuxFieldTimeMonitor, PermittivityMonitor, ModeMonitor ] - symmetry: Tuple[Symmetry, Symmetry, Symmetry] = pd.Field( + symmetry: tuple[Symmetry, Symmetry, Symmetry] = pd.Field( (0, 0, 0), title="Symmetry", description="Symmetry eigenvalues of the original simulation in x, y, and z.", @@ -277,7 +270,7 @@ def symmetry_expanded_copy(self) -> AbstractFieldData: return self.copy(update=self._symmetry_update_dict) @property - def _symmetry_update_dict(self) -> Dict: + def _symmetry_update_dict(self) -> dict: """Dictionary of data fields to create data with expanded symmetry.""" update_dict = {} @@ -446,7 +439,7 @@ def _grid_correction_dict(self): } @property - def _tangential_dims(self) -> List[str]: + def _tangential_dims(self) -> list[str]: """For a 2D monitor data, return the names of the tangential dimensions. Raise if cannot confirm that the associated monitor is 2D.""" if len(self.monitor.zero_dims) != 1: @@ -491,7 +484,7 @@ def colocation_centers(self) -> Coords: return Coords(**colocate_centers) @property - def _plane_grid_boundaries(self) -> Tuple[Coords1D, Coords1D]: + def _plane_grid_boundaries(self) -> tuple[Coords1D, Coords1D]: """For a 2D monitor data, return the boundaries of the in-plane grid to be used to compute differential area and to colocate fields if needed.""" if np.any(np.array(self.monitor.interval_space) > 1): @@ -504,7 +497,7 @@ def _plane_grid_boundaries(self) -> Tuple[Coords1D, Coords1D]: return (bounds_dict[dim1], bounds_dict[dim2]) @property - def _plane_grid_centers(self) -> Tuple[Coords1D, Coords1D]: + def _plane_grid_centers(self) -> tuple[Coords1D, Coords1D]: """For 2D monitor data, return the centers of the in-plane grid""" return [(bs[1:] + bs[:-1]) / 2 for bs in self._plane_grid_boundaries] @@ -522,8 +515,8 @@ def _diff_area(self) -> DataArray: # Append the first and last boundary _, plane_inds = self.monitor.pop_axis([0, 1, 2], self.monitor.size.index(0.0)) - coords[0] = np.array([bounds[0][0]] + coords[0].tolist() + [bounds[0][-1]]) - coords[1] = np.array([bounds[1][0]] + coords[1].tolist() + [bounds[1][-1]]) + coords[0] = np.array([bounds[0][0], *coords[0].tolist(), bounds[0][-1]]) + coords[1] = np.array([bounds[1][0], *coords[1].tolist(), bounds[1][-1]]) """Truncate coords to monitor boundaries. This implicitly makes extra pixels which may be present have size 0 and so won't be included in the integration. For pixels intersected @@ -548,7 +541,7 @@ def _diff_area(self) -> DataArray: return DataArray(np.outer(sizes_dim0, sizes_dim1), dims=self._tangential_dims) - def _tangential_corrected(self, fields: Dict[str, DataArray]) -> Dict[str, DataArray]: + def _tangential_corrected(self, fields: dict[str, DataArray]) -> dict[str, DataArray]: """For a 2D monitor data, extract the tangential components from fields and orient them such that the third component would be the normal axis. This just means that the H field gets an extra minus sign if the normal axis is ``"y"``. Raise if any of the tangential @@ -591,7 +584,7 @@ def _tangential_corrected(self, fields: Dict[str, DataArray]) -> Dict[str, DataA return tan_fields @property - def _tangential_fields(self) -> Dict[str, DataArray]: + def _tangential_fields(self) -> dict[str, DataArray]: """For a 2D monitor data, get the tangential E and H fields in the 2D plane grid. Fields are oriented such that the third component would be the normal axis. This just means that the H field gets an extra minus sign if the normal axis is ``"y"``. @@ -603,7 +596,7 @@ def _tangential_fields(self) -> Dict[str, DataArray]: return self._tangential_corrected(self.symmetry_expanded.field_components) @property - def _colocated_fields(self) -> Dict[str, DataArray]: + def _colocated_fields(self) -> dict[str, DataArray]: """For a 2D monitor data, get all E and H fields colocated to the cell boundaries in the 2D plane grid, with symmetries expanded. """ @@ -623,7 +616,7 @@ def _colocated_fields(self) -> Dict[str, DataArray]: return colocated_fields @property - def _colocated_tangential_fields(self) -> Dict[str, DataArray]: + def _colocated_tangential_fields(self) -> dict[str, DataArray]: """For a 2D monitor data, get the tangential E and H fields colocated to the cell boundaries in the 2D plane grid. Fields are oriented such that the third component would be the normal axis. This just means that the H field gets an extra minus sign if the normal axis is @@ -786,7 +779,7 @@ def dot( return ModeAmpsDataArray(0.25 * integrand.sum(dim=d_area.dims)) - def _interpolated_tangential_fields(self, coords: ArrayFloat2D) -> Dict[str, DataArray]: + def _interpolated_tangential_fields(self, coords: ArrayFloat2D) -> dict[str, DataArray]: """For 2D monitors, interpolate this fields to given coords in the tangential plane. Parameters @@ -942,11 +935,11 @@ def fn(fields_1, fields_2): @staticmethod def _outer_fn_summation( - fields_1: Dict[str, xr.DataArray], - fields_2: Dict[str, xr.DataArray], + fields_1: dict[str, xr.DataArray], + fields_2: dict[str, xr.DataArray], outer_dim_1: str, outer_dim_2: str, - sum_dims: List[str], + sum_dims: list[str], fn: Callable, ) -> DataArray: """ @@ -1083,7 +1076,7 @@ def to_zbf( z_y: float = 0, rec_efficiency: float = 0, sys_efficiency: float = 0, - ) -> Tuple[ScalarFieldDataArray, ScalarFieldDataArray]: + ) -> tuple[ScalarFieldDataArray, ScalarFieldDataArray]: """For a 2D monitor, export the fields to a Zemax Beam File (``.zbf``). The mode area is used to approximate the beam waist, which is only valid @@ -1335,7 +1328,7 @@ def to_source( def _make_adjoint_sources( self, dataset_names: list[str], fwidth: float - ) -> List[CustomCurrentSource]: + ) -> list[CustomCurrentSource]: """Converts a :class:`.FieldData` to a list of adjoint current or point sources.""" sources = [] @@ -1584,7 +1577,7 @@ class ModeData(ModeSolverDataset, ElectromagneticFieldData): ..., title="Amplitudes", description="Complex-valued amplitudes associated with the mode." ) - eps_spec: List[EpsSpecType] = pd.Field( + eps_spec: list[EpsSpecType] = pd.Field( None, title="Permettivity Specification", description="Characterization of the permittivity profile on the plane where modes are " @@ -1607,7 +1600,7 @@ def normalize(self, source_spectrum_fn) -> ModeData: """Return copy of self after normalization is applied using source spectrum function.""" source_freq_amps = source_spectrum_fn(self.amps.f)[None, :, None] new_amps = (self.amps / source_freq_amps).astype(self.amps.dtype) - return self.copy(update=dict(amps=new_amps)) + return self.copy(update={"amps": new_amps}) def overlap_sort( self, @@ -1726,7 +1719,7 @@ def _find_ordering_one_freq( self, data_to_sort: ModeData, overlap_thresh: float, - ) -> Tuple[Numpy, Numpy]: + ) -> tuple[Numpy, Numpy]: """Find new ordering of modes in data_to_sort based on their similarity to own modes.""" num_modes = self.n_complex.sizes["mode_index"] @@ -1763,7 +1756,7 @@ def _find_ordering_one_freq( return pairs, complex_amps @staticmethod - def _find_closest_pairs(arr: Numpy) -> Tuple[Numpy, Numpy]: + def _find_closest_pairs(arr: Numpy) -> tuple[Numpy, Numpy]: """Given a complex overlap matrix pair row and column entries.""" n, k = np.shape(arr) @@ -1820,8 +1813,8 @@ def _reorder_modes( ] # Update mode_spec in the monitor - mode_spec = self.monitor.mode_spec.copy(update=dict(track_freq=track_freq)) - update_dict["monitor"] = self.monitor.copy(update=dict(mode_spec=mode_spec)) + mode_spec = self.monitor.mode_spec.copy(update={"track_freq": track_freq}) + update_dict["monitor"] = self.monitor.copy(update={"mode_spec": mode_spec}) return self.copy(update=update_dict) @@ -1927,7 +1920,7 @@ def _colocated_propagation_axes_field(self, field_name: Literal["E", "H"]) -> Da # fields as a (3, ...) numpy array ordered as [tangential1, tagential2, normal] field = [fields[field_name + dim].values for dim in tan_dims] - field = np.array(field + [fields[field_name + normal_dim].values]) + field = np.array([*field, fields[field_name + normal_dim].values]) # rotate axes if mode_spec.angle_phi != 0: @@ -2268,7 +2261,7 @@ class FluxData(MonitorData): def _make_adjoint_sources( self, dataset_names: list[str], fwidth: float - ) -> List[Union[CustomCurrentSource, PointDipole]]: + ) -> list[Union[CustomCurrentSource, PointDipole]]: """Converts a :class:`.FieldData` to a list of adjoint current or point sources.""" # avoids error in edge case where there are extraneous flux monitors not used in objective @@ -2289,7 +2282,7 @@ def normalize(self, source_spectrum_fn) -> FluxData: source_freq_amps = source_spectrum_fn(self.flux.f) source_power = abs(source_freq_amps) ** 2 new_flux = (self.flux / source_power).astype(self.flux.dtype) - return self.copy(update=dict(flux=new_flux)) + return self.copy(update={"flux": new_flux}) class FluxTimeData(MonitorData): @@ -2392,16 +2385,16 @@ class AbstractFieldProjectionData(MonitorData): ) @property - def field_components(self) -> Dict[str, DataArray]: + def field_components(self) -> dict[str, DataArray]: """Maps the field components to their associated data.""" - return dict( - Er=self.Er, - Etheta=self.Etheta, - Ephi=self.Ephi, - Hr=self.Hr, - Htheta=self.Htheta, - Hphi=self.Hphi, - ) + return { + "Er": self.Er, + "Etheta": self.Etheta, + "Ephi": self.Ephi, + "Hr": self.Hr, + "Htheta": self.Htheta, + "Hphi": self.Hphi, + } @property def f(self) -> np.ndarray: @@ -2409,12 +2402,12 @@ def f(self) -> np.ndarray: return np.array(self.Etheta.coords["f"]) @property - def coords(self) -> Dict[str, np.ndarray]: + def coords(self) -> dict[str, np.ndarray]: """Coordinates of the fields contained.""" return self.Etheta.coords @property - def coords_spherical(self) -> Dict[str, np.ndarray]: + def coords_spherical(self) -> dict[str, np.ndarray]: """Coordinates grid for the fields in the spherical system.""" if "theta" in self.coords.keys(): r, theta, phi = np.meshgrid( @@ -2442,7 +2435,7 @@ def coords_spherical(self) -> Dict[str, np.ndarray]: return {"r": r, "theta": theta, "phi": phi} @property - def dims(self) -> Tuple[str, ...]: + def dims(self) -> tuple[str, ...]: """Dimensions of the radiation vectors contained.""" return self.Etheta.dims @@ -2450,7 +2443,7 @@ def make_data_array(self, data: np.ndarray) -> DataArray: """Make an DataArray with data and same coords and dims as fields of self.""" return DataArray(data=data, coords=self.coords, dims=self.dims) - def make_dataset(self, keys: Tuple[str, ...], vals: Tuple[np.ndarray, ...]) -> xr.Dataset: + def make_dataset(self, keys: tuple[str, ...], vals: tuple[np.ndarray, ...]) -> xr.Dataset: """Make an xr.Dataset with keys and data with same coords and dims as fields.""" data_arrays = tuple(map(self.make_data_array, vals)) return xr.Dataset(dict(zip(keys, data_arrays))) @@ -2484,7 +2477,7 @@ def wavenumber(medium: MediumType, frequency: float) -> complex: return (2 * np.pi * frequency / C_0) * (index_n + 1j * index_k) @property - def nk(self) -> Tuple[float, float]: + def nk(self) -> tuple[float, float]: """Returns the real and imaginary parts of the background medium's refractive index.""" return self.medium.nk_model(frequency=self.f) @@ -2608,7 +2601,7 @@ def radar_cross_section(self) -> DataArray: def _make_adjoint_sources( self, dataset_names: list[str], fwidth: float - ) -> List[Union[CustomCurrentSource, PointDipole]]: + ) -> list[Union[CustomCurrentSource, PointDipole]]: """Error if server-side field projection is used for autograd""" raise NotImplementedError( @@ -2650,7 +2643,7 @@ class FieldProjectionAngleData(AbstractFieldProjectionData): description="Field projection monitor with an angle-based projection grid.", ) - projection_surfaces: Tuple[FieldProjectionSurface, ...] = pd.Field( + projection_surfaces: tuple[FieldProjectionSurface, ...] = pd.Field( ..., title="Projection surfaces", description="Surfaces of the monitor where near fields were recorded for projection", @@ -2860,7 +2853,7 @@ class FieldProjectionCartesianData(AbstractFieldProjectionData): description="Field projection monitor with a Cartesian projection grid.", ) - projection_surfaces: Tuple[FieldProjectionSurface, ...] = pd.Field( + projection_surfaces: tuple[FieldProjectionSurface, ...] = pd.Field( ..., title="Projection surfaces", description="Surfaces of the monitor where near fields were recorded for projection", @@ -3013,7 +3006,7 @@ class FieldProjectionKSpaceData(AbstractFieldProjectionData): description="Field projection monitor with a projection grid defined in k-space.", ) - projection_surfaces: Tuple[FieldProjectionSurface, ...] = pd.Field( + projection_surfaces: tuple[FieldProjectionSurface, ...] = pd.Field( ..., title="Projection surfaces", description="Surfaces of the monitor where near fields were recorded for projection", @@ -3171,14 +3164,14 @@ class DiffractionData(AbstractFieldProjectionData): description="Spatial distribution of phi-component of the magnetic field.", ) - sim_size: Tuple[float, float] = pd.Field( + sim_size: tuple[float, float] = pd.Field( ..., title="Domain size", description="Size of the near field in the local x and y directions.", units=MICROMETER, ) - bloch_vecs: Union[Tuple[float, float], Tuple[ArrayFloat1D, ArrayFloat1D]] = pd.Field( + bloch_vecs: Union[tuple[float, float], tuple[ArrayFloat1D, ArrayFloat1D]] = pd.Field( ..., title="Bloch vectors", description="Bloch vectors along the local x and y directions in units of " @@ -3186,7 +3179,7 @@ class DiffractionData(AbstractFieldProjectionData): ) @staticmethod - def shifted_orders(orders: Tuple[int, ...], bloch_vec: Union[float, np.ndarray]) -> np.ndarray: + def shifted_orders(orders: tuple[int, ...], bloch_vec: Union[float, np.ndarray]) -> np.ndarray: """Diffraction orders shifted by the Bloch vector.""" return bloch_vec + np.atleast_2d(orders).T @@ -3207,8 +3200,8 @@ def reciprocal_coords( @staticmethod def compute_angles( - reciprocal_vectors: Tuple[np.ndarray, np.ndarray], - ) -> Tuple[np.ndarray, np.ndarray]: + reciprocal_vectors: tuple[np.ndarray, np.ndarray], + ) -> tuple[np.ndarray, np.ndarray]: """Compute the polar and azimuth angles associated with the given reciprocal vectors.""" # some wave number pairs are outside the light cone, leading to warnings from numpy.arcsin with warnings.catch_warnings(): @@ -3220,7 +3213,7 @@ def compute_angles( return (thetas, phis) @property - def coords_spherical(self) -> Dict[str, np.ndarray]: + def coords_spherical(self) -> dict[str, np.ndarray]: """Coordinates grid for the fields in the spherical system.""" theta, phi = self.angles return {"r": None, "theta": theta, "phi": phi} @@ -3236,7 +3229,7 @@ def orders_y(self) -> np.ndarray: return np.atleast_1d(np.array(self.Etheta.coords["orders_y"])) @property - def reciprocal_vectors(self) -> Tuple[np.ndarray, np.ndarray]: + def reciprocal_vectors(self) -> tuple[np.ndarray, np.ndarray]: """Get the normalized "ux" and "uy" reciprocal vectors.""" return (self.ux, self.uy) @@ -3267,7 +3260,7 @@ def uy(self) -> np.ndarray: ) @property - def angles(self) -> Tuple[DataArray]: + def angles(self) -> tuple[DataArray]: """The (theta, phi) angles corresponding to each allowed pair of diffraction orders storeds as data arrays. Disallowed angles are set to ``np.nan``. """ @@ -3350,7 +3343,7 @@ def fields_cartesian(self) -> xr.Dataset: keys = ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"] return self._make_dataset(fields, keys) - def _make_dataset(self, fields: Tuple[np.ndarray, ...], keys: Tuple[str, ...]) -> xr.Dataset: + def _make_dataset(self, fields: tuple[np.ndarray, ...], keys: tuple[str, ...]) -> xr.Dataset: """Make an xr.Dataset for fields with given field names.""" data_arrays = [] for field in fields: @@ -3412,7 +3405,7 @@ def adjoint_source_amp(self, amp: DataArray, fwidth: float) -> PlaneWave: # compute the angle corresponding to this amplitude theta_data, phi_data = self.angles - angle_sel_kwargs = dict(orders_x=int(order_x), orders_y=int(order_y), f=float(freq0)) + angle_sel_kwargs = {"orders_x": int(order_x), "orders_y": int(order_y), "f": float(freq0)} angle_theta = float(theta_data.sel(**angle_sel_kwargs)) angle_phi = float(phi_data.sel(**angle_sel_kwargs)) @@ -3508,7 +3501,7 @@ def from_spherical_field_dataset( New :class:`.DirectivityData` instance with computed flux from spherical field integration. """ f = list(monitor.freqs) - flux = FluxDataArray(np.zeros(len(f)), coords=dict(f=f)) + flux = FluxDataArray(np.zeros(len(f)), coords={"f": f}) dir_data = DirectivityData( monitor=monitor, flux=flux, @@ -3563,7 +3556,7 @@ def _check_valid_pol_basis(pol_basis: PolarizationBasis, tilt_angle: float): raise ValueError("'tilt_angle' is only defined for linear polarization.") def partial_radiation_intensity( - self, pol_basis: PolarizationBasis = "linear", tilt_angle: float = None + self, pol_basis: PolarizationBasis = "linear", tilt_angle: Optional[float] = None ) -> xr.Dataset: """Partial radiation intensity in the frequency domain as a function of angles theta and phi. The partial radiation intensities are computed in the ``linear`` or ``circular`` polarization @@ -3635,13 +3628,13 @@ def radiated_power(self) -> FreqDataArray: isinstance(self.monitor, FieldProjectionAngleMonitor) or self.monitor.size.count(0.0) == 0 ): - return FreqDataArray(self.flux.values, dict(f=self.f)) + return FreqDataArray(self.flux.values, {"f": self.f}) # The monitor could be planar and directed downward sign = 1.0 if self.monitor.normal_dir == "+" else -1.0 - return FreqDataArray(sign * self.flux.values, dict(f=self.f)) + return FreqDataArray(sign * self.flux.values, {"f": self.f}) def partial_directivity( - self, pol_basis: PolarizationBasis = "linear", tilt_angle: float = None + self, pol_basis: PolarizationBasis = "linear", tilt_angle: Optional[float] = None ) -> xr.Dataset: """Directivity in the frequency domain as a function of angles theta and phi. The partial directivities are computed in the ``linear`` or ``circular`` polarization @@ -3704,13 +3697,13 @@ def calc_radiation_efficiency(self, power_in: FreqDataArray) -> FreqDataArray: Radiation efficiency (dimensionless) in the frequency domain, computed as radiated_power / power_in. """ - return FreqDataArray((self.radiated_power / power_in).values, dict(f=self.f)) + return FreqDataArray((self.radiated_power / power_in).values, {"f": self.f}) def calc_partial_gain( self, power_in: FreqDataArray, pol_basis: PolarizationBasis = "linear", - tilt_angle: float = None, + tilt_angle: Optional[float] = None, ) -> xr.Dataset: """The partial gain figures of merit for antennas. The partial gains are computed in the ``linear`` or ``circular`` polarization bases. If ``tilt_angle`` is not ``None``, diff --git a/tidy3d/components/data/sim_data.py b/tidy3d/components/data/sim_data.py index d0c1142b44..3807a88f24 100644 --- a/tidy3d/components/data/sim_data.py +++ b/tidy3d/components/data/sim_data.py @@ -6,35 +6,31 @@ import pathlib from abc import ABC from collections import defaultdict -from typing import Callable, Tuple, Union +from typing import Callable, Optional, Union import h5py import numpy as np import pydantic.v1 as pd import xarray as xr -from ...constants import C_0, inf -from ...exceptions import DataError, FileError, Tidy3dKeyError -from ...log import log -from ..autograd.utils import split_list -from ..base import JSON_TAG, Tidy3dBaseModel -from ..base_sim.data.sim_data import AbstractSimulationData -from ..file_util import replace_values -from ..monitor import Monitor -from ..simulation import Simulation -from ..source.current import CustomCurrentSource -from ..source.time import GaussianPulse -from ..source.utils import SourceType -from ..structure import Structure -from ..types import Ax, Axis, ColormapType, FieldVal, PlotScale, annotate_type -from ..viz import add_ax_if_none, equal_aspect +from tidy3d.components.autograd.utils import split_list +from tidy3d.components.base import JSON_TAG, Tidy3dBaseModel +from tidy3d.components.base_sim.data.sim_data import AbstractSimulationData +from tidy3d.components.file_util import replace_values +from tidy3d.components.monitor import Monitor +from tidy3d.components.simulation import Simulation +from tidy3d.components.source.current import CustomCurrentSource +from tidy3d.components.source.time import GaussianPulse +from tidy3d.components.source.utils import SourceType +from tidy3d.components.structure import Structure +from tidy3d.components.types import Ax, Axis, ColormapType, FieldVal, PlotScale, annotate_type +from tidy3d.components.viz import add_ax_if_none, equal_aspect +from tidy3d.constants import C_0, inf +from tidy3d.exceptions import DataError, FileError, Tidy3dKeyError +from tidy3d.log import log + from .data_array import FreqDataArray -from .monitor_data import ( - AbstractFieldData, - FieldTimeData, - MonitorDataType, - MonitorDataTypes, -) +from .monitor_data import AbstractFieldData, FieldTimeData, MonitorDataType, MonitorDataTypes DATA_TYPE_MAP = {data.__fields__["monitor"].type_: data for data in MonitorDataTypes} @@ -54,7 +50,7 @@ class AdjointSourceInfo(Tidy3dBaseModel): """Stores information about the adjoint sources to pass to autograd pipeline.""" - sources: Tuple[annotate_type(SourceType), ...] = pd.Field( + sources: tuple[annotate_type(SourceType), ...] = pd.Field( ..., title="Adjoint Sources", description="Set of processed sources to include in the adjoint simulation.", @@ -452,8 +448,8 @@ def plot_field_monitor_data( eps_alpha: float = 0.2, phase: float = 0.0, robust: bool = True, - vmin: float = None, - vmax: float = None, + vmin: Optional[float] = None, + vmax: Optional[float] = None, ax: Ax = None, shading: str = "flat", **sel_kwargs, @@ -567,7 +563,7 @@ def plot_field_monitor_data( if field_data.coords[axis].size <= 1: field_data = field_data.sel(**{axis: pos}, method="nearest") else: - field_data = field_data.interp(**{axis: pos}, kwargs=dict(bounds_error=True)) + field_data = field_data.interp(**{axis: pos}, kwargs={"bounds_error": True}) # warn about new API changes and replace the values if "freq" in sel_kwargs: @@ -597,7 +593,7 @@ def plot_field_monitor_data( field_data = field_data.sel(**{coord_name: coord_val}, method=None) else: field_data = field_data.interp( - **{coord_name: coord_val}, kwargs=dict(bounds_error=True) + **{coord_name: coord_val}, kwargs={"bounds_error": True} ) # before dropping coordinates, check if a frequency can be derived from the data that can @@ -663,8 +659,8 @@ def plot_field( eps_alpha: float = 0.2, phase: float = 0.0, robust: bool = True, - vmin: float = None, - vmax: float = None, + vmin: Optional[float] = None, + vmax: Optional[float] = None, ax: Ax = None, shading: str = "flat", **sel_kwargs, @@ -741,11 +737,11 @@ def plot_scalar_array( field_data: xr.DataArray, axis: Axis, position: float, - freq: float = None, + freq: Optional[float] = None, eps_alpha: float = 0.2, robust: bool = True, - vmin: float = None, - vmax: float = None, + vmin: Optional[float] = None, + vmax: Optional[float] = None, cmap_type: ColormapType = "divergent", ax: Ax = None, **kwargs, @@ -919,7 +915,7 @@ class SimulationData(AbstractYeeGridSimulationData): description="Original :class:`.Simulation` associated with the data.", ) - data: Tuple[annotate_type(MonitorDataType), ...] = pd.Field( + data: tuple[annotate_type(MonitorDataType), ...] = pd.Field( ..., title="Monitor Data", description="List of :class:`.MonitorData` instances " @@ -997,9 +993,9 @@ def source_spectrum_fn(freqs): # Make a new monitor_data dictionary with renormalized data data_normalized = [mnt_data.normalize(source_spectrum_fn) for mnt_data in self.data] - simulation = self.simulation.copy(update=dict(normalize_index=normalize_index)) + simulation = self.simulation.copy(update={"normalize_index": normalize_index}) - return self.copy(update=dict(simulation=simulation, data=data_normalized)) + return self.copy(update={"simulation": simulation, "data": data_normalized}) def _split_adjoint_data(self: SimulationData, num_mnts_original: int) -> tuple[list, list]: """Split data list into original, adjoint field, and adjoint permittivity.""" @@ -1015,7 +1011,7 @@ def _split_adjoint_data(self: SimulationData, num_mnts_original: int) -> tuple[l return data_original, data_adjoint - def _split_original_fwd(self, num_mnts_original: int) -> Tuple[SimulationData, SimulationData]: + def _split_original_fwd(self, num_mnts_original: int) -> tuple[SimulationData, SimulationData]: """Split this simulation data into original and fwd data from number of original mnts.""" # split the data and monitors into the original ones & adjoint gradient ones (for 'fwd') @@ -1083,12 +1079,12 @@ def _make_adjoint_sims( ] # fields to update the 'fwd' simulation with to make it 'adj' - sim_adj_update_dict = dict( - sources=adjoint_source_info.sources, - boundary_spec=bc_adj, - monitors=monitors, - post_norm=adjoint_source_info.post_norm, - ) + sim_adj_update_dict = { + "sources": adjoint_source_info.sources, + "boundary_spec": bc_adj, + "monitors": monitors, + "post_norm": adjoint_source_info.post_norm, + } if not adjoint_source_info.normalize_sim: sim_adj_update_dict["normalize_index"] = None @@ -1266,7 +1262,7 @@ def _make_post_norm_amps(adj_srcs: list[SourceType]) -> xr.DataArray: amp_complex = src_time.amplitude * np.exp(1j * src_time.phase) amps_complex.append(amp_complex) - coords = dict(f=freqs) + coords = {"f": freqs} amps_complex = np.array(amps_complex) return xr.DataArray(amps_complex, coords=coords) diff --git a/tidy3d/components/data/unstructured/base.py b/tidy3d/components/data/unstructured/base.py index cd27e32707..b6be6dce38 100644 --- a/tidy3d/components/data/unstructured/base.py +++ b/tidy3d/components/data/unstructured/base.py @@ -4,7 +4,7 @@ import numbers from abc import ABC, abstractmethod -from typing import Literal, Tuple, Union +from typing import Literal, Optional, Union import numpy as np import pydantic.v1 as pd @@ -169,7 +169,7 @@ def check_cell_vertex_range(cls, val, values): raise ValidationError( "Cell connections array uses undefined point indices in the range " f"[{min_index_used}, {max_index_used}]. The valid range of point indices is " - f"[0, {num_points-1}]." + f"[0, {num_points - 1}]." ) return val @@ -312,7 +312,7 @@ def _remove_degenerate_cells(cls, cells: CellDataArray): data = np.delete(cells.values, list(degenerate_cells), axis=0) cell_index = np.delete(cells.cell_index.values, list(degenerate_cells)) return CellDataArray( - data=data, coords=dict(cell_index=cell_index, vertex_index=cells.vertex_index) + data=data, coords={"cell_index": cell_index, "vertex_index": cells.vertex_index} ) return cells @@ -489,7 +489,7 @@ def _read_vtkUnstructuredGrid(fname: str): def _from_vtk_obj( cls, vtk_obj, - field: str = None, + field: Optional[str] = None, remove_degenerate_cells: bool = False, remove_unused_points: bool = False, values_type=IndexedDataArray, @@ -521,7 +521,7 @@ def _from_vtk_obj_internal( def from_vtu( cls, file: str, - field: str = None, + field: Optional[str] = None, remove_degenerate_cells: bool = False, remove_unused_points: bool = False, ) -> UnstructuredGridDataset: @@ -572,7 +572,7 @@ def _get_values_from_vtk( cls, vtk_obj, num_points: pd.PositiveInt, - field: str = None, + field: Optional[str] = None, values_type=IndexedDataArray, expect_complex=None, ) -> IndexedDataArray: @@ -633,7 +633,7 @@ def _get_values_from_vtk( "'.values' while the rest will be ignored." ) - values_coords = dict(index=np.arange(num_points)) + values_coords = {"index": np.arange(num_points)} if isinstance(field, dict): values_coords.update(field) @@ -796,8 +796,8 @@ def interp( x: Union[float, ArrayLike] = None, y: Union[float, ArrayLike] = None, z: Union[float, ArrayLike] = None, - fill_value: Union[ - float, Literal["extrapolate"] + fill_value: Optional[ + Union[float, Literal["extrapolate"]] ] = None, # TODO: an array if multiple fields? use_vtk: bool = False, method: Literal["linear", "nearest"] = "linear", @@ -909,7 +909,7 @@ def _non_spatial_interp(self, method="linear", fill_value=np.nan, **coords_kwarg values=self.values.interp( **coords_kwargs_only_lists, method="linear", - kwargs=dict(fill_value=fill_value), + kwargs={"fill_value": fill_value}, ) ) @@ -918,8 +918,8 @@ def _spatial_interp( x: Union[float, ArrayLike], y: Union[float, ArrayLike], z: Union[float, ArrayLike], - fill_value: Union[ - float, Literal["extrapolate"] + fill_value: Optional[ + Union[float, Literal["extrapolate"]] ] = None, # TODO: an array if multiple fields? use_vtk: bool = False, method: Literal["linear", "nearest"] = "linear", @@ -1001,7 +1001,7 @@ def _spatial_interp( interpolated_values, x=x, y=y, z=z ) - coords_dict = dict(x=x, y=y, z=z) + coords_dict = {"x": x, "y": y, "z": z} coords_dict.update(self._values_coords_dict) if len(self._values_coords_dict) == 0: @@ -1370,7 +1370,7 @@ def _interp_py_general( # in case of 2d grid broadcast results along normal direction assuming translational # invariance if num_dims == 2: - orig_shape = [len(x), len(y), len(z)] + self._fields_shape + orig_shape = [len(x), len(y), len(z), *self._fields_shape] flat_shape = orig_shape.copy() flat_shape[axis_ignore] = 1 interpolated_values = np.reshape(interpolated_values, flat_shape) @@ -1380,12 +1380,12 @@ def _interp_py_general( def _interp_py_chunk( self, - xyz_grid: Tuple[ArrayLike[float], ...], + xyz_grid: tuple[ArrayLike[float], ...], cell_inds: ArrayLike[int], cell_ind_min: ArrayLike[int], cell_ind_max: ArrayLike[int], sdf_tol: float, - ) -> Tuple[Tuple[ArrayLike, ...], ArrayLike]: + ) -> tuple[tuple[ArrayLike, ...], ArrayLike]: """For each cell listed in ``cell_inds`` perform interpolation at a rectilinear subarray of xyz_grid given by a (3D) index span (cell_ind_min, cell_ind_max). @@ -1552,7 +1552,7 @@ def _interp_py_chunk( # interpolated_value = value0 * face0_sdf / dist0_sdf + ... # (because face0_sdf / dist0_sdf is linear shape function for vertex0) sdf = -inf * np.ones(num_samples_total) - interpolated = np.zeros([num_samples_total] + self._fields_shape, dtype=self._double_type) + interpolated = np.zeros([num_samples_total, *self._fields_shape], dtype=self._double_type) # coordinates of each sample point sample_xyz = np.zeros((num_samples_total, num_dims)) @@ -1623,7 +1623,7 @@ def sel( x: Union[float, ArrayLike] = None, y: Union[float, ArrayLike] = None, z: Union[float, ArrayLike] = None, - method: Literal["None", "nearest", "pad", "ffill", "backfill", "bfill"] = None, + method: Optional[Literal["None", "nearest", "pad", "ffill", "backfill", "bfill"]] = None, **sel_kwargs, ) -> Union[UnstructuredGridDataset, XrDataArray]: """Extract/interpolate data along one or more spatial or non-spatial directions. Must provide at least one argument diff --git a/tidy3d/components/data/unstructured/tetrahedral.py b/tidy3d/components/data/unstructured/tetrahedral.py index 6c7d8ffc16..0ef09ff5b9 100644 --- a/tidy3d/components/data/unstructured/tetrahedral.py +++ b/tidy3d/components/data/unstructured/tetrahedral.py @@ -118,14 +118,15 @@ def _from_vtk_obj( cells = CellDataArray( cells_numpy, - coords=dict( - cell_index=np.arange(num_cells), vertex_index=np.arange(cls._cell_num_vertices()) - ), + coords={ + "cell_index": np.arange(num_cells), + "vertex_index": np.arange(cls._cell_num_vertices()), + }, ) points = PointDataArray( points_numpy, - coords=dict(index=np.arange(len(points_numpy)), axis=np.arange(cls._point_dims())), + coords={"index": np.arange(len(points_numpy)), "axis": np.arange(cls._point_dims())}, ) if remove_degenerate_cells: diff --git a/tidy3d/components/data/unstructured/triangular.py b/tidy3d/components/data/unstructured/triangular.py index 21c2416da9..6845216ce6 100644 --- a/tidy3d/components/data/unstructured/triangular.py +++ b/tidy3d/components/data/unstructured/triangular.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, Literal, Union +from typing import Literal, Optional, Union import numpy as np import pydantic.v1 as pd @@ -180,14 +180,15 @@ def _from_vtk_obj( cells = CellDataArray( cells_numpy, - coords=dict( - cell_index=np.arange(num_cells), vertex_index=np.arange(cls._cell_num_vertices()) - ), + coords={ + "cell_index": np.arange(num_cells), + "vertex_index": np.arange(cls._cell_num_vertices()), + }, ) points = PointDataArray( points_2d_numpy, - coords=dict(index=np.arange(len(points_numpy)), axis=np.arange(cls._point_dims())), + coords={"index": np.arange(len(points_numpy)), "axis": np.arange(cls._point_dims())}, ) if remove_degenerate_cells: @@ -304,7 +305,7 @@ def _spatial_interp( x: Union[float, ArrayLike], y: Union[float, ArrayLike], z: Union[float, ArrayLike], - fill_value: Union[float, Literal["extrapolate"]] = None, + fill_value: Optional[Union[float, Literal["extrapolate"]]] = None, use_vtk: bool = False, method: Literal["linear", "nearest"] = "linear", ignore_normal_pos: bool = True, @@ -376,7 +377,7 @@ def _spatial_interp( interp_inplane, [len(np.atleast_1d(comp)) for comp in [x, y, z]] + self._fields_shape ) - coords_dict = dict(x=x, y=y, z=z) + coords_dict = {"x": x, "y": y, "z": z} coords_dict.update(self._values_coords_dict) if len(self._values_coords_dict) == 0: @@ -443,7 +444,7 @@ def sel( x: Union[float, ArrayLike] = None, y: Union[float, ArrayLike] = None, z: Union[float, ArrayLike] = None, - method: Literal["None", "nearest", "pad", "ffill", "backfill", "bfill"] = None, + method: Optional[Literal["None", "nearest", "pad", "ffill", "backfill", "bfill"]] = None, **sel_kwargs, ) -> XrDataArray: """Extract/interpolate data along one or more spatial or non-spatial directions. Must provide at least one argument @@ -574,11 +575,11 @@ def plot( grid: bool = True, cbar: bool = True, cmap: str = "viridis", - vmin: float = None, - vmax: float = None, + vmin: Optional[float] = None, + vmax: Optional[float] = None, shading: Literal["gourand", "flat"] = "gouraud", - cbar_kwargs: Dict = None, - pcolor_kwargs: Dict = None, + cbar_kwargs: Optional[dict] = None, + pcolor_kwargs: Optional[dict] = None, ) -> Ax: """Plot the data field and/or the unstructured grid. diff --git a/tidy3d/components/data/utils.py b/tidy3d/components/data/utils.py index 9e64765fd3..e2c96c0031 100644 --- a/tidy3d/components/data/utils.py +++ b/tidy3d/components/data/utils.py @@ -7,7 +7,8 @@ import numpy as np import xarray as xr -from ..types import ArrayLike, annotate_type +from tidy3d.components.types import ArrayLike, annotate_type + from .data_array import DataArray, SpatialDataArray from .unstructured.base import UnstructuredGridDataset from .unstructured.tetrahedral import TetrahedralGridDataset diff --git a/tidy3d/components/data/validators.py b/tidy3d/components/data/validators.py index 98d49ef56b..4988c36044 100644 --- a/tidy3d/components/data/validators.py +++ b/tidy3d/components/data/validators.py @@ -1,9 +1,13 @@ # special validators for Datasets +from __future__ import annotations + +from typing import Optional import numpy as np import pydantic.v1 as pd -from ...exceptions import ValidationError +from tidy3d.exceptions import ValidationError + from .data_array import DataArray from .dataset import AbstractFieldDataset, ScalarFieldDataArray @@ -19,7 +23,7 @@ def no_nans(cls, val): if val is None: return val - def error_if_has_nans(value, identifier: str = None) -> None: + def error_if_has_nans(value, identifier: Optional[str] = None) -> None: """Recursively check if value (or iterable) has nans and error if so.""" def has_nans(values) -> bool: diff --git a/tidy3d/components/data/zbf.py b/tidy3d/components/data/zbf.py index 947953d00c..d05d058d25 100644 --- a/tidy3d/components/data/zbf.py +++ b/tidy3d/components/data/zbf.py @@ -7,7 +7,7 @@ import numpy as np import pydantic.v1 as pd -from ..base import Tidy3dBaseModel +from tidy3d.components.base import Tidy3dBaseModel class ZBFData(Tidy3dBaseModel): @@ -118,7 +118,7 @@ def read_zbf(filename: str) -> ZBFData: except KeyError: raise KeyError( f"Invalid units specified in the zbf file (expected '0', '1', '2', or '3', got '{units}')." - ) + ) from None # load E field Ex_real = np.asarray(rawx[0::2]).reshape(nx, ny) diff --git a/tidy3d/components/dispersion_fitter.py b/tidy3d/components/dispersion_fitter.py index 07fecf866e..04dd1e1605 100644 --- a/tidy3d/components/dispersion_fitter.py +++ b/tidy3d/components/dispersion_fitter.py @@ -2,16 +2,17 @@ from __future__ import annotations -from typing import Optional, Tuple +from typing import Optional import numpy as np import scipy from pydantic.v1 import Field, NonNegativeFloat, PositiveFloat, PositiveInt, validator from rich.progress import Progress -from ..constants import fp_eps -from ..exceptions import ValidationError -from ..log import get_logging_console, log +from tidy3d.constants import fp_eps +from tidy3d.exceptions import ValidationError +from tidy3d.log import get_logging_console, log + from .base import Tidy3dBaseModel, cached_property, skip_if_fields_missing from .types import ArrayComplex1D, ArrayComplex2D, ArrayFloat1D, ArrayFloat2D @@ -111,7 +112,7 @@ def _extrema_loss_freq_finder(areal, aimag, creal, cimag): class AdvancedFastFitterParam(Tidy3dBaseModel): """Advanced fast fitter parameters.""" - loss_bounds: Tuple[float, float] = Field( + loss_bounds: tuple[float, float] = Field( (0, np.inf), title="Loss bounds", description="Bounds (lower, upper) on Im[resp]. Default corresponds to only passivity. " @@ -121,7 +122,7 @@ class AdvancedFastFitterParam(Tidy3dBaseModel): "A finite upper bound may be helpful when fitting lossless materials. " "In this case, consider also increasing the weight for fitting the imaginary part.", ) - weights: Tuple[NonNegativeFloat, NonNegativeFloat] = Field( + weights: tuple[NonNegativeFloat, NonNegativeFloat] = Field( None, title="Weights", description="Weights (real, imag) in objective function for fitting. The weights " @@ -350,7 +351,7 @@ def complex_poles(self) -> ArrayFloat1D: return self.poles[np.iscomplex(self.poles)] @classmethod - def get_default_weights(cls, eps: ArrayComplex1D) -> Tuple[float, float]: + def get_default_weights(cls, eps: ArrayComplex1D) -> tuple[float, float]: """Default weights based on real and imaginary part of eps.""" rms = np.array([np.sqrt(np.mean(x**2)) for x in (np.real(eps), np.imag(eps))]) rms = np.maximum(RMS_MIN, rms) @@ -360,7 +361,7 @@ def get_default_weights(cls, eps: ArrayComplex1D) -> Tuple[float, float]: return tuple(weights) @cached_property - def pole_residue(self) -> Tuple[float, ArrayComplex1D, ArrayComplex1D]: + def pole_residue(self) -> tuple[float, ArrayComplex1D, ArrayComplex1D]: """Parameters for pole-residue model in original units.""" if self.eps_inf is None or self.poles is None: return 1, [], [] @@ -647,7 +648,7 @@ def iterate_fit(self) -> FastFitterData: return model - def iterate_passivity(self, passivity_omega: ArrayFloat1D) -> Tuple[FastFitterData, int]: + def iterate_passivity(self, passivity_omega: ArrayFloat1D) -> tuple[FastFitterData, int]: """Iterate passivity enforcement algorithm.""" size = len(self.real_poles) + 2 * len(self.complex_poles) @@ -724,7 +725,7 @@ def enforce_passivity( def _fit_fixed_parameters( - num_poles_range: Tuple[PositiveInt, PositiveInt], model: FastFitterData + num_poles_range: tuple[PositiveInt, PositiveInt], model: FastFitterData ) -> FastFitterData: def fit_non_passive(model: FastFitterData) -> FastFitterData: best_model = model @@ -755,11 +756,11 @@ def fit( resp_data: ArrayComplex1D, min_num_poles: PositiveInt = 1, max_num_poles: PositiveInt = DEFAULT_MAX_POLES, - resp_inf: float = None, + resp_inf: Optional[float] = None, tolerance_rms: NonNegativeFloat = DEFAULT_TOLERANCE_RMS, advanced_param: AdvancedFastFitterParam = None, scale_factor: PositiveFloat = 1, -) -> Tuple[Tuple[float, ArrayComplex1D, ArrayComplex1D], float]: +) -> tuple[tuple[float, ArrayComplex1D, ArrayComplex1D], float]: """Fit data using a fast fitting algorithm. Note @@ -840,7 +841,7 @@ def fit( advanced_param=advanced_param or AdvancedFastFitterParam(), scale_factor=scale_factor, ) - log.info(f"Fitting weights=({init_model.weights[0]:.3g}, " f"{init_model.weights[1]:.3g}).") + log.info(f"Fitting weights=({init_model.weights[0]:.3g}, {init_model.weights[1]:.3g}).") def make_configs(): configs = [[p] for p in range(max(min_num_poles // 2, 1), max_num_poles + 1)] @@ -851,9 +852,9 @@ def make_configs(): init_model.optimize_eps_inf, ]: if setting is None: - configs = [c + [r] for c in configs for r in [True, False]] + configs = [[*c, r] for c in configs for r in [True, False]] else: - configs = [c + [r] for c in configs for r in [setting]] + configs = [[*c, r] for c in configs for r in [setting]] return configs best_model = init_model diff --git a/tidy3d/components/eme/data/dataset.py b/tidy3d/components/eme/data/dataset.py index f0dcd772c8..33f45cef2c 100644 --- a/tidy3d/components/eme/data/dataset.py +++ b/tidy3d/components/eme/data/dataset.py @@ -4,14 +4,14 @@ import pydantic.v1 as pd -from ...data.data_array import ( +from tidy3d.components.data.data_array import ( EMECoefficientDataArray, EMEModeIndexDataArray, EMEScalarFieldDataArray, EMEScalarModeFieldDataArray, EMESMatrixDataArray, ) -from ...data.dataset import Dataset, ElectromagneticFieldDataset +from tidy3d.components.data.dataset import Dataset, ElectromagneticFieldDataset class EMESMatrixDataset(Dataset): diff --git a/tidy3d/components/eme/data/monitor_data.py b/tidy3d/components/eme/data/monitor_data.py index 7014562e11..65817a6de7 100644 --- a/tidy3d/components/eme/data/monitor_data.py +++ b/tidy3d/components/eme/data/monitor_data.py @@ -6,9 +6,18 @@ import pydantic.v1 as pd -from ...base_sim.data.monitor_data import AbstractMonitorData -from ...data.monitor_data import ElectromagneticFieldData, ModeSolverData, PermittivityData -from ..monitor import EMECoefficientMonitor, EMEFieldMonitor, EMEModeSolverMonitor +from tidy3d.components.base_sim.data.monitor_data import AbstractMonitorData +from tidy3d.components.data.monitor_data import ( + ElectromagneticFieldData, + ModeSolverData, + PermittivityData, +) +from tidy3d.components.eme.monitor import ( + EMECoefficientMonitor, + EMEFieldMonitor, + EMEModeSolverMonitor, +) + from .dataset import EMECoefficientDataset, EMEFieldDataset, EMEModeSolverDataset diff --git a/tidy3d/components/eme/data/sim_data.py b/tidy3d/components/eme/data/sim_data.py index ef15a45c55..da6f04dbe5 100644 --- a/tidy3d/components/eme/data/sim_data.py +++ b/tidy3d/components/eme/data/sim_data.py @@ -2,18 +2,19 @@ from __future__ import annotations -from typing import List, Literal, Optional, Tuple, Union +from typing import Literal, Optional, Union import numpy as np import pydantic.v1 as pd -from ....exceptions import SetupError -from ...base import cached_property -from ...data.data_array import EMEScalarFieldDataArray, EMESMatrixDataArray -from ...data.monitor_data import FieldData, ModeData, ModeSolverData -from ...data.sim_data import AbstractYeeGridSimulationData -from ...types import annotate_type -from ..simulation import EMESimulation +from tidy3d.components.base import cached_property +from tidy3d.components.data.data_array import EMEScalarFieldDataArray, EMESMatrixDataArray +from tidy3d.components.data.monitor_data import FieldData, ModeData, ModeSolverData +from tidy3d.components.data.sim_data import AbstractYeeGridSimulationData +from tidy3d.components.eme.simulation import EMESimulation +from tidy3d.components.types import annotate_type +from tidy3d.exceptions import SetupError + from .dataset import EMESMatrixDataset from .monitor_data import EMEFieldData, EMEModeSolverData, EMEMonitorDataType @@ -25,7 +26,7 @@ class EMESimulationData(AbstractYeeGridSimulationData): ..., title="EME simulation", description="EME simulation associated with this data." ) - data: Tuple[annotate_type(EMEMonitorDataType), ...] = pd.Field( + data: tuple[annotate_type(EMEMonitorDataType), ...] = pd.Field( ..., title="Monitor Data", description="List of EME monitor data " @@ -44,7 +45,7 @@ class EMESimulationData(AbstractYeeGridSimulationData): ) def _extract_mode_solver_data( - self, data: EMEModeSolverData, eme_cell_index: int, sweep_index: int = None + self, data: EMEModeSolverData, eme_cell_index: int, sweep_index: Optional[int] = None ) -> ModeSolverData: """Extract :class:`.ModeSolverData` at a given ``eme_cell_index``. Assumes the :class:`.EMEModeSolverMonitor` spans the entire simulation and has @@ -78,7 +79,7 @@ def _extract_mode_solver_data( return ModeSolverData(**update_dict, monitor=monitor, grid_expanded=grid_expanded) @cached_property - def port_modes_tuple(self) -> Tuple[ModeSolverData, ModeSolverData]: + def port_modes_tuple(self) -> tuple[ModeSolverData, ModeSolverData]: """Port modes as a tuple ``(port_modes_1, port_modes_2)``.""" if self.port_modes is None: raise SetupError( @@ -101,7 +102,7 @@ def port_modes_tuple(self) -> Tuple[ModeSolverData, ModeSolverData]: return port_modes_1, port_modes_2 @cached_property - def port_modes_list_sweep(self) -> List[Tuple[ModeSolverData, ModeSolverData]]: + def port_modes_list_sweep(self) -> list[tuple[ModeSolverData, ModeSolverData]]: """Port modes as a list of tuples ``(port_modes_1, port_modes_2)``. There is one entry for every sweep index if the port modes vary with sweep index.""" if self.port_modes is None: @@ -287,18 +288,30 @@ def smatrix_in_basis( data21[:, sweep_index, :, :] = S21.to_numpy() data22[:, sweep_index, :, :] = S22.to_numpy() - coords11 = dict( - f=f, sweep_index=sweep_indices, mode_index_out=mode_index_1, mode_index_in=mode_index_1 - ) - coords12 = dict( - f=f, sweep_index=sweep_indices, mode_index_out=mode_index_1, mode_index_in=mode_index_2 - ) - coords21 = dict( - f=f, sweep_index=sweep_indices, mode_index_out=mode_index_2, mode_index_in=mode_index_1 - ) - coords22 = dict( - f=f, sweep_index=sweep_indices, mode_index_out=mode_index_2, mode_index_in=mode_index_2 - ) + coords11 = { + "f": f, + "sweep_index": sweep_indices, + "mode_index_out": mode_index_1, + "mode_index_in": mode_index_1, + } + coords12 = { + "f": f, + "sweep_index": sweep_indices, + "mode_index_out": mode_index_1, + "mode_index_in": mode_index_2, + } + coords21 = { + "f": f, + "sweep_index": sweep_indices, + "mode_index_out": mode_index_2, + "mode_index_in": mode_index_1, + } + coords22 = { + "f": f, + "sweep_index": sweep_indices, + "mode_index_out": mode_index_2, + "mode_index_in": mode_index_2, + } xrS11 = EMESMatrixDataArray(data11, coords=coords11) xrS12 = EMESMatrixDataArray(data12, coords=coords12) xrS21 = EMESMatrixDataArray(data21, coords=coords21) @@ -389,15 +402,15 @@ def field_in_basis( shape[-2] = 1 field_data[field_key] = np.empty(shape, dtype=complex) field_data[field_key][:] = np.nan - field_coords[field_key] = dict( - x=field_comp.x.to_numpy(), - y=field_comp.y.to_numpy(), - z=field_comp.z.to_numpy(), - f=field_comp.f.to_numpy(), - sweep_index=sweep_indices, - eme_port_index=[port_index], - mode_index=mode_index, - ) + field_coords[field_key] = { + "x": field_comp.x.to_numpy(), + "y": field_comp.y.to_numpy(), + "z": field_comp.z.to_numpy(), + "f": field_comp.f.to_numpy(), + "sweep_index": sweep_indices, + "eme_port_index": [port_index], + "mode_index": mode_index, + } # populate the arrays for sweep_index in sweep_indices: diff --git a/tidy3d/components/eme/grid.py b/tidy3d/components/eme/grid.py index 18c0403b3e..b2dae4fcaa 100644 --- a/tidy3d/components/eme/grid.py +++ b/tidy3d/components/eme/grid.py @@ -3,19 +3,19 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Dict, List, Literal, Optional, Tuple, Union +from typing import Literal, Optional, Union import numpy as np import pydantic.v1 as pd -from ...constants import RADIAN, fp_eps, inf -from ...exceptions import SetupError, ValidationError -from ..base import Tidy3dBaseModel, skip_if_fields_missing -from ..geometry.base import Box -from ..grid.grid import Coords1D -from ..mode_spec import ModeSpec -from ..structure import Structure -from ..types import ArrayFloat1D, Axis, Coordinate, Size, TrackFreq +from tidy3d.components.base import Tidy3dBaseModel, skip_if_fields_missing +from tidy3d.components.geometry.base import Box +from tidy3d.components.grid.grid import Coords1D +from tidy3d.components.mode_spec import ModeSpec +from tidy3d.components.structure import Structure +from tidy3d.components.types import ArrayFloat1D, Axis, Coordinate, Size, TrackFreq +from tidy3d.constants import RADIAN, fp_eps, inf +from tidy3d.exceptions import SetupError, ValidationError # grid limits MAX_NUM_MODES = 100 @@ -163,7 +163,7 @@ def num_virtual_cells(self) -> int: """Number of virtual cells in this EME grid spec.""" return len(self.virtual_cell_indices) - def _updated_copy_num_reps(self, num_reps: Dict[str, pd.PositiveInt]) -> EMEGridSpec: + def _updated_copy_num_reps(self, num_reps: dict[str, pd.PositiveInt]) -> EMEGridSpec: """Update ``num_reps`` of named subgrids.""" if self.name is not None: new_num_reps = num_reps.get(self.name) @@ -172,7 +172,7 @@ def _updated_copy_num_reps(self, num_reps: Dict[str, pd.PositiveInt]) -> EMEGrid return self @property - def _cell_index_pairs(self) -> List[pd.NonNegativeInt]: + def _cell_index_pairs(self) -> list[pd.NonNegativeInt]: """Pairs of adjacent cell indices.""" cell_indices = self.virtual_cell_indices pairs = [] @@ -246,10 +246,10 @@ class EMEExplicitGrid(EMEGridSpec): ... ) """ - mode_specs: List[EMEModeSpec] = pd.Field( + mode_specs: list[EMEModeSpec] = pd.Field( ..., title="Mode Specifications", - description="Mode specifications for each cell " "in the explicit EME grid.", + description="Mode specifications for each cell in the explicit EME grid.", ) boundaries: ArrayFloat1D = pd.Field( @@ -270,7 +270,7 @@ def _validate_boundaries(cls, val, values): boundaries = val if len(mode_specs) - 1 != len(boundaries): raise ValidationError( - "There must be exactly one fewer item in 'boundaries' than " "in 'mode_specs'." + "There must be exactly one fewer item in 'boundaries' than in 'mode_specs'." ) if len(boundaries) > 0: rmin = boundaries[0] @@ -310,7 +310,7 @@ def make_grid(self, center: Coordinate, size: Size, axis: Axis) -> EMEGrid: "The last item in 'boundaries' is outside the simulation domain." ) - boundaries = [sim_rmin] + list(self.boundaries) + [sim_rmax] + boundaries = [sim_rmin, *list(self.boundaries), sim_rmax] return EMEGrid( boundaries=boundaries, center=center, @@ -321,7 +321,7 @@ def make_grid(self, center: Coordinate, size: Size, axis: Axis) -> EMEGrid: @classmethod def from_structures( - cls, structures: List[Structure], axis: Axis, mode_spec: EMEModeSpec, **kwargs + cls, structures: list[Structure], axis: Axis, mode_spec: EMEModeSpec, **kwargs ) -> EMEExplicitGrid: """Create an explicit EME grid with boundaries aligned with structure bounding boxes. Every cell in the resulting grid @@ -398,7 +398,7 @@ class EMECompositeGrid(EMEGridSpec): ... ) """ - subgrids: List[EMESubgridType] = pd.Field( + subgrids: list[EMESubgridType] = pd.Field( ..., title="Subgrids", description="Subgrids in the composite grid." ) @@ -419,8 +419,7 @@ def _validate_subgrid_boundaries(cls, val, values): subgrid_boundaries = val if len(subgrids) - 1 != len(subgrid_boundaries): raise ValidationError( - "There must be exactly one fewer item in 'subgrid_boundaries' than " - "in 'subgrids'." + "There must be exactly one fewer item in 'subgrid_boundaries' than in 'subgrids'." ) rmin = subgrid_boundaries[0] for rmax in subgrid_boundaries[1:]: @@ -431,7 +430,7 @@ def _validate_subgrid_boundaries(cls, val, values): def subgrid_bounds( self, center: Coordinate, size: Size, axis: Axis - ) -> List[Tuple[float, float]]: + ) -> list[tuple[float, float]]: """Subgrid bounds: a list of pairs (rmin, rmax) of the bounds of the subgrids along the propagation axis. @@ -524,7 +523,7 @@ def virtual_cell_indices(self) -> int: inds += [ind + start_ind for ind in subgrid.virtual_cell_indices] return list(inds) * self.num_reps - def _updated_copy_num_reps(self, num_reps: Dict[str, pd.PositiveInt]) -> EMEGridSpec: + def _updated_copy_num_reps(self, num_reps: dict[str, pd.PositiveInt]) -> EMEGridSpec: """Update ``num_reps`` of named subgrids.""" new_self = super()._updated_copy_num_reps(num_reps=num_reps) new_subgrids = [ @@ -535,11 +534,11 @@ def _updated_copy_num_reps(self, num_reps: Dict[str, pd.PositiveInt]) -> EMEGrid @classmethod def from_structure_groups( cls, - structure_groups: List[List[Structure]], + structure_groups: list[list[Structure]], axis: Axis, - mode_specs: List[EMEModeSpec], - names: List[str] = None, - num_reps: List[pd.PositiveInt] = None, + mode_specs: list[EMEModeSpec], + names: Optional[list[str]] = None, + num_reps: Optional[list[pd.PositiveInt]] = None, ) -> EMECompositeGrid: """Create a composite EME grid with boundaries aligned with structure bounding boxes. @@ -598,7 +597,7 @@ def from_structure_groups( raise ValidationError("The list 'structure_groups' cannot be empty.") if len(mode_specs) != len(structure_groups): raise ValidationError( - "The lists 'mode_specs' and 'structure_groups' must " "have the same length." + "The lists 'mode_specs' and 'structure_groups' must have the same length." ) subgrids = [] @@ -612,7 +611,7 @@ def from_structure_groups( if names is not None: if len(names) != len(structure_groups): raise ValidationError( - "The lists 'names' and 'structure_groups' must " "have the same length." + "The lists 'names' and 'structure_groups' must have the same length." ) for i in range(len(subgrids)): subgrids[i] = subgrids[i].updated_copy(name=names[i]) @@ -620,7 +619,7 @@ def from_structure_groups( if num_reps is not None: if len(num_reps) != len(structure_groups): raise ValidationError( - "The lists 'num_reps' and 'structure_groups' must " "have the same length." + "The lists 'num_reps' and 'structure_groups' must have the same length." ) for i in range(len(subgrids)): subgrids[i] = subgrids[i].updated_copy(num_reps=num_reps[i]) @@ -676,7 +675,7 @@ class EMEGrid(Box): ..., title="Propagation axis", description="Propagation axis for the EME simulation." ) - mode_specs: List[EMEModeSpec] = pd.Field( + mode_specs: list[EMEModeSpec] = pd.Field( ..., title="Mode Specifications", description="Mode specifications for the EME cells." ) @@ -744,7 +743,7 @@ def centers(self) -> Coords1D: return centers @property - def lengths(self) -> List[pd.NonNegativeFloat]: + def lengths(self) -> list[pd.NonNegativeFloat]: """Lengths of the EME cells along the propagation axis.""" rmin = self.boundaries[0] lengths = [] @@ -760,7 +759,7 @@ def num_cells(self) -> pd.NonNegativeInteger: return len(self.centers) @property - def mode_planes(self) -> List[Box]: + def mode_planes(self) -> list[Box]: """Planes for mode solving, aligned with cell centers.""" size = [inf, inf, inf] center = list(self.center) @@ -773,7 +772,7 @@ def mode_planes(self) -> List[Box]: return mode_planes @property - def boundary_planes(self) -> List[Box]: + def boundary_planes(self) -> list[Box]: """Planes aligned with cell boundaries.""" size = list(self.size) center = list(self.center) @@ -786,7 +785,7 @@ def boundary_planes(self) -> List[Box]: return boundary_planes @property - def cells(self) -> List[Box]: + def cells(self) -> list[Box]: """EME cells in the grid. Each cell is a :class:`.Box`.""" size = list(self.size) center = list(self.center) @@ -798,7 +797,7 @@ def cells(self) -> List[Box]: cells.append(Box(center=center, size=size)) return cells - def cell_indices_in_box(self, box: Box) -> List[pd.NonNegativeInteger]: + def cell_indices_in_box(self, box: Box) -> list[pd.NonNegativeInteger]: """Indices of cells that overlap with 'box'. Used to determine which data is recorded by a monitor. diff --git a/tidy3d/components/eme/monitor.py b/tidy3d/components/eme/monitor.py index aad5e5eb7f..345ed3790a 100644 --- a/tidy3d/components/eme/monitor.py +++ b/tidy3d/components/eme/monitor.py @@ -3,13 +3,13 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Literal, Optional, Tuple, Union +from typing import Literal, Optional, Union import pydantic.v1 as pd -from ..base_sim.monitor import AbstractMonitor -from ..monitor import AbstractFieldMonitor, ModeSolverMonitor, PermittivityMonitor -from ..types import FreqArray +from tidy3d.components.base_sim.monitor import AbstractMonitor +from tidy3d.components.monitor import AbstractFieldMonitor, ModeSolverMonitor, PermittivityMonitor +from tidy3d.components.types import FreqArray BYTES_COMPLEX = 8 @@ -42,7 +42,7 @@ class EMEMonitor(AbstractMonitor, ABC): "will be omitted. A value of 'None' will record all sweep indices.", ) - interval_space: Tuple[Literal[1], Literal[1], Literal[1]] = pd.Field( + interval_space: tuple[Literal[1], Literal[1], Literal[1]] = pd.Field( (1, 1, 1), title="Spatial Interval", description="Number of grid step intervals between monitor recordings. If equal to 1, " @@ -124,7 +124,7 @@ class EMEModeSolverMonitor(EMEMonitor): ... ) """ - interval_space: Tuple[Literal[1], Literal[1], Literal[1]] = pd.Field( + interval_space: tuple[Literal[1], Literal[1], Literal[1]] = pd.Field( (1, 1, 1), title="Spatial Interval", description="Note: not yet supported. Number of grid step intervals between monitor recordings. If equal to 1, " @@ -198,7 +198,7 @@ class EMEFieldMonitor(EMEMonitor, AbstractFieldMonitor): ... ) """ - interval_space: Tuple[Literal[1], Literal[1], Literal[1]] = pd.Field( + interval_space: tuple[Literal[1], Literal[1], Literal[1]] = pd.Field( (1, 1, 1), title="Spatial Interval", description="Note: not yet supported. Number of grid step intervals between monitor recordings. If equal to 1, " @@ -262,7 +262,7 @@ class EMECoefficientMonitor(EMEMonitor): ... ) """ - interval_space: Tuple[Literal[1], Literal[1], Literal[1]] = pd.Field( + interval_space: tuple[Literal[1], Literal[1], Literal[1]] = pd.Field( (1, 1, 1), title="Spatial Interval", description="Number of grid step intervals between monitor recordings. If equal to 1, " diff --git a/tidy3d/components/eme/simulation.py b/tidy3d/components/eme/simulation.py index a624ad9024..5b3502f1a2 100644 --- a/tidy3d/components/eme/simulation.py +++ b/tidy3d/components/eme/simulation.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, List, Literal, Optional, Tuple, Union +from typing import Literal, Optional, Union try: import matplotlib as mpl @@ -11,25 +11,26 @@ import numpy as np import pydantic.v1 as pd -from ...constants import C_0 -from ...exceptions import SetupError, ValidationError -from ...log import log -from ..base import cached_property -from ..boundary import BoundarySpec, PECBoundary -from ..geometry.base import Box -from ..grid.grid import Grid -from ..grid.grid_spec import GridSpec -from ..medium import FullyAnisotropicMedium -from ..monitor import AbstractModeMonitor, ModeSolverMonitor, Monitor, MonitorType -from ..scene import Scene -from ..simulation import AbstractYeeGridSimulation, Simulation, validate_boundaries_for_zero_dims -from ..types import Ax, Axis, FreqArray, Symmetry, annotate_type -from ..validators import ( - MIN_FREQUENCY, - validate_freqs_min, - validate_freqs_not_empty, +from tidy3d.components.base import cached_property +from tidy3d.components.boundary import BoundarySpec, PECBoundary +from tidy3d.components.geometry.base import Box +from tidy3d.components.grid.grid import Grid +from tidy3d.components.grid.grid_spec import GridSpec +from tidy3d.components.medium import FullyAnisotropicMedium +from tidy3d.components.monitor import AbstractModeMonitor, ModeSolverMonitor, Monitor, MonitorType +from tidy3d.components.scene import Scene +from tidy3d.components.simulation import ( + AbstractYeeGridSimulation, + Simulation, + validate_boundaries_for_zero_dims, ) -from ..viz import add_ax_if_none, equal_aspect +from tidy3d.components.types import Ax, Axis, FreqArray, Symmetry, annotate_type +from tidy3d.components.validators import MIN_FREQUENCY, validate_freqs_min, validate_freqs_not_empty +from tidy3d.components.viz import add_ax_if_none, equal_aspect +from tidy3d.constants import C_0 +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.log import log + from .grid import EMECompositeGrid, EMEExplicitGrid, EMEGrid, EMEGridSpec, EMEGridSpecType from .monitor import ( EMECoefficientMonitor, @@ -181,7 +182,7 @@ class EMESimulation(AbstractYeeGridSimulation): "tangential directions, as well as the grid used for field monitors.", ) - monitors: Tuple[annotate_type(EMEMonitorType), ...] = pd.Field( + monitors: tuple[annotate_type(EMEMonitorType), ...] = pd.Field( (), title="Monitors", description="Tuple of monitors in the simulation. " @@ -199,7 +200,7 @@ class EMESimulation(AbstractYeeGridSimulation): "apply PML layers in the mode solver.", ) - sources: Tuple[None, ...] = pd.Field( + sources: tuple[None, ...] = pd.Field( (), title="Sources", description="Sources in the simulation. NOTE: sources are not currently supported " @@ -231,7 +232,7 @@ class EMESimulation(AbstractYeeGridSimulation): "thereby normalizing the scattering matrix and expansion coefficients.", ) - port_offsets: Tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( + port_offsets: tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( (0, 0), title="Port Offsets", description="Offsets for the two ports, relative to the simulation bounds " @@ -299,12 +300,12 @@ def _validate_structures(cls, val): @add_ax_if_none def plot_eme_ports( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, **kwargs, ) -> Ax: """Plot the EME ports.""" @@ -343,12 +344,12 @@ def plot_eme_ports( def plot_eme_subgrid_boundaries( self, eme_grid_spec: EMEGridSpec, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, **kwargs, ) -> Ax: """Plot the EME subgrid boundaries. @@ -395,12 +396,12 @@ def plot_eme_subgrid_boundaries( @add_ax_if_none def plot_eme_grid( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, **kwargs, ) -> Ax: """Plot the EME grid.""" @@ -436,14 +437,14 @@ def plot_eme_grid( @add_ax_if_none def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - source_alpha: float = None, - monitor_alpha: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + source_alpha: Optional[float] = None, + monitor_alpha: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, **patch_kwargs, ) -> Ax: """Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate. @@ -536,7 +537,7 @@ def from_scene(cls, scene: Scene, **kwargs) -> EMESimulation: ) @property - def mode_solver_monitors(self) -> List[ModeSolverMonitor]: + def mode_solver_monitors(self) -> list[ModeSolverMonitor]: """A list of mode solver monitors at the cell centers. Each monitor has a mode spec. The cells and mode specs are specified by 'eme_grid_spec'.""" @@ -692,8 +693,7 @@ def _validate_sweep_spec(self): scale_factors_shape = self.sweep_spec.scale_factors.shape if len(scale_factors_shape) > 2: raise SetupError( - "Simulation 'sweep_spec.scale_factors' must " - "have either one or two dimensions." + "Simulation 'sweep_spec.scale_factors' must have either one or two dimensions." ) if len(scale_factors_shape) == 2: num_scale_factors = scale_factors_shape[1] @@ -720,7 +720,7 @@ def _validate_sweep_spec(self): f"Monitor at 'monitors[{i}]' is an 'EMEFieldMonitor', " "which is not compatible with 'EMEPeriodicitySweep'." ) - elif isinstance(monitor, EMECoefficientMonitor): + if isinstance(monitor, EMECoefficientMonitor): raise SetupError( f"Monitor at 'monitors[{i}]' is an 'EMECoefficientMonitor', " "which is not compatible with 'EMEPeriodicitySweep'." @@ -856,7 +856,7 @@ def _validate_monitor_size(self) -> None: def _validate_modes_size(self) -> None: """Warn if mode sources or monitors have a large number of points.""" - def warn_mode_size(monitor: AbstractModeMonitor, msg_header: str, custom_loc: List): + def warn_mode_size(monitor: AbstractModeMonitor, msg_header: str, custom_loc: list): """Warn if a mode component has a large number of points.""" num_cells = np.prod(self.discretize_monitor(monitor).num_cells) if num_cells > WARN_MODE_NUM_CELLS: @@ -880,14 +880,14 @@ def warn_mode_size(monitor: AbstractModeMonitor, msg_header: str, custom_loc: Li warn_mode_size(monitor=monitor, msg_header=msg_header, custom_loc=custom_loc) @property - def _monitors_full(self) -> Tuple[EMEMonitorType, ...]: + def _monitors_full(self) -> tuple[EMEMonitorType, ...]: """All monitors, including port modes monitor.""" if self.store_port_modes: - return list(self.monitors) + [self.port_modes_monitor] + return [*list(self.monitors), self.port_modes_monitor] return list(self.monitors) @cached_property - def monitors_data_size(self) -> Dict[str, float]: + def monitors_data_size(self) -> dict[str, float]: """Dictionary mapping monitor names to their estimated storage size in bytes.""" data_size = {} for monitor in self._monitors_full: @@ -971,7 +971,7 @@ def _monitor_num_sweep(self, monitor: EMEMonitor) -> pd.PositiveInt: return self.sweep_spec.num_sweep return min(self.sweep_spec.num_sweep, monitor.num_sweep) - def _monitor_eme_cell_indices(self, monitor: EMEMonitor) -> List[pd.NonNegativeInt]: + def _monitor_eme_cell_indices(self, monitor: EMEMonitor) -> list[pd.NonNegativeInt]: """EME cell indices inside monitor. Takes into account 'eme_cell_interval_space'.""" cell_indices_full = self.eme_grid.cell_indices_in_box(box=monitor.geometry) if len(cell_indices_full) == 0: @@ -986,7 +986,7 @@ def _monitor_num_eme_cells(self, monitor: EMEMonitor) -> int: """Total number of EME cells included in monitor based on simulation grid.""" return len(self._monitor_eme_cell_indices(monitor=monitor)) - def _monitor_freqs(self, monitor: Monitor) -> List[pd.NonNegativeFloat]: + def _monitor_freqs(self, monitor: Monitor) -> list[pd.NonNegativeFloat]: """Monitor frequencies.""" if monitor.freqs is None: return list(self.freqs) @@ -1094,8 +1094,8 @@ def subsection( region: Box, grid_spec: Union[GridSpec, Literal["identical"]] = None, eme_grid_spec: Union[EMEGridSpec, Literal["identical"]] = None, - symmetry: Tuple[Symmetry, Symmetry, Symmetry] = None, - monitors: Tuple[MonitorType, ...] = None, + symmetry: Optional[tuple[Symmetry, Symmetry, Symmetry]] = None, + monitors: Optional[tuple[MonitorType, ...]] = None, remove_outside_structures: bool = True, remove_outside_custom_mediums: bool = False, **kwargs, @@ -1170,7 +1170,7 @@ def subsection( return new_sim @property - def _cell_index_pairs(self) -> List[pd.NonNegativeInt]: + def _cell_index_pairs(self) -> list[pd.NonNegativeInt]: """All the pairs of adjacent EME cells needed, taken over all sweep indices.""" pairs = set() if isinstance(self.sweep_spec, EMEPeriodicitySweep): diff --git a/tidy3d/components/eme/sweep.py b/tidy3d/components/eme/sweep.py index d4130e00f3..6ded35c645 100644 --- a/tidy3d/components/eme/sweep.py +++ b/tidy3d/components/eme/sweep.py @@ -3,13 +3,14 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Dict, List, Union +from typing import Union import pydantic.v1 as pd -from ...exceptions import SetupError -from ..base import Tidy3dBaseModel -from ..types import ArrayFloat1D, ArrayInt1D, ArrayLike +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.components.types import ArrayFloat1D, ArrayInt1D, ArrayLike +from tidy3d.exceptions import SetupError + from .grid import MAX_NUM_REPS @@ -99,7 +100,7 @@ class EMEPeriodicitySweep(EMESweepSpec): >>> sweep_spec = EMEPeriodicitySweep(num_reps=[{"unit_cell": n} for n in n_list]) """ - num_reps: List[Dict[str, pd.PositiveInt]] = pd.Field( + num_reps: list[dict[str, pd.PositiveInt]] = pd.Field( ..., title="Number of Repetitions", description="Number of periodic repetitions of named subgrids in this EME grid. " diff --git a/tidy3d/components/field_projection.py b/tidy3d/components/field_projection.py index b66e14484a..4346a85d50 100644 --- a/tidy3d/components/field_projection.py +++ b/tidy3d/components/field_projection.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Iterable, List, Tuple, Union +from collections.abc import Iterable +from typing import Union import autograd.numpy as anp import numpy as np @@ -10,9 +11,10 @@ import xarray as xr from rich.progress import track -from ..constants import C_0, EPSILON_0, ETA_0, MICROMETER, MU_0 -from ..exceptions import SetupError -from ..log import get_logging_console +from tidy3d.constants import C_0, EPSILON_0, ETA_0, MICROMETER, MU_0 +from tidy3d.exceptions import SetupError +from tidy3d.log import get_logging_console + from .autograd.functions import add_at, trapz from .base import Tidy3dBaseModel, cached_property, skip_if_fields_missing from .data.data_array import ( @@ -44,7 +46,7 @@ # Numpy float array and related array types -ArrayLikeN2F = Union[float, Tuple[float, ...], ArrayComplex4D] +ArrayLikeN2F = Union[float, tuple[float, ...], ArrayComplex4D] class FieldProjector(Tidy3dBaseModel): @@ -70,7 +72,7 @@ class FieldProjector(Tidy3dBaseModel): description="Container for simulation data containing the near field monitors.", ) - surfaces: Tuple[FieldProjectionSurface, ...] = pydantic.Field( + surfaces: tuple[FieldProjectionSurface, ...] = pydantic.Field( ..., title="Surface monitor with direction", description="Tuple of each :class:`.FieldProjectionSurface` to use as source of " @@ -116,7 +118,7 @@ def medium(self) -> MediumType: return sim.monitor_medium(monitor) @cached_property - def frequencies(self) -> List[float]: + def frequencies(self) -> list[float]: """Return the list of frequencies associated with the field monitors.""" return self.surfaces[0].monitor.freqs @@ -124,8 +126,8 @@ def frequencies(self) -> List[float]: def from_near_field_monitors( cls, sim_data: SimulationData, - near_monitors: List[FieldMonitor], - normal_dirs: List[Direction], + near_monitors: list[FieldMonitor], + normal_dirs: list[Direction], pts_per_wavelength: int = PTS_PER_WVL, origin: Coordinate = None, ): @@ -268,7 +270,7 @@ def _fields_to_currents(field_data: FieldData, surface: FieldProjectionSurface) surface_currents[H2] = field_data.field_components[E1] * signs[0] surface_currents[H1] = field_data.field_components[E2] * signs[1] - new_monitor = surface.monitor.copy(update=dict(fields=[E1, E2, H1, H2])) + new_monitor = surface.monitor.copy(update={"fields": [E1, E2, H1, H2]}) return FieldData( monitor=new_monitor, @@ -905,7 +907,7 @@ def _fields_for_surface_exact( d2G_dr2 = dG_dr * (ikr - 1.0) / r + G / (r**2) # operations between unit vectors and currents - def r_x_current(current: Tuple[np.ndarray, ...]) -> Tuple[np.ndarray, ...]: + def r_x_current(current: tuple[np.ndarray, ...]) -> tuple[np.ndarray, ...]: """Cross product between the r unit vector and the current.""" return [ sin_theta * sin_phi * current[2] - cos_theta * current[1], @@ -913,7 +915,7 @@ def r_x_current(current: Tuple[np.ndarray, ...]) -> Tuple[np.ndarray, ...]: sin_theta * cos_phi * current[1] - sin_theta * sin_phi * current[0], ] - def r_dot_current(current: Tuple[np.ndarray, ...]) -> np.ndarray: + def r_dot_current(current: tuple[np.ndarray, ...]) -> np.ndarray: """Dot product between the r unit vector and the current.""" return ( sin_theta * cos_phi * current[0] @@ -921,7 +923,7 @@ def r_dot_current(current: Tuple[np.ndarray, ...]) -> np.ndarray: + cos_theta * current[2] ) - def r_dot_current_dtheta(current: Tuple[np.ndarray, ...]) -> np.ndarray: + def r_dot_current_dtheta(current: tuple[np.ndarray, ...]) -> np.ndarray: """Theta derivative of the dot product between the r unit vector and the current.""" return ( cos_theta * cos_phi * current[0] @@ -929,12 +931,12 @@ def r_dot_current_dtheta(current: Tuple[np.ndarray, ...]) -> np.ndarray: - sin_theta * current[2] ) - def r_dot_current_dphi_div_sin_theta(current: Tuple[np.ndarray, ...]) -> np.ndarray: + def r_dot_current_dphi_div_sin_theta(current: tuple[np.ndarray, ...]) -> np.ndarray: """Phi derivative of the dot product between the r unit vector and the current, analytically divided by sin theta.""" return -sin_phi * current[0] + cos_phi * current[1] - def grad_Gr_r_dot_current(current: Tuple[np.ndarray, ...]) -> Tuple[np.ndarray, ...]: + def grad_Gr_r_dot_current(current: tuple[np.ndarray, ...]) -> tuple[np.ndarray, ...]: """Gradient of the product of the gradient of the Green's function and the dot product between the r unit vector and the current.""" temp = [ @@ -945,7 +947,7 @@ def grad_Gr_r_dot_current(current: Tuple[np.ndarray, ...]) -> Tuple[np.ndarray, # convert to Cartesian coordinates return surface.monitor.sph_2_car_field(temp[0], temp[1], temp[2], theta_obs, phi_obs) - def potential_terms(current: Tuple[np.ndarray, ...], const: complex): + def potential_terms(current: tuple[np.ndarray, ...], const: complex): """Assemble vector potential and its derivatives.""" r_x_c = r_x_current(current) pot = [const * item * G for item in current] diff --git a/tidy3d/components/file_util.py b/tidy3d/components/file_util.py index b530baddcd..c57fb15322 100644 --- a/tidy3d/components/file_util.py +++ b/tidy3d/components/file_util.py @@ -1,5 +1,7 @@ """File compression utilities""" +from __future__ import annotations + import gzip import shutil from typing import Any @@ -58,7 +60,7 @@ def replace_values(values: Any, search_value: Any, replace_value: Any) -> Any: return { key: replace_values(val, search_value, replace_value) for key, val in values.items() } - elif isinstance( + if isinstance( values, (tuple, list) ): # Parts of the nested dict structure include tuples with more dicts return type(values)(replace_values(val, search_value, replace_value) for val in values) diff --git a/tidy3d/components/frequencies.py b/tidy3d/components/frequencies.py index 6a14fb5b6b..810558ac62 100644 --- a/tidy3d/components/frequencies.py +++ b/tidy3d/components/frequencies.py @@ -1,9 +1,12 @@ """Frequency utilities.""" +from __future__ import annotations + import numpy as np import pydantic as pd -from ..constants import C_0 +from tidy3d.constants import C_0 + from .base import Tidy3dBaseModel O_BAND = (1.260, 1.360) @@ -44,54 +47,54 @@ def classification(self, value: float) -> tuple[str]: value = C_0 / value if value < 3: return ("near static",) - elif value < 300e6: + if value < 300e6: if value < 30: return ("radio wave", "ELF") - elif value < 300: + if value < 300: return ("radio wave", "SLF") - elif value < 3e3: + if value < 3e3: return ("radio wave", "ULF") - elif value < 30e3: + if value < 30e3: return ("radio wave", "VLF") - elif value < 300e3: + if value < 300e3: return ("radio wave", "LF") - elif value < 3e6: + if value < 3e6: return ("radio wave", "MF") - elif value < 30e6: + if value < 30e6: return ("radio wave", "HF") return ("radio wave", "VHF") - elif value < 300e9: + if value < 300e9: if value < 3e9: return ("microwave", "UHF") - elif value < 30e9: + if value < 30e9: return ("microwave", "SHF") return ("microwave", "EHF") - elif value < 400e12: + if value < 400e12: if value < 6e12: return ("infrared", "FIR") - elif value < 100e12: + if value < 100e12: return ("infrared", "MIR") return ("infrared", "NIR") - elif value < 790e12: + if value < 790e12: if value < 480e12: return ("visible", "red") - elif value < 510e12: + if value < 510e12: return ("visible", "orange") - elif value < 530e12: + if value < 530e12: return ("visible", "yellow") - elif value < 600e12: + if value < 600e12: return ("visible", "green") - elif value < 620e12: + if value < 620e12: return ("visible", "cyan") - elif value < 670e12: + if value < 670e12: return ("visible", "blue") return ("visible", "violet") - elif value < 30e15: + if value < 30e15: if value < 1e15: return ("ultraviolet", "NUV") - elif value < 1.5e15: + if value < 1.5e15: return ("ultraviolet", "MUV") - elif value < 2.47e15: + if value < 2.47e15: return ("ultraviolet", "FUV") return ("ultraviolet", "EUV") if value < 30e18: diff --git a/tidy3d/components/geometry/base.py b/tidy3d/components/geometry/base.py index b473d1060b..4e6e7d2b20 100644 --- a/tidy3d/components/geometry/base.py +++ b/tidy3d/components/geometry/base.py @@ -5,7 +5,7 @@ import functools import pathlib from abc import ABC, abstractmethod -from typing import Any, Callable, List, Tuple, Union +from typing import Any, Callable, Optional, Union import autograd.numpy as np import pydantic.v1 as pydantic @@ -17,21 +17,11 @@ except ImportError: pass -from ...constants import LARGE_NUMBER, MICROMETER, RADIAN, fp_eps, inf -from ...exceptions import ( - SetupError, - Tidy3dError, - Tidy3dImportError, - Tidy3dKeyError, - ValidationError, -) -from ...log import log -from ...packaging import verify_packages_import -from ..autograd import AutogradFieldMap, TracedCoordinate, TracedSize, get_static -from ..autograd.derivative_utils import DerivativeInfo, integrate_within_bounds -from ..base import Tidy3dBaseModel, cached_property -from ..transformation import ReflectionFromPlane, RotationAroundAxis -from ..types import ( +from tidy3d.components.autograd import AutogradFieldMap, TracedCoordinate, TracedSize, get_static +from tidy3d.components.autograd.derivative_utils import DerivativeInfo, integrate_within_bounds +from tidy3d.components.base import Tidy3dBaseModel, cached_property +from tidy3d.components.transformation import ReflectionFromPlane, RotationAroundAxis +from tidy3d.components.types import ( ArrayFloat2D, ArrayFloat3D, Ax, @@ -47,7 +37,7 @@ Size, annotate_type, ) -from ..viz import ( +from tidy3d.components.viz import ( ARROW_LENGTH, PLOT_BUFFER, PlotParams, @@ -59,6 +49,17 @@ polygon_patch, set_default_labels_and_title, ) +from tidy3d.constants import LARGE_NUMBER, MICROMETER, RADIAN, fp_eps, inf +from tidy3d.exceptions import ( + SetupError, + Tidy3dError, + Tidy3dImportError, + Tidy3dKeyError, + ValidationError, +) +from tidy3d.log import log +from tidy3d.packaging import verify_packages_import + from .bound_ops import bounds_intersection, bounds_union POLY_GRID_SIZE = 1e-12 @@ -152,7 +153,7 @@ def make_shapely_point(minx: float, miny: float) -> shapely.Point: def _inds_inside_bounds( self, x: np.ndarray[float], y: np.ndarray[float], z: np.ndarray[float] - ) -> Tuple[slice, slice, slice]: + ) -> tuple[slice, slice, slice]: """Return slices into the sorted input arrays that are inside the geometry bounds. Parameters @@ -214,7 +215,7 @@ def inside_meshgrid( @abstractmethod def intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Parameters @@ -235,8 +236,8 @@ def intersections_tilted_plane( """ def intersections_plane( - self, x: float = None, y: float = None, z: float = None - ) -> List[Shapely]: + self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + ) -> list[Shapely]: """Returns list of shapely geometries at plane specified by one non-None value of x,y,z. Parameters @@ -261,10 +262,10 @@ def intersections_plane( to_2D = np.eye(4) if axis != 2: last, indices = self.pop_axis((0, 1, 2), axis) - to_2D = to_2D[list(indices) + [last, 3]] + to_2D = to_2D[[*list(indices), last, 3]] return self.intersections_tilted_plane(normal, origin, to_2D) - def intersections_2dbox(self, plane: Box) -> List[Shapely]: + def intersections_2dbox(self, plane: Box) -> list[Shapely]: """Returns list of shapely geometries representing the intersections of the geometry with a 2D box. @@ -281,7 +282,7 @@ def intersections_2dbox(self, plane: Box) -> List[Shapely]: return plane.intersections_with(self) def intersects( - self, other, strict_inequality: Tuple[bool, bool, bool] = [False, False, False] + self, other, strict_inequality: tuple[bool, bool, bool] = [False, False, False] ) -> bool: """Returns ``True`` if two :class:`Geometry` have intersecting `.bounds`. @@ -319,7 +320,7 @@ def intersects( return True def contains( - self, other: Geometry, strict_inequality: Tuple[bool, bool, bool] = [False, False, False] + self, other: Geometry, strict_inequality: tuple[bool, bool, bool] = [False, False, False] ) -> bool: """Returns ``True`` if the `.bounds` of ``other`` are contained within the `.bounds` of ``self``. @@ -357,7 +358,9 @@ def contains( return True - def intersects_plane(self, x: float = None, y: float = None, z: float = None) -> bool: + def intersects_plane( + self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + ) -> bool: """Whether self intersects plane specified by one non-None value of x,y,z. Parameters @@ -428,7 +431,7 @@ def bounding_box(self): return Box.from_bounds(*self.bounds) @cached_property - def zero_dims(self) -> List[Axis]: + def zero_dims(self) -> list[Axis]: """A list of axes along which the :class:`Geometry` is zero-sized based on its bounds.""" zero_dims = [] for dim in range(3): @@ -436,7 +439,7 @@ def zero_dims(self) -> List[Axis]: zero_dims.append(dim) return zero_dims - def _pop_bounds(self, axis: Axis) -> Tuple[Coordinate2D, Tuple[Coordinate2D, Coordinate2D]]: + def _pop_bounds(self, axis: Axis) -> tuple[Coordinate2D, tuple[Coordinate2D, Coordinate2D]]: """Returns min and max bounds in plane normal to and tangential to ``axis``. Parameters @@ -476,7 +479,7 @@ def _normal_2dmaterial(self) -> Axis: """Get the normal to the given geometry, checking that it is a 2D geometry.""" raise ValidationError("'Medium2D' is not compatible with this geometry class.") - def _update_from_bounds(self, bounds: Tuple[float, float], axis: Axis) -> Geometry: + def _update_from_bounds(self, bounds: tuple[float, float], axis: Axis) -> Geometry: """Returns an updated geometry which has been transformed to fit within ``bounds`` along the ``axis`` direction.""" raise NotImplementedError( @@ -487,9 +490,9 @@ def _update_from_bounds(self, bounds: Tuple[float, float], axis: Axis) -> Geomet @add_ax_if_none def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, plot_length_units: LengthUnit = None, viz_spec: VisualizationSpec = None, @@ -587,7 +590,7 @@ def _do_not_intersect(bounds_a, bounds_b, shape_a, shape_b): return False @staticmethod - def _get_plot_labels(axis: Axis) -> Tuple[str, str]: + def _get_plot_labels(axis: Axis) -> tuple[str, str]: """Returns planar coordinate x and y axis labels for cross section plots. Parameters @@ -605,7 +608,7 @@ def _get_plot_labels(axis: Axis) -> Tuple[str, str]: def _get_plot_limits( self, axis: Axis, buffer: float = PLOT_BUFFER - ) -> Tuple[Coordinate2D, Coordinate2D]: + ) -> tuple[Coordinate2D, Coordinate2D]: """Gets planar coordinate limits for cross section plots. Parameters @@ -652,9 +655,9 @@ def add_ax_lims(self, axis: Axis, ax: Ax, buffer: float = PLOT_BUFFER) -> Ax: @staticmethod def add_ax_labels_and_title( ax: Ax, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, plot_length_units: LengthUnit = None, ) -> Ax: """Sets the axis labels, tick labels, and title based on ``axis`` @@ -719,7 +722,7 @@ def evaluate_inf_shape(shape: Shapely) -> Shapely: return shape @staticmethod - def pop_axis(coord: Tuple[Any, Any, Any], axis: int) -> Tuple[Any, Tuple[Any, Any]]: + 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 @@ -741,7 +744,7 @@ def pop_axis(coord: Tuple[Any, Any, Any], axis: int) -> Tuple[Any, Tuple[Any, An return axis_val, tuple(plane_vals) @staticmethod - def unpop_axis(ax_coord: Any, plane_coords: Tuple[Any, Any], axis: int) -> Tuple[Any, Any, Any]: + def unpop_axis(ax_coord: Any, plane_coords: tuple[Any, Any], axis: int) -> tuple[Any, Any, Any]: """Combine coordinate along axis with coordinates on the plane tangent to the axis. Parameters @@ -763,7 +766,7 @@ def unpop_axis(ax_coord: Any, plane_coords: Tuple[Any, Any], axis: int) -> Tuple return tuple(coords) @staticmethod - def parse_xyz_kwargs(**xyz) -> Tuple[Axis, float]: + def parse_xyz_kwargs(**xyz) -> tuple[Axis, float]: """Turns x,y,z kwargs into index of the normal axis and position along that axis. Parameters @@ -788,7 +791,7 @@ def parse_xyz_kwargs(**xyz) -> Tuple[Axis, float]: return axis, position @staticmethod - def parse_two_xyz_kwargs(**xyz) -> List[Tuple[Axis, float]]: + def parse_two_xyz_kwargs(**xyz) -> list[tuple[Axis, float]]: """Turns x,y,z kwargs into indices of axes and the position along each axis. Parameters @@ -987,7 +990,7 @@ def reflected(self, normal: Coordinate) -> Geometry: """ Field and coordinate transformations """ @staticmethod - def car_2_sph(x: float, y: float, z: float) -> Tuple[float, float, float]: + def car_2_sph(x: float, y: float, z: float) -> tuple[float, float, float]: """Convert Cartesian to spherical coordinates. Parameters @@ -1010,7 +1013,7 @@ def car_2_sph(x: float, y: float, z: float) -> Tuple[float, float, float]: return r, theta, phi @staticmethod - def sph_2_car(r: float, theta: float, phi: float) -> Tuple[float, float, float]: + def sph_2_car(r: float, theta: float, phi: float) -> tuple[float, float, float]: """Convert spherical to Cartesian coordinates. Parameters @@ -1036,7 +1039,7 @@ def sph_2_car(r: float, theta: float, phi: float) -> Tuple[float, float, float]: @staticmethod def sph_2_car_field( f_r: float, f_theta: float, f_phi: float, theta: float, phi: float - ) -> Tuple[complex, complex, complex]: + ) -> tuple[complex, complex, complex]: """Convert vector field components in spherical coordinates to cartesian. Parameters @@ -1069,7 +1072,7 @@ def sph_2_car_field( @staticmethod def car_2_sph_field( f_x: float, f_y: float, f_z: float, theta: float, phi: float - ) -> Tuple[complex, complex, complex]: + ) -> tuple[complex, complex, complex]: """Convert vector field components in cartesian coordinates to spherical. Parameters @@ -1101,7 +1104,7 @@ def car_2_sph_field( return f_r, f_theta, f_phi @staticmethod - def kspace_2_sph(ux: float, uy: float, axis: Axis) -> Tuple[float, float]: + def kspace_2_sph(ux: float, uy: float, axis: Axis) -> tuple[float, float]: """Convert normalized k-space coordinates to angles. Parameters @@ -1140,8 +1143,11 @@ def kspace_2_sph(ux: float, uy: float, axis: Axis) -> Tuple[float, float]: @staticmethod @verify_packages_import(["gdstk"]) def load_gds_vertices_gdstk( - gds_cell, gds_layer: int, gds_dtype: int = None, gds_scale: pydantic.PositiveFloat = 1.0 - ) -> List[ArrayFloat2D]: + gds_cell, + gds_layer: int, + gds_dtype: Optional[int] = None, + gds_scale: pydantic.PositiveFloat = 1.0, + ) -> list[ArrayFloat2D]: """Load polygon vertices from a ``gdstk.Cell``. Parameters @@ -1191,9 +1197,9 @@ def load_gds_vertices_gdstk( def from_gds( gds_cell, axis: Axis, - slab_bounds: Tuple[float, float], + slab_bounds: tuple[float, float], gds_layer: int, - gds_dtype: int = None, + gds_dtype: Optional[int] = None, gds_scale: pydantic.PositiveFloat = 1.0, dilation: float = 0.0, sidewall_angle: float = 0, @@ -1266,7 +1272,7 @@ def from_gds( def from_shapely( shape: Shapely, axis: Axis, - slab_bounds: Tuple[float, float], + slab_bounds: tuple[float, float], dilation: float = 0.0, sidewall_angle: float = 0, reference_plane: PlanePosition = "middle", @@ -1304,12 +1310,12 @@ def from_shapely( @verify_packages_import(["gdstk"]) def to_gdstk( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, gds_layer: pydantic.NonNegativeInt = 0, gds_dtype: pydantic.NonNegativeInt = 0, - ) -> List: + ) -> list: """Convert a Geometry object's planar slice to a .gds type polygon. Parameters @@ -1354,9 +1360,9 @@ def to_gdstk( def to_gds( self, cell, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, gds_layer: pydantic.NonNegativeInt = 0, gds_dtype: pydantic.NonNegativeInt = 0, ) -> None: @@ -1394,9 +1400,9 @@ def to_gds( def to_gds_file( self, fname: str, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, gds_layer: pydantic.NonNegativeInt = 0, gds_dtype: pydantic.NonNegativeInt = 0, gds_cell_name: str = "MAIN", @@ -1438,7 +1444,7 @@ def _compute_derivatives(self, derivative_info: DerivativeInfo) -> AutogradField """Compute the adjoint derivatives for this object.""" raise NotImplementedError(f"Can't compute derivative for 'Geometry': '{type(self)}'.") - def _as_union(self) -> List[Geometry]: + def _as_union(self) -> list[Geometry]: """Return a list of geometries that, united, make up the given geometry.""" if isinstance(self, GeometryGroup): return self.geometries @@ -1538,7 +1544,7 @@ class SimplePlaneIntersection(Geometry, ABC): def intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Checks special cases before relying on the complete computation. @@ -1575,13 +1581,13 @@ def transform(p_array): transformed_section = shapely.transform(section, transformation=transform) return transformed_section - else: # Otherwise compute the arbitrary intersection - return self._do_intersections_tilted_plane(normal=normal, origin=origin, to_2D=to_2D) + # Otherwise compute the arbitrary intersection + return self._do_intersections_tilted_plane(normal=normal, origin=origin, to_2D=to_2D) @abstractmethod def _do_intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Parameters @@ -1659,7 +1665,9 @@ def finite_length_axis(self) -> float: """ return min(self.length_axis, LARGE_NUMBER) - def intersections_plane(self, x: float = None, y: float = None, z: float = None): + def intersections_plane( + self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + ): """Returns shapely geometry at plane specified by one non None value of x,y,z. Parameters @@ -1738,7 +1746,7 @@ def _order_axis(self, axis: int) -> int: axis_index.insert(self.axis, 2) return axis_index[axis] - def _order_by_axis(self, plane_val: Any, axis_val: Any, axis: int) -> Tuple[Any, Any]: + def _order_by_axis(self, plane_val: Any, axis_val: Any, axis: int) -> tuple[Any, Any]: """Orders a value in the plane and value along axis in correct (x,y) order for plotting. Note: sometimes if axis=1 and we compute cross section values orthogonal to axis, they can either be x or y in the plots. @@ -1976,7 +1984,7 @@ def surfaces_with_exclusion(cls, size: Size, center: Coordinate, **kwargs): @verify_packages_import(["trimesh"]) def _do_intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Parameters @@ -2024,7 +2032,9 @@ def _do_intersections_tilted_plane( path, _ = section.to_planar(to_2D=to_2D) return path.polygons_full - def intersections_plane(self, x: float = None, y: float = None, z: float = None): + def intersections_plane( + self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + ): """Returns shapely geometry at plane specified by one non None value of x,y,z. Parameters @@ -2155,7 +2165,7 @@ def geometry(self): return Box(center=self.center, size=self.size) @cached_property - def zero_dims(self) -> List[Axis]: + def zero_dims(self) -> list[Axis]: """A list of axes along which the :class:`Box` is zero-sized.""" return [dim for dim, size in enumerate(self.size) if size == 0] @@ -2168,7 +2178,7 @@ def _normal_2dmaterial(self) -> Axis: ) return self.size.index(0) - def _update_from_bounds(self, bounds: Tuple[float, float], axis: Axis) -> Box: + def _update_from_bounds(self, bounds: tuple[float, float], axis: Axis) -> Box: """Returns an updated geometry which has been transformed to fit within ``bounds`` along the ``axis`` direction.""" new_center = list(self.center) @@ -2179,13 +2189,13 @@ def _update_from_bounds(self, bounds: Tuple[float, float], axis: Axis) -> Box: def _plot_arrow( self, - direction: Tuple[float, float, float], - x: float = None, - y: float = None, - z: float = None, - color: str = None, - alpha: float = None, - bend_radius: float = None, + direction: tuple[float, float, float], + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + color: Optional[str] = None, + alpha: Optional[float] = None, + bend_radius: Optional[float] = None, bend_axis: Axis = None, both_dirs: bool = False, ax: Ax = None, @@ -2390,10 +2400,10 @@ def _derivatives_center_size(vjps_faces: Bound) -> dict[str, Coordinate]: vjp_center = vjps_faces_max - vjps_faces_min vjp_size = (vjps_faces_min + vjps_faces_max) / 2.0 - return dict( - center=tuple(vjp_center.tolist()), - size=tuple(vjp_size.tolist()), - ) + return { + "center": tuple(vjp_center.tolist()), + "size": tuple(vjp_size.tolist()), + } def _derivative_faces(self, derivative_info: DerivativeInfo) -> Bound: """Derivative with respect to normal position of 6 faces of ``Box``.""" @@ -2605,7 +2615,7 @@ def bounds(self) -> Bound: def intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Parameters @@ -2802,7 +2812,7 @@ def _normal_2dmaterial(self) -> Axis: return normal - def _update_from_bounds(self, bounds: Tuple[float, float], axis: Axis) -> Transformed: + def _update_from_bounds(self, bounds: tuple[float, float], axis: Axis) -> Transformed: """Returns an updated geometry which has been transformed to fit within ``bounds`` along the ``axis`` direction.""" min_bound = np.array([0, 0, 0, 1.0]) @@ -2850,7 +2860,7 @@ def _geometries_untraced(cls, val): return val @staticmethod - def to_polygon_list(base_geometry: Shapely) -> List[Shapely]: + def to_polygon_list(base_geometry: Shapely) -> list[Shapely]: """Return a list of valid polygons from a shapely geometry, discarding points, lines, and empty polygons. @@ -2896,7 +2906,7 @@ def _bit_operation(self) -> Callable[[Any, Any], Any]: def intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Parameters @@ -2922,8 +2932,8 @@ def intersections_tilted_plane( return ClipOperation.to_polygon_list(self._shapely_operation(geom_a, geom_b)) def intersections_plane( - self, x: float = None, y: float = None, z: float = None - ) -> List[Shapely]: + self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + ) -> list[Shapely]: """Returns list of shapely geometries at plane specified by one non-None value of x,y,z. Parameters @@ -3060,7 +3070,7 @@ def _normal_2dmaterial(self) -> Axis: ) return normal_a - def _update_from_bounds(self, bounds: Tuple[float, float], axis: Axis) -> ClipOperation: + def _update_from_bounds(self, bounds: tuple[float, float], axis: Axis) -> ClipOperation: """Returns an updated geometry which has been transformed to fit within ``bounds`` along the ``axis`` direction.""" new_geom_a = self.geometry_a._update_from_bounds(bounds=bounds, axis=axis) @@ -3071,7 +3081,7 @@ def _update_from_bounds(self, bounds: Tuple[float, float], axis: Axis) -> ClipOp class GeometryGroup(Geometry): """A collection of Geometry objects that can be called as a single geometry object.""" - geometries: Tuple[annotate_type(GeometryType), ...] = pydantic.Field( + geometries: tuple[annotate_type(GeometryType), ...] = pydantic.Field( ..., title="Geometries", description="Tuple of geometries in a single grouping. " @@ -3104,7 +3114,7 @@ def bounds(self) -> Bound: def intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Parameters @@ -3130,8 +3140,8 @@ def intersections_tilted_plane( ] def intersections_plane( - self, x: float = None, y: float = None, z: float = None - ) -> List[Shapely]: + self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + ) -> list[Shapely]: """Returns list of shapely geometries at plane specified by one non-None value of x,y,z. Parameters @@ -3251,7 +3261,7 @@ def _normal_2dmaterial(self) -> Axis: ) return normal - def _update_from_bounds(self, bounds: Tuple[float, float], axis: Axis) -> GeometryGroup: + def _update_from_bounds(self, bounds: tuple[float, float], axis: Axis) -> GeometryGroup: """Returns an updated geometry which has been transformed to fit within ``bounds`` along the ``axis`` direction.""" new_geometries = [ diff --git a/tidy3d/components/geometry/bound_ops.py b/tidy3d/components/geometry/bound_ops.py index 0043bacd86..2cd3428c2c 100644 --- a/tidy3d/components/geometry/bound_ops.py +++ b/tidy3d/components/geometry/bound_ops.py @@ -1,9 +1,11 @@ """Geometry operations for bounding box type with minimal imports.""" +from __future__ import annotations + from math import isclose -from ...constants import fp_eps -from ..types import Bound +from tidy3d.components.types import Bound +from tidy3d.constants import fp_eps def bounds_intersection(bounds1: Bound, bounds2: Bound) -> Bound: diff --git a/tidy3d/components/geometry/mesh.py b/tidy3d/components/geometry/mesh.py index ab3975a9f5..a05c887c7a 100644 --- a/tidy3d/components/geometry/mesh.py +++ b/tidy3d/components/geometry/mesh.py @@ -3,21 +3,22 @@ from __future__ import annotations from abc import ABC -from typing import Callable, List, Literal, Optional, Tuple, Union +from typing import Callable, Literal, Optional, Union import numpy as np import pydantic.v1 as pydantic -from ...constants import fp_eps, inf -from ...exceptions import DataError, ValidationError -from ...log import log -from ...packaging import verify_packages_import -from ..base import cached_property -from ..data.data_array import DATA_ARRAY_MAP, TriangleMeshDataArray -from ..data.dataset import TriangleMeshDataset -from ..data.validators import validate_no_nans -from ..types import Ax, Bound, Coordinate, MatrixReal4x4, Shapely -from ..viz import add_ax_if_none, equal_aspect +from tidy3d.components.base import cached_property +from tidy3d.components.data.data_array import DATA_ARRAY_MAP, TriangleMeshDataArray +from tidy3d.components.data.dataset import TriangleMeshDataset +from tidy3d.components.data.validators import validate_no_nans +from tidy3d.components.types import Ax, Bound, Coordinate, MatrixReal4x4, Shapely +from tidy3d.components.viz import add_ax_if_none, equal_aspect +from tidy3d.constants import fp_eps, inf +from tidy3d.exceptions import DataError, ValidationError +from tidy3d.log import log +from tidy3d.packaging import verify_packages_import + from . import base AREA_SIZE_THRESHOLD = 1e-36 @@ -150,8 +151,8 @@ def from_stl( cls, filename: str, scale: float = 1.0, - origin: Tuple[float, float, float] = (0, 0, 0), - solid_index: int = None, + origin: tuple[float, float, float] = (0, 0, 0), + solid_index: Optional[int] = None, **kwargs, ) -> Union[TriangleMesh, base.GeometryGroup]: """Load a :class:`.TriangleMesh` directly from an STL file. @@ -181,7 +182,7 @@ def from_stl( """ import trimesh - from ..types_extra import TrimeshType + from tidy3d.components.types_extra import TrimeshType def process_single(mesh: TrimeshType) -> TriangleMesh: """Process a single 'trimesh.Trimesh' using scale and origin.""" @@ -253,11 +254,11 @@ def from_triangles(cls, triangles: np.ndarray) -> TriangleMesh: f"Provided 'triangles' must be an N x 3 x 3 array, given {triangles.shape}." ) num_faces = len(triangles) - coords = dict( - face_index=np.arange(num_faces), - vertex_index=np.arange(3), - axis=np.arange(3), - ) + coords = { + "face_index": np.arange(num_faces), + "vertex_index": np.arange(3), + "axis": np.arange(3), + } vertices = TriangleMeshDataArray(triangles, coords=coords) mesh_dataset = TriangleMeshDataset(surface_mesh=vertices) return TriangleMesh(mesh_dataset=mesh_dataset) @@ -314,7 +315,7 @@ def from_height_grid( axis: Ax, direction: Literal["-", "+"], base: float, - grid: Tuple[np.ndarray, np.ndarray], + grid: tuple[np.ndarray, np.ndarray], height: np.ndarray, ) -> TriangleMesh: """Construct a TriangleMesh object from grid based height information. @@ -432,9 +433,9 @@ def from_height_function( axis: Ax, direction: Literal["-", "+"], base: float, - center: Tuple[float, float], - size: Tuple[float, float], - grid_size: Tuple[int, int], + center: tuple[float, float], + size: tuple[float, float], + grid_size: tuple[int, int], height_func: Callable[[np.ndarray, np.ndarray], np.ndarray], ) -> TriangleMesh: """Construct a TriangleMesh object from analytical expression of height function. @@ -527,7 +528,7 @@ def bounds(self) -> Bound: def intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Parameters @@ -553,8 +554,8 @@ def intersections_tilted_plane( return path.polygons_full def intersections_plane( - self, x: float = None, y: float = None, z: float = None - ) -> List[Shapely]: + self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + ) -> list[Shapely]: """Returns list of shapely geometries at plane specified by one non-None value of x,y,z. Parameters @@ -654,7 +655,12 @@ def inside( @equal_aspect @add_ax_if_none def plot( - self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **patch_kwargs + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, + **patch_kwargs, ) -> Ax: """Plot geometry cross section at single (x,y,z) coordinate. diff --git a/tidy3d/components/geometry/polyslab.py b/tidy3d/components/geometry/polyslab.py index 164062a2ae..7d7c1e6f00 100644 --- a/tidy3d/components/geometry/polyslab.py +++ b/tidy3d/components/geometry/polyslab.py @@ -4,23 +4,19 @@ import math from copy import copy -from typing import List, Tuple, Union +from typing import Optional, Union import autograd.numpy as np import pydantic.v1 as pydantic import shapely from autograd.tracer import getval, isbox -from ...constants import LARGE_NUMBER, MICROMETER, fp_eps -from ...exceptions import SetupError, Tidy3dImportError, ValidationError -from ...log import log -from ...packaging import verify_packages_import -from ..autograd import AutogradFieldMap, TracedVertices, get_static -from ..autograd.derivative_utils import DerivativeInfo, DerivativeSurfaceMesh -from ..autograd.types import TracedFloat -from ..base import cached_property, skip_if_fields_missing -from ..transformation import ReflectionFromPlane, RotationAroundAxis -from ..types import ( +from tidy3d.components.autograd import AutogradFieldMap, TracedVertices, get_static +from tidy3d.components.autograd.derivative_utils import DerivativeInfo, DerivativeSurfaceMesh +from tidy3d.components.autograd.types import TracedFloat +from tidy3d.components.base import cached_property, skip_if_fields_missing +from tidy3d.components.transformation import ReflectionFromPlane, RotationAroundAxis +from tidy3d.components.types import ( ArrayFloat1D, ArrayFloat2D, ArrayLike, @@ -31,6 +27,11 @@ PlanePosition, Shapely, ) +from tidy3d.constants import LARGE_NUMBER, MICROMETER, fp_eps +from tidy3d.exceptions import SetupError, Tidy3dImportError, ValidationError +from tidy3d.log import log +from tidy3d.packaging import verify_packages_import + from . import base, triangulation # sampling polygon along dilation for validating polygon to be @@ -60,7 +61,7 @@ class PolySlab(base.Planar): >>> p = PolySlab(vertices=vertices, axis=2, slab_bounds=(-1, 1)) """ - slab_bounds: Tuple[TracedFloat, TracedFloat] = pydantic.Field( + slab_bounds: tuple[TracedFloat, TracedFloat] = pydantic.Field( ..., title="Slab Bounds", description="Minimum and maximum positions of the slab along axis dimension.", @@ -252,14 +253,14 @@ def from_gds( cls, gds_cell, axis: Axis, - slab_bounds: Tuple[float, float], + slab_bounds: tuple[float, float], gds_layer: int, - gds_dtype: int = None, + gds_dtype: Optional[int] = None, gds_scale: pydantic.PositiveFloat = 1.0, dilation: float = 0.0, sidewall_angle: float = 0, reference_plane: PlanePosition = "middle", - ) -> List[PolySlab]: + ) -> list[PolySlab]: """Import :class:`PolySlab` from a ``gdstk.Cell``. Parameters @@ -316,9 +317,9 @@ def from_gds( def _load_gds_vertices( gds_cell, gds_layer: int, - gds_dtype: int = None, + gds_dtype: Optional[int] = None, gds_scale: pydantic.PositiveFloat = 1.0, - ) -> List[ArrayFloat2D]: + ) -> list[ArrayFloat2D]: """Import :class:`PolySlab` from a ``gdstk.Cell``. Parameters @@ -464,7 +465,7 @@ def _normal_2dmaterial(self) -> Axis: raise ValidationError("'Medium2D' requires the 'PolySlab' bounds to be equal.") return self.axis - def _update_from_bounds(self, bounds: Tuple[float, float], axis: Axis) -> PolySlab: + def _update_from_bounds(self, bounds: tuple[float, float], axis: Axis) -> PolySlab: """Returns an updated geometry which has been transformed to fit within ``bounds`` along the ``axis`` direction.""" if axis != self.axis: @@ -576,7 +577,7 @@ def _move_axis_reverse(arr): @verify_packages_import(["trimesh"]) def _do_intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Parameters @@ -798,7 +799,7 @@ def _find_intersecting_height(self, position: float, axis: int) -> np.ndarray: def _find_intersecting_ys_angle_vertical( self, vertices: np.ndarray, position: float, axis: int, exclude_on_vertices: bool = False - ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Finds pairs of forward and backwards vertices where polygon intersects position at axis, Find intersection point (in y) assuming straight line,and intersecting angle between plane and edges. (For unslanted polyslab). @@ -878,7 +879,7 @@ def _find_intersecting_ys_angle_vertical( def _find_intersecting_ys_angle_slant( self, vertices: np.ndarray, position: float, axis: int - ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Finds pairs of forward and backwards vertices where polygon intersects position at axis, Find intersection point (in y) assuming straight line,and intersecting angle between plane and edges. (For slanted polyslab) @@ -1250,7 +1251,7 @@ def normalize(v): @staticmethod def _shift_vertices( vertices: np.ndarray, dist - ) -> Tuple[np.ndarray, np.ndarray, Tuple[np.ndarray, np.ndarray]]: + ) -> tuple[np.ndarray, np.ndarray, tuple[np.ndarray, np.ndarray]]: """Shifts the vertices of a polygon outward uniformly by distances `dists`. @@ -1311,7 +1312,7 @@ def normalize(v): return np.swapaxes(vs_orig + shift_total, -2, -1), parallel_shift, (shift_x, shift_y) @staticmethod - def _edge_length_and_reduction_rate(vertices: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: + def _edge_length_and_reduction_rate(vertices: np.ndarray) -> tuple[np.ndarray, np.ndarray]: """Edge length of reduction rate of each edge with unit offset length. Parameters @@ -1352,7 +1353,7 @@ def _heal_polygon(vertices: np.ndarray) -> np.ndarray: shapely_poly = PolySlab.make_shapely_polygon(vertices) if shapely_poly.is_valid: return vertices - elif isbox(vertices): + if isbox(vertices): raise NotImplementedError( "The dilation caused damage to the polygon. " "Automatically healing this is currently not supported when " @@ -1626,7 +1627,7 @@ def edge_basis_vectors( if self.axis != 1: normals_norm_xyz *= -1 - return dict(norm=normals_norm_xyz, perp1=edges_norm_xyz, perp2=slabs_norm_xyz) + return {"norm": normals_norm_xyz, "perp1": edges_norm_xyz, "perp2": slabs_norm_xyz} def unpop_axis_vect(self, ax_coords: np.ndarray, plane_coords: np.ndarray) -> np.ndarray: """Combine coordinate along axis with coordinates on the plane tangent to the axis. @@ -1640,7 +1641,7 @@ def unpop_axis_vect(self, ax_coords: np.ndarray, plane_coords: np.ndarray) -> np arr_xyz = np.stack(arr_xyz, axis=-1) return arr_xyz - def pop_axis_vect(self, coord: np.ndarray) -> Tuple[np.ndarray, Tuple[np.ndarray, np.ndarray]]: + def pop_axis_vect(self, coord: np.ndarray) -> tuple[np.ndarray, tuple[np.ndarray, np.ndarray]]: """Combine coordinate along axis with coordinates on the plane tangent to the axis. coord.shape == [N, 3] @@ -1777,14 +1778,14 @@ def from_gds( cls, gds_cell, axis: Axis, - slab_bounds: Tuple[float, float], + slab_bounds: tuple[float, float], gds_layer: int, - gds_dtype: int = None, + gds_dtype: Optional[int] = None, gds_scale: pydantic.PositiveFloat = 1.0, dilation: float = 0.0, sidewall_angle: float = 0, reference_plane: PlanePosition = "middle", - ) -> List[PolySlab]: + ) -> list[PolySlab]: """Import :class:`.PolySlab` from a ``gdstk.Cell``. Parameters @@ -1853,7 +1854,7 @@ def geometry_group(self) -> base.GeometryGroup: return base.GeometryGroup(geometries=self.sub_polyslabs) @property - def sub_polyslabs(self) -> List[PolySlab]: + def sub_polyslabs(self) -> list[PolySlab]: """Divide a complex polyslab into a list of simple polyslabs. Only neighboring vertex-vertex crossing events are treated in this version. @@ -1904,11 +1905,11 @@ def sub_polyslabs(self) -> List[PolySlab]: # direction of marching reference_plane = "bottom" if dist_val / self._tanq < 0 else "top" sub_polyslab_dict.update( - dict( - slab_bounds=tuple(slab_bounds), - vertices=vertices_now, - reference_plane=reference_plane, - ) + { + "slab_bounds": tuple(slab_bounds), + "vertices": vertices_now, + "reference_plane": reference_plane, + } ) sub_polyslab_list.append(PolySlab.parse_obj(sub_polyslab_dict)) @@ -1938,7 +1939,7 @@ def sub_polyslabs(self) -> List[PolySlab]: return sub_polyslab_list @property - def _dilation_length(self) -> List[float]: + def _dilation_length(self) -> list[float]: """dilation length from reference plane to the top/bottom of the polyslab.""" # for "bottom", only needs to compute the offset length to the top @@ -1964,7 +1965,7 @@ def _dilation_value_at_reference_to_coord(self, dilation: float) -> float: def intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Parameters diff --git a/tidy3d/components/geometry/primitives.py b/tidy3d/components/geometry/primitives.py index 55f260d9b0..9e13cbe5fa 100644 --- a/tidy3d/components/geometry/primitives.py +++ b/tidy3d/components/geometry/primitives.py @@ -3,20 +3,21 @@ from __future__ import annotations from math import isclose -from typing import List +from typing import Optional import autograd.numpy as anp import numpy as np import pydantic.v1 as pydantic import shapely -from ...constants import C_0, LARGE_NUMBER, MICROMETER -from ...exceptions import SetupError, ValidationError -from ...packaging import verify_packages_import -from ..autograd import AutogradFieldMap, TracedSize1D -from ..autograd.derivative_utils import DerivativeInfo -from ..base import cached_property, skip_if_fields_missing -from ..types import Axis, Bound, Coordinate, MatrixReal4x4, Shapely, Tuple +from tidy3d.components.autograd import AutogradFieldMap, TracedSize1D +from tidy3d.components.autograd.derivative_utils import DerivativeInfo +from tidy3d.components.base import cached_property, skip_if_fields_missing +from tidy3d.components.types import Axis, Bound, Coordinate, MatrixReal4x4, Shapely +from tidy3d.constants import C_0, LARGE_NUMBER, MICROMETER +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.packaging import verify_packages_import + from . import base from .polyslab import PolySlab @@ -71,7 +72,7 @@ def inside( def intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Parameters @@ -110,7 +111,9 @@ def intersections_tilted_plane( vertices = np.dot(np.hstack((circ, np.ones((angles.size, 1)))), to_2D.T) return [shapely.Polygon(vertices[:, :2])] - def intersections_plane(self, x: float = None, y: float = None, z: float = None): + def intersections_plane( + self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + ): """Returns shapely geometry at plane specified by one non None value of x,y,z. Parameters @@ -354,7 +357,7 @@ def _normal_2dmaterial(self) -> Axis: raise ValidationError("'Medium2D' requires the 'Cylinder' length to be zero.") return self.axis - def _update_from_bounds(self, bounds: Tuple[float, float], axis: Axis) -> Cylinder: + def _update_from_bounds(self, bounds: tuple[float, float], axis: Axis) -> Cylinder: """Returns an updated geometry which has been transformed to fit within ``bounds`` along the ``axis`` direction.""" if axis != self.axis: @@ -370,7 +373,7 @@ def _update_from_bounds(self, bounds: Tuple[float, float], axis: Axis) -> Cylind @verify_packages_import(["trimesh"]) def _do_intersections_tilted_plane( self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4 - ) -> List[Shapely]: + ) -> list[Shapely]: """Return a list of shapely geometries at the plane specified by normal and origin. Parameters @@ -728,7 +731,7 @@ def _radius_z(self, z: float): return radius_middle - (z - self.center_axis) * self._tanq - def _local_to_global_side_cross_section(self, coords: List[float], axis: int) -> List[float]: + def _local_to_global_side_cross_section(self, coords: list[float], axis: int) -> list[float]: """Map a point (x,y) from local to global coordinate system in the side cross section. diff --git a/tidy3d/components/geometry/triangulation.py b/tidy3d/components/geometry/triangulation.py index a6a6e00d2c..c4ac2641d7 100644 --- a/tidy3d/components/geometry/triangulation.py +++ b/tidy3d/components/geometry/triangulation.py @@ -1,11 +1,12 @@ +from __future__ import annotations + from dataclasses import dataclass -from typing import List, Tuple import numpy as np import shapely -from ...exceptions import Tidy3dError -from ..types import ArrayFloat1D, ArrayFloat2D +from tidy3d.components.types import ArrayFloat1D, ArrayFloat2D +from tidy3d.exceptions import Tidy3dError @dataclass @@ -33,7 +34,7 @@ class Vertex: is_ear: bool -def update_convexity(vertices: List[Vertex], i: int) -> int: +def update_convexity(vertices: list[Vertex], i: int) -> int: """Update the convexity of a vertex in a polygon. Parameters @@ -69,7 +70,7 @@ def update_convexity(vertices: List[Vertex], i: int) -> int: def is_inside( - vertex: ArrayFloat1D, triangle: Tuple[ArrayFloat1D, ArrayFloat1D, ArrayFloat1D] + vertex: ArrayFloat1D, triangle: tuple[ArrayFloat1D, ArrayFloat1D, ArrayFloat1D] ) -> bool: """Check if a vertex is inside a triangle. @@ -90,7 +91,7 @@ def is_inside( ) -def update_ear_flag(vertices: List[Vertex], i: int) -> None: +def update_ear_flag(vertices: list[Vertex], i: int) -> None: """Update the ear flag of a vertex in a polygon. Parameters @@ -112,7 +113,7 @@ def update_ear_flag(vertices: List[Vertex], i: int) -> None: # TODO: This is an inefficient algorithm that runs in O(n^2). We should use something # better, and probably as a compiled extension. -def triangulate(vertices: ArrayFloat2D) -> List[Tuple[int, int, int]]: +def triangulate(vertices: ArrayFloat2D) -> list[tuple[int, int, int]]: """Triangulate a simple polygon. Parameters diff --git a/tidy3d/components/geometry/utils.py b/tidy3d/components/geometry/utils.py index 9eaa7542e3..8c166fdd21 100644 --- a/tidy3d/components/geometry/utils.py +++ b/tidy3d/components/geometry/utils.py @@ -4,17 +4,25 @@ from enum import Enum from math import isclose -from typing import Any, List, Optional, Tuple, Union +from typing import Any, Optional, Union import numpy as np -import pydantic as pydantic - -from ...constants import fp_eps -from ...exceptions import SetupError, Tidy3dError -from ..base import Tidy3dBaseModel -from ..geometry.base import Box -from ..grid.grid import Grid -from ..types import ArrayFloat2D, Axis, Coordinate, MatrixReal4x4, PlanePosition, Shapely +import pydantic + +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.components.geometry.base import Box +from tidy3d.components.grid.grid import Grid +from tidy3d.components.types import ( + ArrayFloat2D, + Axis, + Coordinate, + MatrixReal4x4, + PlanePosition, + Shapely, +) +from tidy3d.constants import fp_eps +from tidy3d.exceptions import SetupError, Tidy3dError + from . import base, mesh, polyslab, primitives GeometryType = Union[ @@ -31,10 +39,10 @@ def merging_geometries_on_plane( - geometries: List[GeometryType], + geometries: list[GeometryType], plane: Box, - property_list: List[Any], -) -> List[Tuple[Any, Shapely]]: + property_list: list[Any], +) -> list[tuple[Any, Shapely]]: """Compute list of shapes on plane. Overlaps are removed or merged depending on provided property_list. @@ -191,7 +199,7 @@ def traverse_geometries(geometry: GeometryType) -> GeometryType: def from_shapely( shape: Shapely, axis: Axis, - slab_bounds: Tuple[float, float], + slab_bounds: tuple[float, float], dilation: float = 0.0, sidewall_angle: float = 0, reference_plane: PlanePosition = "middle", @@ -284,7 +292,7 @@ def vertices_from_shapely(shape: Shapely) -> ArrayFloat2D: if shape.geom_type == "LinearRing": return [(shape.coords[:-1],)] if shape.geom_type == "Polygon": - return [(shape.exterior.coords[:-1],) + tuple(hole.coords[:-1] for hole in shape.interiors)] + return [(shape.exterior.coords[:-1], *tuple(hole.coords[:-1] for hole in shape.interiors))] if shape.geom_type in {"MultiPolygon", "GeometryCollection"}: return sum(vertices_from_shapely(geo) for geo in shape.geoms) diff --git a/tidy3d/components/geometry/utils_2d.py b/tidy3d/components/geometry/utils_2d.py index 5d7b0860f9..46b238accd 100644 --- a/tidy3d/components/geometry/utils_2d.py +++ b/tidy3d/components/geometry/utils_2d.py @@ -1,18 +1,19 @@ """Utilities for 2D geometry manipulation.""" +from __future__ import annotations + from math import isclose -from typing import List, Tuple import numpy as np import shapely -from ...constants import fp_eps, inf -from ..geometry.base import Box, ClipOperation, Geometry -from ..geometry.polyslab import _MIN_POLYGON_AREA, PolySlab -from ..grid.grid import Grid -from ..scene import Scene -from ..structure import Structure -from ..types import Axis +from tidy3d.components.geometry.base import Box, ClipOperation, Geometry +from tidy3d.components.geometry.polyslab import _MIN_POLYGON_AREA, PolySlab +from tidy3d.components.grid.grid import Grid +from tidy3d.components.scene import Scene +from tidy3d.components.structure import Structure +from tidy3d.components.types import Axis +from tidy3d.constants import fp_eps, inf def increment_float(val: float, sign) -> float: @@ -46,7 +47,7 @@ def snap_coordinate_to_grid(grid: Grid, center: float, axis: Axis) -> float: return new_center -def get_bounds(geom: Geometry, axis: Axis) -> Tuple[float, float]: +def get_bounds(geom: Geometry, axis: Axis) -> tuple[float, float]: """Get the bounds of a geometry in the axis direction.""" return (geom.bounds[0][axis], geom.bounds[1][axis]) @@ -62,7 +63,7 @@ def get_thickened_geom(geom: Geometry, axis: Axis): def get_neighbors( geom: Geometry, axis: Axis, - structures: List[Structure], + structures: list[Structure], ): """Find the neighboring structures and return the tested positions above and below.""" center = get_bounds(geom, axis)[0] @@ -100,8 +101,8 @@ def get_neighbors( def subdivide( - geom: Geometry, structures: List[Structure] -) -> List[Tuple[Geometry, Structure, Structure]]: + geom: Geometry, structures: list[Structure] +) -> list[tuple[Geometry, Structure, Structure]]: """Subdivide geometry associated with a :class:`.Medium2D` into partitions that each have a homogeneous substrate / superstrate. Partitions are computed using ``shapely`` boolean operations on polygons. diff --git a/tidy3d/components/grid/corner_finder.py b/tidy3d/components/grid/corner_finder.py index aa3c5347bf..272aa28131 100644 --- a/tidy3d/components/grid/corner_finder.py +++ b/tidy3d/components/grid/corner_finder.py @@ -1,17 +1,19 @@ """Find corners of structures on a 2D plane.""" -from typing import Any, List, Literal, Optional, Tuple +from __future__ import annotations + +from typing import Any, Literal, Optional import numpy as np import pydantic.v1 as pd -from ...constants import inf -from ..base import Tidy3dBaseModel, cached_property -from ..geometry.base import Box, ClipOperation -from ..geometry.utils import merging_geometries_on_plane -from ..medium import PEC, LossyMetalMedium -from ..structure import Structure -from ..types import ArrayFloat1D, ArrayFloat2D, Axis, Shapely +from tidy3d.components.base import Tidy3dBaseModel, cached_property +from tidy3d.components.geometry.base import Box, ClipOperation +from tidy3d.components.geometry.utils import merging_geometries_on_plane +from tidy3d.components.medium import PEC, LossyMetalMedium +from tidy3d.components.structure import Structure +from tidy3d.components.types import ArrayFloat1D, ArrayFloat2D, Axis, Shapely +from tidy3d.constants import inf CORNER_ANGLE_THRESOLD = 0.1 * np.pi @@ -80,10 +82,10 @@ def _merged_pec_on_plane( cls, normal_axis: Axis, coord: float, - structure_list: List[Structure], - center: Tuple[float, float] = [0, 0, 0], - size: Tuple[float, float, float] = [inf, inf, inf], - ) -> List[Tuple[Any, Shapely]]: + structure_list: list[Structure], + center: tuple[float, float] = [0, 0, 0], + size: tuple[float, float, float] = [inf, inf, inf], + ) -> list[tuple[Any, Shapely]]: """On a 2D plane specified by axis = `normal_axis` and coordinate `coord`, merge geometries made of PEC. Parameters @@ -129,9 +131,9 @@ def _corners_and_convexity( self, normal_axis: Axis, coord: float, - structure_list: List[Structure], + structure_list: list[Structure], ravel: bool, - ) -> Tuple[ArrayFloat2D, ArrayFloat1D]: + ) -> tuple[ArrayFloat2D, ArrayFloat1D]: """On a 2D plane specified by axis = `normal_axis` and coordinate `coord`, find out corners of merged geometries made of PEC. @@ -192,7 +194,7 @@ def corners( self, normal_axis: Axis, coord: float, - structure_list: List[Structure], + structure_list: list[Structure], ) -> ArrayFloat2D: """On a 2D plane specified by axis = `normal_axis` and coordinate `coord`, find out corners of merged geometries made of `medium`. @@ -220,7 +222,7 @@ def corners( def _filter_collinear_vertices( self, vertices: ArrayFloat2D - ) -> Tuple[ArrayFloat2D, ArrayFloat1D]: + ) -> tuple[ArrayFloat2D, ArrayFloat1D]: """Filter collinear vertices of a polygon, and return corners locations and their convexity. Parameters diff --git a/tidy3d/components/grid/grid.py b/tidy3d/components/grid/grid.py index 62353b659e..5baf3828e0 100644 --- a/tidy3d/components/grid/grid.py +++ b/tidy3d/components/grid/grid.py @@ -2,17 +2,17 @@ from __future__ import annotations -from typing import Dict, List, Tuple, Union +from typing import Literal, Union import numpy as np import pydantic.v1 as pd -from ...exceptions import SetupError -from ..base import Tidy3dBaseModel, cached_property -from ..data.data_array import DataArray, ScalarFieldDataArray, SpatialDataArray -from ..data.utils import UnstructuredGridDataset, UnstructuredGridDatasetType -from ..geometry.base import Box -from ..types import ArrayFloat1D, Axis, Coordinate, InterpMethod, Literal +from tidy3d.components.base import Tidy3dBaseModel, cached_property +from tidy3d.components.data.data_array import DataArray, ScalarFieldDataArray, SpatialDataArray +from tidy3d.components.data.utils import UnstructuredGridDataset, UnstructuredGridDatasetType +from tidy3d.components.geometry.base import Box +from tidy3d.components.types import ArrayFloat1D, Axis, Coordinate, InterpMethod +from tidy3d.exceptions import SetupError # data type of one dimensional coordinate array. Coords1D = ArrayFloat1D @@ -84,7 +84,7 @@ def cell_size_meshgrid(self): if len(meshgrid_elements) > 1: meshgrid = np.meshgrid(*meshgrid_elements, indexing="ij") - for idx in range(0, len(meshgrid)): + for idx in range(len(meshgrid)): cell_size_meshgrid *= np.reshape(meshgrid[idx], cell_size_meshgrid.shape) elif len(meshgrid_elements) == 1: cell_size_meshgrid = meshgrid_elements[0] @@ -262,10 +262,9 @@ def spatial_interp( return self._interp_from_unstructured( array=array, interp_method=interp_method, fill_value=fill_value ) - else: - return self._interp_from_xarray( - array=array, interp_method=interp_method, fill_value=fill_value - ) + return self._interp_from_xarray( + array=array, interp_method=interp_method, fill_value=fill_value + ) class FieldGrid(Tidy3dBaseModel): @@ -410,7 +409,7 @@ def sizes(self) -> Coords: return Coords(**{key: np.diff(val) for key, val in self.boundaries.to_dict.items()}) @property - def num_cells(self) -> Tuple[int, int, int]: + def num_cells(self) -> tuple[int, int, int]: """Return sizes of the cells in the :class:`Grid`. Returns @@ -452,7 +451,7 @@ def max_size(self) -> float: return float(max(max(sizes) for sizes in self.sizes.to_list)) @property - def info(self) -> Dict: + def info(self) -> dict: """Dictionary collecting various properties of the grids.""" num_cells = self.num_cells total_cells = int(np.prod(num_cells)) @@ -567,7 +566,7 @@ def _yee_h(self, axis: Axis): return Coords(**yee_coords) - def discretize_inds(self, box: Box, extend: bool = False) -> List[Tuple[int, int]]: + def discretize_inds(self, box: Box, extend: bool = False) -> list[tuple[int, int]]: """Start and stopping indexes for the cells that intersect with a :class:`Box`. Parameters diff --git a/tidy3d/components/grid/grid_spec.py b/tidy3d/components/grid/grid_spec.py index ed72ae26a6..5fe7b51c73 100644 --- a/tidy3d/components/grid/grid_spec.py +++ b/tidy3d/components/grid/grid_spec.py @@ -3,20 +3,17 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, List, Literal, Optional, Tuple, Union +from typing import Any, Literal, Optional, Union import numpy as np import pydantic.v1 as pd -from ...constants import C_0, MICROMETER, dp_eps, inf -from ...exceptions import SetupError -from ...log import log -from ..base import Tidy3dBaseModel, cached_property, skip_if_fields_missing -from ..geometry.base import Box, ClipOperation -from ..lumped_element import LumpedElementType -from ..source.utils import SourceType -from ..structure import MeshOverrideStructure, Structure, StructureType -from ..types import ( +from tidy3d.components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing +from tidy3d.components.geometry.base import Box, ClipOperation +from tidy3d.components.lumped_element import LumpedElementType +from tidy3d.components.source.utils import SourceType +from tidy3d.components.structure import MeshOverrideStructure, Structure, StructureType +from tidy3d.components.types import ( TYPE_TAG_STR, ArrayFloat2D, Axis, @@ -24,8 +21,13 @@ CoordinateOptional, PriorityMode, Symmetry, + Undefined, annotate_type, ) +from tidy3d.constants import C_0, MICROMETER, dp_eps, inf +from tidy3d.exceptions import SetupError +from tidy3d.log import log + from .corner_finder import CornerFinderSpec from .grid import Coords, Coords1D, Grid from .mesher import GradedMesher, MesherType @@ -47,12 +49,12 @@ class GridSpec1d(Tidy3dBaseModel, ABC): def make_coords( self, axis: Axis, - structures: List[StructureType], - symmetry: Tuple[Symmetry, Symmetry, Symmetry], + structures: list[StructureType], + symmetry: tuple[Symmetry, Symmetry, Symmetry], periodic: bool, wavelength: pd.PositiveFloat, - num_pml_layers: Tuple[pd.NonNegativeInt, pd.NonNegativeInt], - snapping_points: Tuple[CoordinateOptional, ...], + num_pml_layers: tuple[pd.NonNegativeInt, pd.NonNegativeInt], + snapping_points: tuple[CoordinateOptional, ...], ) -> Coords1D: """Generate 1D coords to be used as grid boundaries, based on simulation parameters. Symmetry, and PML layers will be treated here. @@ -113,7 +115,7 @@ def make_coords( def _make_coords_initial( self, axis: Axis, - structures: List[StructureType], + structures: list[StructureType], **kwargs, ) -> Coords1D: """Generate 1D coords to be used as grid boundaries, based on simulation parameters. @@ -135,7 +137,7 @@ def _make_coords_initial( """ @staticmethod - def _add_pml_to_bounds(num_layers: Tuple[int, int], bounds: Coords1D) -> Coords1D: + def _add_pml_to_bounds(num_layers: tuple[int, int], bounds: Coords1D) -> Coords1D: """Append absorber layers to the beginning and end of the simulation bounds along one dimension. @@ -210,31 +212,30 @@ def _postprocess_unaligned_grid( return bound_coords[ind - 1 : ind + 1] - else: - bound_coords = bound_coords[bound_coords <= bound_max] - bound_coords = bound_coords[bound_coords >= bound_min] - - # if not extending to simulation bounds, repeat beginning and end - dl_min = bound_coords[1] - bound_coords[0] - dl_max = bound_coords[-1] - bound_coords[-2] - while bound_coords[0] - dl_min >= bound_min: + bound_coords = bound_coords[bound_coords <= bound_max] + bound_coords = bound_coords[bound_coords >= bound_min] + + # if not extending to simulation bounds, repeat beginning and end + dl_min = bound_coords[1] - bound_coords[0] + dl_max = bound_coords[-1] - bound_coords[-2] + while bound_coords[0] - dl_min >= bound_min: + bound_coords = np.insert(bound_coords, 0, bound_coords[0] - dl_min) + while bound_coords[-1] + dl_max <= bound_max: + bound_coords = np.append(bound_coords, bound_coords[-1] + dl_max) + + # in case operations are applied to coords, it's possible the bounds were numerically within + # the simulation bounds but were still chopped off, which is fixed here + if machine_error_relaxation: + if np.isclose(bound_coords[0] - dl_min, bound_min): bound_coords = np.insert(bound_coords, 0, bound_coords[0] - dl_min) - while bound_coords[-1] + dl_max <= bound_max: + if np.isclose(bound_coords[-1] + dl_max, bound_max): bound_coords = np.append(bound_coords, bound_coords[-1] + dl_max) - # in case operations are applied to coords, it's possible the bounds were numerically within - # the simulation bounds but were still chopped off, which is fixed here - if machine_error_relaxation: - if np.isclose(bound_coords[0] - dl_min, bound_min): - bound_coords = np.insert(bound_coords, 0, bound_coords[0] - dl_min) - if np.isclose(bound_coords[-1] + dl_max, bound_max): - bound_coords = np.append(bound_coords, bound_coords[-1] + dl_max) - - return bound_coords + return bound_coords @abstractmethod def estimated_min_dl( - self, wavelength: float, structure_list: List[Structure], sim_size: Tuple[float, 3] + self, wavelength: float, structure_list: list[Structure], sim_size: tuple[float, 3] ) -> float: """Estimated minimal grid size along the axis. The actual minimal grid size from mesher might be smaller. @@ -299,7 +300,7 @@ def _validate_dl(cls, val): def _make_coords_initial( self, axis: Axis, - structures: List[StructureType], + structures: list[StructureType], **kwargs, ) -> Coords1D: """Uniform 1D coords to be used as grid boundaries. @@ -331,7 +332,7 @@ def _make_coords_initial( return center - size / 2 + np.arange(num_cells + 1) * dl_snapped def estimated_min_dl( - self, wavelength: float, structure_list: List[Structure], sim_size: Tuple[float, 3] + self, wavelength: float, structure_list: list[Structure], sim_size: tuple[float, 3] ) -> float: """Minimal grid size, which equals grid size here. @@ -371,7 +372,7 @@ class CustomGridBoundaries(GridSpec1d): def _make_coords_initial( self, axis: Axis, - structures: List[StructureType], + structures: list[StructureType], **kwargs, ) -> Coords1D: """Customized 1D coords to be used as grid boundaries. @@ -397,7 +398,7 @@ def _make_coords_initial( ) def estimated_min_dl( - self, wavelength: float, structure_list: List[Structure], sim_size: Tuple[float, 3] + self, wavelength: float, structure_list: list[Structure], sim_size: tuple[float, 3] ) -> float: """Minimal grid size from grid specification. @@ -427,7 +428,7 @@ class CustomGrid(GridSpec1d): >>> grid_1d = CustomGrid(dl=[0.2, 0.2, 0.1, 0.1, 0.1, 0.2, 0.2]) """ - dl: Tuple[pd.PositiveFloat, ...] = pd.Field( + dl: tuple[pd.PositiveFloat, ...] = pd.Field( ..., title="Customized grid sizes.", description="An array of custom nonuniform grid sizes. The resulting grid is centered on " @@ -450,7 +451,7 @@ class CustomGrid(GridSpec1d): def _make_coords_initial( self, axis: Axis, - structures: List[StructureType], + structures: list[StructureType], **kwargs, ) -> Coords1D: """Customized 1D coords to be used as grid boundaries. @@ -489,7 +490,7 @@ def _make_coords_initial( ) def estimated_min_dl( - self, wavelength: float, structure_list: List[Structure], sim_size: Tuple[float, 3] + self, wavelength: float, structure_list: list[Structure], sim_size: tuple[float, 3] ) -> float: """Minimal grid size from grid specification. @@ -539,11 +540,11 @@ class AbstractAutoGrid(GridSpec1d): ) @abstractmethod - def _preprocessed_structures(self, structures: List[StructureType]) -> List[StructureType]: + def _preprocessed_structures(self, structures: list[StructureType]) -> list[StructureType]: """Preprocess structure list before passing to ``mesher``.""" @abstractmethod - def _dl_collapsed_axis(self, wavelength: float, sim_size: Tuple[float, 3]) -> float: + def _dl_collapsed_axis(self, wavelength: float, sim_size: tuple[float, 3]) -> float: """The grid step size if just a single grid along an axis in the simulation domain.""" @property @@ -557,7 +558,7 @@ def _min_steps_per_wvl(self) -> float: """Minimal steps per wavelength applied internally.""" @abstractmethod - def _dl_max(self, sim_size: Tuple[float, 3]) -> float: + def _dl_max(self, sim_size: tuple[float, 3]) -> float: """Upper bound of grid size applied internally.""" @property @@ -565,18 +566,18 @@ def _undefined_dl_min(self) -> bool: """Whether `dl_min` has been specified or not.""" return self.dl_min is None or self.dl_min == 0 - def _filtered_dl(self, dl: float, sim_size: Tuple[float, 3]) -> float: + def _filtered_dl(self, dl: float, sim_size: tuple[float, 3]) -> float: """Grid step size after applying minimal and maximal filtering.""" return max(min(dl, self._dl_max(sim_size)), self._dl_min) def _make_coords_initial( self, axis: Axis, - structures: List[StructureType], + structures: list[StructureType], wavelength: float, symmetry: Symmetry, is_periodic: bool, - snapping_points: Tuple[CoordinateOptional, ...], + snapping_points: tuple[CoordinateOptional, ...], ) -> Coords1D: """Customized 1D coords to be used as grid boundaries. @@ -704,7 +705,7 @@ class QuasiUniformGrid(AbstractAutoGrid): units=MICROMETER, ) - def _preprocessed_structures(self, structures: List[StructureType]) -> List[StructureType]: + def _preprocessed_structures(self, structures: list[StructureType]) -> list[StructureType]: """Processing structure list before passing to ``mesher``. Adjust all structures to drop their material properties so that they all have step size ``dl``. """ @@ -732,16 +733,16 @@ def _min_steps_per_wvl(self) -> float: # irrelevant in this class, just supply an arbitrary number return 1 - def _dl_max(self, sim_size: Tuple[float, 3]) -> float: + def _dl_max(self, sim_size: tuple[float, 3]) -> float: """Upper bound of grid size.""" return self.dl - def _dl_collapsed_axis(self, wavelength: float, sim_size: Tuple[float, 3]) -> float: + def _dl_collapsed_axis(self, wavelength: float, sim_size: tuple[float, 3]) -> float: """The grid step size if just a single grid along an axis.""" return self._filtered_dl(self.dl, sim_size) def estimated_min_dl( - self, wavelength: float, structure_list: List[Structure], sim_size: Tuple[float, 3] + self, wavelength: float, structure_list: list[Structure], sim_size: tuple[float, 3] ) -> float: """Estimated minimal grid size, which equals grid size here. @@ -802,7 +803,7 @@ class AutoGrid(AbstractAutoGrid): ge=1.0, ) - def _dl_max(self, sim_size: Tuple[float, 3]) -> float: + def _dl_max(self, sim_size: tuple[float, 3]) -> float: """Upper bound of grid size, constrained by `min_steps_per_sim_size`.""" return max(sim_size) / self.min_steps_per_sim_size @@ -819,20 +820,20 @@ def _min_steps_per_wvl(self) -> float: """Minimal steps per wavelength.""" return self.min_steps_per_wvl - def _preprocessed_structures(self, structures: List[StructureType]) -> List[StructureType]: + def _preprocessed_structures(self, structures: list[StructureType]) -> list[StructureType]: """Processing structure list before passing to ``mesher``.""" return structures - def _dl_collapsed_axis(self, wavelength: float, sim_size: Tuple[float, 3]) -> float: + def _dl_collapsed_axis(self, wavelength: float, sim_size: tuple[float, 3]) -> float: """The grid step size if just a single grid along an axis.""" return self._vacuum_dl(wavelength, sim_size) - def _vacuum_dl(self, wavelength: float, sim_size: Tuple[float, 3]) -> float: + def _vacuum_dl(self, wavelength: float, sim_size: tuple[float, 3]) -> float: """Grid step size when computed in vacuum region.""" return self._filtered_dl(wavelength / self.min_steps_per_wvl, sim_size) def estimated_min_dl( - self, wavelength: float, structure_list: List[Structure], sim_size: Tuple[float, 3] + self, wavelength: float, structure_list: list[Structure], sim_size: tuple[float, 3] ) -> float: """Estimated minimal grid size along the axis. The actual minimal grid size from mesher might be smaller. @@ -1071,13 +1072,13 @@ def _finite_size_along_axis(cls, val, values): def from_layer_bounds( cls, axis: Axis, - bounds: Tuple[float, float], + bounds: tuple[float, float], min_steps_along_axis: np.PositiveFloat = None, bounds_refinement: GridRefinement = None, bounds_snapping: Literal["bounds", "lower", "upper", "center"] = "lower", - corner_finder: CornerFinderSpec = CornerFinderSpec(), + corner_finder: Union[CornerFinderSpec, None, object] = Undefined, corner_snapping: bool = True, - corner_refinement: GridRefinement = GridRefinement(), + corner_refinement: Union[GridRefinement, None, object] = Undefined, refinement_inside_sim_only: bool = True, gap_meshing_iters: pd.NonNegativeInt = 1, dl_min_from_gap_width: bool = True, @@ -1117,6 +1118,11 @@ def from_layer_bounds( >>> layer = LayerRefinementSpec.from_layer_bounds(axis=2, bounds=(0,1)) """ + if corner_finder is Undefined: + corner_finder = CornerFinderSpec() + if corner_refinement is Undefined: + corner_refinement = GridRefinement() + center = Box.unpop_axis((bounds[0] + bounds[1]) / 2, (0, 0), axis) size = Box.unpop_axis((bounds[1] - bounds[0]), (inf, inf), axis) @@ -1144,9 +1150,9 @@ def from_bounds( min_steps_along_axis: np.PositiveFloat = None, bounds_refinement: GridRefinement = None, bounds_snapping: Literal["bounds", "lower", "upper", "center"] = "lower", - corner_finder: CornerFinderSpec = CornerFinderSpec(), + corner_finder: CornerFinderSpec = Undefined, corner_snapping: bool = True, - corner_refinement: GridRefinement = GridRefinement(), + corner_refinement: GridRefinement = Undefined, refinement_inside_sim_only: bool = True, gap_meshing_iters: pd.NonNegativeInt = 1, dl_min_from_gap_width: bool = True, @@ -1188,6 +1194,11 @@ def from_bounds( >>> layer = LayerRefinementSpec.from_bounds(axis=2, rmin=(0,0,0), rmax=(1,1,1)) """ + if corner_finder is Undefined: + corner_finder = CornerFinderSpec() + if corner_refinement is Undefined: + corner_refinement = GridRefinement() + box = Box.from_bounds(rmin=rmin, rmax=rmax) if axis is None: axis = np.argmin(box.size) @@ -1209,14 +1220,14 @@ def from_bounds( @classmethod def from_structures( cls, - structures: List[Structure], + structures: list[Structure], axis: Axis = None, min_steps_along_axis: np.PositiveFloat = None, bounds_refinement: GridRefinement = None, bounds_snapping: Literal["bounds", "lower", "upper", "center"] = "lower", - corner_finder: CornerFinderSpec = CornerFinderSpec(), + corner_finder: CornerFinderSpec = Undefined, corner_snapping: bool = True, - corner_refinement: GridRefinement = GridRefinement(), + corner_refinement: GridRefinement = Undefined, refinement_inside_sim_only: bool = True, gap_meshing_iters: pd.NonNegativeInt = 1, dl_min_from_gap_width: bool = True, @@ -1251,6 +1262,10 @@ def from_structures( Take into account autodetected minimal PEC gap width when determining ``dl_min``. """ + if corner_finder is Undefined: + corner_finder = CornerFinderSpec() + if corner_refinement is Undefined: + corner_refinement = GridRefinement() all_bounds = tuple(structure.geometry.bounds for structure in structures) rmin = tuple(min(b[i] for b, _ in all_bounds) for i in range(3)) @@ -1308,7 +1323,7 @@ def _unpop_axis(self, ax_coord: float, plane_coord: Any) -> CoordinateOptional: """ return self.unpop_axis(ax_coord, [plane_coord, plane_coord], self.axis) - def suggested_dl_min(self, grid_size_in_vacuum: float, structures: List[Structure]) -> float: + def suggested_dl_min(self, grid_size_in_vacuum: float, structures: list[Structure]) -> float: """Suggested lower bound of grid step size for this layer. Parameters @@ -1347,7 +1362,7 @@ def suggested_dl_min(self, grid_size_in_vacuum: float, structures: List[Structur return dl_min - def generate_snapping_points(self, structure_list: List[Structure]) -> List[CoordinateOptional]: + def generate_snapping_points(self, structure_list: list[Structure]) -> list[CoordinateOptional]: """generate snapping points for mesh refinement.""" snapping_points = self._snapping_points_along_axis if self.corner_snapping: @@ -1355,8 +1370,8 @@ def generate_snapping_points(self, structure_list: List[Structure]) -> List[Coor return snapping_points def generate_override_structures( - self, grid_size_in_vacuum: float, structure_list: List[Structure] - ) -> List[MeshOverrideStructure]: + self, grid_size_in_vacuum: float, structure_list: list[Structure] + ) -> list[MeshOverrideStructure]: """Generate mesh override structures for mesh refinement.""" return self._override_structures_along_axis( grid_size_in_vacuum @@ -1382,8 +1397,8 @@ def _inplane_inside(self, point: ArrayFloat2D) -> bool: return self.inside(point_3d[0], point_3d[1], point_3d[2]) def _corners_and_convexity_2d( - self, structure_list: List[Structure], ravel: bool - ) -> List[CoordinateOptional]: + self, structure_list: list[Structure], ravel: bool + ) -> list[CoordinateOptional]: """Raw inplane corners and their convexity.""" if self.corner_finder is None: return [], [] @@ -1412,7 +1427,7 @@ def _corners_and_convexity_2d( return inplane_points, convexity - def _dl_min_from_smallest_feature(self, structure_list: List[Structure]): + def _dl_min_from_smallest_feature(self, structure_list: list[Structure]): """Calculate `dl_min` suggestion based on smallest feature size.""" inplane_points, convexity = self._corners_and_convexity_2d( @@ -1450,7 +1465,7 @@ def _dl_min_from_smallest_feature(self, structure_list: List[Structure]): return dl_min - def _corners(self, structure_list: List[Structure]) -> List[CoordinateOptional]: + def _corners(self, structure_list: list[Structure]) -> list[CoordinateOptional]: """Inplane corners in 3D coordinate.""" inplane_points, _ = self._corners_and_convexity_2d( structure_list=structure_list, ravel=True @@ -1463,7 +1478,7 @@ def _corners(self, structure_list: List[Structure]) -> List[CoordinateOptional]: ] @property - def _snapping_points_along_axis(self) -> List[CoordinateOptional]: + def _snapping_points_along_axis(self) -> list[CoordinateOptional]: """Snapping points for layer bounds.""" if self.bounds_snapping is None: @@ -1488,8 +1503,8 @@ def _snapping_points_along_axis(self) -> List[CoordinateOptional]: ] def _override_structures_inplane( - self, structure_list: List[Structure], grid_size_in_vacuum: float - ) -> List[MeshOverrideStructure]: + self, structure_list: list[Structure], grid_size_in_vacuum: float + ) -> list[MeshOverrideStructure]: """Inplane mesh override structures for refining mesh around corners.""" if self.corner_refinement is None: return [] @@ -1503,7 +1518,7 @@ def _override_structures_inplane( def _override_structures_along_axis( self, grid_size_in_vacuum: float - ) -> List[MeshOverrideStructure]: + ) -> list[MeshOverrideStructure]: """Mesh override structures for refining mesh along layer axis dimension.""" override_structures = [] @@ -1559,7 +1574,7 @@ def _override_structures_along_axis( def _find_vertical_intersections( self, grid_x_coords, grid_y_coords, poly_vertices, boundary - ) -> Tuple[List[Tuple[int, int]], List[float]]: + ) -> tuple[list[tuple[int, int]], list[float]]: """Detect intersection points of single polygon and vertical grid lines.""" # indices of cells that contain intersection with grid lines (left edge of a cell) @@ -1712,7 +1727,7 @@ def _find_vertical_intersections( def _process_poly( self, grid_x_coords, grid_y_coords, poly_vertices, boundaries - ) -> Tuple[List[Tuple[int, int]], List[float], List[Tuple[int, int]], List[float]]: + ) -> tuple[list[tuple[int, int]], list[float], list[tuple[int, int]], list[float]]: """Detect intersection points of single polygon and grid lines.""" # find cells that contain intersections of vertical grid lines @@ -1735,7 +1750,7 @@ def _process_poly( def _process_slice( self, x, y, merged_geos, boundaries - ) -> Tuple[List[Tuple[int, int]], List[float], List[Tuple[int, int]], List[float]]: + ) -> tuple[list[tuple[int, int]], list[float], list[tuple[int, int]], list[float]]: """Detect intersection points of geometries boundaries and grid lines.""" # cells that contain intersections of vertical grid lines @@ -1818,7 +1833,7 @@ def _process_slice( def _generate_horizontal_snapping_lines( self, grid_y_coords, intersected_cells_ij, relative_vert_disp - ) -> Tuple[List[CoordinateOptional], float]: + ) -> tuple[list[CoordinateOptional], float]: """Convert a list of intersections of vertical grid lines, given as coordinates of cells and relative vertical displacement inside each cell, into locations of snapping lines that resolve thin gaps and strips. @@ -1895,8 +1910,8 @@ def _generate_horizontal_snapping_lines( return snapping_lines_y, min_gap_width def _resolve_gaps( - self, structures: List[Structure], grid: Grid, boundaries: Tuple, center, size - ) -> Tuple[List[CoordinateOptional], float]: + self, structures: list[Structure], grid: Grid, boundaries: tuple, center, size + ) -> tuple[list[CoordinateOptional], float]: """Detect underresolved gaps and place snapping lines in them. Also return the detected minimal gap width.""" # get x and y coordinates of grid lines @@ -1916,28 +1931,27 @@ def _resolve_gaps( for coord, cmin, cmax, bdry in zip([x, y], rmin, rmax, boundaries_tan): if cmax <= coord[0] or cmin >= coord[-1]: return [], inf + if cmin < coord[0]: + ind_min = 0 else: - if cmin < coord[0]: - ind_min = 0 - else: - ind_min = max(0, np.argmax(coord >= cmin) - 1) - - if cmax > coord[-1]: - ind_max = len(coord) - 1 - else: - ind_max = np.argmax(coord >= cmax) - - if ind_min >= ind_max - 1: - return [], inf - - new_coords.append(coord[ind_min : (ind_max + 1)]) - # ignore boundary conditions if we are not touching them - new_boundaries.append( - [ - None if ind_min > 0 else bdry[0], - None if ind_max < len(coord) - 1 else bdry[1], - ] - ) + ind_min = max(0, np.argmax(coord >= cmin) - 1) + + if cmax > coord[-1]: + ind_max = len(coord) - 1 + else: + ind_max = np.argmax(coord >= cmax) + + if ind_min >= ind_max - 1: + return [], inf + + new_coords.append(coord[ind_min : (ind_max + 1)]) + # ignore boundary conditions if we are not touching them + new_boundaries.append( + [ + None if ind_min > 0 else bdry[0], + None if ind_max < len(coord) - 1 else bdry[1], + ] + ) x, y = new_coords @@ -2060,7 +2074,7 @@ class GridSpec(Tidy3dBaseModel): units=MICROMETER, ) - override_structures: Tuple[annotate_type(StructureType), ...] = pd.Field( + override_structures: tuple[annotate_type(StructureType), ...] = pd.Field( (), title="Grid specification override structures", description="A set of structures that is added on top of the simulation structures in " @@ -2070,7 +2084,7 @@ class GridSpec(Tidy3dBaseModel): "uses :class:`.AutoGrid` or :class:`.QuasiUniformGrid`.", ) - snapping_points: Tuple[CoordinateOptional, ...] = pd.Field( + snapping_points: tuple[CoordinateOptional, ...] = pd.Field( (), title="Grid specification snapping_points", description="A set of points that enforce grid boundaries to pass through them. " @@ -2081,7 +2095,7 @@ class GridSpec(Tidy3dBaseModel): "uses :class:`.AutoGrid` or :class:`.QuasiUniformGrid`.", ) - layer_refinement_specs: Tuple[LayerRefinementSpec, ...] = pd.Field( + layer_refinement_specs: tuple[LayerRefinementSpec, ...] = pd.Field( (), title="Mesh Refinement In Layered Structures", description="Automatic mesh refinement according to layer specifications. The material " @@ -2111,7 +2125,7 @@ def custom_grid_used(self) -> bool: return np.any([isinstance(mesh, (CustomGrid, CustomGridBoundaries)) for mesh in grid_list]) @staticmethod - def wavelength_from_sources(sources: List[SourceType]) -> pd.PositiveFloat: + def wavelength_from_sources(sources: list[SourceType]) -> pd.PositiveFloat: """Define a wavelength based on supplied sources. Called if auto mesh is used and ``self.wavelength is None``.""" @@ -2139,7 +2153,7 @@ def layer_refinement_used(self) -> bool: return len(self.layer_refinement_specs) > 0 @property - def snapping_points_used(self) -> List[bool, bool, bool]: + def snapping_points_used(self) -> list[bool, bool, bool]: """Along each axis, ``True`` if any snapping point is used. However, it is still ``False`` if all snapping points take value ``None`` along the axis. """ @@ -2158,7 +2172,7 @@ def snapping_points_used(self) -> List[bool, bool, bool]: return snapping_used @property - def override_structures_used(self) -> List[bool, bool, bool]: + def override_structures_used(self) -> list[bool, bool, bool]: """Along each axis, ``True`` if any override structure is used. However, it is still ``False`` if only :class:`.MeshOverrideStructure` is supplied, and their ``dl[axis]`` all take the ``None`` value. @@ -2180,8 +2194,8 @@ def override_structures_used(self) -> List[bool, bool, bool]: return override_used def internal_snapping_points( - self, structures: List[Structure], lumped_elements: List[LumpedElementType] - ) -> List[CoordinateOptional]: + self, structures: list[Structure], lumped_elements: list[LumpedElementType] + ) -> list[CoordinateOptional]: """Internal snapping points. So far, internal snapping points are generated by `layer_refinement_specs` and lumped element. @@ -2214,10 +2228,10 @@ def internal_snapping_points( def all_snapping_points( self, - structures: List[Structure], - lumped_elements: List[LumpedElementType], - internal_snapping_points: List[CoordinateOptional] = None, - ) -> List[CoordinateOptional]: + structures: list[Structure], + lumped_elements: list[LumpedElementType], + internal_snapping_points: Optional[list[CoordinateOptional]] = None, + ) -> list[CoordinateOptional]: """Internal and external snapping points. External snapping points take higher priority. So far, internal snapping points are generated by `layer_refinement_specs`. @@ -2243,17 +2257,17 @@ def all_snapping_points( return internal_snapping_points + list(self.snapping_points) @property - def external_override_structures(self) -> List[StructureType]: + def external_override_structures(self) -> list[StructureType]: """External supplied override structure list.""" return [s.to_static() for s in self.override_structures] def internal_override_structures( self, - structures: List[Structure], + structures: list[Structure], wavelength: pd.PositiveFloat, - sim_size: Tuple[float, 3], - lumped_elements: List[LumpedElementType], - ) -> List[StructureType]: + sim_size: tuple[float, 3], + lumped_elements: list[LumpedElementType], + ) -> list[StructureType]: """Internal mesh override structures. So far, internal override structures are generated by `layer_refinement_specs` and lumped element. @@ -2292,13 +2306,13 @@ def internal_override_structures( def all_override_structures( self, - structures: List[Structure], + structures: list[Structure], wavelength: pd.PositiveFloat, - sim_size: Tuple[float, 3], - lumped_elements: List[LumpedElementType], + sim_size: tuple[float, 3], + lumped_elements: list[LumpedElementType], structure_priority_mode: PriorityMode = "equal", - internal_override_structures: List[MeshOverrideStructure] = None, - ) -> List[StructureType]: + internal_override_structures: Optional[list[MeshOverrideStructure]] = None, + ) -> list[StructureType]: """Internal and external mesh override structures sorted based on their priority. By default, the priority of internal override structures is -1, and 0 for external ones. @@ -2330,7 +2344,7 @@ def all_override_structures( all_structures = internal_override_structures + self.external_override_structures return Structure._sort_structures(all_structures, structure_priority_mode) - def _min_vacuum_dl_in_autogrid(self, wavelength: float, sim_size: Tuple[float, 3]) -> float: + def _min_vacuum_dl_in_autogrid(self, wavelength: float, sim_size: tuple[float, 3]) -> float: """Compute grid step size in vacuum for Autogrd. If AutoGrid is applied along more than 1 dimension, return the minimal. """ @@ -2343,9 +2357,9 @@ def _min_vacuum_dl_in_autogrid(self, wavelength: float, sim_size: Tuple[float, 3 def _dl_min( self, wavelength: float, - structure_list: List[StructureType], - sim_size: Tuple[float, 3], - lumped_elements: List[LumpedElementType], + structure_list: list[StructureType], + sim_size: tuple[float, 3], + lumped_elements: list[LumpedElementType], ) -> float: """Lower bound of grid size to be applied to dimensions where AutoGrid with unset `dl_min` (0 or None) is applied. @@ -2380,7 +2394,7 @@ def _dl_min( min_dl = min(min_dl, min(override_structure.dl)) return min_dl * MIN_STEP_BOUND_SCALE - def get_wavelength(self, sources: List[SourceType]) -> float: + def get_wavelength(self, sources: list[SourceType]) -> float: """Get wavelength for automatic mesh generation if needed.""" wavelength = self.wavelength if wavelength is None and self.auto_grid_used: @@ -2390,15 +2404,15 @@ def get_wavelength(self, sources: List[SourceType]) -> float: def make_grid( self, - structures: List[Structure], - symmetry: Tuple[Symmetry, Symmetry, Symmetry], - periodic: Tuple[bool, bool, bool], - sources: List[SourceType], - num_pml_layers: List[Tuple[pd.NonNegativeInt, pd.NonNegativeInt]], - lumped_elements: List[LumpedElementType] = (), - internal_override_structures: List[MeshOverrideStructure] = None, - internal_snapping_points: List[CoordinateOptional] = None, - boundary_types: Tuple[Tuple[str, str], Tuple[str, str], Tuple[str, str]] = [ + structures: list[Structure], + symmetry: tuple[Symmetry, Symmetry, Symmetry], + periodic: tuple[bool, bool, bool], + sources: list[SourceType], + num_pml_layers: list[tuple[pd.NonNegativeInt, pd.NonNegativeInt]], + lumped_elements: list[LumpedElementType] = (), + internal_override_structures: Optional[list[MeshOverrideStructure]] = None, + internal_snapping_points: Optional[list[CoordinateOptional]] = None, + boundary_types: tuple[tuple[str, str], tuple[str, str], tuple[str, str]] = [ [None, None], [None, None], [None, None], @@ -2456,21 +2470,21 @@ def make_grid( def _make_grid_and_snapping_lines( self, - structures: List[Structure], - symmetry: Tuple[Symmetry, Symmetry, Symmetry], - periodic: Tuple[bool, bool, bool], - sources: List[SourceType], - num_pml_layers: List[Tuple[pd.NonNegativeInt, pd.NonNegativeInt]], - lumped_elements: List[LumpedElementType] = (), - internal_override_structures: List[MeshOverrideStructure] = None, - internal_snapping_points: List[CoordinateOptional] = None, - boundary_types: Tuple[Tuple[str, str], Tuple[str, str], Tuple[str, str]] = [ + structures: list[Structure], + symmetry: tuple[Symmetry, Symmetry, Symmetry], + periodic: tuple[bool, bool, bool], + sources: list[SourceType], + num_pml_layers: list[tuple[pd.NonNegativeInt, pd.NonNegativeInt]], + lumped_elements: list[LumpedElementType] = (), + internal_override_structures: Optional[list[MeshOverrideStructure]] = None, + internal_snapping_points: Optional[list[CoordinateOptional]] = None, + boundary_types: tuple[tuple[str, str], tuple[str, str], tuple[str, str]] = [ [None, None], [None, None], [None, None], ], structure_priority_mode: PriorityMode = "equal", - ) -> Tuple[Grid, List[CoordinateOptional]]: + ) -> tuple[Grid, list[CoordinateOptional]]: """Make the entire simulation grid based on some simulation parameters. Also return snappiung point resulted from iterative gap meshing. @@ -2546,7 +2560,7 @@ def _make_grid_and_snapping_lines( if len(new_snapping_lines) == 0: log.info( "Grid is no longer changing. " - f"Stopping iterative gap meshing after {ind+1}/{num_iters} iterations." + f"Stopping iterative gap meshing after {ind + 1}/{num_iters} iterations." ) break @@ -2570,7 +2584,7 @@ def _make_grid_and_snapping_lines( if same: log.info( "Grid is no longer changing. " - f"Stopping iterative gap meshing after {ind+1}/{num_iters} iterations." + f"Stopping iterative gap meshing after {ind + 1}/{num_iters} iterations." ) break @@ -2580,14 +2594,14 @@ def _make_grid_and_snapping_lines( def _make_grid_one_iteration( self, - structures: List[Structure], - symmetry: Tuple[Symmetry, Symmetry, Symmetry], - periodic: Tuple[bool, bool, bool], - sources: List[SourceType], - num_pml_layers: List[Tuple[pd.NonNegativeInt, pd.NonNegativeInt]], - lumped_elements: List[LumpedElementType] = (), - internal_override_structures: List[MeshOverrideStructure] = None, - internal_snapping_points: List[CoordinateOptional] = None, + structures: list[Structure], + symmetry: tuple[Symmetry, Symmetry, Symmetry], + periodic: tuple[bool, bool, bool], + sources: list[SourceType], + num_pml_layers: list[tuple[pd.NonNegativeInt, pd.NonNegativeInt]], + lumped_elements: list[LumpedElementType] = (), + internal_override_structures: Optional[list[MeshOverrideStructure]] = None, + internal_snapping_points: Optional[list[CoordinateOptional]] = None, dl_min_from_gaps: pd.PositiveFloat = inf, structure_priority_mode: PriorityMode = "equal", ) -> Grid: @@ -2733,12 +2747,12 @@ def auto( wavelength: pd.PositiveFloat = None, min_steps_per_wvl: pd.PositiveFloat = 10.0, max_scale: pd.PositiveFloat = 1.4, - override_structures: List[StructureType] = (), - snapping_points: Tuple[CoordinateOptional, ...] = (), - layer_refinement_specs: List[LayerRefinementSpec] = (), + override_structures: list[StructureType] = (), + snapping_points: tuple[CoordinateOptional, ...] = (), + layer_refinement_specs: list[LayerRefinementSpec] = (), dl_min: pd.NonNegativeFloat = 0.0, min_steps_per_sim_size: pd.PositiveFloat = 10.0, - mesher: MesherType = GradedMesher(), + mesher: MesherType = Undefined, ) -> GridSpec: """Use the same :class:`AutoGrid` along each of the three directions. @@ -2772,6 +2786,8 @@ def auto( GridSpec :class:`GridSpec` with the same automatic nonuniform grid settings in each direction. """ + if mesher is Undefined: + mesher = GradedMesher() grid_1d = AutoGrid( min_steps_per_wvl=min_steps_per_wvl, @@ -2813,9 +2829,9 @@ def quasiuniform( cls, dl: float, max_scale: pd.PositiveFloat = 1.4, - override_structures: List[StructureType] = (), - snapping_points: Tuple[CoordinateOptional, ...] = (), - mesher: MesherType = GradedMesher(), + override_structures: list[StructureType] = (), + snapping_points: tuple[CoordinateOptional, ...] = (), + mesher: MesherType = Undefined, ) -> GridSpec: """Use the same :class:`QuasiUniformGrid` along each of the three directions. @@ -2839,6 +2855,8 @@ def quasiuniform( GridSpec :class:`GridSpec` with the same uniform grid size in each direction. """ + if mesher is Undefined: + mesher = GradedMesher() grid_1d = QuasiUniformGrid(dl=dl, max_scale=max_scale, mesher=mesher) return cls( diff --git a/tidy3d/components/grid/mesher.py b/tidy3d/components/grid/mesher.py index 554f7e1051..fb5c0e39e6 100644 --- a/tidy3d/components/grid/mesher.py +++ b/tidy3d/components/grid/mesher.py @@ -1,10 +1,12 @@ """Collection of functions for automatically generating a nonuniform grid.""" +from __future__ import annotations + import warnings from abc import ABC, abstractmethod from itertools import compress from math import isclose -from typing import Dict, List, Tuple, Union +from typing import Union import numpy as np import pydantic.v1 as pd @@ -13,12 +15,12 @@ from shapely.geometry import box as shapely_box from shapely.strtree import STRtree -from ...constants import C_0, fp_eps -from ...exceptions import SetupError, ValidationError -from ...log import log -from ..base import Tidy3dBaseModel -from ..structure import MeshOverrideStructure, Structure, StructureType -from ..types import ArrayFloat1D, Axis, Bound, CoordinateOptional +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.components.structure import MeshOverrideStructure, Structure, StructureType +from tidy3d.components.types import ArrayFloat1D, Axis, Bound, CoordinateOptional +from tidy3d.constants import C_0, fp_eps +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.log import log _ROOTS_TOL = 1e-10 @@ -35,12 +37,12 @@ class Mesher(Tidy3dBaseModel, ABC): def parse_structures( self, axis: Axis, - structures: List[StructureType], + structures: list[StructureType], wavelength: pd.PositiveFloat, min_steps_per_wvl: pd.NonNegativeInt, dl_min: pd.NonNegativeFloat, dl_max: pd.NonNegativeFloat, - ) -> Tuple[ArrayFloat1D, ArrayFloat1D]: + ) -> tuple[ArrayFloat1D, ArrayFloat1D]: """Calculate the positions of all bounding box interfaces along a given axis.""" @abstractmethod @@ -50,8 +52,8 @@ def insert_snapping_points( axis: Axis, interval_coords: ArrayFloat1D, max_dl_list: ArrayFloat1D, - snapping_points: List[CoordinateOptional], - ) -> Tuple[ArrayFloat1D, ArrayFloat1D]: + snapping_points: list[CoordinateOptional], + ) -> tuple[ArrayFloat1D, ArrayFloat1D]: """Insert snapping_points to the intervals.""" @abstractmethod @@ -61,7 +63,7 @@ def make_grid_multiple_intervals( len_interval_list: ArrayFloat1D, max_scale: float, is_periodic: bool, - ) -> List[ArrayFloat1D]: + ) -> list[ArrayFloat1D]: """Create grid steps in multiple connecting intervals.""" @staticmethod @@ -100,8 +102,8 @@ def insert_snapping_points( axis: Axis, interval_coords: ArrayFloat1D, max_dl_list: ArrayFloat1D, - snapping_points: List[CoordinateOptional], - ) -> Tuple[ArrayFloat1D, ArrayFloat1D]: + snapping_points: list[CoordinateOptional], + ) -> tuple[ArrayFloat1D, ArrayFloat1D]: """Insert snapping_points to the intervals. Parameters @@ -158,7 +160,7 @@ def insert_snapping_points( continue # or, if the snapping point is near the first interval boundary, # give priority to the snapping_point and replace - elif d_1 < min_step: + if d_1 < min_step: # Don't replace if the boundary is the simulation boundary if ind == 1: continue @@ -166,7 +168,7 @@ def insert_snapping_points( continue # or, if the snapping point is near the second interval boundary, # give priority to the snapping_point and replace - elif d_2 < min_step: + if d_2 < min_step: # Don't replace if the boundary is the simulation boundary if ind == len(interval_coords) - 1: continue @@ -180,12 +182,12 @@ def insert_snapping_points( def parse_structures( self, axis: Axis, - structures: List[StructureType], + structures: list[StructureType], wavelength: pd.PositiveFloat, min_steps_per_wvl: pd.NonNegativeInt, dl_min: pd.NonNegativeFloat, dl_max: pd.NonNegativeFloat, - ) -> Tuple[ArrayFloat1D, ArrayFloat1D]: + ) -> tuple[ArrayFloat1D, ArrayFloat1D]: """Calculate the positions of all bounding box interfaces along a given axis. In this implementation, in most cases the complexity should be O(len(structures)**2), although the worst-case complexity may approach O(len(structures)**3). @@ -382,14 +384,14 @@ def parse_structures( def insert_bbox( self, - intervals: Dict[str, List], + intervals: dict[str, list], str_ind: int, str_bbox: ArrayFloat1D, - bbox_contained_2d: List[ArrayFloat1D], + bbox_contained_2d: list[ArrayFloat1D], min_step: float, structure_steps: ArrayFloat1D, unshadowed: bool, - ) -> Dict[str, List]: + ) -> dict[str, list]: """Figure out where to place the bounding box coordinates of current structure. For both the left and the right bounds of the structure along the meshing direction, we check if they are not too close to an already existing coordinate, if the @@ -508,8 +510,8 @@ def insert_bbox( @staticmethod def reorder_structures( - structures: List[StructureType], - ) -> Tuple[int, List[StructureType]]: + structures: list[StructureType], + ) -> tuple[int, list[StructureType]]: """Reorder structure list to order as follows: 1). simulation structure `str[0]` remains as the first structure; 2). MeshOverrideStructures with ``shadow=False``; @@ -565,8 +567,8 @@ def reorder_structures( @staticmethod def filter_structures_effective_dl( - structures: List[StructureType], axis: Axis - ) -> List[StructureType]: + structures: list[StructureType], axis: Axis + ) -> list[StructureType]: """For :class:`.MeshOverrideStructure`, we allow ``dl`` along some axis to be ``None`` so that no override occurs along this axis.Here those structures with ``dl[axis]=None`` is filtered. @@ -623,7 +625,7 @@ def structure_step( @staticmethod def structure_steps( - structures: List[StructureType], + structures: list[StructureType], wavelength: float, min_steps_per_wvl: float, dl_min: pd.NonNegativeFloat, @@ -662,7 +664,7 @@ def structure_steps( return np.where(min_steps < dl_min, dl_min, min_steps) @staticmethod - def rotate_structure_bounds(structures: List[StructureType], axis: Axis) -> List[ArrayFloat1D]: + def rotate_structure_bounds(structures: list[StructureType], axis: Axis) -> list[ArrayFloat1D]: """Get structure bounding boxes with a given ``axis`` rotated to z. Parameters @@ -684,12 +686,12 @@ def rotate_structure_bounds(structures: List[StructureType], axis: Axis) -> List bmin, bmax = structure.geometry.bounds bmin_ax, bmin_plane = structure.geometry.pop_axis(bmin, axis=axis) bmax_ax, bmax_plane = structure.geometry.pop_axis(bmax, axis=axis) - bounds = np.array([list(bmin_plane) + [bmin_ax], list(bmax_plane) + [bmax_ax]]) + bounds = np.array([[*list(bmin_plane), bmin_ax], [*list(bmax_plane), bmax_ax]]) struct_bbox.append(bounds) return struct_bbox @staticmethod - def bounds_2d_tree(struct_bbox: List[ArrayFloat1D]): + def bounds_2d_tree(struct_bbox: list[ArrayFloat1D]): """Make a shapely Rtree for the 2D bounding boxes of all structures in the plane perpendicular to the meshing axis.""" @@ -704,7 +706,7 @@ def bounds_2d_tree(struct_bbox: List[ArrayFloat1D]): return stree @staticmethod - def contained_2d(bbox0: ArrayFloat1D, query_bbox: List[ArrayFloat1D]) -> List[ArrayFloat1D]: + def contained_2d(bbox0: ArrayFloat1D, query_bbox: list[ArrayFloat1D]) -> list[ArrayFloat1D]: """Return a list of all bounding boxes among ``query_bbox`` that contain ``bbox0`` in 2D.""" return [ bbox @@ -720,7 +722,7 @@ def contained_2d(bbox0: ArrayFloat1D, query_bbox: List[ArrayFloat1D]) -> List[Ar ] @staticmethod - def contains_3d(bbox0: ArrayFloat1D, query_bbox: List[ArrayFloat1D]) -> List[int]: + def contains_3d(bbox0: ArrayFloat1D, query_bbox: list[ArrayFloat1D]) -> list[int]: """Return a list of all indexes of bounding boxes in the ``query_bbox`` list that ``bbox0`` fully contains.""" return [ @@ -739,7 +741,7 @@ def contains_3d(bbox0: ArrayFloat1D, query_bbox: List[ArrayFloat1D]) -> List[int ] @staticmethod - def is_close(coord: float, interval_coords: List[float], coord_ind: int, atol: float) -> bool: + def is_close(coord: float, interval_coords: list[float], coord_ind: int, atol: float) -> bool: """Check if a given ``coord`` is within ``atol`` of an interval coordinate at a given interval index. If the index is out of bounds, return ``False``.""" return ( @@ -749,7 +751,7 @@ def is_close(coord: float, interval_coords: List[float], coord_ind: int, atol: f ) @staticmethod - def is_contained(normal_pos: float, contained_2d: List[ArrayFloat1D]) -> bool: + def is_contained(normal_pos: float, contained_2d: list[ArrayFloat1D]) -> bool: """Check if a given ``normal_pos`` along the meshing direction is contained inside any of the bounding boxes that are in the ``contained_2d`` list. """ @@ -759,8 +761,8 @@ def is_contained(normal_pos: float, contained_2d: List[ArrayFloat1D]) -> bool: @staticmethod def filter_min_step( - interval_coords: List[float], max_steps: List[float] - ) -> Tuple[List[float], List[float]]: + interval_coords: list[float], max_steps: list[float] + ) -> tuple[list[float], list[float]]: """Filter intervals that are smaller than the absolute smallest of the ``max_steps``.""" # Re-compute minimum step in case some high-index structures were completely covered @@ -784,7 +786,7 @@ def make_grid_multiple_intervals( len_interval_list: ArrayFloat1D, max_scale: float, is_periodic: bool, - ) -> List[ArrayFloat1D]: + ) -> list[ArrayFloat1D]: """Create grid steps in multiple connecting intervals of length specified by ``len_interval_list``. The maximal allowed step size in each interval is given by ``max_dl_list``. The maximum ratio between neighboring steps is bounded by ``max_scale``. @@ -880,7 +882,7 @@ def grid_multiple_interval_analy_refinement( len_interval_list: ArrayFloat1D, max_scale: float, is_periodic: bool, - ) -> Tuple[ArrayFloat1D, ArrayFloat1D]: + ) -> tuple[ArrayFloat1D, ArrayFloat1D]: """Analytical refinement for multiple intervals. "analytical" meaning we allow non-integar step sizes, so that we don't consider snapping here. diff --git a/tidy3d/components/lumped_element.py b/tidy3d/components/lumped_element.py index 8a9be9e517..70cd6559f1 100644 --- a/tidy3d/components/lumped_element.py +++ b/tidy3d/components/lumped_element.py @@ -9,23 +9,15 @@ import numpy as np import pydantic.v1 as pd +from tidy3d.components.grid.grid import Grid +from tidy3d.components.medium import PEC2D, Debye, Drude, Lorentz, Medium, Medium2D, PoleResidue +from tidy3d.components.monitor import FieldMonitor +from tidy3d.components.structure import MeshOverrideStructure, Structure +from tidy3d.components.validators import assert_line_or_plane, assert_plane, validate_name_str +from tidy3d.constants import EPSILON_0, FARAD, HENRY, MICROMETER, OHM, fp_eps +from tidy3d.exceptions import ValidationError from tidy3d.log import log -from ..components.grid.grid import Grid -from ..components.medium import ( - PEC2D, - Debye, - Drude, - Lorentz, - Medium, - Medium2D, - PoleResidue, -) -from ..components.monitor import FieldMonitor -from ..components.structure import MeshOverrideStructure, Structure -from ..components.validators import assert_line_or_plane, assert_plane, validate_name_str -from ..constants import EPSILON_0, FARAD, HENRY, MICROMETER, OHM, fp_eps -from ..exceptions import ValidationError from .base import Tidy3dBaseModel, cached_property, skip_if_fields_missing from .geometry.base import Box, ClipOperation, Geometry, GeometryGroup from .geometry.primitives import Cylinder @@ -160,8 +152,7 @@ def _voltage_axis_2d(self) -> Axis2D: """Returns the voltage axis using the in-plane dimensions used by :class:`.Medium2D`.""" if self.normal_axis > self.voltage_axis: return self.voltage_axis - else: - return self.voltage_axis - 1 + return self.voltage_axis - 1 @cached_property def _snapping_spec(self) -> SnappingSpec: @@ -750,19 +741,18 @@ def _series_network_to_equivalent_medium( tau = 2 * np.pi * R * C med = Debye(eps_inf=1.0, coeffs=[(delta_eps, tau)]) return med.pole_residue - elif R and L: + if R and L: # RL series fi = np.sqrt(admittance_scaling_factor / (EPSILON_0 * (2 * np.pi) ** 2 * L)) di = R / (2 * np.pi * L) med = Drude(eps_inf=1.0, coeffs=[(fi, di)]) return med.pole_residue - else: - # LC series - delta_eps = admittance_scaling_factor * C / EPSILON_0 - di = 0 - fi = np.sqrt(1 / ((2 * np.pi) ** 2 * L * C)) - med = Lorentz(eps_inf=1.0, coeffs=[(delta_eps, fi, di)]) - return med + # LC series + delta_eps = admittance_scaling_factor * C / EPSILON_0 + di = 0 + fi = np.sqrt(1 / ((2 * np.pi) ** 2 * L * C)) + med = Lorentz(eps_inf=1.0, coeffs=[(delta_eps, fi, di)]) + return med @staticmethod def _parallel_network_to_equivalent_medium( @@ -1167,7 +1157,7 @@ def estimate_parasitic_elements(self, grid: Grid) -> tuple[float, float]: else: C = capacitance_rectangular_sheets(width_eff, l_eff, d_sep) return (L, C) - elif connections[0] or connections[1]: + if connections[0] or connections[1]: # Possible to only have a single connection, where the capacitance will be 0 # but there will be a contribution to inductance from the single connection L = inductance_straight_rectangular_wire(common_size, v_axis) diff --git a/tidy3d/components/material/multi_physics.py b/tidy3d/components/material/multi_physics.py index 11013f488f..d4a7ed8142 100644 --- a/tidy3d/components/material/multi_physics.py +++ b/tidy3d/components/material/multi_physics.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Optional import pydantic.v1 as pd @@ -182,5 +184,4 @@ def heat_spec(self): if self.optical is not None: return self.optical.heat_spec - else: - return None + return None diff --git a/tidy3d/components/material/solver_types.py b/tidy3d/components/material/solver_types.py index 6cee7e745e..f597276aa1 100644 --- a/tidy3d/components/material/solver_types.py +++ b/tidy3d/components/material/solver_types.py @@ -2,6 +2,8 @@ Note in the future we might want to implement interpolation models here. """ +from __future__ import annotations + from typing import Union from tidy3d.components.material.tcad.charge import ( diff --git a/tidy3d/components/material/tcad/charge.py b/tidy3d/components/material/tcad/charge.py index 406a862d9d..707e0ebd64 100644 --- a/tidy3d/components/material/tcad/charge.py +++ b/tidy3d/components/material/tcad/charge.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Tuple - import pydantic.v1 as pd from tidy3d.components.data.data_array import SpatialDataArray @@ -289,7 +287,7 @@ class SemiconductorMedium(AbstractChargeMedium): description="Mobility model for holes", ) - R: Tuple[RecombinationModelType, ...] = pd.Field( + R: tuple[RecombinationModelType, ...] = pd.Field( [], title="Generation-Recombination models", description="Array containing the R models to be applied to the material.", @@ -301,14 +299,14 @@ class SemiconductorMedium(AbstractChargeMedium): description="Bandgap narrowing model.", ) - N_a: Union[pd.NonNegativeFloat, SpatialDataArray, Tuple[DopingBoxType, ...]] = pd.Field( + N_a: Union[pd.NonNegativeFloat, SpatialDataArray, tuple[DopingBoxType, ...]] = pd.Field( 0, title="Doping: Acceptor concentration", description="Units of 1/cm^3", units="1/cm^3", ) - N_d: Union[pd.NonNegativeFloat, SpatialDataArray, Tuple[DopingBoxType, ...]] = pd.Field( + N_d: Union[pd.NonNegativeFloat, SpatialDataArray, tuple[DopingBoxType, ...]] = pd.Field( 0, title="Doping: Donor concentration", description="Units of 1/cm^3", diff --git a/tidy3d/components/material/types.py b/tidy3d/components/material/types.py index cc7150e7f2..3ea694c581 100644 --- a/tidy3d/components/material/types.py +++ b/tidy3d/components/material/types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Union from .multi_physics import MultiPhysicsMedium diff --git a/tidy3d/components/medium.py b/tidy3d/components/medium.py index 93b22b9225..ade214f7c2 100644 --- a/tidy3d/components/medium.py +++ b/tidy3d/components/medium.py @@ -6,7 +6,7 @@ import warnings from abc import ABC, abstractmethod from math import isclose -from typing import Callable, Dict, List, Optional, Tuple, Union +from typing import Callable, Optional, Union import autograd as ag import autograd.numpy as np @@ -19,8 +19,7 @@ from scipy import signal from tidy3d.components.material.tcad.heat import ThermalSpecType - -from ..constants import ( +from tidy3d.constants import ( C_0, CONDUCTIVITY, EPSILON_0, @@ -37,8 +36,9 @@ fp_eps, pec_val, ) -from ..exceptions import SetupError, ValidationError -from ..log import log +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.log import log + from .autograd.derivative_utils import DerivativeInfo, integrate_within_bounds from .autograd.types import AutogradFieldMap, TracedFloat, TracedPoleAndResidue, TracedPositiveFloat from .base import Tidy3dBaseModel, cached_property, skip_if_fields_missing @@ -170,19 +170,17 @@ def _validate_medium_type(self, medium: AbstractMedium): def _validate_medium(self, medium: AbstractMedium): """Any additional validation that depends on the medium""" - pass - def _validate_medium_freqs(self, medium: AbstractMedium, freqs: List[pd.PositiveFloat]) -> None: + def _validate_medium_freqs(self, medium: AbstractMedium, freqs: list[pd.PositiveFloat]) -> None: """Any additional validation that depends on the central frequencies of the sources.""" - pass def _hardcode_medium_freqs( - self, medium: AbstractMedium, freqs: List[pd.PositiveFloat] + self, medium: AbstractMedium, freqs: list[pd.PositiveFloat] ) -> NonlinearSpec: """Update the nonlinear model to hardcode information on medium and freqs.""" return self - def _get_freq0(self, freq0, freqs: List[pd.PositiveFloat]) -> float: + def _get_freq0(self, freq0, freqs: list[pd.PositiveFloat]) -> float: """Get a single value for freq0.""" # freq0 is not specified; need to calculate it @@ -219,7 +217,7 @@ def _get_n0( self, n0: complex, medium: AbstractMedium, - freqs: List[pd.PositiveFloat], + freqs: list[pd.PositiveFloat], ) -> complex: """Get a single value for n0.""" if freqs is None: @@ -265,7 +263,7 @@ def complex_fields(self) -> bool: return False @property - def aux_fields(self) -> List[str]: + def aux_fields(self) -> list[str]: """List of available aux fields in this model.""" return [] @@ -468,7 +466,7 @@ def _validate_beta_real(cls, val, values): ) return val - def _validate_medium_freqs(self, medium: AbstractMedium, freqs: List[pd.PositiveFloat]) -> None: + def _validate_medium_freqs(self, medium: AbstractMedium, freqs: list[pd.PositiveFloat]) -> None: """Any validation that depends on knowing the central frequencies of the sources. This includes passivity checking, if necessary.""" n0 = self._get_n0(self.n0, medium, freqs) @@ -487,7 +485,7 @@ def _validate_medium_freqs(self, medium: AbstractMedium, freqs: List[pd.Positive ) def _hardcode_medium_freqs( - self, medium: AbstractMedium, freqs: List[pd.PositiveFloat] + self, medium: AbstractMedium, freqs: list[pd.PositiveFloat] ) -> TwoPhotonAbsorption: """Update the nonlinear model to hardcode information on medium and freqs.""" n0 = self._get_n0(n0=self.n0, medium=medium, freqs=freqs) @@ -506,7 +504,7 @@ def complex_fields(self) -> bool: return self.use_complex_fields @property - def aux_fields(self) -> List[str]: + def aux_fields(self) -> list[str]: """List of available aux fields in this model.""" if self.tau == 0: return [] @@ -602,7 +600,7 @@ def _validate_n2_real(cls, val, values): ) return val - def _validate_medium_freqs(self, medium: AbstractMedium, freqs: List[pd.PositiveFloat]) -> None: + def _validate_medium_freqs(self, medium: AbstractMedium, freqs: list[pd.PositiveFloat]) -> None: """Any validation that depends on knowing the central frequencies of the sources. This includes passivity checking, if necessary.""" n0 = self._get_n0(self.n0, medium, freqs) @@ -627,7 +625,7 @@ def _validate_medium(self, medium: AbstractMedium): self._validate_medium_freqs(medium, []) def _hardcode_medium_freqs( - self, medium: AbstractMedium, freqs: List[pd.PositiveFloat] + self, medium: AbstractMedium, freqs: list[pd.PositiveFloat] ) -> KerrNonlinearity: """Update the nonlinear model to hardcode information on medium and freqs.""" n0 = self._get_n0(n0=self.n0, medium=medium, freqs=freqs) @@ -657,7 +655,7 @@ class NonlinearSpec(ABC, Tidy3dBaseModel): >>> medium = Medium(permittivity=2, nonlinear_spec=nonlinear_spec) """ - models: Tuple[NonlinearModelType, ...] = pd.Field( + models: tuple[NonlinearModelType, ...] = pd.Field( (), title="Nonlinear models", description="The nonlinear models present in this nonlinear spec. " @@ -720,7 +718,7 @@ def _validate_num_iters(cls, val, values): return val def _hardcode_medium_freqs( - self, medium: AbstractMedium, freqs: List[pd.PositiveFloat] + self, medium: AbstractMedium, freqs: list[pd.PositiveFloat] ) -> NonlinearSpec: """Update the nonlinear spec to hardcode information on medium and freqs.""" new_models = [] @@ -730,7 +728,7 @@ def _hardcode_medium_freqs( return self.updated_copy(models=new_models) @property - def aux_fields(self) -> List[str]: + def aux_fields(self) -> list[str]: """List of available aux fields in all present models.""" fields = [] for model in self.models: @@ -779,7 +777,7 @@ class AbstractMedium(ABC, Tidy3dBaseModel): ) @cached_property - def _nonlinear_models(self) -> List: + def _nonlinear_models(self) -> list: """The nonlinear models in the nonlinear_spec.""" if self.nonlinear_spec is None: return [] @@ -923,7 +921,7 @@ def is_fully_anisotropic(self) -> bool: return isinstance(self, FullyAnisotropicMedium) @cached_property - def _incompatible_material_types(self) -> List[str]: + def _incompatible_material_types(self) -> list[str]: """A list of material properties present which may lead to incompatibilities.""" properties = [ self.is_time_modulated, @@ -974,7 +972,7 @@ def eps_model(self, frequency: float) -> complex: Complex-valued relative permittivity evaluated at ``frequency``. """ - def nk_model(self, frequency: float) -> Tuple[float, float]: + def nk_model(self, frequency: float) -> tuple[float, float]: """Real and imaginary parts of the refactive index as a function of frequency. Parameters @@ -990,7 +988,7 @@ def nk_model(self, frequency: float) -> Tuple[float, float]: eps_complex = self.eps_model(frequency=frequency) return self.eps_complex_to_nk(eps_complex) - def loss_tangent_model(self, frequency: float) -> Tuple[float, float]: + def loss_tangent_model(self, frequency: float) -> tuple[float, float]: """Permittivity and loss tangent as a function of frequency. Parameters @@ -1007,7 +1005,7 @@ def loss_tangent_model(self, frequency: float) -> Tuple[float, float]: return self.eps_complex_to_eps_loss_tangent(eps_complex) @ensure_freq_in_range - def eps_diagonal(self, frequency: float) -> Tuple[complex, complex, complex]: + def eps_diagonal(self, frequency: float) -> tuple[complex, complex, complex]: """Main diagonal of the complex-valued permittivity tensor as a function of frequency. Parameters @@ -1025,7 +1023,7 @@ def eps_diagonal(self, frequency: float) -> Tuple[complex, complex, complex]: eps = self.eps_model(frequency) return (eps, eps, eps) - def eps_diagonal_numerical(self, frequency: float) -> Tuple[complex, complex, complex]: + def eps_diagonal_numerical(self, frequency: float) -> tuple[complex, complex, complex]: """Main diagonal of the complex-valued permittivity tensor for numerical considerations such as meshing and runtime estimation. @@ -1163,7 +1161,7 @@ def nk_to_eps_complex(n: float, k: float = 0.0) -> complex: return eps_real + 1j * eps_imag @staticmethod - def eps_complex_to_nk(eps_c: complex) -> Tuple[float, float]: + def eps_complex_to_nk(eps_c: complex) -> tuple[float, float]: """Convert complex permittivity to n, k values. Parameters @@ -1181,7 +1179,7 @@ def eps_complex_to_nk(eps_c: complex) -> Tuple[float, float]: return np.real(ref_index), np.imag(ref_index) @staticmethod - def nk_to_eps_sigma(n: float, k: float, freq: float) -> Tuple[float, float]: + def nk_to_eps_sigma(n: float, k: float, freq: float) -> tuple[float, float]: """Convert ``n``, ``k`` at frequency ``freq`` to permittivity and conductivity values. Parameters @@ -1230,7 +1228,7 @@ def eps_sigma_to_eps_complex(eps_real: float, sigma: float, freq: float) -> comp return eps_real + 1j * sigma / omega / EPSILON_0 @staticmethod - def eps_complex_to_eps_sigma(eps_complex: complex, freq: float) -> Tuple[float, float]: + def eps_complex_to_eps_sigma(eps_complex: complex, freq: float) -> tuple[float, float]: """Convert complex permittivity at frequency ``freq`` to permittivity and conductivity values. @@ -1252,7 +1250,7 @@ def eps_complex_to_eps_sigma(eps_complex: complex, freq: float) -> Tuple[float, return eps_real, sigma @staticmethod - def eps_complex_to_eps_loss_tangent(eps_complex: complex) -> Tuple[float, float]: + def eps_complex_to_eps_loss_tangent(eps_complex: complex) -> tuple[float, float]: """Convert complex permittivity to permittivity and loss tangent. Parameters @@ -1399,7 +1397,7 @@ def _derivative_eps_sigma_volume( eps_vjp = np.sum(eps_vjp) sigma_vjp = np.sum(sigma_vjp) - return dict(permittivity=eps_vjp, conductivity=sigma_vjp) + return {"permittivity": eps_vjp, "conductivity": sigma_vjp} def _derivative_eps_complex_volume( self, E_der_map: ElectromagneticFieldDataset, bounds: Bound, freqs: NDArray @@ -1422,8 +1420,7 @@ def __repr__(self): """If the medium has a name, use it as the representation. Otherwise, use the default representation.""" if self.name: return self.name - else: - return super().__repr__() + return super().__repr__() class AbstractCustomMedium(AbstractMedium, ABC): @@ -1460,7 +1457,7 @@ def _interp_method(self, comp: Axis) -> InterpMethod: @abstractmethod def eps_dataarray_freq( self, frequency: float - ) -> Tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: + ) -> tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: """Permittivity array at ``frequency``. Parameters @@ -1494,7 +1491,7 @@ def eps_diagonal_on_grid( self, frequency: float, coords: Coords, - ) -> Tuple[ArrayComplex3D, ArrayComplex3D, ArrayComplex3D]: + ) -> tuple[ArrayComplex3D, ArrayComplex3D, ArrayComplex3D]: """Spatial profile of main diagonal of the complex-valued permittivity at ``frequency`` interpolated at the supplied coordinates. @@ -1564,7 +1561,7 @@ def eps_model(self, frequency: float) -> complex: ) @ensure_freq_in_range - def eps_diagonal(self, frequency: float) -> Tuple[complex, complex, complex]: + def eps_diagonal(self, frequency: float) -> tuple[complex, complex, complex]: """Main diagonal of the complex-valued permittivity tensor at ``frequency``. Spatially, we take max{||eps||}, so that autoMesh generation works appropriately. @@ -1584,8 +1581,10 @@ def _get_real_vals(self, x: np.ndarray) -> np.ndarray: return _get_numpy_array(np.real(x)).ravel() def _eps_bounds( - self, frequency: float = None, eps_component: Optional[PermittivityComponent] = None - ) -> Tuple[float, float]: + self, + frequency: Optional[float] = None, + eps_component: Optional[PermittivityComponent] = None, + ) -> tuple[float, float]: """Returns permittivity bounds for setting the color bounds when plotting. Parameters @@ -1614,7 +1613,7 @@ def _validate_isreal_dataarray(dataarray: CustomSpatialDataType) -> bool: @staticmethod def _validate_isreal_dataarray_tuple( - dataarray_tuple: Tuple[CustomSpatialDataType, ...], + dataarray_tuple: tuple[CustomSpatialDataType, ...], ) -> bool: """Validate that the dataarray is real""" return np.all([AbstractCustomMedium._validate_isreal_dataarray(f) for f in dataarray_tuple]) @@ -1650,7 +1649,7 @@ def _not_loaded(field): if isinstance(field, str) and field in DATA_ARRAY_MAP: return True # attempting to construct an UnstructuredGridDataset from a dict - elif isinstance(field, dict) and field.get("type") in ( + if isinstance(field, dict) and field.get("type") in ( "TriangularGridDataset", "TetrahedralGridDataset", ): @@ -1659,7 +1658,7 @@ def _not_loaded(field): for subfield in [field["points"], field["cells"], field["values"]] ) # attempting to pass an UnstructuredGridDataset with zero points - elif isinstance(field, UnstructuredGridDataset): + if isinstance(field, UnstructuredGridDataset): return any(len(subfield) == 0 for subfield in [field.points, field.cells, field.values]) def _derivative_field_cmp( @@ -1955,7 +1954,7 @@ def _derivative_eps_sigma_volume( eps_vjp = np.sum(eps_vjp) sigma_vjp = np.sum(sigma_vjp) - return dict(permittivity=eps_vjp, conductivity=sigma_vjp) + return {"permittivity": eps_vjp, "conductivity": sigma_vjp} def _derivative_eps_complex_volume( self, E_der_map: ElectromagneticFieldDataset, bounds: Bound, freqs: NDArray @@ -2079,7 +2078,7 @@ def is_isotropic(self): def eps_dataarray_freq( self, frequency: float - ) -> Tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: + ) -> tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: """Permittivity array at ``frequency``. Parameters @@ -2216,8 +2215,8 @@ def _warn_if_none(cls, values): ) fail_load = True if fail_load: - eps_real = SpatialDataArray(np.ones((1, 1, 1)), coords=dict(x=[0], y=[0], z=[0])) - return dict(permittivity=eps_real) + eps_real = SpatialDataArray(np.ones((1, 1, 1)), coords={"x": [0], "y": [0], "z": [0]}) + return {"permittivity": eps_real} return values @pd.root_validator(pre=True) @@ -2507,7 +2506,7 @@ def n_cfl(self): def eps_dataarray_freq( self, frequency: float - ) -> Tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: + ) -> tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: """Permittivity array at ``frequency``. () Parameters @@ -2542,7 +2541,7 @@ def eps_diagonal_on_grid( self, frequency: float, coords: Coords, - ) -> Tuple[ArrayComplex3D, ArrayComplex3D, ArrayComplex3D]: + ) -> tuple[ArrayComplex3D, ArrayComplex3D, ArrayComplex3D]: """Spatial profile of main diagonal of the complex-valued permittivity at ``frequency`` interpolated at the supplied coordinates. @@ -2562,7 +2561,7 @@ def eps_diagonal_on_grid( return self._medium.eps_diagonal_on_grid(frequency, coords) @ensure_freq_in_range - def eps_diagonal(self, frequency: float) -> Tuple[complex, complex, complex]: + def eps_diagonal(self, frequency: float) -> tuple[complex, complex, complex]: """Main diagonal of the complex-valued permittivity tensor at ``frequency``. Spatially, we take max{|eps|}, so that autoMesh generation works appropriately. @@ -2580,7 +2579,7 @@ def eps_model(self, frequency: float) -> complex: def from_eps_raw( cls, eps: Union[ScalarFieldDataArray, CustomSpatialDataType], - freq: float = None, + freq: Optional[float] = None, interp_method: InterpMethod = "nearest", **kwargs, ) -> CustomMedium: @@ -2650,7 +2649,7 @@ def from_nk( cls, n: Union[ScalarFieldDataArray, CustomSpatialDataType], k: Optional[Union[ScalarFieldDataArray, CustomSpatialDataType]] = None, - freq: float = None, + freq: Optional[float] = None, interp_method: InterpMethod = "nearest", **kwargs, ) -> CustomMedium: @@ -2733,7 +2732,7 @@ def from_nk( sigma = SpatialDataArray(sigma.squeeze(dim="f", drop=True)) return cls(permittivity=eps_real, conductivity=sigma, interp_method=interp_method, **kwargs) - def grids(self, bounds: Bound) -> Dict[str, Grid]: + def grids(self, bounds: Bound) -> dict[str, Grid]: """Make a :class:`.Grid` corresponding to the data in each ``eps_ii`` component. The min and max coordinates along each dimension are bounded by ``bounds``.""" @@ -2744,7 +2743,7 @@ def grids(self, bounds: Bound) -> Dict[str, Grid]: def make_grid(scalar_field: Union[ScalarFieldDataArray, SpatialDataArray]) -> Grid: """Make a grid for a single dataset.""" - def make_bound_coords(coords: np.ndarray, pt_min: float, pt_max: float) -> List[float]: + def make_bound_coords(coords: np.ndarray, pt_min: float, pt_max: float) -> list[float]: """Convert user supplied coords into boundary coords to use in :class:`.Grid`.""" # get coordinates of the bondaries halfway between user-supplied data @@ -2755,7 +2754,7 @@ def make_bound_coords(coords: np.ndarray, pt_min: float, pt_max: float) -> List[ coord_bounds[coord_bounds >= pt_max] = pt_max # add the geometry bounds in explicitly - return [pt_min] + coord_bounds.tolist() + [pt_max] + return [pt_min, *coord_bounds.tolist(), pt_max] # grab user supplied data long this dimension coords = {key: np.array(val) for key, val in scalar_field.coords.items()} @@ -3024,7 +3023,7 @@ def _validate_conductivity_modulation(cls, val, values): return _validate_conductivity_modulation @abstractmethod - def _pole_residue_dict(self) -> Dict: + def _pole_residue_dict(self) -> dict: """Dict representation of Medium as a pole-residue model.""" @cached_property @@ -3048,14 +3047,14 @@ def n_cfl(self): return n @staticmethod - def tuple_to_complex(value: Tuple[float, float]) -> complex: + def tuple_to_complex(value: tuple[float, float]) -> complex: """Convert a tuple of real and imaginary parts to complex number.""" val_r, val_i = value return val_r + 1j * val_i @staticmethod - def complex_to_tuple(value: complex) -> Tuple[float, float]: + def complex_to_tuple(value: complex) -> tuple[float, float]: """Convert a complex number to a tuple of real and imaginary parts.""" return (value.real, value.imag) @@ -3125,7 +3124,9 @@ def _warn_if_none(cls, values): if fail_load and eps_inf is None: return {nested_tuple_field: ()} if fail_load: - eps_inf = SpatialDataArray(np.ones((1, 1, 1)), coords=dict(x=[0], y=[0], z=[0])) + eps_inf = SpatialDataArray( + np.ones((1, 1, 1)), coords={"x": [0], "y": [0], "z": [0]} + ) return {"eps_inf": eps_inf, nested_tuple_field: ()} return values @@ -3171,7 +3172,7 @@ class PoleResidue(DispersiveMedium): units=PERMITTIVITY, ) - poles: Tuple[TracedPoleAndResidue, ...] = pd.Field( + poles: tuple[TracedPoleAndResidue, ...] = pd.Field( (), title="Poles", description="Tuple of complex-valued (:math:`a_i, c_i`) poles for the model.", @@ -3191,7 +3192,7 @@ def _causality_validation(cls, val): @staticmethod def _eps_model( - eps_inf: pd.PositiveFloat, poles: Tuple[PoleAndResidue, ...], frequency: float + eps_inf: pd.PositiveFloat, poles: tuple[PoleAndResidue, ...], frequency: float ) -> complex: """Complex-valued permittivity as a function of frequency.""" @@ -3209,15 +3210,15 @@ def eps_model(self, frequency: float) -> complex: """Complex-valued permittivity as a function of frequency.""" return self._eps_model(eps_inf=self.eps_inf, poles=self.poles, frequency=frequency) - def _pole_residue_dict(self) -> Dict: + def _pole_residue_dict(self) -> dict: """Dict representation of Medium as a pole-residue model.""" - return dict( - eps_inf=self.eps_inf, - poles=self.poles, - frequency_range=self.frequency_range, - name=self.name, - ) + return { + "eps_inf": self.eps_inf, + "poles": self.poles, + "frequency_range": self.frequency_range, + "name": self.name, + } def __str__(self): """string representation""" @@ -3271,7 +3272,7 @@ def to_medium(self) -> Medium: @staticmethod def lo_to_eps_model( - poles: Tuple[Tuple[float, float, float, float], ...], + poles: tuple[tuple[float, float, float, float], ...], eps_inf: pd.PositiveFloat, frequency: float, ) -> complex: @@ -3303,7 +3304,7 @@ def lo_to_eps_model( @classmethod def from_lo_to( - cls, poles: Tuple[Tuple[float, float, float, float], ...], eps_inf: pd.PositiveFloat = 1 + cls, poles: tuple[tuple[float, float, float, float], ...], eps_inf: pd.PositiveFloat = 1 ) -> PoleResidue: """Construct a pole residue model from the LO-TO form (longitudinal and transverse optical modes). @@ -3375,7 +3376,7 @@ def from_lo_to( return PoleResidue(eps_inf=eps_inf, poles=list(zip(a_coeffs, c_coeffs))) @staticmethod - def imag_ep_extrema(poles: Tuple[PoleAndResidue, ...]) -> ArrayFloat1D: + def imag_ep_extrema(poles: tuple[PoleAndResidue, ...]) -> ArrayFloat1D: """Extrema of Im[eps] in the same unit as poles. Parameters @@ -3637,9 +3638,8 @@ def from_admittance_coeffs( "Transfer function is invalid. Direct polynomial term must be real and positive for " "conversion to an equivalent 'PoleResidue' medium." ) - else: - # A pure capacitance will translate to an increased permittivity at infinite frequency. - eps_inf = eps_inf + k[0] + # A pure capacitance will translate to an increased permittivity at infinite frequency. + eps_inf = eps_inf + k[0] pole_residue_from_transfer = PoleResidue(eps_inf=eps_inf, poles=poles_and_residues) @@ -3724,7 +3724,7 @@ class CustomPoleResidue(CustomDispersiveMedium, PoleResidue): units=PERMITTIVITY, ) - poles: Tuple[Tuple[CustomSpatialDataTypeAnnotated, CustomSpatialDataTypeAnnotated], ...] = ( + poles: tuple[tuple[CustomSpatialDataTypeAnnotated, CustomSpatialDataTypeAnnotated], ...] = ( pd.Field( (), title="Poles", @@ -3774,7 +3774,7 @@ def is_spatially_uniform(self) -> bool: def eps_dataarray_freq( self, frequency: float - ) -> Tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: + ) -> tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: """Permittivity array at ``frequency``. Parameters @@ -3806,7 +3806,7 @@ def eps_dataarray_freq( eps = PoleResidue.eps_model(self, frequency) return (eps, eps, eps) - def poles_on_grid(self, coords: Coords) -> Tuple[Tuple[ArrayComplex3D, ArrayComplex3D], ...]: + def poles_on_grid(self, coords: Coords) -> tuple[tuple[ArrayComplex3D, ArrayComplex3D], ...]: """Spatial profile of poles interpolated at the supplied coordinates. Parameters @@ -4010,7 +4010,7 @@ class Sellmeier(DispersiveMedium): * `Modeling dispersive material in FDTD `_ """ - coeffs: Tuple[Tuple[float, pd.PositiveFloat], ...] = pd.Field( + coeffs: tuple[tuple[float, pd.PositiveFloat], ...] = pd.Field( title="Coefficients", description="List of Sellmeier (:math:`B_i, C_i`) coefficients.", units=(None, MICROMETER + "^2"), @@ -4065,7 +4065,7 @@ def eps_model(self, frequency: float) -> complex: n = self._n_model(frequency) return AbstractMedium.nk_to_eps_complex(n) - def _pole_residue_dict(self) -> Dict: + def _pole_residue_dict(self) -> dict: """Dict representation of Medium as a pole-residue model""" poles = [] for B, C in self.coeffs: @@ -4074,7 +4074,12 @@ def _pole_residue_dict(self) -> Dict: a = 1j * beta c = 1j * alpha poles.append((a, c)) - return dict(eps_inf=1, poles=poles, frequency_range=self.frequency_range, name=self.name) + return { + "eps_inf": 1, + "poles": poles, + "frequency_range": self.frequency_range, + "name": self.name, + } @staticmethod def _from_dispersion_to_coeffs(n: float, freq: float, dn_dwvl: float): @@ -4149,7 +4154,7 @@ class CustomSellmeier(CustomDispersiveMedium, Sellmeier): * `Modeling dispersive material in FDTD `_ """ - coeffs: Tuple[Tuple[CustomSpatialDataTypeAnnotated, CustomSpatialDataTypeAnnotated], ...] = ( + coeffs: tuple[tuple[CustomSpatialDataTypeAnnotated, CustomSpatialDataTypeAnnotated], ...] = ( pd.Field( ..., title="Coefficients", @@ -4203,7 +4208,7 @@ def is_spatially_uniform(self) -> bool: return False return True - def _pole_residue_dict(self) -> Dict: + def _pole_residue_dict(self) -> dict: """Dict representation of Medium as a pole-residue model.""" poles_dict = Sellmeier._pole_residue_dict(self) if len(self.coeffs) > 0: @@ -4212,7 +4217,7 @@ def _pole_residue_dict(self) -> Dict: def eps_dataarray_freq( self, frequency: float - ) -> Tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: + ) -> tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: """Permittivity array at ``frequency``. Parameters @@ -4245,7 +4250,7 @@ def eps_dataarray_freq( # if `eps` is simply a float, convert it to a SpatialDataArray ; this is possible when # `coeffs` is empty. if isinstance(eps, (int, float, complex)): - eps = SpatialDataArray(eps * np.ones((1, 1, 1)), coords=dict(x=[0], y=[0], z=[0])) + eps = SpatialDataArray(eps * np.ones((1, 1, 1)), coords={"x": [0], "y": [0], "z": [0]}) return (eps, eps, eps) @classmethod @@ -4366,7 +4371,7 @@ class Lorentz(DispersiveMedium): units=PERMITTIVITY, ) - coeffs: Tuple[Tuple[float, float, pd.NonNegativeFloat], ...] = pd.Field( + coeffs: tuple[tuple[float, float, pd.NonNegativeFloat], ...] = pd.Field( ..., title="Coefficients", description="List of (:math:`\\Delta\\epsilon_i, f_i, \\delta_i`) values for model.", @@ -4409,7 +4414,7 @@ def eps_model(self, frequency: float) -> complex: eps = eps + (de * f**2) / (f**2 - 2j * frequency * delta - frequency**2) return eps - def _pole_residue_dict(self) -> Dict: + def _pole_residue_dict(self) -> dict: """Dict representation of Medium as a pole-residue model.""" poles = [] @@ -4430,12 +4435,12 @@ def _pole_residue_dict(self) -> Dict: c = 1j * de * w**2 / 2 / r poles.append((a, c)) - return dict( - eps_inf=self.eps_inf, - poles=poles, - frequency_range=self.frequency_range, - name=self.name, - ) + return { + "eps_inf": self.eps_inf, + "poles": poles, + "frequency_range": self.frequency_range, + "name": self.name, + } @staticmethod def _all_larger(coeff_a, coeff_b) -> bool: @@ -4547,8 +4552,8 @@ class CustomLorentz(CustomDispersiveMedium, Lorentz): units=PERMITTIVITY, ) - coeffs: Tuple[ - Tuple[ + coeffs: tuple[ + tuple[ CustomSpatialDataTypeAnnotated, CustomSpatialDataTypeAnnotated, CustomSpatialDataTypeAnnotated, @@ -4644,7 +4649,7 @@ def is_spatially_uniform(self) -> bool: def eps_dataarray_freq( self, frequency: float - ) -> Tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: + ) -> tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: """Permittivity array at ``frequency``. Parameters @@ -4756,7 +4761,7 @@ class Drude(DispersiveMedium): units=PERMITTIVITY, ) - coeffs: Tuple[Tuple[float, pd.PositiveFloat], ...] = pd.Field( + coeffs: tuple[tuple[float, pd.PositiveFloat], ...] = pd.Field( ..., title="Coefficients", description="List of (:math:`f_i, \\delta_i`) values for model.", @@ -4775,7 +4780,7 @@ def eps_model(self, frequency: float) -> complex: eps = eps - (f**2) / (frequency**2 + 1j * frequency * delta) return eps - def _pole_residue_dict(self) -> Dict: + def _pole_residue_dict(self) -> dict: """Dict representation of Medium as a pole-residue model.""" poles = [] @@ -4795,12 +4800,12 @@ def _pole_residue_dict(self) -> Dict: poles.extend(((a0, c0), (a1, c1))) - return dict( - eps_inf=self.eps_inf, - poles=poles, - frequency_range=self.frequency_range, - name=self.name, - ) + return { + "eps_inf": self.eps_inf, + "poles": poles, + "frequency_range": self.frequency_range, + "name": self.name, + } class CustomDrude(CustomDispersiveMedium, Drude): @@ -4849,7 +4854,7 @@ class CustomDrude(CustomDispersiveMedium, Drude): units=PERMITTIVITY, ) - coeffs: Tuple[Tuple[CustomSpatialDataTypeAnnotated, CustomSpatialDataTypeAnnotated], ...] = ( + coeffs: tuple[tuple[CustomSpatialDataTypeAnnotated, CustomSpatialDataTypeAnnotated], ...] = ( pd.Field( ..., title="Coefficients", @@ -4903,7 +4908,7 @@ def is_spatially_uniform(self) -> bool: def eps_dataarray_freq( self, frequency: float - ) -> Tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: + ) -> tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: """Permittivity array at ``frequency``. Parameters @@ -5008,7 +5013,7 @@ class Debye(DispersiveMedium): units=PERMITTIVITY, ) - coeffs: Tuple[Tuple[float, pd.PositiveFloat], ...] = pd.Field( + coeffs: tuple[tuple[float, pd.PositiveFloat], ...] = pd.Field( ..., title="Coefficients", description="List of (:math:`\\Delta\\epsilon_i, \\tau_i`) values for model.", @@ -5053,12 +5058,12 @@ def _pole_residue_dict(self): poles.append((a, c)) - return dict( - eps_inf=self.eps_inf, - poles=poles, - frequency_range=self.frequency_range, - name=self.name, - ) + return { + "eps_inf": self.eps_inf, + "poles": poles, + "frequency_range": self.frequency_range, + "name": self.name, + } class CustomDebye(CustomDispersiveMedium, Debye): @@ -5106,7 +5111,7 @@ class CustomDebye(CustomDispersiveMedium, Debye): units=PERMITTIVITY, ) - coeffs: Tuple[Tuple[CustomSpatialDataTypeAnnotated, CustomSpatialDataTypeAnnotated], ...] = ( + coeffs: tuple[tuple[CustomSpatialDataTypeAnnotated, CustomSpatialDataTypeAnnotated], ...] = ( pd.Field( ..., title="Coefficients", @@ -5175,7 +5180,7 @@ def is_spatially_uniform(self) -> bool: def eps_dataarray_freq( self, frequency: float - ) -> Tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: + ) -> tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: """Permittivity array at ``frequency``. Parameters @@ -5406,7 +5411,7 @@ class HuraySurfaceRoughness(AbstractSurfaceRoughness): description="Relative area of the matte base compared to a flat surface", ) - coeffs: Tuple[Tuple[pd.PositiveFloat, pd.PositiveFloat], ...] = pd.Field( + coeffs: tuple[tuple[pd.PositiveFloat, pd.PositiveFloat], ...] = pd.Field( ..., title="Coefficients for surface ratio and sphere radius", description="List of (:math:`f_i, r_i`) values for model, where :math:`f_i` is " @@ -5552,7 +5557,7 @@ def _positive_conductivity(cls, val): return val @cached_property - def _fitting_result(self) -> Tuple[PoleResidue, float]: + def _fitting_result(self) -> tuple[PoleResidue, float]: """Fitted scaled surface impedance and residue.""" omega_data = self.Hz_to_angular_freq(self.sampling_frequencies) @@ -5630,7 +5635,7 @@ def sampling_frequencies(self) -> ArrayFloat1D: self.fit_param.frequency_sampling_points, ) - def eps_diagonal_numerical(self, frequency: float) -> Tuple[complex, complex, complex]: + def eps_diagonal_numerical(self, frequency: float) -> tuple[complex, complex, complex]: """Main diagonal of the complex-valued permittivity tensor for numerical considerations such as meshing and runtime estimation. @@ -5774,9 +5779,9 @@ def _ignored_fields(cls, values): return values @cached_property - def components(self) -> Dict[str, Medium]: + def components(self) -> dict[str, Medium]: """Dictionary of diagonal medium components.""" - return dict(xx=self.xx, yy=self.yy, zz=self.zz) + return {"xx": self.xx, "yy": self.yy, "zz": self.zz} @cached_property def is_time_modulated(self) -> bool: @@ -5800,7 +5805,7 @@ def eps_model(self, frequency: float) -> complex: return np.mean(self.eps_diagonal(frequency), axis=0) @ensure_freq_in_range - def eps_diagonal(self, frequency: float) -> Tuple[complex, complex, complex]: + def eps_diagonal(self, frequency: float) -> tuple[complex, complex, complex]: """Main diagonal of the complex-valued permittivity tensor as a function of frequency.""" eps_xx = self.xx.eps_model(frequency) @@ -5851,7 +5856,7 @@ def _eps_plot( if eps_component is None: # return the average of the diag return self.eps_model(frequency).real - elif eps_component in ["xx", "yy", "zz"]: + if eps_component in ["xx", "yy", "zz"]: # return the requested diagonal component comp2indx = {"x": 0, "y": 1, "z": 2} return self.eps_comp( @@ -5859,10 +5864,9 @@ def _eps_plot( col=comp2indx[eps_component[1]], frequency=frequency, ).real - else: - raise ValueError( - f"Plotting component '{eps_component}' of a diagonally-anisotropic permittivity tensor is not supported." - ) + raise ValueError( + f"Plotting component '{eps_component}' of a diagonally-anisotropic permittivity tensor is not supported." + ) @add_ax_if_none def plot(self, freqs: float, ax: Ax = None) -> Ax: @@ -5884,9 +5888,9 @@ def plot(self, freqs: float, ax: Ax = None) -> Ax: return ax @property - def elements(self) -> Dict[str, IsotropicUniformMediumType]: + def elements(self) -> dict[str, IsotropicUniformMediumType]: """The diagonal elements of the medium as a dictionary.""" - return dict(xx=self.xx, yy=self.yy, zz=self.zz) + return {"xx": self.xx, "yy": self.yy, "zz": self.zz} @cached_property def is_pec(self): @@ -6101,7 +6105,7 @@ def _to_diagonal(self) -> AnisotropicMedium: @cached_property def eps_sigma_diag( self, - ) -> Tuple[Tuple[float, float, float], Tuple[float, float, float], TensorReal]: + ) -> tuple[tuple[float, float, float], tuple[float, float, float], TensorReal]: """Main components of permittivity and conductivity tensors and their directions.""" perm_diag, vecs = np.linalg.eig(self.permittivity) @@ -6121,7 +6125,7 @@ def eps_model(self, frequency: float) -> complex: return np.mean(eps_diag) @ensure_freq_in_range - def eps_diagonal(self, frequency: float) -> Tuple[complex, complex, complex]: + def eps_diagonal(self, frequency: float) -> tuple[complex, complex, complex]: """Main diagonal of the complex-valued permittivity tensor as a function of frequency.""" perm_diag, cond_diag, _ = self.eps_sigma_diag @@ -6355,7 +6359,7 @@ def _interp_method(self, comp: Axis) -> InterpMethod: def eps_dataarray_freq( self, frequency: float - ) -> Tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: + ) -> tuple[CustomSpatialDataType, CustomSpatialDataType, CustomSpatialDataType]: """Permittivity array at ``frequency``. Parameters @@ -6390,8 +6394,10 @@ def eps_dataarray_freq( ) def _eps_bounds( - self, frequency: float = None, eps_component: Optional[PermittivityComponent] = None - ) -> Tuple[float, float]: + self, + frequency: Optional[float] = None, + eps_component: Optional[PermittivityComponent] = None, + ) -> tuple[float, float]: """Returns permittivity bounds for setting the color bounds when plotting. Parameters @@ -6415,13 +6421,12 @@ def _eps_bounds( eps_dataarray = self.eps_dataarray_freq(frequency) eps = self._get_real_vals(eps_dataarray[comps.index(eps_component)]) return (np.min(eps), np.max(eps)) - elif eps_component is None: + if eps_component is None: # Returns the bounds across all components return super()._eps_bounds(frequency=frequency) - else: - raise ValueError( - f"Plotting component '{eps_component}' of a diagonally-anisotropic permittivity tensor is not supported." - ) + raise ValueError( + f"Plotting component '{eps_component}' of a diagonally-anisotropic permittivity tensor is not supported." + ) def _sel_custom_data_inside(self, bounds: Bound): return self @@ -6829,7 +6834,7 @@ class PerturbationPoleResidue(PoleResidue, AbstractPerturbationMedium): ) poles_perturbation: Optional[ - Tuple[Tuple[Optional[ParameterPerturbation], Optional[ParameterPerturbation]], ...] + tuple[tuple[Optional[ParameterPerturbation], Optional[ParameterPerturbation]], ...] ] = pd.Field( None, title="Perturbations of Poles", @@ -6973,7 +6978,7 @@ def perturbed_copy( eps_inf_field = eps_inf_field + delta_eps if delta_sigma is not None: - poles_field = poles_field + [[zeros, 0.5 * delta_sigma / EPSILON_0]] + poles_field = [*poles_field, [zeros, 0.5 * delta_sigma / EPSILON_0]] else: # sample eps_inf if self.eps_inf_perturbation is not None: @@ -7089,7 +7094,7 @@ def _validate_inplane_pec(cls, val, values): @classmethod def _weighted_avg( - cls, meds: List[IsotropicUniformMediumType], weights: List[float] + cls, meds: list[IsotropicUniformMediumType], weights: list[float] ) -> Union[PoleResidue, PECMedium]: """Average ``meds`` with weights ``weights``.""" eps_inf = 1 @@ -7112,8 +7117,8 @@ def _weighted_avg( def volumetric_equivalent( self, axis: Axis, - adjacent_media: Tuple[MediumType3D, MediumType3D], - adjacent_dls: Tuple[float, float], + adjacent_media: tuple[MediumType3D, MediumType3D], + adjacent_dls: tuple[float, float], ) -> AnisotropicMedium: """Produces a 3D volumetric equivalent medium. The new medium has thickness equal to the average of the ``dls`` in the ``axis`` direction. @@ -7317,7 +7322,7 @@ def eps_model(self, frequency: float) -> complex: return np.mean(self.eps_diagonal(frequency=frequency), axis=0) @ensure_freq_in_range - def eps_diagonal(self, frequency: float) -> Tuple[complex, complex]: + def eps_diagonal(self, frequency: float) -> tuple[complex, complex]: """Main diagonal of the complex-valued permittivity tensor as a function of frequency.""" log.warning( "The permittivity of a 'Medium2D' is unphysical. " @@ -7329,7 +7334,7 @@ def eps_diagonal(self, frequency: float) -> Tuple[complex, complex]: eps_tt = self.tt.eps_model(frequency) return (eps_ss, eps_tt) - def eps_diagonal_numerical(self, frequency: float) -> Tuple[complex, complex, complex]: + def eps_diagonal_numerical(self, frequency: float) -> tuple[complex, complex, complex]: """Main diagonal of the complex-valued permittivity tensor for numerical considerations such as meshing and runtime estimation. @@ -7405,9 +7410,9 @@ def sigma_model(self, freq: float) -> complex: return np.mean([self.ss.sigma_model(freq), self.tt.sigma_model(freq)], axis=0) @property - def elements(self) -> Dict[str, IsotropicUniformMediumType]: + def elements(self) -> dict[str, IsotropicUniformMediumType]: """The diagonal elements of the 2D medium as a dictionary.""" - return dict(ss=self.ss, tt=self.tt) + return {"ss": self.ss, "tt": self.tt} @cached_property def n_cfl(self): diff --git a/tidy3d/components/microwave/data/monitor_data.py b/tidy3d/components/microwave/data/monitor_data.py index aa4048b862..72a34c0d37 100644 --- a/tidy3d/components/microwave/data/monitor_data.py +++ b/tidy3d/components/microwave/data/monitor_data.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing import Optional + import pydantic.v1 as pd import xarray as xr @@ -119,7 +121,7 @@ def reflection_efficiency(self) -> FreqDataArray: return reflection_efficiency def partial_gain( - self, pol_basis: PolarizationBasis = "linear", tilt_angle: float = None + self, pol_basis: PolarizationBasis = "linear", tilt_angle: Optional[float] = None ) -> xr.Dataset: """The partial gain figures of merit for antennas. The partial gains are computed in the ``linear`` or ``circular`` polarization bases. If ``tilt_angle`` is not ``None``, @@ -161,7 +163,7 @@ def gain(self) -> FieldProjectionAngleDataArray: return partial_G.Gtheta + partial_G.Gphi def partial_realized_gain( - self, pol_basis: PolarizationBasis = "linear", tilt_angle: float = None + self, pol_basis: PolarizationBasis = "linear", tilt_angle: Optional[float] = None ) -> xr.Dataset: """The partial realized gain figures of merit for antennas. The partial gains are computed in the ``linear`` or ``circular`` polarization bases. If ``tilt_angle`` is not ``None``, diff --git a/tidy3d/components/microwave/formulas/circuit_parameters.py b/tidy3d/components/microwave/formulas/circuit_parameters.py index 9eb7c172b8..4514d6e993 100644 --- a/tidy3d/components/microwave/formulas/circuit_parameters.py +++ b/tidy3d/components/microwave/formulas/circuit_parameters.py @@ -11,11 +11,13 @@ Foreign Technology Division Air Force Systems Command U.S. Air Force, 1971. """ +from __future__ import annotations + import numpy as np -from ....constants import EPSILON_0 -from ...geometry.base import Geometry -from ...types import Axis +from tidy3d.components.geometry.base import Geometry +from tidy3d.components.types import Axis +from tidy3d.constants import EPSILON_0 def inductance_straight_rectangular_wire( diff --git a/tidy3d/components/mode/data/sim_data.py b/tidy3d/components/mode/data/sim_data.py index a01bf78375..aed1efde1a 100644 --- a/tidy3d/components/mode/data/sim_data.py +++ b/tidy3d/components/mode/data/sim_data.py @@ -2,18 +2,15 @@ from __future__ import annotations -from typing import Literal, Tuple +from typing import Literal, Optional import pydantic.v1 as pd -from ...base import cached_property -from ...data.monitor_data import ModeSolverData, PermittivityData -from ...data.sim_data import AbstractYeeGridSimulationData -from ...types import ( - Ax, - PlotScale, -) -from ..simulation import ModeSimulation +from tidy3d.components.base import cached_property +from tidy3d.components.data.monitor_data import ModeSolverData, PermittivityData +from tidy3d.components.data.sim_data import AbstractYeeGridSimulationData +from tidy3d.components.mode.simulation import ModeSimulation +from tidy3d.components.types import Ax, PlotScale ModeSimulationMonitorDataType = PermittivityData @@ -31,7 +28,7 @@ class ModeSimulationData(AbstractYeeGridSimulationData): description=":class:`.ModeSolverData` containing the field and effective index on unexpanded grid.", ) - data: Tuple[ModeSimulationMonitorDataType, ...] = pd.Field( + data: tuple[ModeSimulationMonitorDataType, ...] = pd.Field( (), title="Monitor Data", description="List of monitor data " @@ -50,8 +47,8 @@ def plot_field( scale: PlotScale = "lin", eps_alpha: float = 0.2, robust: bool = True, - vmin: float = None, - vmax: float = None, + vmin: Optional[float] = None, + vmax: Optional[float] = None, ax: Ax = None, **sel_kwargs, ) -> Ax: diff --git a/tidy3d/components/mode/derivatives.py b/tidy3d/components/mode/derivatives.py index 4c13ca7835..2ff9cef1c4 100644 --- a/tidy3d/components/mode/derivatives.py +++ b/tidy3d/components/mode/derivatives.py @@ -1,9 +1,11 @@ """Finite-difference derivatives and PML absorption operators expressed as sparse matrices.""" +from __future__ import annotations + import numpy as np import scipy.sparse as sp -from ...constants import EPSILON_0, ETA_0 +from tidy3d.constants import EPSILON_0, ETA_0 def make_dxf(dls, shape, pmc): diff --git a/tidy3d/components/mode/mode_solver.py b/tidy3d/components/mode/mode_solver.py index 41b32c91cd..732ac6307e 100644 --- a/tidy3d/components/mode/mode_solver.py +++ b/tidy3d/components/mode/mode_solver.py @@ -6,7 +6,7 @@ from functools import wraps from math import isclose -from typing import Dict, List, Tuple, Union +from typing import Literal, Optional, Union import numpy as np import pydantic.v1 as pydantic @@ -14,33 +14,30 @@ from matplotlib.collections import PatchCollection from matplotlib.patches import Rectangle -from ...constants import C_0 -from ...exceptions import SetupError, ValidationError -from ...log import log -from ..base import Tidy3dBaseModel, cached_property, skip_if_fields_missing -from ..boundary import PML, Absorber, Boundary, BoundarySpec, PECBoundary, StablePML -from ..data.data_array import ( +from tidy3d.components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing +from tidy3d.components.boundary import PML, Absorber, Boundary, BoundarySpec, PECBoundary, StablePML +from tidy3d.components.data.data_array import ( FreqModeDataArray, ModeIndexDataArray, ScalarModeFieldCylindricalDataArray, ScalarModeFieldDataArray, ) -from ..data.monitor_data import ModeSolverData -from ..data.sim_data import SimulationData -from ..eme.data.sim_data import EMESimulationData -from ..eme.simulation import EMESimulation -from ..geometry.base import Box -from ..grid.grid import Coords, Grid -from ..medium import FullyAnisotropicMedium, LossyMetalMedium -from ..mode_spec import ModeSpec -from ..monitor import ModeMonitor, ModeSolverMonitor -from ..scene import Scene -from ..simulation import Simulation -from ..source.field import ModeSource -from ..source.time import SourceTime -from ..structure import Structure -from ..subpixel_spec import SurfaceImpedance -from ..types import ( +from tidy3d.components.data.monitor_data import ModeSolverData +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.eme.data.sim_data import EMESimulationData +from tidy3d.components.eme.simulation import EMESimulation +from tidy3d.components.geometry.base import Box +from tidy3d.components.grid.grid import Coords, Grid +from tidy3d.components.medium import FullyAnisotropicMedium, LossyMetalMedium +from tidy3d.components.mode_spec import ModeSpec +from tidy3d.components.monitor import ModeMonitor, ModeSolverMonitor +from tidy3d.components.scene import Scene +from tidy3d.components.simulation import Simulation +from tidy3d.components.source.field import ModeSource +from tidy3d.components.source.time import SourceTime +from tidy3d.components.structure import Structure +from tidy3d.components.subpixel_spec import SurfaceImpedance +from tidy3d.components.types import ( TYPE_TAG_STR, ArrayComplex3D, ArrayComplex4D, @@ -53,16 +50,18 @@ EMField, EpsSpecType, FreqArray, - Literal, PlotScale, Symmetry, ) -from ..validators import ( +from tidy3d.components.validators import ( validate_freqs_min, validate_freqs_not_empty, validate_mode_plane_radius, ) -from ..viz import make_ax, plot_params_pml +from tidy3d.components.viz import make_ax, plot_params_pml +from tidy3d.constants import C_0 +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.log import log # Importing the local solver may not work if e.g. scipy is not installed IMPORT_ERROR_MSG = """Could not import local solver, 'ModeSolver' objects can still be constructed @@ -76,7 +75,7 @@ log.warning(IMPORT_ERROR_MSG) LOCAL_SOLVER_IMPORTED = False -FIELD = Tuple[ArrayComplex3D, ArrayComplex3D, ArrayComplex3D] +FIELD = tuple[ArrayComplex3D, ArrayComplex3D, ArrayComplex3D] MODE_MONITOR_NAME = "<<>>" # Warning for field intensity at edges over total field intensity larger than this value @@ -169,7 +168,7 @@ class ModeSolver(Tidy3dBaseModel): "primal grid nodes). Default is ``True``.", ) - fields: Tuple[EMField, ...] = pydantic.Field( + fields: tuple[EMField, ...] = pydantic.Field( ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"], title="Field Components", description="Collection of field components to store in the monitor. Note that some " @@ -258,7 +257,7 @@ def normal_axis_2d(self) -> Axis2D: return idx_plane.index(self.normal_axis) @staticmethod - def _solver_symmetry(simulation: Simulation, plane: Box) -> Tuple[Symmetry, Symmetry]: + def _solver_symmetry(simulation: Simulation, plane: Box) -> tuple[Symmetry, Symmetry]: """Get symmetry for solver for propagation along self.normal axis.""" normal_axis = plane.size.index(0.0) mode_symmetry = list(simulation.symmetry) @@ -269,7 +268,7 @@ def _solver_symmetry(simulation: Simulation, plane: Box) -> Tuple[Symmetry, Symm return solver_sym @cached_property - def solver_symmetry(self) -> Tuple[Symmetry, Symmetry]: + def solver_symmetry(self) -> tuple[Symmetry, Symmetry]: """Get symmetry for solver for propagation along self.normal axis.""" return self._solver_symmetry(simulation=self.simulation, plane=self.plane) @@ -337,7 +336,7 @@ def _solver_grid(self) -> Grid: ) @cached_property - def _num_cells_freqs_modes(self) -> Tuple[int, int, int]: + def _num_cells_freqs_modes(self) -> tuple[int, int, int]: """Get the number of spatial points, number of freqs, and number of modes requested.""" num_cells = np.prod(self._solver_grid.num_cells) num_modes = self.mode_spec.num_modes @@ -493,7 +492,7 @@ def rotated_mode_solver_data(self) -> ModeSolverData: log.warning( "Mode solver reduced_simulation_copy failed. " "Falling back to non-reduced simulation, which may be slower. " - f"Exception: {str(e)}" + f"Exception: {e!s}" ) # Compute the mode solution by rotating the reference data to the monitor plane @@ -555,7 +554,7 @@ def rotated_structures_copy(self): return self.updated_copy(simulation=rotated_simulation, mode_spec=rotated_mode_spec) - def _rotate_structures(self) -> List[Structure]: + def _rotate_structures(self) -> list[Structure]: """Rotate the structures intersecting with modal plane by angle theta if bend_correction is enabeled for bend simulations.""" @@ -600,7 +599,7 @@ def _rotate_structures(self) -> List[Structure]: return rotated_structures @cached_property - def rotated_bend_center(self) -> List: + def rotated_bend_center(self) -> list: """Calculate the center at the rotated bend such that the modal plane is normal to the azimuthal direction of the bend.""" rotated_bend_center = list(self.plane.center) @@ -636,7 +635,7 @@ def rotated_bend_center(self) -> List: def _car_2_cyn( self, mode_solver_data: ModeSolverData - ) -> Dict[Union[ScalarModeFieldCylindricalDataArray, ModeIndexDataArray]]: + ) -> dict[Union[ScalarModeFieldCylindricalDataArray, ModeIndexDataArray]]: """Convert cartesian fields to cylindrical fields centered at the rotated bend center.""" @@ -836,7 +835,7 @@ def _car_2_cyn( def _mode_rotation( self, - solver_ref_data_cylindrical: Dict[ + solver_ref_data_cylindrical: dict[ Union[ScalarModeFieldCylindricalDataArray, ModeIndexDataArray] ], solver: ModeSolver, @@ -991,7 +990,7 @@ def _bend_radius(self): return EFFECTIVE_RADIUS_FACTOR * largest_dim @cached_property - def bend_center(self) -> List: + def bend_center(self) -> list: """Computes the bend center based on plane center, angle_theta and angle_phi.""" _, id_bend_uv = self.plane.pop_axis((0, 1, 2), axis=self.bend_axis_3d) @@ -1042,7 +1041,7 @@ def _data_on_yee_grid(self) -> ModeSolverData: log.warning( "Mode solver reduced_simulation_copy failed. " "Falling back to non-reduced simulation, which may be slower. " - f"Exception: {str(e)}" + f"Exception: {e!s}" ) _, _solver_coords = solver.plane.pop_axis( @@ -1057,10 +1056,10 @@ def _data_on_yee_grid(self) -> ModeSolverData: # start a dictionary storing the data arrays for the ModeSolverData index_data = ModeIndexDataArray( np.stack(n_complex, axis=0), - coords=dict( - f=list(solver.freqs), - mode_index=np.arange(solver.mode_spec.num_modes), - ), + coords={ + "f": list(solver.freqs), + "mode_index": np.arange(solver.mode_spec.num_modes), + }, ) data_dict = {"n_complex": index_data} @@ -1069,13 +1068,13 @@ def _data_on_yee_grid(self) -> ModeSolverData: xyz_coords = solver.grid_snapped[field_name].to_list scalar_field_data = ScalarModeFieldDataArray( np.stack([field_freq[field_name] for field_freq in fields], axis=-2), - coords=dict( - x=xyz_coords[0], - y=xyz_coords[1], - z=xyz_coords[2], - f=list(solver.freqs), - mode_index=np.arange(solver.mode_spec.num_modes), - ), + coords={ + "x": xyz_coords[0], + "y": xyz_coords[1], + "z": xyz_coords[2], + "f": list(solver.freqs), + "mode_index": np.arange(solver.mode_spec.num_modes), + }, ) data_dict[field_name] = scalar_field_data @@ -1129,10 +1128,10 @@ def _data_on_yee_grid_relative(self, basis: ModeSolverData) -> ModeSolverData: # start a dictionary storing the data arrays for the ModeSolverData index_data = ModeIndexDataArray( np.stack(n_complex, axis=0), - coords=dict( - f=list(self.freqs), - mode_index=np.arange(self.mode_spec.num_modes), - ), + coords={ + "f": list(self.freqs), + "mode_index": np.arange(self.mode_spec.num_modes), + }, ) data_dict = {"n_complex": index_data} @@ -1141,13 +1140,13 @@ def _data_on_yee_grid_relative(self, basis: ModeSolverData) -> ModeSolverData: xyz_coords = self.grid_snapped[field_name].to_list scalar_field_data = ScalarModeFieldDataArray( np.stack([field_freq[field_name] for field_freq in fields], axis=-2), - coords=dict( - x=xyz_coords[0], - y=xyz_coords[1], - z=xyz_coords[2], - f=list(self.freqs), - mode_index=np.arange(self.mode_spec.num_modes), - ), + coords={ + "x": xyz_coords[0], + "y": xyz_coords[1], + "z": xyz_coords[2], + "f": list(self.freqs), + "mode_index": np.arange(self.mode_spec.num_modes), + }, ) data_dict[field_name] = scalar_field_data @@ -1176,7 +1175,7 @@ def _data_on_yee_grid_relative(self, basis: ModeSolverData) -> ModeSolverData: return mode_solver_data - def _get_colocation_coordinates(self) -> Dict[str, ArrayFloat1D]: + def _get_colocation_coordinates(self) -> dict[str, ArrayFloat1D]: """Get colocation coordinates in the solver plane. Returns: @@ -1249,7 +1248,8 @@ def _filter_polarization(self, mode_solver_data: ModeSolverData): np.where(np.isnan(te_frac))[0], ) ) - for data in list(mode_solver_data.field_components.values()) + [ + for data in [ + *list(mode_solver_data.field_components.values()), mode_solver_data.n_complex, mode_solver_data.grid_primal_correction, mode_solver_data.grid_dual_correction, @@ -1278,19 +1278,18 @@ def sim_data(self) -> MODE_SIMULATION_DATA_TYPE: :class:`.SimulationData` object containing the effective index and mode fields. """ monitor_data = self.data - new_monitors = list(self.simulation.monitors) + [monitor_data.monitor] - new_simulation = self.simulation.copy(update=dict(monitors=new_monitors)) + new_monitors = [*list(self.simulation.monitors), monitor_data.monitor] + new_simulation = self.simulation.copy(update={"monitors": new_monitors}) if isinstance(new_simulation, Simulation): return SimulationData(simulation=new_simulation, data=(monitor_data,)) - elif isinstance(new_simulation, EMESimulation): + if isinstance(new_simulation, EMESimulation): return EMESimulationData( simulation=new_simulation, data=(monitor_data,), smatrix=None, port_modes=None ) - else: - raise SetupError( - "The 'simulation' provided does not correspond to any known " - "'AbstractSimulationData' type." - ) + raise SetupError( + "The 'simulation' provided does not correspond to any known " + "'AbstractSimulationData' type." + ) def _get_epsilon(self, freq: float) -> ArrayComplex4D: """Compute the epsilon tensor in the plane. Order of components is xx, xy, xz, yx, etc.""" @@ -1313,7 +1312,7 @@ def _tensorial_material_profile_modal_plane_tranform( # convert to into 3-by-3 representation for easier axis swap flat_shape = np.shape(mat_tensor) # 9 components flat - tensor_shape = [3, 3] + list(flat_shape[1:]) # 3-by-3 matrix + tensor_shape = [3, 3, *flat_shape[1:]] # 3-by-3 matrix mat_tensor = mat_tensor.reshape(tensor_shape) # swap axes to plane coordinates (normal_axis goes to z) @@ -1364,9 +1363,9 @@ def _solver_eps(self, freq: float) -> ArrayComplex4D: def _solve_all_freqs( self, - coords: Tuple[ArrayFloat1D, ArrayFloat1D], - symmetry: Tuple[Symmetry, Symmetry], - ) -> Tuple[List[float], List[Dict[str, ArrayComplex4D]], List[EpsSpecType]]: + coords: tuple[ArrayFloat1D, ArrayFloat1D], + symmetry: tuple[Symmetry, Symmetry], + ) -> tuple[list[float], list[dict[str, ArrayComplex4D]], list[EpsSpecType]]: """Call the mode solver at all requested frequencies.""" fields = [] @@ -1383,10 +1382,10 @@ def _solve_all_freqs( def _solve_all_freqs_relative( self, - coords: Tuple[ArrayFloat1D, ArrayFloat1D], - symmetry: Tuple[Symmetry, Symmetry], - basis_fields: List[Dict[str, ArrayComplex4D]], - ) -> Tuple[List[float], List[Dict[str, ArrayComplex4D]], List[EpsSpecType]]: + coords: tuple[ArrayFloat1D, ArrayFloat1D], + symmetry: tuple[Symmetry, Symmetry], + basis_fields: list[dict[str, ArrayComplex4D]], + ) -> tuple[list[float], list[dict[str, ArrayComplex4D]], list[EpsSpecType]]: """Call the mode solver at all requested frequencies.""" fields = [] @@ -1426,9 +1425,9 @@ def _postprocess_solver_fields(solver_fields, normal_axis, plane, mode_spec, coo def _solve_single_freq( self, freq: float, - coords: Tuple[ArrayFloat1D, ArrayFloat1D], - symmetry: Tuple[Symmetry, Symmetry], - ) -> Tuple[float, Dict[str, ArrayComplex4D], EpsSpecType]: + coords: tuple[ArrayFloat1D, ArrayFloat1D], + symmetry: tuple[Symmetry, Symmetry], + ) -> tuple[float, dict[str, ArrayComplex4D], EpsSpecType]: """Call the mode solver at a single frequency. The fields are rotated from propagation coordinates back to global coordinates. @@ -1479,10 +1478,10 @@ def _postprocess_solver_fields_inverse(self, fields): def _solve_single_freq_relative( self, freq: float, - coords: Tuple[ArrayFloat1D, ArrayFloat1D], - symmetry: Tuple[Symmetry, Symmetry], - basis_fields: Dict[str, ArrayComplex4D], - ) -> Tuple[float, Dict[str, ArrayComplex4D], EpsSpecType]: + coords: tuple[ArrayFloat1D, ArrayFloat1D], + symmetry: tuple[Symmetry, Symmetry], + basis_fields: dict[str, ArrayComplex4D], + ) -> tuple[float, dict[str, ArrayComplex4D], EpsSpecType]: """Call the mode solver at a single frequency. Modes are computed as linear combinations of ``basis_fields``. """ @@ -1518,7 +1517,7 @@ def _rotate_field_coords(field: FIELD, normal_axis: Axis, plane: MODE_PLANE_TYPE @staticmethod def _weighted_coord_max( array: ArrayFloat2D, u: ArrayFloat1D, v: ArrayFloat1D - ) -> Tuple[int, int]: + ) -> tuple[int, int]: """2D argmax for an array weighted in both directions.""" if not np.all(np.isfinite(array)): # make sure the array is valid return 0, 0 @@ -1536,7 +1535,7 @@ def _weighted_coord_max( return i, j @staticmethod - def _inverted_gauge(e_field: FIELD, diff_coords: Tuple[ArrayFloat1D, ArrayFloat1D]) -> bool: + def _inverted_gauge(e_field: FIELD, diff_coords: tuple[ArrayFloat1D, ArrayFloat1D]) -> bool: """Check if the lower xy region of the mode has a negative sign.""" dx, dy = diff_coords e_x, e_y = e_field[:2, :, :, 0] @@ -1549,18 +1548,17 @@ def _inverted_gauge(e_field: FIELD, diff_coords: Tuple[ArrayFloat1D, ArrayFloat1 while i > 0 and j > 0: if (e[:i, :j] > 0).all(): return False - elif (e[:i, :j] < 0).all(): + if (e[:i, :j] < 0).all(): return True - else: - threshold = abs_e[:i, :j].max() * 0.5 - i, j = ModeSolver._weighted_coord_max(e_2[:i, :j], dx[:i], dy[:j]) - if abs(e[i, j]) >= threshold: - return e[i, j] < 0 - # Do not close the window for 1D mode solvers - if e.shape[0] == 1: - i = 1 - elif e.shape[1] == 1: - j = 1 + threshold = abs_e[:i, :j].max() * 0.5 + i, j = ModeSolver._weighted_coord_max(e_2[:i, :j], dx[:i], dy[:j]) + if abs(e[i, j]) >= threshold: + return e[i, j] < 0 + # Do not close the window for 1D mode solvers + if e.shape[0] == 1: + i = 1 + elif e.shape[1] == 1: + j = 1 return False @staticmethod @@ -1569,8 +1567,8 @@ def _process_fields( mode_index: pydantic.NonNegativeInt, normal_axis: Axis, plane: MODE_PLANE_TYPE, - diff_coords: Tuple[ArrayFloat1D, ArrayFloat1D], - ) -> Tuple[FIELD, FIELD]: + diff_coords: tuple[ArrayFloat1D, ArrayFloat1D], + ) -> tuple[FIELD, FIELD]: """Transform solver fields to simulation axes and set gauge.""" # Separate E and H fields (in solver coordinates) @@ -1697,7 +1695,7 @@ def _is_tensorial(self) -> bool: return abs(self.mode_spec.angle_theta) > 0 or self._has_fully_anisotropic_media @cached_property - def _intersecting_media(self) -> List: + def _intersecting_media(self) -> list: """List of media (including simulation background) intersecting the mode plane.""" total_structures = [self.simulation.scene.background_structure] total_structures += list(self.simulation.structures) @@ -1794,7 +1792,9 @@ def to_source( **kwargs, ) - def to_monitor(self, freqs: List[float] = None, name: str = None) -> ModeMonitor: + def to_monitor( + self, freqs: Optional[list[float]] = None, name: Optional[str] = None + ) -> ModeMonitor: """Creates :class:`ModeMonitor` from a :class:`ModeSolver` instance plus additional specifications. @@ -1830,7 +1830,9 @@ def to_monitor(self, freqs: List[float] = None, name: str = None) -> ModeMonitor name=name, ) - def to_mode_solver_monitor(self, name: str, colocate: bool = None) -> ModeSolverMonitor: + def to_mode_solver_monitor( + self, name: str, colocate: Optional[bool] = None + ) -> ModeSolverMonitor: """Creates :class:`ModeSolverMonitor` from a :class:`ModeSolver` instance. Parameters @@ -1891,15 +1893,15 @@ def sim_with_source( mode_source = self.to_source( mode_index=mode_index, direction=direction, source_time=source_time ) - new_sources = list(self.simulation.sources) + [mode_source] + new_sources = [*list(self.simulation.sources), mode_source] new_sim = self.simulation.updated_copy(sources=new_sources) return new_sim @require_fdtd_simulation def sim_with_monitor( self, - freqs: List[float] = None, - name: str = None, + freqs: Optional[list[float]] = None, + name: Optional[str] = None, ) -> Simulation: """Creates :class:`.Simulation` from a :class:`ModeSolver`. Creates a copy of the ModeSolver's original simulation with a mode monitor added corresponding to @@ -1921,7 +1923,7 @@ def sim_with_monitor( """ mode_monitor = self.to_monitor(freqs=freqs, name=name) - new_monitors = list(self.simulation.monitors) + [mode_monitor] + new_monitors = [*list(self.simulation.monitors), mode_monitor] new_sim = self.simulation.updated_copy(monitors=new_monitors) return new_sim @@ -1945,7 +1947,7 @@ def sim_with_mode_solver_monitor( from the ModeSolver instance and ``name``. """ mode_solver_monitor = self.to_mode_solver_monitor(name=name) - new_monitors = list(self.simulation.monitors) + [mode_solver_monitor] + new_monitors = [*list(self.simulation.monitors), mode_solver_monitor] new_sim = self.simulation.updated_copy(monitors=new_monitors) return new_sim @@ -1956,8 +1958,8 @@ def plot_field( scale: PlotScale = "lin", eps_alpha: float = 0.2, robust: bool = True, - vmin: float = None, - vmax: float = None, + vmin: Optional[float] = None, + vmax: Optional[float] = None, ax: Ax = None, **sel_kwargs, ) -> Ax: @@ -2059,8 +2061,8 @@ def plot( def plot_eps( self, - freq: float = None, - alpha: float = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, ax: Ax = None, ) -> Ax: """Plot the mode plane simulation's components. @@ -2113,8 +2115,8 @@ def plot_eps( def plot_structures_eps( self, - freq: float = None, - alpha: float = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, @@ -2203,7 +2205,7 @@ def plot_grid( ) @classmethod - def _plane_grid(cls, simulation: Simulation, plane: Box) -> Tuple[Coords, Coords]: + def _plane_grid(cls, simulation: Simulation, plane: Box) -> tuple[Coords, Coords]: """Plane grid for mode solver.""" # Get the mode plane normal axis, center, and limits. _, _, _, t_axes = cls._center_and_lims(simulation=simulation, plane=plane) @@ -2219,7 +2221,7 @@ def _plane_grid(cls, simulation: Simulation, plane: Box) -> Tuple[Coords, Coords @classmethod def _effective_num_pml( cls, simulation: Simulation, plane: Box, mode_spec: ModeSpec - ) -> Tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat]: + ) -> tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat]: """Number of cells of the mode solver pml.""" coord_0, coord_1 = cls._plane_grid(simulation=simulation, plane=plane) @@ -2233,9 +2235,9 @@ def _effective_num_pml( @classmethod def _pml_thickness( cls, simulation: Simulation, plane: Box, mode_spec: ModeSpec - ) -> Tuple[ - Tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat], - Tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat], + ) -> tuple[ + tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat], + tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat], ]: """Thickness of the mode solver pml in the form ((plus0, minus0), (plus1, minus1)) @@ -2273,7 +2275,7 @@ def _pml_thickness( @classmethod def _mode_plane_size( cls, simulation: Simulation, plane: Box - ) -> Tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat]: + ) -> tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat]: """The size of the mode plane intersected with the simulation.""" _, h_lim, v_lim, _ = cls._center_and_lims(simulation=simulation, plane=plane) return h_lim[1] - h_lim[0], v_lim[1] - v_lim[0] @@ -2281,7 +2283,7 @@ def _mode_plane_size( @classmethod def _mode_plane_size_no_pml( cls, simulation: Simulation, plane: Box, mode_spec: ModeSpec - ) -> Tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat]: + ) -> tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat]: """The size of the remaining portion of the mode plane, after the pml has been removed.""" size = cls._mode_plane_size(simulation=simulation, plane=plane) @@ -2364,7 +2366,7 @@ def _plot_pml( return ax @staticmethod - def _center_and_lims(simulation: Simulation, plane: Box) -> Tuple[List, List, List, List]: + def _center_and_lims(simulation: Simulation, plane: Box) -> tuple[list, list, list, list]: """Get the mode plane center and limits.""" normal_axis = plane.size.index(0.0) diff --git a/tidy3d/components/mode/simulation.py b/tidy3d/components/mode/simulation.py index 290bae48c5..505b949179 100644 --- a/tidy3d/components/mode/simulation.py +++ b/tidy3d/components/mode/simulation.py @@ -2,31 +2,30 @@ from __future__ import annotations -from typing import Optional, Tuple, Union +from typing import Optional, Union import numpy as np import pydantic.v1 as pd -from ...constants import C_0 -from ...exceptions import SetupError, ValidationError -from ...log import log -from ..base import cached_property -from ..boundary import Boundary, BoundarySpec -from ..geometry.base import Box -from ..grid.grid import Grid -from ..grid.grid_spec import GridSpec -from ..mode_spec import ModeSpec -from ..monitor import ModeMonitor, ModeSolverMonitor, PermittivityMonitor -from ..simulation import AbstractYeeGridSimulation, Simulation, validate_boundaries_for_zero_dims -from ..source.field import ModeSource -from ..types import ( - TYPE_TAG_STR, - Ax, - Direction, - EMField, - FreqArray, +from tidy3d.components.base import cached_property +from tidy3d.components.boundary import Boundary, BoundarySpec +from tidy3d.components.geometry.base import Box +from tidy3d.components.grid.grid import Grid +from tidy3d.components.grid.grid_spec import GridSpec +from tidy3d.components.mode_spec import ModeSpec +from tidy3d.components.monitor import ModeMonitor, ModeSolverMonitor, PermittivityMonitor +from tidy3d.components.simulation import ( + AbstractYeeGridSimulation, + Simulation, + validate_boundaries_for_zero_dims, ) -from ..validators import validate_mode_plane_radius +from tidy3d.components.source.field import ModeSource +from tidy3d.components.types import TYPE_TAG_STR, Ax, Direction, EMField, FreqArray +from tidy3d.components.validators import validate_mode_plane_radius +from tidy3d.constants import C_0 +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.log import log + from .mode_solver import ModeSolver ModeSimulationMonitorType = PermittivityMonitor @@ -138,7 +137,7 @@ class ModeSimulation(AbstractYeeGridSimulation): "primal grid nodes). Default is ``True``.", ) - fields: Tuple[EMField, ...] = pd.Field( + fields: tuple[EMField, ...] = pd.Field( ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"], title="Field Components", description="Collection of field components to store in the monitor. Note that some " @@ -156,14 +155,14 @@ class ModeSimulation(AbstractYeeGridSimulation): "apply PML layers in the mode solver.", ) - monitors: Tuple[ModeSimulationMonitorType, ...] = pd.Field( + monitors: tuple[ModeSimulationMonitorType, ...] = pd.Field( (), title="Monitors", description="Tuple of monitors in the simulation. " "Note: monitor names are used to access data after simulation is run.", ) - sources: Tuple[()] = pd.Field( + sources: tuple[()] = pd.Field( (), title="Sources", description="Sources in the simulation. Note: sources are not supported in mode " @@ -403,8 +402,8 @@ def plot_mode_plane( def plot_eps_mode_plane( self, - freq: float = None, - alpha: float = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, ax: Ax = None, ) -> Ax: """Plot the mode plane simulation's components. @@ -436,8 +435,8 @@ def plot_eps_mode_plane( def plot_structures_eps_mode_plane( self, - freq: float = None, - alpha: float = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, diff --git a/tidy3d/components/mode/solver.py b/tidy3d/components/mode/solver.py index 2cd12ef4de..37aefe57f6 100644 --- a/tidy3d/components/mode/solver.py +++ b/tidy3d/components/mode/solver.py @@ -1,15 +1,18 @@ """Mode solver for propagating EM modes.""" -from typing import Tuple +from __future__ import annotations + +from typing import Optional import numpy as np import scipy.linalg as linalg import scipy.sparse as sp import scipy.sparse.linalg as spl -from ...constants import C_0, ETA_0, fp_eps, pec_val -from ..base import Tidy3dBaseModel -from ..types import EpsSpecType, ModeSolverType, Numpy +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.components.types import EpsSpecType, ModeSolverType, Numpy +from tidy3d.constants import C_0, ETA_0, fp_eps, pec_val + from .derivatives import create_d_matrices as d_mats from .derivatives import create_s_matrices as s_mats from .transforms import angled_transform, radial_transform @@ -49,8 +52,8 @@ def compute_modes( symmetry=(0, 0), direction="+", solver_basis_fields=None, - plane_center: tuple[float, float] = None, - ) -> Tuple[Numpy, Numpy, EpsSpecType]: + plane_center: Optional[tuple[float, float]] = None, + ) -> tuple[Numpy, Numpy, EpsSpecType]: """ Solve for the modes of a waveguide cross-section. @@ -249,7 +252,7 @@ def compute_modes( "Shape mismatch between 'basis_fields' and requested mode data. " "Make sure the mode solvers are set up the same, and that the " "basis mode solver data has 'colocate=False'." - ) + ) from None if split_curl_scaling is not None: basis_E = cls.split_curl_field_postprocess_inverse(split_curl_scaling, basis_E) jac_e_inv = np.moveaxis( @@ -469,8 +472,9 @@ def solver_diagonal( # code associated with these options is included below in case it's useful in the future enable_preconditioner = False analyze_conditioning = False + _threshold = 0.9 * np.abs(pec_val) - def incidence_matrix_for_pec(eps_vec, threshold=0.9 * np.abs(pec_val)): + def incidence_matrix_for_pec(eps_vec, threshold=_threshold): """Incidence matrix indicating non-PEC entries associated with 'eps_vec'.""" nnz = eps_vec[np.abs(eps_vec) < threshold] eps_nz = eps_vec.copy() @@ -595,10 +599,10 @@ def conditional_inverted_vec(eps_vec, threshold=1): aac = mat * mat.conjugate().T diff = aca - aac print( - f"inf-norm: A*A: {spl.norm(aca, ord=np.inf)}, AA*: {spl.norm(aac, ord=np.inf)}, nonnormality: {spl.norm(diff, ord=np.inf)}, relative nonnormality: {spl.norm(diff, ord=np.inf)/spl.norm(aca, ord=np.inf)}" + f"inf-norm: A*A: {spl.norm(aca, ord=np.inf)}, AA*: {spl.norm(aac, ord=np.inf)}, nonnormality: {spl.norm(diff, ord=np.inf)}, relative nonnormality: {spl.norm(diff, ord=np.inf) / spl.norm(aca, ord=np.inf)}" ) print( - f"fro-norm: A*A: {spl.norm(aca, ord='fro')}, AA*: {spl.norm(aac, ord='fro')}, nonnormality: {spl.norm(diff, ord='fro')}, relative nonnormality: {spl.norm(diff, ord='fro')/spl.norm(aca, ord='fro')}" + f"fro-norm: A*A: {spl.norm(aca, ord='fro')}, AA*: {spl.norm(aac, ord='fro')}, nonnormality: {spl.norm(diff, ord='fro')}, relative nonnormality: {spl.norm(diff, ord='fro') / spl.norm(aca, ord='fro')}" ) # preprocess basis modes @@ -1044,6 +1048,6 @@ def mode_plane_contain_good_conductor(material_response) -> bool: return np.any(np.abs(material_response) > GOOD_CONDUCTOR_THRESHOLD * np.abs(pec_val)) -def compute_modes(*args, **kwargs) -> Tuple[Numpy, Numpy, str]: +def compute_modes(*args, **kwargs) -> tuple[Numpy, Numpy, str]: """A wrapper around ``EigSolver.compute_modes``, which is used in ``ModeSolver``.""" return EigSolver.compute_modes(*args, **kwargs) diff --git a/tidy3d/components/mode/transforms.py b/tidy3d/components/mode/transforms.py index 53372b395f..c498be1af9 100644 --- a/tidy3d/components/mode/transforms.py +++ b/tidy3d/components/mode/transforms.py @@ -8,6 +8,8 @@ Similarly, the jacobian for mu and H is evaluated at the r' positions of H-field components. Currently, the half-step offset in w is ignored, which should be a pretty good approximation.""" +from __future__ import annotations + import numpy as np diff --git a/tidy3d/components/mode_spec.py b/tidy3d/components/mode_spec.py index 4f63465bce..1d1742f7f1 100644 --- a/tidy3d/components/mode_spec.py +++ b/tidy3d/components/mode_spec.py @@ -1,16 +1,19 @@ """Defines specification for mode solver.""" +from __future__ import annotations + from math import isclose -from typing import Tuple, Union +from typing import Literal, Union import numpy as np import pydantic.v1 as pd -from ..constants import GLANCING_CUTOFF, MICROMETER, RADIAN, fp_eps -from ..exceptions import SetupError, ValidationError -from ..log import log +from tidy3d.constants import GLANCING_CUTOFF, MICROMETER, RADIAN, fp_eps +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.log import log + from .base import Tidy3dBaseModel, skip_if_fields_missing -from .types import Axis2D, Literal, TrackFreq +from .types import Axis2D, TrackFreq GROUP_INDEX_STEP = 0.005 @@ -65,7 +68,7 @@ class ModeSpec(Tidy3dBaseModel): None, title="Target effective index", description="Guess for effective index of the mode." ) - num_pml: Tuple[pd.NonNegativeInt, pd.NonNegativeInt] = pd.Field( + num_pml: tuple[pd.NonNegativeInt, pd.NonNegativeInt] = pd.Field( (0, 0), title="Number of PML layers", description="Number of standard pml layers to add in the two tangential axes.", diff --git a/tidy3d/components/monitor.py b/tidy3d/components/monitor.py index e16fc13c70..1ad5cef331 100644 --- a/tidy3d/components/monitor.py +++ b/tidy3d/components/monitor.py @@ -1,14 +1,17 @@ """Objects that define how data is recorded from simulation.""" +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Tuple, Union +from typing import Optional, Union import numpy as np import pydantic.v1 as pydantic -from ..constants import HERTZ, MICROMETER, RADIAN, SECOND, inf -from ..exceptions import SetupError, ValidationError -from ..log import log +from tidy3d.constants import HERTZ, MICROMETER, RADIAN, SECOND, inf +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.log import log + from .apodization import ApodizationSpec from .base import Tidy3dBaseModel, cached_property, skip_if_fields_missing from .base_sim.monitor import AbstractMonitor @@ -48,7 +51,7 @@ class Monitor(AbstractMonitor): """Abstract base class for monitors.""" - interval_space: Tuple[Literal[1], Literal[1], Literal[1]] = pydantic.Field( + interval_space: tuple[Literal[1], Literal[1], Literal[1]] = pydantic.Field( (1, 1, 1), title="Spatial Interval", description="Number of grid step intervals between monitor recordings. If equal to 1, " @@ -191,7 +194,7 @@ def stop_greater_than_start(cls, val, values): raise SetupError("Monitor start time is greater than stop time.") return val - def time_inds(self, tmesh: ArrayFloat1D) -> Tuple[int, int]: + def time_inds(self, tmesh: ArrayFloat1D) -> tuple[int, int]: """Compute the starting and stopping index of the monitor in a given discrete time mesh.""" tmesh = np.array(tmesh) @@ -230,13 +233,13 @@ def num_steps(self, tmesh: ArrayFloat1D) -> int: class AbstractFieldMonitor(Monitor, ABC): """:class:`Monitor` that records electromagnetic field data as a function of x,y,z.""" - fields: Tuple[EMField, ...] = pydantic.Field( + fields: tuple[EMField, ...] = pydantic.Field( ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"], title="Field Components", description="Collection of field components to store in the monitor.", ) - interval_space: Tuple[pydantic.PositiveInt, pydantic.PositiveInt, pydantic.PositiveInt] = ( + interval_space: tuple[pydantic.PositiveInt, pydantic.PositiveInt, pydantic.PositiveInt] = ( pydantic.Field( (1, 1, 1), title="Spatial Interval", @@ -278,14 +281,14 @@ class AbstractAuxFieldMonitor(Monitor, ABC): :class:`.TwoPhotonAbsorption` uses `Nfx`, `Nfy`, and `Nfz` for the free-carrier density.""" - fields: Tuple[AuxField, ...] = pydantic.Field( + fields: tuple[AuxField, ...] = pydantic.Field( (), title="Aux Field Components", description="Collection of auxiliary field components to store in the monitor. " "Auxiliary fields which are not present in the simulation will be zero.", ) - interval_space: Tuple[pydantic.PositiveInt, pydantic.PositiveInt, pydantic.PositiveInt] = ( + interval_space: tuple[pydantic.PositiveInt, pydantic.PositiveInt, pydantic.PositiveInt] = ( pydantic.Field( (1, 1, 1), title="Spatial Interval", @@ -351,9 +354,9 @@ class AbstractModeMonitor(PlanarMonitor, FreqMonitor): def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, **patch_kwargs, ) -> Ax: @@ -385,7 +388,7 @@ def plot( return ax @cached_property - def _dir_arrow(self) -> Tuple[float, float, float]: + def _dir_arrow(self) -> tuple[float, float, float]: """Source direction normal vector in cartesian coordinates.""" dx = np.cos(self.mode_spec.angle_phi) * np.sin(self.mode_spec.angle_theta) dy = np.sin(self.mode_spec.angle_phi) * np.sin(self.mode_spec.angle_theta) @@ -565,7 +568,7 @@ class PermittivityMonitor(FreqMonitor): "physical meaning - they do not correspond to the subpixel-averaged ones.", ) - interval_space: Tuple[pydantic.PositiveInt, pydantic.PositiveInt, pydantic.PositiveInt] = ( + interval_space: tuple[pydantic.PositiveInt, pydantic.PositiveInt, pydantic.PositiveInt] = ( pydantic.Field( (1, 1, 1), title="Spatial Interval", @@ -599,7 +602,7 @@ class SurfaceIntegrationMonitor(Monitor, ABC): "Applies to surface monitors only, and defaults to ``'+'`` if not provided.", ) - exclude_surfaces: Tuple[BoxSurface, ...] = pydantic.Field( + exclude_surfaces: tuple[BoxSurface, ...] = pydantic.Field( None, title="Excluded Surfaces", description="Surfaces to exclude in the integration, if a volume monitor.", @@ -796,7 +799,7 @@ class ModeSolverMonitor(AbstractModeMonitor): "dimension.", ) - fields: Tuple[EMField, ...] = pydantic.Field( + fields: tuple[EMField, ...] = pydantic.Field( ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"], title="Field Components", description="Collection of field components to store in the monitor. Note that some " @@ -891,7 +894,7 @@ class AbstractFieldProjectionMonitor(SurfaceIntegrationMonitor, FreqMonitor): "in the far field of the device.", ) - interval_space: Tuple[pydantic.PositiveInt, pydantic.PositiveInt, pydantic.PositiveInt] = ( + interval_space: tuple[pydantic.PositiveInt, pydantic.PositiveInt, pydantic.PositiveInt] = ( pydantic.Field( (1, 1, 1), title="Spatial Interval", @@ -906,7 +909,7 @@ class AbstractFieldProjectionMonitor(SurfaceIntegrationMonitor, FreqMonitor): ) ) - window_size: Tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat] = pydantic.Field( + window_size: tuple[pydantic.NonNegativeFloat, pydantic.NonNegativeFloat] = pydantic.Field( (0, 0), title="Spatial filtering window size", description="Size of the transition region of the windowing function used to ensure that " @@ -961,7 +964,7 @@ def window_size_leq_one(cls, val, values): return val @property - def projection_surfaces(self) -> Tuple[FieldProjectionSurface, ...]: + def projection_surfaces(self) -> tuple[FieldProjectionSurface, ...]: """Surfaces of the monitor where near fields will be recorded for subsequent projection.""" surfaces = self.integration_surfaces return [ @@ -985,7 +988,7 @@ def local_origin(self) -> Coordinate: return self.center return self.custom_origin - def window_parameters(self, custom_bounds: Bound = None) -> Tuple[Size, Coordinate, Coordinate]: + def window_parameters(self, custom_bounds: Bound = None) -> tuple[Size, Coordinate, Coordinate]: """Return the physical size of the window transition region based on the monitor's size and optional custom bounds (useful in case the monitor has infinite dimensions). The window size is returned in 3D. Also returns the coordinate where the transition region beings on diff --git a/tidy3d/components/parameter_perturbation.py b/tidy3d/components/parameter_perturbation.py index 48b08e66e4..1c92621380 100644 --- a/tidy3d/components/parameter_perturbation.py +++ b/tidy3d/components/parameter_perturbation.py @@ -4,7 +4,7 @@ import functools from abc import ABC, abstractmethod -from typing import Callable, List, Optional, Tuple, Union +from typing import Callable, Optional, Union try: import matplotlib.pyplot as plt @@ -14,12 +14,13 @@ import pydantic.v1 as pd import xarray as xr -from ..components.data.validators import validate_no_nans -from ..components.types import TYPE_TAG_STR, ArrayLike, Ax, Complex, FieldVal, InterpMethod -from ..components.viz import add_ax_if_none -from ..constants import C_0, CMCUBE, EPSILON_0, HERTZ, KELVIN, PERCMCUBE, inf -from ..exceptions import DataError -from ..log import log +from tidy3d.components.data.validators import validate_no_nans +from tidy3d.components.types import TYPE_TAG_STR, ArrayLike, Ax, Complex, FieldVal, InterpMethod +from tidy3d.components.viz import add_ax_if_none +from tidy3d.constants import C_0, CMCUBE, EPSILON_0, HERTZ, KELVIN, PERCMCUBE, inf +from tidy3d.exceptions import DataError +from tidy3d.log import log + from .base import Tidy3dBaseModel, cached_property from .data.data_array import ( ChargeDataArray, @@ -44,7 +45,7 @@ class AbstractPerturbation(ABC, Tidy3dBaseModel): @cached_property @abstractmethod - def perturbation_range(self) -> Union[Tuple[float, float], Tuple[Complex, Complex]]: + def perturbation_range(self) -> Union[tuple[float, float], tuple[Complex, Complex]]: """Perturbation range.""" @cached_property @@ -53,7 +54,7 @@ def is_complex(self) -> bool: """Whether perturbation is complex valued.""" @staticmethod - def _linear_range(interval: Tuple[float, float], ref: float, coeff: Union[float, Complex]): + def _linear_range(interval: tuple[float, float], ref: float, coeff: Union[float, Complex]): """Find value range for a linear perturbation.""" if coeff in (0, 0j): # to avoid 0*inf return np.array([0, 0]) @@ -124,7 +125,7 @@ def _sample( class HeatPerturbation(AbstractPerturbation): """Abstract class for heat perturbation.""" - temperature_range: Tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( + temperature_range: tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( (0, inf), title="Temperature range", description="Temperature range in which perturbation model is valid.", @@ -242,7 +243,7 @@ class LinearHeatPerturbation(HeatPerturbation): ) @cached_property - def perturbation_range(self) -> Union[Tuple[float, float], Tuple[Complex, Complex]]: + def perturbation_range(self) -> Union[tuple[float, float], tuple[Complex, Complex]]: """Range of possible perturbation values in the provided ``temperature_range``.""" return self._linear_range(self.temperature_range, self.temperature_ref, self.coeff) @@ -321,7 +322,7 @@ class CustomHeatPerturbation(HeatPerturbation): description="Sampled perturbation values.", ) - temperature_range: Tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( + temperature_range: tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( None, title="Temperature range", description="Temperature range in which perturbation model is valid. For " @@ -339,7 +340,7 @@ class CustomHeatPerturbation(HeatPerturbation): _no_nans = validate_no_nans("perturbation_values") @cached_property - def perturbation_range(self) -> Union[Tuple[float, float], Tuple[Complex, Complex]]: + def perturbation_range(self) -> Union[tuple[float, float], tuple[Complex, Complex]]: """Range of possible parameter perturbation values.""" return np.min(self.perturbation_values).item(), np.max(self.perturbation_values).item() @@ -486,13 +487,13 @@ def _sample( class ChargePerturbation(AbstractPerturbation): """Abstract class for charge perturbation.""" - electron_range: Tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( + electron_range: tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( (0, inf), title="Electron Density Range", description="Range of electrons densities in which perturbation model is valid.", ) - hole_range: Tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( + hole_range: tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( (0, inf), title="Hole Density Range", description="Range of holes densities in which perturbation model is valid.", @@ -664,7 +665,7 @@ class LinearChargePerturbation(ChargePerturbation): ) @cached_property - def perturbation_range(self) -> Union[Tuple[float, float], Tuple[Complex, Complex]]: + def perturbation_range(self) -> Union[tuple[float, float], tuple[Complex, Complex]]: """Range of possible perturbation values within provided ``electron_range`` and ``hole_range``. """ @@ -800,7 +801,7 @@ class CustomChargePerturbation(ChargePerturbation): description="2D array (vs electron and hole densities) of sampled perturbation values.", ) - electron_range: Tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( + electron_range: tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( None, title="Electron Density Range", description="Range of electrons densities in which perturbation model is valid. For " @@ -808,7 +809,7 @@ class CustomChargePerturbation(ChargePerturbation): "provided ``perturbation_values``", ) - hole_range: Tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( + hole_range: tuple[pd.NonNegativeFloat, pd.NonNegativeFloat] = pd.Field( None, title="Hole Density Range", description="Range of holes densities in which perturbation model is valid. For " @@ -825,7 +826,7 @@ class CustomChargePerturbation(ChargePerturbation): _no_nans = validate_no_nans("perturbation_values") @cached_property - def perturbation_range(self) -> Union[Tuple[float, float], Tuple[complex, complex]]: + def perturbation_range(self) -> Union[tuple[float, float], tuple[complex, complex]]: """Range of possible parameter perturbation values.""" return np.min(self.perturbation_values).item(), np.max(self.perturbation_values).item() @@ -1027,7 +1028,7 @@ def _check_not_empty(cls, values): return values @cached_property - def perturbation_list(self) -> List[PerturbationType]: + def perturbation_list(self) -> list[PerturbationType]: """Provided perturbations as a list.""" perturb_list = [] for p in [self.heat, self.charge]: @@ -1036,7 +1037,7 @@ def perturbation_list(self) -> List[PerturbationType]: return perturb_list @cached_property - def perturbation_range(self) -> Union[Tuple[float, float], Tuple[Complex, Complex]]: + def perturbation_range(self) -> Union[tuple[float, float], tuple[Complex, Complex]]: """Range of possible parameter perturbation values due to both heat and charge effects.""" prange = np.zeros(2) @@ -1498,7 +1499,7 @@ class NedeljkovicSorefMashanovich(AbstractDeltaModel): ), dims=("wvl", "coeff"), coords={ - "wvl": np.array([1.3, 1.55] + list(np.arange(2, 14.5, 0.5))), + "wvl": np.array([1.3, 1.55, *list(np.arange(2, 14.5, 0.5))]), "coeff": ["a", "b", "c", "d", "p", "q", "r", "s"], }, name="perturb_coeffs", @@ -1587,7 +1588,7 @@ def delta_k(self) -> ChargePerturbationType: dk_mesh = dk_mesh * k_factor # convert t ChargeDataArray - dk_data = ChargeDataArray(dk_mesh, coords=dict(n=Ne_range, p=Nh_range)) + dk_data = ChargeDataArray(dk_mesh, coords={"n": Ne_range, "p": Nh_range}) # create CustomChargePerturbation k_si_charge = CustomChargePerturbation(perturbation_values=dk_data) @@ -1615,7 +1616,7 @@ def delta_n(self) -> ChargePerturbationType: dn_mesh = -ne_coeff * Ne_mesh**ne_pow - nh_coeff * Nh_mesh**nh_pow # create ChargeDataArray - dn_data = ChargeDataArray(dn_mesh, coords=dict(n=Ne_range, p=Nh_range)) + dn_data = ChargeDataArray(dn_mesh, coords={"n": Ne_range, "p": Nh_range}) # create CustomChargePerturbation n_si_charge = CustomChargePerturbation(perturbation_values=dn_data) @@ -1677,8 +1678,7 @@ def _check_not_complex(cls, values): if dn_complex or dk_complex: raise DataError( - "Perturbation models 'dn' and 'dk' in 'IndexPerturbation' cannot be " - "complex-valued." + "Perturbation models 'dn' and 'dk' in 'IndexPerturbation' cannot be complex-valued." ) return values diff --git a/tidy3d/components/run_time_spec.py b/tidy3d/components/run_time_spec.py index 5299817609..669ff12e4d 100644 --- a/tidy3d/components/run_time_spec.py +++ b/tidy3d/components/run_time_spec.py @@ -1,4 +1,6 @@ # Defines specifications for how long to run a simulation +from __future__ import annotations + import pydantic.v1 as pd from .base import Tidy3dBaseModel diff --git a/tidy3d/components/scene.py b/tidy3d/components/scene.py index a61aa372fc..17752120a4 100644 --- a/tidy3d/components/scene.py +++ b/tidy3d/components/scene.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, List, Literal, Optional, Set, Tuple, Union +from typing import Literal, Optional, Union import autograd.numpy as np @@ -22,10 +22,10 @@ from tidy3d.components.material.types import MultiPhysicsMediumType3D, StructureMediumType from tidy3d.components.tcad.doping import ConstantDoping, GaussianDoping from tidy3d.components.tcad.viz import HEAT_SOURCE_CMAP +from tidy3d.constants import CONDUCTIVITY, THERMAL_CONDUCTIVITY, inf +from tidy3d.exceptions import SetupError, Tidy3dError +from tidy3d.log import log -from ..constants import CONDUCTIVITY, THERMAL_CONDUCTIVITY, inf -from ..exceptions import SetupError, Tidy3dError -from ..log import log from .base import Tidy3dBaseModel, cached_property from .data.utils import ( CustomSpatialDataType, @@ -103,7 +103,7 @@ class Scene(Tidy3dBaseModel): discriminator=TYPE_TAG_STR, ) - structures: Tuple[Structure, ...] = pd.Field( + structures: tuple[Structure, ...] = pd.Field( (), title="Structures", description="Tuple of structures present in scene. " @@ -233,7 +233,7 @@ def box(self) -> Box: return Box(center=self.center, size=self.size) @cached_property - def mediums(self) -> Set[StructureMediumType]: + def mediums(self) -> set[StructureMediumType]: """Returns set of distinct :class:`.AbstractMedium` in scene. Returns @@ -246,7 +246,7 @@ def mediums(self) -> Set[StructureMediumType]: return list(medium_dict.keys()) @cached_property - def medium_map(self) -> Dict[StructureMediumType, pd.NonNegativeInt]: + def medium_map(self) -> dict[StructureMediumType, pd.NonNegativeInt]: """Returns dict mapping medium to index in material. ``medium_map[medium]`` returns unique global index of :class:`.AbstractMedium` in scene. @@ -259,7 +259,7 @@ def medium_map(self) -> Dict[StructureMediumType, pd.NonNegativeInt]: return {medium: index for index, medium in enumerate(self.mediums)} @cached_property - def sorted_structures(self) -> List[Structure]: + def sorted_structures(self) -> list[Structure]: """Returns a list of sorted structures based on their priority.In the sorted list, latter added structures take higher priority. @@ -276,14 +276,14 @@ def background_structure(self) -> Structure: return Structure(geometry=geometry, medium=self.medium) @cached_property - def all_structures(self) -> List[Structure]: + def all_structures(self) -> list[Structure]: """List of all structures in the simulation including the background.""" - return [self.background_structure] + self.sorted_structures + return [self.background_structure, *self.sorted_structures] @staticmethod def intersecting_media( - test_object: Box, structures: Tuple[Structure, ...] - ) -> Tuple[StructureMediumType, ...]: + test_object: Box, structures: tuple[Structure, ...] + ) -> tuple[StructureMediumType, ...]: """From a given list of structures, returns a list of :class:`.AbstractMedium` associated with those structures that intersect with the ``test_object``, if it is a surface, or its surfaces, if it is a volume. @@ -317,8 +317,8 @@ def intersecting_media( @staticmethod def intersecting_structures( - test_object: Box, structures: Tuple[Structure, ...] - ) -> Tuple[Structure, ...]: + test_object: Box, structures: tuple[Structure, ...] + ) -> tuple[Structure, ...]: """From a given list of structures, returns a list of :class:`.Structure` that intersect with the ``test_object``, if it is a surface, or its surfaces, if it is a volume. @@ -361,12 +361,12 @@ def intersecting_structures( @staticmethod def _get_plot_lims( bounds: Bound, - x: float = None, - y: float = None, - z: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, - ) -> Tuple[Tuple[float, float], Tuple[float, float]]: + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, + ) -> tuple[tuple[float, float], tuple[float, float]]: # if no hlim and/or vlim given, the bounds will then be the usual pml bounds axis, _ = Box.parse_xyz_kwargs(x=x, y=y, z=z) _, (hmin, vmin) = Box.pop_axis(bounds[0], axis=axis) @@ -389,12 +389,12 @@ def _get_plot_lims( @add_ax_if_none def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, fill_structures: bool = True, **patch_kwargs, ) -> Ax: @@ -433,12 +433,12 @@ def plot( @add_ax_if_none def plot_structures( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, fill: bool = True, ) -> Ax: """Plot each of scene's structures on a plane defined by one nonzero x,y,z coordinate. @@ -575,11 +575,11 @@ def _add_cbar(vmin: float, vmax: float, label: str, cmap: str, ax: Ax = None) -> def _set_plot_bounds( bounds: Bound, ax: Ax, - x: float = None, - y: float = None, - z: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ) -> Ax: """Sets the xy limits of the scene at a plane, useful after plotting. @@ -610,13 +610,13 @@ def _set_plot_bounds( def _get_structures_2dbox( self, - structures: List[Structure], - x: float = None, - y: float = None, - z: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, - ) -> List[Tuple[Medium, Shapely]]: + structures: list[Structure], + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, + ) -> list[tuple[Medium, Shapely]]: """Compute list of shapes to plot on 2d box specified by (x_min, x_max), (y_min, y_max). Parameters @@ -671,8 +671,8 @@ def _get_structures_2dbox( @staticmethod def _filter_structures_plane_medium( - structures: List[Structure], plane: Box - ) -> List[Tuple[Medium, Shapely]]: + structures: list[Structure], plane: Box + ) -> list[tuple[Medium, Shapely]]: """Compute list of shapes to plot on plane. Overlaps are removed or merged depending on medium. @@ -696,10 +696,10 @@ def _filter_structures_plane_medium( @staticmethod def _filter_structures_plane( - structures: List[Structure], + structures: list[Structure], plane: Box, - property_list: List, - ) -> List[Tuple[Medium, Shapely]]: + property_list: list, + ) -> list[tuple[Medium, Shapely]]: """Compute list of shapes to plot on plane. Overlaps are removed or merged depending on provided property_list. @@ -727,14 +727,14 @@ def _filter_structures_plane( @add_ax_if_none def plot_eps( self, - x: float = None, - y: float = None, - z: float = None, - freq: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ) -> Ax: """Plot each of scene'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. @@ -778,17 +778,17 @@ def plot_eps( @add_ax_if_none def plot_structures_eps( self, - x: float = None, - y: float = None, - z: float = None, - freq: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, reverse: bool = False, - eps_lim: Tuple[Union[float, None], Union[float, None]] = (None, None), + eps_lim: tuple[Union[float, None], Union[float, None]] = (None, None), ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, grid: Grid = None, eps_component: Optional[PermittivityComponent] = None, ) -> Ax: @@ -854,17 +854,17 @@ def plot_structures_eps( @add_ax_if_none def plot_structures_property( self, - x: float = None, - y: float = None, - z: float = None, - freq: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, reverse: bool = False, - limits: Tuple[Union[float, None], Union[float, None]] = (None, None), + limits: tuple[Union[float, None], Union[float, None]] = (None, None), ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, grid: Grid = None, property: Literal["eps", "doping", "N_a", "N_d"] = "eps", eps_component: Optional[PermittivityComponent] = None, @@ -936,10 +936,10 @@ def plot_structures_property( # for doping background structure could be a non-doping structure # that needs to be rendered if property in ["N_d", "N_a", "doping"]: - structures = [self.background_structure] + list(structures) + structures = [self.background_structure, *list(structures)] medium_shapes = self._filter_structures_plane_medium(structures=structures, plane=plane) else: - structures = [self.background_structure] + list(structures) + structures = [self.background_structure, *list(structures)] medium_shapes = self._get_structures_2dbox( structures=structures, x=x, y=y, z=z, hlim=hlim, vlim=vlim ) @@ -1057,9 +1057,9 @@ def _add_cbar_eps(eps_min: float, eps_max: float, ax: Ax = None, reverse: bool = @staticmethod def _eps_bounds( medium_list: list[Medium], - freq: float = None, + freq: Optional[float] = None, eps_component: Optional[PermittivityComponent] = None, - ) -> Tuple[float, float]: + ) -> tuple[float, float]: """Compute range of (real) permittivity present in the mediums at frequency "freq".""" medium_list = [medium for medium in medium_list if not medium.is_pec] eps_list = [medium._eps_plot(freq, eps_component) for medium in medium_list] @@ -1074,7 +1074,9 @@ def _eps_bounds( eps_max = max(eps_max, mat_epsmax) return eps_min, eps_max - def eps_bounds(self, freq: float = None, eps_component: str = None) -> Tuple[float, float]: + def eps_bounds( + self, freq: Optional[float] = None, eps_component: Optional[str] = None + ) -> tuple[float, float]: """Compute range of (real) permittivity present in the scene at frequency "freq". Parameters @@ -1093,7 +1095,7 @@ def eps_bounds(self, freq: float = None, eps_component: str = None) -> Tuple[flo Minimal and maximal values of relative permittivity in scene. """ - medium_list = [self.medium] + list(self.mediums) + medium_list = [self.medium, *list(self.mediums)] return self._eps_bounds(medium_list=medium_list, freq=freq, eps_component=eps_component) def _pcolormesh_shape_custom_medium_structure_eps( @@ -1172,11 +1174,11 @@ def _pcolormesh_shape_custom_medium_structure_eps( cmap=STRUCTURE_EPS_CMAP, vmin=eps_min, vmax=eps_max, - pcolor_kwargs=dict( - clip_path=(polygon_path(shape), ax.transData), - clip_box=ax.bbox, - alpha=alpha, - ), + pcolor_kwargs={ + "clip_path": (polygon_path(shape), ax.transData), + "clip_box": ax.bbox, + "alpha": alpha, + }, ) return @@ -1266,7 +1268,7 @@ def _get_structure_eps_plot_params( eps_min: float, eps_max: float, reverse: bool = False, - alpha: float = None, + alpha: Optional[float] = None, eps_component: Optional[PermittivityComponent] = None, ) -> PlotParams: """Constructs the plot parameters for a given medium in scene.plot_eps().""" @@ -1306,7 +1308,7 @@ def _plot_shape_structure_eps( eps_max: float, ax: Ax, reverse: bool = False, - alpha: float = None, + alpha: Optional[float] = None, eps_component: Optional[PermittivityComponent] = None, ) -> Ax: """Plot a structure's cross section shape for a given medium, grayscale for permittivity.""" @@ -1328,15 +1330,15 @@ def _plot_shape_structure_eps( @add_ax_if_none def plot_heat_charge_property( self, - x: float = None, - y: float = None, - z: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, property: str = "heat_conductivity", ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ) -> Ax: """Plot each of scebe's components on a plane defined by one nonzero x,y,z coordinate. The thermal conductivity is plotted in grayscale based on its value. @@ -1382,15 +1384,15 @@ def plot_heat_charge_property( @add_ax_if_none def plot_structures_heat_conductivity( self, - x: float = None, - y: float = None, - z: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ) -> Ax: """Plot each of scene's structures on a plane defined by one nonzero x,y,z coordinate. The thermal conductivity is plotted in grayscale based on its value. @@ -1447,16 +1449,16 @@ def plot_structures_heat_conductivity( @add_ax_if_none def plot_structures_heat_charge_property( self, - x: float = None, - y: float = None, - z: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, property: str = "heat_conductivity", reverse: bool = False, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ) -> Ax: """Plot each of scene's structures on a plane defined by one nonzero x,y,z coordinate. The thermal conductivity is plotted in grayscale based on its value. @@ -1506,7 +1508,7 @@ def plot_structures_heat_charge_property( plane = Box(center=center, size=size) medium_shapes = self._filter_structures_plane_medium(structures=structures, plane=plane) else: - structures = [self.background_structure] + list(structures) + structures = [self.background_structure, *list(structures)] medium_shapes = self._get_structures_2dbox( structures=structures, x=x, y=y, z=z, hlim=hlim, vlim=vlim ) @@ -1548,7 +1550,7 @@ def plot_structures_heat_charge_property( ) return ax - def heat_charge_property_bounds(self, property) -> Tuple[float, float]: + def heat_charge_property_bounds(self, property) -> tuple[float, float]: """Compute range of the heat-charge simulation property present in the scene. Returns @@ -1557,7 +1559,7 @@ def heat_charge_property_bounds(self, property) -> Tuple[float, float]: Minimal and maximal values of thermal conductivity in scene. """ - medium_list = [self.medium] + list(self.mediums) + medium_list = [self.medium, *list(self.mediums)] if property == "heat_conductivity": SolidType = (SolidSpec, SolidMedium) medium_list = [ @@ -1577,7 +1579,7 @@ def heat_charge_property_bounds(self, property) -> Tuple[float, float]: cond_max = max(cond_list) return cond_min, cond_max - def heat_conductivity_bounds(self) -> Tuple[float, float]: + def heat_conductivity_bounds(self) -> tuple[float, float]: """Compute range of thermal conductivities present in the scene. Returns @@ -1599,7 +1601,7 @@ def _get_structure_heat_charge_property_plot_params( property_val_min: float, property_val_max: float, reverse: bool = False, - alpha: float = None, + alpha: Optional[float] = None, property: str = "heat_conductivity", ) -> PlotParams: """Constructs the plot parameters for a given medium in @@ -1646,7 +1648,7 @@ def _plot_shape_structure_heat_charge_property( property: str, ax: Ax, reverse: bool = False, - alpha: float = None, + alpha: Optional[float] = None, ) -> Ax: """Plot a structure's cross section shape for a given medium, grayscale for thermal conductivity. @@ -1666,14 +1668,14 @@ def _plot_shape_structure_heat_charge_property( @add_ax_if_none def plot_heat_conductivity( self, - x: float = None, - y: float = None, - z: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ): """Plot each of scebe's components on a plane defined by one nonzero x,y,z coordinate. The thermal conductivity is plotted in grayscale based on its value. diff --git a/tidy3d/components/simulation.py b/tidy3d/components/simulation.py index 2599bb08a8..b1f63fe147 100644 --- a/tidy3d/components/simulation.py +++ b/tidy3d/components/simulation.py @@ -6,7 +6,7 @@ import pathlib from abc import ABC, abstractmethod from collections import defaultdict -from typing import Dict, List, Optional, Set, Tuple, Union, get_args +from typing import Literal, Optional, Union, get_args import autograd.numpy as np @@ -19,10 +19,11 @@ import pydantic.v1 as pydantic import xarray as xr -from ..constants import C_0, SECOND, fp_eps, inf -from ..exceptions import SetupError, Tidy3dError, Tidy3dImportError, ValidationError -from ..log import log -from ..updater import Updater +from tidy3d.constants import C_0, SECOND, fp_eps, inf +from tidy3d.exceptions import SetupError, Tidy3dError, Tidy3dImportError, ValidationError +from tidy3d.log import log +from tidy3d.updater import Updater + from .base import cached_property, skip_if_fields_missing from .base_sim.simulation import AbstractSimulation from .boundary import ( @@ -108,7 +109,6 @@ CoordinateOptional, FreqBound, InterpMethod, - Literal, PermittivityComponent, Symmetry, annotate_type, @@ -231,7 +231,7 @@ class AbstractYeeGridSimulation(AbstractSimulation, ABC): Abstract class for a simulation involving electromagnetic fields defined on a Yee grid. """ - lumped_elements: Tuple[LumpedElementType, ...] = pydantic.Field( + lumped_elements: tuple[LumpedElementType, ...] = pydantic.Field( (), title="Lumped Elements", description="Tuple of lumped elements in the simulation. " @@ -392,7 +392,6 @@ def _check_3d_simulation_with_lumped_elements(cls, val, values): @abstractmethod def _validate_auto_grid_wavelength(cls, val, values): """Check that wavelength can be defined if there is auto grid spec.""" - pass def _monitor_num_cells(self, monitor: Monitor) -> int: """Total number of cells included in monitor based on simulation grid.""" @@ -442,15 +441,15 @@ def _subpixel(self) -> SubpixelSpec: @add_ax_if_none def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - source_alpha: float = None, - monitor_alpha: float = None, - lumped_element_alpha: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + source_alpha: Optional[float] = None, + monitor_alpha: Optional[float] = None, + lumped_element_alpha: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, fill_structures: bool = True, **patch_kwargs, ) -> Ax: @@ -523,19 +522,19 @@ def plot( @add_ax_if_none def plot_eps( self, - x: float = None, - y: float = None, - z: float = None, - freq: float = None, - alpha: float = None, - source_alpha: float = None, - monitor_alpha: float = None, - lumped_element_alpha: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, + source_alpha: Optional[float] = None, + monitor_alpha: Optional[float] = None, + lumped_element_alpha: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ax: Ax = None, eps_component: Optional[PermittivityComponent] = None, - eps_lim: Tuple[Union[float, None], Union[float, None]] = (None, None), + eps_lim: tuple[Union[float, None], Union[float, None]] = (None, None), ) -> Ax: """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. @@ -630,18 +629,18 @@ def plot_eps( @add_ax_if_none def plot_structures_eps( self, - x: float = None, - y: float = None, - z: float = None, - freq: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, eps_component: Optional[PermittivityComponent] = None, - eps_lim: Tuple[Union[float, None], Union[float, None]] = (None, None), + eps_lim: tuple[Union[float, None], Union[float, None]] = (None, None), ) -> Ax: """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. @@ -721,11 +720,11 @@ def plot_structures_eps( @add_ax_if_none def plot_pml( self, - x: float = None, - y: float = None, - z: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's absorbing boundaries @@ -766,7 +765,7 @@ def plot_pml( # candidate for removal in 3.0 @cached_property - def bounds_pml(self) -> Tuple[Tuple[float, float, float], Tuple[float, float, float]]: + def bounds_pml(self) -> tuple[tuple[float, float, float], tuple[float, float, float]]: """Simulation bounds including the PML regions.""" log.warning( "'Simulation.bounds_pml' will be removed in Tidy3D 3.0. " @@ -775,7 +774,7 @@ def bounds_pml(self) -> Tuple[Tuple[float, float, float], Tuple[float, float, fl return self.simulation_bounds @cached_property - def simulation_bounds(self) -> Tuple[Tuple[float, float, float], Tuple[float, float, float]]: + def simulation_bounds(self) -> tuple[tuple[float, float, float], tuple[float, float, float]]: """Simulation bounds including the PML regions.""" pml_thick = self.pml_thicknesses bounds_in = self.bounds @@ -784,7 +783,7 @@ def simulation_bounds(self) -> Tuple[Tuple[float, float, float], Tuple[float, fl return (bounds_min, bounds_max) - def _make_pml_boxes(self, normal_axis: Axis) -> List[Box]: + def _make_pml_boxes(self, normal_axis: Axis) -> list[Box]: """make a list of Box objects representing the pml to plot on plane.""" pml_boxes = [] pml_thicks = self.pml_thicknesses @@ -817,7 +816,7 @@ def _make_pml_box(self, pml_axis: Axis, pml_height: float, sign: int) -> Box: return pml_box # candidate for removal in 3.0 - def eps_bounds(self, freq: float = None) -> Tuple[float, float]: + def eps_bounds(self, freq: Optional[float] = None) -> tuple[float, float]: """Compute range of (real) permittivity present in the simulation at frequency "freq".""" log.warning( @@ -827,7 +826,7 @@ def eps_bounds(self, freq: float = None) -> Tuple[float, float]: return self.scene.eps_bounds(freq=freq) @cached_property - def pml_thicknesses(self) -> List[Tuple[float, float]]: + def pml_thicknesses(self) -> list[tuple[float, float]]: """Thicknesses (um) of absorbers in all three axes and directions (-, +) Returns @@ -845,7 +844,7 @@ def pml_thicknesses(self) -> List[Tuple[float, float]]: return pml_thicknesses @cached_property - def internal_override_structures(self) -> List[MeshOverrideStructure]: + def internal_override_structures(self) -> list[MeshOverrideStructure]: """Internal mesh override structures. So far, internal override structures all come from `layer_refinement_specs`. Returns @@ -862,7 +861,7 @@ def internal_override_structures(self) -> List[MeshOverrideStructure]: ) @cached_property - def internal_snapping_points(self) -> List[CoordinateOptional]: + def internal_snapping_points(self) -> list[CoordinateOptional]: """Internal snapping points. So far, internal snapping points are generated by `layer_refinement_specs`. Returns @@ -878,12 +877,12 @@ def internal_snapping_points(self) -> List[CoordinateOptional]: @add_ax_if_none def plot_lumped_elements( self, - x: float = None, - y: float = None, - z: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, + alpha: Optional[float] = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's lumped elements on a plane defined by one @@ -923,12 +922,12 @@ def plot_lumped_elements( @add_ax_if_none def plot_grid( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, override_structures_alpha: float = 1, snapping_points_alpha: float = 1, **kwargs, @@ -1071,9 +1070,9 @@ def plot_grid( @add_ax_if_none def plot_boundaries( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, **kwargs, ) -> Ax: @@ -1201,7 +1200,7 @@ def set_plot_params(boundary_edge, lim, side, thickness): # return plot_sim_3d(self, width=width, height=height) @cached_property - def _grid_and_snapping_lines(self) -> Tuple[Grid, List[CoordinateOptional]]: + def _grid_and_snapping_lines(self) -> tuple[Grid, list[CoordinateOptional]]: """FDTD grid spatial locations and information. Returns @@ -1259,7 +1258,7 @@ def grid(self) -> Grid: return grid @cached_property - def _gap_meshing_snapping_lines(self) -> List[CoordinateOptional]: + def _gap_meshing_snapping_lines(self) -> list[CoordinateOptional]: """Snapping points resulted from iterative gap meshing. Returns @@ -1290,7 +1289,7 @@ def num_cells(self) -> int: return np.prod(self.grid.num_cells, dtype=np.int64) @cached_property - def grid_info(self) -> Dict: + def grid_info(self) -> dict: """Dictionary collecting various properties of the grids in the simulation.""" return self.grid.info @@ -1311,7 +1310,7 @@ def _subgrid(self, span_inds: np.ndarray, grid: Grid = None): return Grid(boundaries=Coords(**boundary_dict)) @cached_property - def _periodic(self) -> Tuple[bool, bool, bool]: + def _periodic(self) -> tuple[bool, bool, bool]: """For each dimension, ``True`` if periodic/Bloch boundaries and ``False`` otherwise. We check on both sides but in practice there should be no cases in which a periodic/Bloch BC is on one side only. This is explicitly validated for Bloch, and implicitly done for @@ -1323,7 +1322,7 @@ def _periodic(self) -> Tuple[bool, bool, bool]: return periodic @cached_property - def num_pml_layers(self) -> List[Tuple[float, float]]: + def num_pml_layers(self) -> list[tuple[float, float]]: """Number of absorbing layers in all three axes and directions (-, +). Returns @@ -1364,7 +1363,9 @@ def _discretize_grid(self, box: Box, grid: Grid, extend: bool = False) -> Grid: span_inds = grid.discretize_inds(box=box, extend=extend) return self._subgrid(span_inds=span_inds, grid=grid) - def _discretize_inds_monitor(self, monitor: Union[Monitor, Box], colocate: bool = None): + def _discretize_inds_monitor( + self, monitor: Union[Monitor, Box], colocate: Optional[bool] = None + ): """Start and stopping indexes for the cells where data needs to be recorded to fully cover a ``monitor``. This is used during the solver run. The final grid on which a monitor data lives is computed in ``discretize_monitor``, with the difference being that 0-sized @@ -1423,7 +1424,7 @@ def epsilon( self, box: Box, coord_key: str = "centers", - freq: float = None, + freq: Optional[float] = None, ) -> xr.DataArray: """Get array of permittivity at volume specified by box and freq. @@ -1464,7 +1465,7 @@ def epsilon_on_grid( self, grid: Grid, coord_key: str = "centers", - freq: float = None, + freq: Optional[float] = None, ) -> xr.DataArray: """Get array of permittivity at a given freq on a given grid. @@ -1568,7 +1569,7 @@ def make_eps_data(coords: Coords): coords = grid[coord_key] return make_eps_data(coords) - def _volumetric_structures_grid(self, grid: Grid) -> Tuple[Structure]: + def _volumetric_structures_grid(self, grid: Grid) -> tuple[Structure]: """Generate a tuple of structures wherein any 2D materials are converted to 3D volumetric equivalents, using ``grid`` as the simulation grid.""" @@ -1578,7 +1579,7 @@ def _volumetric_structures_grid(self, grid: Grid) -> Tuple[Structure]: ): return self.scene.sorted_structures - def get_dls(geom: Geometry, axis: Axis, num_dls: int) -> List[float]: + def get_dls(geom: Geometry, axis: Axis, num_dls: int) -> list[float]: """Get grid size around the 2D material.""" dls = self._discretize_grid(Box.from_bounds(*geom.bounds), grid=grid).sizes.to_list[ axis @@ -1667,12 +1668,12 @@ def snap_to_grid(geom: Geometry, axis: Axis) -> Geometry: return tuple(new_structures) @cached_property - def volumetric_structures(self) -> Tuple[Structure]: + def volumetric_structures(self) -> tuple[Structure]: """Generate a tuple of structures wherein any 2D materials are converted to 3D volumetric equivalents.""" return self._volumetric_structures_grid(self.grid) - def suggest_mesh_overrides(self, **kwargs) -> List[MeshOverrideStructure]: + def suggest_mesh_overrides(self, **kwargs) -> list[MeshOverrideStructure]: """Generate a :class:`.MeshOverrideStructure` `List` which is automatically generated from structures in the simulation. """ @@ -1689,9 +1690,9 @@ def subsection( region: Box, boundary_spec: BoundarySpec = None, grid_spec: Union[GridSpec, Literal["identical"]] = None, - symmetry: Tuple[Symmetry, Symmetry, Symmetry] = None, - sources: Tuple[SourceType, ...] = None, - monitors: Tuple[MonitorType, ...] = None, + symmetry: Optional[tuple[Symmetry, Symmetry, Symmetry]] = None, + sources: Optional[tuple[SourceType, ...]] = None, + monitors: Optional[tuple[MonitorType, ...]] = None, remove_outside_structures: bool = True, remove_outside_custom_mediums: bool = False, include_pml_cells: bool = False, @@ -2184,7 +2185,7 @@ class Simulation(AbstractYeeGridSimulation): * `Numerical dispersion in FDTD `_ """ - lumped_elements: Tuple[LumpedElementType, ...] = pydantic.Field( + lumped_elements: tuple[LumpedElementType, ...] = pydantic.Field( (), title="Lumped Elements", description="Tuple of lumped elements in the simulation. ", @@ -2413,7 +2414,7 @@ class Simulation(AbstractYeeGridSimulation): data. If ``None``, the raw field data is returned. If ``None``, the raw field data is returned unnormalized. """ - monitors: Tuple[annotate_type(MonitorType), ...] = pydantic.Field( + monitors: tuple[annotate_type(MonitorType), ...] = pydantic.Field( (), title="Monitors", description="Tuple of monitors in the simulation. " @@ -2429,7 +2430,7 @@ class Simulation(AbstractYeeGridSimulation): All the monitor implementations. """ - sources: Tuple[annotate_type(SourceType), ...] = pydantic.Field( + sources: tuple[annotate_type(SourceType), ...] = pydantic.Field( (), title="Sources", description="Tuple of electric current sources injecting fields into the simulation.", @@ -2481,7 +2482,7 @@ class Simulation(AbstractYeeGridSimulation): Set to ``0`` to disable this feature. """ - structures: Tuple[Structure, ...] = pydantic.Field( + structures: tuple[Structure, ...] = pydantic.Field( (), title="Structures", description="Tuple of structures present in simulation. " @@ -2546,7 +2547,7 @@ class Simulation(AbstractYeeGridSimulation): * `Structures `_ """ - symmetry: Tuple[Symmetry, Symmetry, Symmetry] = pydantic.Field( + symmetry: tuple[Symmetry, Symmetry, Symmetry] = pydantic.Field( (0, 0, 0), title="Symmetries", description="Tuple of integers defining reflection symmetry across a plane " @@ -2866,7 +2867,7 @@ def tfsf_with_symmetry(cls, val, values): return val @staticmethod - def _get_fixed_angle_sources(sources: Tuple[SourceType, ...]) -> Tuple[SourceType, ...]: + def _get_fixed_angle_sources(sources: tuple[SourceType, ...]) -> tuple[SourceType, ...]: """Get list of plane wave sources with ``FixedAngleSpec``.""" return [ @@ -3178,7 +3179,7 @@ def _projection_monitors_homogeneous(cls, val, values): ) structures = values.get("structures") or [] - total_structures = [structure_bg] + list(structures) + total_structures = [structure_bg, *list(structures)] with log as consolidated_logger: for monitor_ind, monitor in enumerate(val): @@ -3253,9 +3254,9 @@ def _projection_direction(cls, val, values): center = np.array(monitor.center) - np.array(monitor.local_origin) pts = [np.array(i) for i in [x, y, z]] normal_displacement = pts[normal_ind] - center[normal_ind] - if np.any(normal_displacement < 0) and normal_dir == "+": - projecting_backwards = True - elif np.any(normal_displacement > 0) and normal_dir == "-": + if (np.any(normal_displacement < 0) and normal_dir == "+") or ( + np.any(normal_displacement > 0) and normal_dir == "-" + ): projecting_backwards = True if projecting_backwards: @@ -3558,7 +3559,7 @@ def _source_homogeneous_isotropic(cls, val, values): ) structures = values.get("structures") or [] - total_structures = [structure_bg] + list(structures) + total_structures = [structure_bg, *list(structures)] # for each plane wave in the sources list with log as consolidated_logger: @@ -3900,7 +3901,7 @@ def _validate_nonlinear_specs(self) -> None: ) @cached_property - def aux_fields(self) -> List[str]: + def aux_fields(self) -> list[str]: """All aux fields available in the simulation.""" fields = [] for medium in self.scene.mediums: @@ -4005,7 +4006,7 @@ def _validate_monitor_size(self) -> None: def _validate_modes_size(self) -> None: """Warn if mode sources or monitors have a large number of points.""" - def warn_mode_size(monitor: AbstractModeMonitor, msg_header: str, custom_loc: List): + def warn_mode_size(monitor: AbstractModeMonitor, msg_header: str, custom_loc: list): """Warn if a mode component has a large number of points.""" num_cells = np.prod(self.discretize_monitor(monitor).num_cells) if num_cells > WARN_MODE_NUM_CELLS: @@ -4044,7 +4045,7 @@ def _validate_num_cells_in_mode_objects(self) -> None: of grid cells in their transverse dimensions.""" def check_num_cells( - mode_object: Tuple[ModeSource, ModeMonitor], normal_axis: Axis, msg_header: str + mode_object: tuple[ModeSource, ModeMonitor], normal_axis: Axis, msg_header: str ): disc_grid = self.discretize(mode_object) _, check_axes = Box.pop_axis([0, 1, 2], axis=normal_axis) @@ -4116,7 +4117,7 @@ def _validate_freq_monitors_freq_range(self) -> None: ) @cached_property - def monitors_data_size(self) -> Dict[str, float]: + def monitors_data_size(self) -> dict[str, float]: """Dictionary mapping monitor names to their estimated storage size in bytes.""" data_size = {} for monitor in self.monitors: @@ -4235,7 +4236,7 @@ def _with_adjoint_monitors(self, sim_fields_keys: list) -> Simulation: mnts_fld, mnts_eps = self._make_adjoint_monitors(sim_fields_keys=sim_fields_keys) monitors = list(self.monitors) + list(mnts_fld) + list(mnts_eps) - return self.copy(update=dict(monitors=monitors)) + return self.copy(update={"monitors": monitors}) def _make_adjoint_monitors(self, sim_fields_keys: list) -> tuple[list, list]: """Get lists of field and permittivity monitors for this simulation.""" @@ -4319,7 +4320,7 @@ def _run_time(self) -> float: # candidate for removal in 3.0 @cached_property - def mediums(self) -> Set[MediumType]: + def mediums(self) -> set[MediumType]: """Returns set of distinct :class:`.AbstractMedium` in simulation. Returns @@ -4335,7 +4336,7 @@ def mediums(self) -> Set[MediumType]: # candidate for removal in 3.0 @cached_property - def medium_map(self) -> Dict[MediumType, pydantic.NonNegativeInt]: + 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. @@ -4364,7 +4365,7 @@ def background_structure(self) -> Structure: return self.scene.background_structure @cached_property - def _fixed_angle_sources(self) -> Tuple[SourceType, ...]: + def _fixed_angle_sources(self) -> tuple[SourceType, ...]: """List of plane wave sources with ``FixedAngleSpec``.""" return self._get_fixed_angle_sources(self.sources) @@ -4376,8 +4377,8 @@ def _is_fixed_angle(self) -> bool: # candidate for removal in 3.0 @staticmethod def intersecting_media( - test_object: Box, structures: Tuple[Structure, ...] - ) -> Tuple[MediumType, ...]: + test_object: Box, structures: tuple[Structure, ...] + ) -> tuple[MediumType, ...]: """From a given list of structures, returns a list of :class:`.AbstractMedium` associated with those structures that intersect with the ``test_object``, if it is a surface, or its surfaces, if it is a volume. @@ -4404,8 +4405,8 @@ def intersecting_media( # candidate for removal in 3.0 @staticmethod def intersecting_structures( - test_object: Box, structures: Tuple[Structure, ...] - ) -> Tuple[Structure, ...]: + test_object: Box, structures: tuple[Structure, ...] + ) -> tuple[Structure, ...]: """From a given list of structures, returns a list of :class:`.Structure` that intersect with the ``test_object``, if it is a surface, or its surfaces, if it is a volume. @@ -4499,15 +4500,15 @@ def _check_bloch_vec( def to_gdstk( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, - gds_layer_dtype_map: Dict[ - AbstractMedium, Tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt] + gds_layer_dtype_map: Optional[ + dict[AbstractMedium, tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt]] ] = None, - ) -> List: + ) -> list: """Convert a simulation's planar slice to a .gds type polygon list. Parameters @@ -4570,13 +4571,13 @@ def to_gdstk( def to_gds( self, cell, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, - gds_layer_dtype_map: Dict[ - AbstractMedium, Tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt] + gds_layer_dtype_map: Optional[ + dict[AbstractMedium, tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt]] ] = None, ) -> None: """Append the simulation structures to a .gds cell. @@ -4624,13 +4625,13 @@ def to_gds( def to_gds_file( self, fname: str, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, - gds_layer_dtype_map: Dict[ - AbstractMedium, Tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt] + gds_layer_dtype_map: Optional[ + dict[AbstractMedium, tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt]] ] = None, gds_cell_name: str = "MAIN", ) -> None: @@ -4792,7 +4793,7 @@ def self_structure(self) -> Structure: return self.scene.background_structure @cached_property - def all_structures(self) -> List[Structure]: + def all_structures(self) -> list[Structure]: """List of all structures in the simulation (including the ``Simulation.medium``).""" return self.scene.all_structures @@ -4995,7 +4996,7 @@ def nyquist_step(self) -> int: return nyquist_step @property - def custom_datasets(self) -> List[Dataset]: + def custom_datasets(self) -> list[Dataset]: """List of custom datasets for verification purposes. If the list is not empty, then the simulation needs to be exported to hdf5 to store the data. """ @@ -5099,10 +5100,10 @@ def perturbed_mediums_copy( "Please select one before calling this function. This can be " "done with, e.g., 'electron_data.sel(voltage=1)'" ) - elif len(data.values.dims) > 1: + if len(data.values.dims) > 1: new_values = IndexedDataArray( np.array(data.values.data).flatten(), - coords=dict(index=data.values.index.data), + coords={"index": data.values.index.data}, ) if isinstance(data, TetrahedralGridDataset): new_carrier_data[carrier] = TetrahedralGridDataset( diff --git a/tidy3d/components/source/base.py b/tidy3d/components/source/base.py index 09472b1f6a..388896fb5b 100644 --- a/tidy3d/components/source/base.py +++ b/tidy3d/components/source/base.py @@ -3,22 +3,23 @@ from __future__ import annotations from abc import ABC -from typing import Tuple +from typing import Optional import pydantic.v1 as pydantic -from ..base import cached_property -from ..base_sim.source import AbstractSource -from ..geometry.base import Box -from ..types import TYPE_TAG_STR, Ax -from ..validators import _assert_min_freq, _warn_unsupported_traced_argument -from ..viz import ( +from tidy3d.components.base import cached_property +from tidy3d.components.base_sim.source import AbstractSource +from tidy3d.components.geometry.base import Box +from tidy3d.components.types import TYPE_TAG_STR, Ax +from tidy3d.components.validators import _assert_min_freq, _warn_unsupported_traced_argument +from tidy3d.components.viz import ( ARROW_ALPHA, ARROW_COLOR_POLARIZATION, ARROW_COLOR_SOURCE, PlotParams, plot_params_source, ) + from .time import SourceTimeType @@ -46,15 +47,15 @@ def geometry(self) -> Box: @cached_property def _injection_axis(self): """Injection axis of the source.""" - return None + return @cached_property - def _dir_vector(self) -> Tuple[float, float, float]: + def _dir_vector(self) -> tuple[float, float, float]: """Returns a vector indicating the source direction for arrow plotting, if not None.""" return None @cached_property - def _pol_vector(self) -> Tuple[float, float, float]: + def _pol_vector(self) -> tuple[float, float, float]: """Returns a vector indicating the source polarization for arrow plotting, if not None.""" return None @@ -69,9 +70,9 @@ def _freqs_lower_bound(cls, val): def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, **patch_kwargs, ) -> Ax: diff --git a/tidy3d/components/source/current.py b/tidy3d/components/source/current.py index 4035b1fbcc..18fe8d93b7 100644 --- a/tidy3d/components/source/current.py +++ b/tidy3d/components/source/current.py @@ -3,17 +3,18 @@ from __future__ import annotations from abc import ABC -from typing import Optional, Tuple +from typing import Optional import pydantic.v1 as pydantic from typing_extensions import Literal -from ...constants import MICROMETER -from ..base import cached_property -from ..data.dataset import FieldDataset -from ..data.validators import validate_can_interpolate, validate_no_nans -from ..types import Polarization -from ..validators import assert_single_freq_in_range, warn_if_dataset_none +from tidy3d.components.base import cached_property +from tidy3d.components.data.dataset import FieldDataset +from tidy3d.components.data.validators import validate_can_interpolate, validate_no_nans +from tidy3d.components.types import Polarization +from tidy3d.components.validators import assert_single_freq_in_range, warn_if_dataset_none +from tidy3d.constants import MICROMETER + from .base import Source @@ -27,7 +28,7 @@ class CurrentSource(Source, ABC): ) @cached_property - def _pol_vector(self) -> Tuple[float, float, float]: + def _pol_vector(self) -> tuple[float, float, float]: """Returns a vector indicating the source polarization for arrow plotting, if not None.""" component = self.polarization[-1] # 'x' 'y' or 'z' pol_axis = "xyz".index(component) @@ -99,7 +100,7 @@ class PointDipole(CurrentSource, ReverseInterpolatedSource): * `Adjoint optimization of quantum emitter light extraction to an integrated waveguide <../../notebooks/AdjointPlugin12LightExtractor.html>`_ """ - size: Tuple[Literal[0], Literal[0], Literal[0]] = pydantic.Field( + size: tuple[Literal[0], Literal[0], Literal[0]] = pydantic.Field( (0, 0, 0), title="Size", description="Size in x, y, and z directions, constrained to ``(0, 0, 0)``.", diff --git a/tidy3d/components/source/field.py b/tidy3d/components/source/field.py index 60e172d76d..f147b8ec7f 100644 --- a/tidy3d/components/source/field.py +++ b/tidy3d/components/source/field.py @@ -3,31 +3,26 @@ from __future__ import annotations from abc import ABC -from typing import Optional, Tuple, Union +from typing import Optional, Union import numpy as np import pydantic.v1 as pydantic -from ...constants import GLANCING_CUTOFF, MICROMETER, RADIAN, inf -from ...exceptions import SetupError -from ...log import log -from ..base import Tidy3dBaseModel, cached_property, skip_if_fields_missing -from ..data.dataset import FieldDataset -from ..data.validators import validate_can_interpolate, validate_no_nans -from ..mode_spec import ModeSpec -from ..types import ( - TYPE_TAG_STR, - Ax, - Axis, - Coordinate, - Direction, -) -from ..validators import ( +from tidy3d.components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing +from tidy3d.components.data.dataset import FieldDataset +from tidy3d.components.data.validators import validate_can_interpolate, validate_no_nans +from tidy3d.components.mode_spec import ModeSpec +from tidy3d.components.types import TYPE_TAG_STR, Ax, Axis, Coordinate, Direction +from tidy3d.components.validators import ( assert_plane, assert_single_freq_in_range, assert_volumetric, warn_if_dataset_none, ) +from tidy3d.constants import GLANCING_CUTOFF, MICROMETER, RADIAN, inf +from tidy3d.exceptions import SetupError +from tidy3d.log import log + from .base import Source # width of Chebyshev grid used for broadband sources (in units of pulse width) @@ -83,7 +78,7 @@ class DirectionalSource(FieldSource, ABC): ) @cached_property - def _dir_vector(self) -> Tuple[float, float, float]: + def _dir_vector(self) -> tuple[float, float, float]: """Returns a vector indicating the source direction for arrow plotting, if not None.""" if self._injection_axis is None: return None @@ -322,7 +317,7 @@ def glancing_incidence(cls, val): return val @cached_property - def _dir_vector(self) -> Tuple[float, float, float]: + def _dir_vector(self) -> tuple[float, float, float]: """Source direction normal vector in cartesian coordinates.""" # Propagation vector assuming propagation along z @@ -335,7 +330,7 @@ def _dir_vector(self) -> Tuple[float, float, float]: return self.unpop_axis(dz, (dx, dy), axis=self._injection_axis) @cached_property - def _pol_vector(self) -> Tuple[float, float, float]: + def _pol_vector(self) -> tuple[float, float, float]: """Source polarization normal vector in cartesian coordinates.""" # Polarization vector assuming propagation along z @@ -448,7 +443,7 @@ def angle_phi(self): return self.mode_spec.angle_phi @cached_property - def _dir_vector(self) -> Tuple[float, float, float]: + def _dir_vector(self) -> tuple[float, float, float]: """Source direction normal vector in cartesian coordinates.""" radius = 1.0 if self.direction == "+" else -1.0 dx = radius * np.cos(self.angle_phi) * np.sin(self.angle_theta) @@ -628,14 +623,14 @@ class AstigmaticGaussianBeam(AngledFieldSource, PlanarSource, BroadbandSource): ... waist_distances = (3.0, 4.0)) """ - waist_sizes: Tuple[pydantic.PositiveFloat, pydantic.PositiveFloat] = pydantic.Field( + waist_sizes: tuple[pydantic.PositiveFloat, pydantic.PositiveFloat] = pydantic.Field( (1.0, 1.0), title="Waist sizes", description="Size of the beam at the waist in the local x and y directions.", units=MICROMETER, ) - waist_distances: Tuple[float, float] = pydantic.Field( + waist_distances: tuple[float, float] = pydantic.Field( (0.0, 0.0), title="Waist distances", description="Distance to the beam waist along the propagation direction " @@ -705,9 +700,9 @@ def injection_plane_center(self) -> Coordinate: def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, **patch_kwargs, ) -> Ax: diff --git a/tidy3d/components/source/time.py b/tidy3d/components/source/time.py index d66ac9afec..3c0b6b56e9 100644 --- a/tidy3d/components/source/time.py +++ b/tidy3d/components/source/time.py @@ -8,21 +8,15 @@ import numpy as np import pydantic.v1 as pydantic -from ...constants import HERTZ -from ...exceptions import ValidationError -from ..data.data_array import TimeDataArray -from ..data.dataset import TimeDataset -from ..data.validators import validate_no_nans -from ..time import AbstractTimeDependence -from ..types import ( - ArrayComplex1D, - ArrayFloat1D, - Ax, - FreqBound, - PlotVal, -) -from ..validators import warn_if_dataset_none -from ..viz import add_ax_if_none +from tidy3d.components.data.data_array import TimeDataArray +from tidy3d.components.data.dataset import TimeDataset +from tidy3d.components.data.validators import validate_no_nans +from tidy3d.components.time import AbstractTimeDependence +from tidy3d.components.types import ArrayComplex1D, ArrayFloat1D, Ax, FreqBound, PlotVal +from tidy3d.components.validators import warn_if_dataset_none +from tidy3d.components.viz import add_ax_if_none +from tidy3d.constants import HERTZ +from tidy3d.exceptions import ValidationError # how many units of ``twidth`` from the ``offset`` until a gaussian pulse is considered "off" END_TIME_FACTOR_GAUSSIAN = 10 @@ -355,7 +349,7 @@ def from_values( """ times = np.arange(len(values)) * dt - source_time_dataarray = TimeDataArray(values, coords=dict(t=times)) + source_time_dataarray = TimeDataArray(values, coords={"t": times}) source_time_dataset = TimeDataset(values=source_time_dataarray) return CustomSourceTime( freq0=freq0, diff --git a/tidy3d/components/spice/analysis/dc.py b/tidy3d/components/spice/analysis/dc.py index 7f6c275602..e9e1e2aca5 100644 --- a/tidy3d/components/spice/analysis/dc.py +++ b/tidy3d/components/spice/analysis/dc.py @@ -2,11 +2,12 @@ This class defines standard SPICE electrical_analysis types (electrical simulations configurations). """ +from __future__ import annotations + import pydantic.v1 as pd from tidy3d.components.base import Tidy3dBaseModel - -from ....constants import KELVIN +from tidy3d.constants import KELVIN class ChargeToleranceSpec(Tidy3dBaseModel): diff --git a/tidy3d/components/spice/sources/dc.py b/tidy3d/components/spice/sources/dc.py index 109202f5ca..e527468795 100644 --- a/tidy3d/components/spice/sources/dc.py +++ b/tidy3d/components/spice/sources/dc.py @@ -19,6 +19,8 @@ """ +from __future__ import annotations + from typing import Literal, Optional import pydantic.v1 as pd diff --git a/tidy3d/components/spice/sources/types.py b/tidy3d/components/spice/sources/types.py index 653f48958f..352f8878c1 100644 --- a/tidy3d/components/spice/sources/types.py +++ b/tidy3d/components/spice/sources/types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Union from .dc import DCCurrentSource, DCVoltageSource diff --git a/tidy3d/components/spice/types.py b/tidy3d/components/spice/types.py index 42336397fa..486bd0e734 100644 --- a/tidy3d/components/spice/types.py +++ b/tidy3d/components/spice/types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Union from tidy3d.components.spice.analysis.dc import IsothermalSteadyChargeDCAnalysis diff --git a/tidy3d/components/structure.py b/tidy3d/components/structure.py index 8a3d3ee2c8..17f175a8bb 100644 --- a/tidy3d/components/structure.py +++ b/tidy3d/components/structure.py @@ -5,15 +5,16 @@ import pathlib from collections import defaultdict from functools import cmp_to_key -from typing import List, Optional, Tuple, Union +from typing import Optional, Union import autograd.numpy as anp import numpy as np import pydantic.v1 as pydantic -from ..constants import MICROMETER -from ..exceptions import SetupError, Tidy3dImportError -from ..log import log +from tidy3d.constants import MICROMETER +from tidy3d.exceptions import SetupError, Tidy3dImportError +from tidy3d.log import log + from .autograd.derivative_utils import DerivativeInfo from .autograd.types import AutogradFieldMap from .autograd.types import Box as AutogradBox @@ -125,8 +126,8 @@ def _priority(self, priority_mode: PriorityMode) -> int: @staticmethod def _sort_structures( - structures: List[StructureType], structure_priority_mode: PriorityMode - ) -> List[StructureType]: + structures: list[StructureType], structure_priority_mode: PriorityMode + ) -> list[StructureType]: """Sort structure lists based on their priority values in ascending order.""" def structure_comparator(struct1, struct2): @@ -143,7 +144,12 @@ def viz_spec(self): @equal_aspect @add_ax_if_none def plot( - self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **patch_kwargs + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, + **patch_kwargs, ) -> Ax: """Plot structure's geometric cross section at single (x,y,z) coordinate. @@ -233,7 +239,7 @@ def _priority(self, priority_mode: PriorityMode) -> int: def viz_spec(self): return self.medium.viz_spec - def eps_diagonal(self, frequency: float, coords: Coords) -> Tuple[complex, complex, complex]: + def eps_diagonal(self, frequency: float, coords: Coords) -> tuple[complex, complex, complex]: """Main diagonal of the complex-valued permittivity tensor as a function of frequency. Parameters @@ -285,10 +291,10 @@ def _compatible_with(self, other: Structure) -> bool: def _get_monitor_name(index: int, data_type: str) -> str: """Get the monitor name for either a field or permittivity monitor at given index.""" - monitor_name_map = dict( - fld=f"adjoint_fld_{index}", - eps=f"adjoint_eps_{index}", - ) + monitor_name_map = { + "fld": f"adjoint_fld_{index}", + "eps": f"adjoint_eps_{index}", + } if data_type not in monitor_name_map: raise KeyError(f"'data_type' must be in {monitor_name_map.keys()}") @@ -363,7 +369,7 @@ def _compute_derivatives(self, derivative_info: DerivativeInfo) -> AutogradField # construct map of {field path -> derivative value} for field_path, derivative_value in derivative_values_map.items(): - path = tuple([med_or_geo] + list(field_path)) + path = (med_or_geo, *list(field_path)) derivative_map[path] = derivative_value return derivative_map @@ -395,9 +401,9 @@ def eps_comp(self, row: Axis, col: Axis, frequency: float, coords: Coords) -> co def to_gdstk( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, gds_layer: pydantic.NonNegativeInt = 0, @@ -464,9 +470,9 @@ def to_gdstk( def to_gds( self, cell, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, gds_layer: pydantic.NonNegativeInt = 0, @@ -516,9 +522,9 @@ def to_gds( def to_gds_file( self, fname: str, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, gds_layer: pydantic.NonNegativeInt = 0, @@ -650,7 +656,7 @@ class MeshOverrideStructure(AbstractStructure): >>> struct_override = MeshOverrideStructure(geometry=box, dl=(0.1,0.2,0.3), name='override_box') """ - dl: Tuple[ + dl: tuple[ Optional[pydantic.PositiveFloat], Optional[pydantic.PositiveFloat], Optional[pydantic.PositiveFloat], diff --git a/tidy3d/components/tcad/analysis/heat_simulation_type.py b/tidy3d/components/tcad/analysis/heat_simulation_type.py index 0f281227de..154f10dce2 100644 --- a/tidy3d/components/tcad/analysis/heat_simulation_type.py +++ b/tidy3d/components/tcad/analysis/heat_simulation_type.py @@ -1,9 +1,11 @@ """Dealing with time specifications for DeviceSimulation""" +from __future__ import annotations + import pydantic.v1 as pd -from ....constants import KELVIN, SECOND -from ...base import Tidy3dBaseModel +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.constants import KELVIN, SECOND class UnsteadySpec(Tidy3dBaseModel): diff --git a/tidy3d/components/tcad/bandgap.py b/tidy3d/components/tcad/bandgap.py index 61b469252c..55c2cedafc 100644 --- a/tidy3d/components/tcad/bandgap.py +++ b/tidy3d/components/tcad/bandgap.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pydantic.v1 as pd from tidy3d.components.base import Tidy3dBaseModel diff --git a/tidy3d/components/tcad/data/monitor_data/abstract.py b/tidy3d/components/tcad/data/monitor_data/abstract.py index 6deab4014f..63027f261f 100644 --- a/tidy3d/components/tcad/data/monitor_data/abstract.py +++ b/tidy3d/components/tcad/data/monitor_data/abstract.py @@ -4,7 +4,7 @@ import copy from abc import ABC, abstractmethod -from typing import Tuple, Union +from typing import Union import numpy as np import pydantic.v1 as pd @@ -34,7 +34,7 @@ class HeatChargeMonitorData(AbstractMonitorData, ABC): description="Monitor associated with the data.", ) - symmetry: Tuple[ScalarSymmetry, ScalarSymmetry, ScalarSymmetry] = pd.Field( + symmetry: tuple[ScalarSymmetry, ScalarSymmetry, ScalarSymmetry] = pd.Field( (0, 0, 0), title="Symmetry", description="Symmetry of the original simulation in x, y, and z.", diff --git a/tidy3d/components/tcad/data/monitor_data/charge.py b/tidy3d/components/tcad/data/monitor_data/charge.py index d3a3f9738c..83898b3011 100644 --- a/tidy3d/components/tcad/data/monitor_data/charge.py +++ b/tidy3d/components/tcad/data/monitor_data/charge.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, Union +from typing import Union import numpy as np import pydantic.v1 as pd @@ -50,9 +50,9 @@ class SteadyPotentialData(HeatChargeMonitorData): ) @property - def field_components(self) -> Dict[str, DataArray]: + def field_components(self) -> dict[str, DataArray]: """Maps the field components to their associated data.""" - return dict(potential=self.potential) + return {"potential": self.potential} @pd.validator("potential", always=True) @skip_if_fields_missing(["monitor"]) @@ -118,9 +118,9 @@ class SteadyFreeCarrierData(HeatChargeMonitorData): # p = holes @property - def field_components(self) -> Dict[str, DataArray]: + def field_components(self) -> dict[str, DataArray]: """Maps the field components to their associated data.""" - return dict(electrons=self.electrons, holes=self.holes) + return {"electrons": self.electrons, "holes": self.holes} @pd.root_validator(skip_on_failure=True) def check_correct_data_type(cls, values): @@ -234,9 +234,9 @@ class SteadyEnergyBandData(HeatChargeMonitorData): ) @property - def field_components(self) -> Dict[str, DataArray]: + def field_components(self) -> dict[str, DataArray]: """Maps the field components to their associated data.""" - return dict(Ec=self.Ec, Ev=self.Ev, Ei=self.Ei, Efn=self.Efn, Efp=self.Efp) + return {"Ec": self.Ec, "Ev": self.Ev, "Ei": self.Ei, "Efn": self.Efn, "Efp": self.Efp} @pd.root_validator(skip_on_failure=True) def check_correct_data_type(cls, values): @@ -318,7 +318,7 @@ def plot(self, ax: Ax = None, **sel_kwargs) -> Ax: The supplied or created matplotlib axes. """ - selection_data = dict() + selection_data = {} if ("voltage" not in sel_kwargs) and (self.Ec.values.coords.sizes["voltage"] > 1): raise DataError( diff --git a/tidy3d/components/tcad/data/monitor_data/heat.py b/tidy3d/components/tcad/data/monitor_data/heat.py index 0f6868c070..13a7991936 100644 --- a/tidy3d/components/tcad/data/monitor_data/heat.py +++ b/tidy3d/components/tcad/data/monitor_data/heat.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, Optional, Union +from typing import Optional, Union import pydantic.v1 as pd @@ -58,9 +58,9 @@ class TemperatureData(HeatChargeMonitorData): ) @property - def field_components(self) -> Dict[str, DataArray]: + def field_components(self) -> dict[str, DataArray]: """Maps the field components to their associated data.""" - return dict(temperature=self.temperature) + return {"temperature": self.temperature} @pd.validator("temperature", always=True) @skip_if_fields_missing(["monitor"]) diff --git a/tidy3d/components/tcad/data/sim_data.py b/tidy3d/components/tcad/data/sim_data.py index 2d0c5cde13..c33a0d6e61 100644 --- a/tidy3d/components/tcad/data/sim_data.py +++ b/tidy3d/components/tcad/data/sim_data.py @@ -2,11 +2,12 @@ from __future__ import annotations -from typing import Optional, Tuple +from typing import Literal, Optional import numpy as np import pydantic.v1 as pd +from tidy3d.components.base import Tidy3dBaseModel from tidy3d.components.base_sim.data.sim_data import AbstractSimulationData from tidy3d.components.data.data_array import ( SpatialDataArray, @@ -24,13 +25,11 @@ ) from tidy3d.components.tcad.simulation.heat import HeatSimulation from tidy3d.components.tcad.simulation.heat_charge import HeatChargeSimulation -from tidy3d.components.types import Ax, Literal, RealFieldVal, annotate_type +from tidy3d.components.types import Ax, RealFieldVal, annotate_type from tidy3d.components.viz import add_ax_if_none, equal_aspect from tidy3d.exceptions import DataError from tidy3d.log import log -from ...base import Tidy3dBaseModel - class DeviceCharacteristics(Tidy3dBaseModel): """Stores device characteristics. For example, in steady-state it stores @@ -131,7 +130,7 @@ class HeatChargeSimulationData(AbstractSimulationData): description="Original :class:`.HeatChargeSimulation` associated with the data.", ) - data: Tuple[annotate_type(TCADMonitorDataType), ...] = pd.Field( + data: tuple[annotate_type(TCADMonitorDataType), ...] = pd.Field( ..., title="Monitor Data", description="List of :class:`.MonitorData` instances " @@ -155,8 +154,8 @@ def plot_field( scale: Literal["lin", "log"] = "lin", structures_alpha: float = 0.2, robust: bool = True, - vmin: float = None, - vmax: float = None, + vmin: Optional[float] = None, + vmax: Optional[float] = None, ax: Ax = None, **sel_kwargs, ) -> Ax: @@ -278,7 +277,7 @@ def plot_field( if field_data.coords[axis].size <= 1: field_data = field_data.sel(**{axis: pos}, method="nearest") else: - field_data = field_data.interp(**{axis: pos}, kwargs=dict(bounds_error=True)) + field_data = field_data.interp(**{axis: pos}, kwargs={"bounds_error": True}) # select the extra coordinates out of the data from user-specified kwargs for coord_name, coord_val in sel_kwargs.items(): @@ -286,7 +285,7 @@ def plot_field( field_data = field_data.sel(**{coord_name: coord_val}, method=None) else: field_data = field_data.interp( - **{coord_name: coord_val}, kwargs=dict(bounds_error=True) + **{coord_name: coord_val}, kwargs={"bounds_error": True} ) field_data = field_data.squeeze(drop=True) diff --git a/tidy3d/components/tcad/doping.py b/tidy3d/components/tcad/doping.py index 9570af2a9a..290bb5dc77 100644 --- a/tidy3d/components/tcad/doping.py +++ b/tidy3d/components/tcad/doping.py @@ -1,15 +1,16 @@ """File containing classes required for the setup of a DEVSIM case.""" +from __future__ import annotations + import numpy as np import pydantic.v1 as pd from tidy3d.components.base import cached_property from tidy3d.components.geometry.base import Box from tidy3d.components.types import Union +from tidy3d.constants import PERCMCUBE from tidy3d.exceptions import SetupError -from ...constants import PERCMCUBE - class AbstractDopingBox(Box): """Derived class from Box to deal with dopings""" @@ -33,7 +34,7 @@ def _get_indices_in_box(self, coords: dict, meshgrid: bool = True): dim_missing = len(list(coords.keys())) < 3 if dim_missing: for var_name in "xyz": - if var_name not in coords.keys(): + if var_name not in coords: coords[var_name] = [0] # work out whether the dimensions are 2D @@ -63,11 +64,11 @@ def _get_indices_in_box(self, coords: dict, meshgrid: bool = True): new_bounds[1][d] = np.inf # let's assume some of these coordinates may lay outside the box - indices_in_box = np.logical_and(X >= new_bounds[0][0], X <= new_bounds[1][0]) - indices_in_box = np.logical_and(indices_in_box, Y >= new_bounds[0][1]) - indices_in_box = np.logical_and(indices_in_box, Y <= new_bounds[1][1]) - indices_in_box = np.logical_and(indices_in_box, Z >= new_bounds[0][2]) - indices_in_box = np.logical_and(indices_in_box, Z <= new_bounds[1][2]) + indices_in_box = np.logical_and(new_bounds[0][0] <= X, new_bounds[1][0] >= X) + indices_in_box = np.logical_and(indices_in_box, new_bounds[0][1] <= Y) + indices_in_box = np.logical_and(indices_in_box, new_bounds[1][1] >= Y) + indices_in_box = np.logical_and(indices_in_box, new_bounds[0][2] <= Z) + indices_in_box = np.logical_and(indices_in_box, new_bounds[1][2] >= Z) return indices_in_box, X, Y, Z, normal_axis @@ -129,8 +130,7 @@ def _get_contrib(self, coords: dict, meshgrid: bool = True): slices = [slice(None)] * X.ndim slices[normal_axis] = 0 return contrib[tuple(slices)] - else: - return contrib + return contrib class GaussianDoping(AbstractDopingBox): @@ -230,7 +230,7 @@ def _get_contrib(self, coords: dict, meshgrid: bool = True): # lower x face if self.source != "xmin": x0 = self.bounds[0][0] - indices = np.logical_and(X >= x0, X <= x0 + self.width) + indices = np.logical_and(x0 <= X, x0 + self.width >= X) indices = np.logical_and(indices, indices_in_box) x_contrib[indices] = np.exp( -(X[indices] - x0 - self.width) @@ -242,7 +242,7 @@ def _get_contrib(self, coords: dict, meshgrid: bool = True): # higher x face if self.source != "xmax": x1 = self.bounds[1][0] - indices = np.logical_and(X >= x1 - self.width, X <= x1) + indices = np.logical_and(x1 - self.width <= X, x1 >= X) indices = np.logical_and(indices, indices_in_box) x_contrib[indices] = np.exp( -(X[indices] - x1 + self.width) @@ -259,7 +259,7 @@ def _get_contrib(self, coords: dict, meshgrid: bool = True): # lower y face if self.source != "ymin": y0 = self.bounds[0][1] - indices = np.logical_and(Y >= y0, Y <= y0 + self.width) + indices = np.logical_and(y0 <= Y, y0 + self.width >= Y) indices = np.logical_and(indices, indices_in_box) y_contrib[indices] = np.exp( -(Y[indices] - y0 - self.width) @@ -271,7 +271,7 @@ def _get_contrib(self, coords: dict, meshgrid: bool = True): # higher y face if self.source != "ymax": y1 = self.bounds[1][1] - indices = np.logical_and(Y >= y1 - self.width, Y <= y1) + indices = np.logical_and(y1 - self.width <= Y, y1 >= Y) indices = np.logical_and(indices, indices_in_box) y_contrib[indices] = np.exp( -(Y[indices] - y1 + self.width) @@ -288,7 +288,7 @@ def _get_contrib(self, coords: dict, meshgrid: bool = True): # lower z face if self.source != "zmin": z0 = self.bounds[0][2] - indices = np.logical_and(Z >= z0, Z <= z0 + self.width) + indices = np.logical_and(z0 <= Z, z0 + self.width >= Z) indices = np.logical_and(indices, indices_in_box) z_contrib[indices] = np.exp( -(Z[indices] - z0 - self.width) @@ -300,7 +300,7 @@ def _get_contrib(self, coords: dict, meshgrid: bool = True): # higher z face if self.source != "zmax": z1 = self.bounds[1][2] - indices = np.logical_and(Z >= z1 - self.width, Z <= z1) + indices = np.logical_and(z1 - self.width <= Z, z1 >= Z) indices = np.logical_and(indices, indices_in_box) z_contrib[indices] = np.exp( -(Z[indices] - z1 + self.width) @@ -316,8 +316,7 @@ def _get_contrib(self, coords: dict, meshgrid: bool = True): slices = [slice(None)] * X.ndim slices[normal_axis] = 0 return total_contrib[tuple(slices)] - else: - return total_contrib + return total_contrib DopingBoxType = Union[ConstantDoping, GaussianDoping] diff --git a/tidy3d/components/tcad/generation_recombination.py b/tidy3d/components/tcad/generation_recombination.py index b33f8412e9..faa10d03e4 100644 --- a/tidy3d/components/tcad/generation_recombination.py +++ b/tidy3d/components/tcad/generation_recombination.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pydantic.v1 as pd from tidy3d.components.base import Tidy3dBaseModel diff --git a/tidy3d/components/tcad/grid.py b/tidy3d/components/tcad/grid.py index 5720f3d667..fcf0cf6f27 100644 --- a/tidy3d/components/tcad/grid.py +++ b/tidy3d/components/tcad/grid.py @@ -3,18 +3,17 @@ from __future__ import annotations from abc import ABC -from typing import Tuple, Union +from typing import Union import numpy as np import pydantic.v1 as pd from tidy3d.components.base import Tidy3dBaseModel, skip_if_fields_missing +from tidy3d.components.geometry.base import Box +from tidy3d.components.types import Coordinate, annotate_type from tidy3d.constants import MICROMETER from tidy3d.exceptions import ValidationError -from ..geometry.base import Box -from ..types import Coordinate, annotate_type - class UnstructuredGrid(Tidy3dBaseModel, ABC): """Abstract unstructured grid.""" @@ -56,7 +55,7 @@ class UniformUnstructuredGrid(UnstructuredGrid): description="Enforced minimum number of mesh segments per any side of an object.", ) - non_refined_structures: Tuple[str, ...] = pd.Field( + non_refined_structures: tuple[str, ...] = pd.Field( (), title="Structures Without Refinement", description="List of structures for which ``min_edges_per_circumference`` and " @@ -202,21 +201,21 @@ class DistanceUnstructuredGrid(UnstructuredGrid): "surface when computing distance values.", ) - uniform_grid_mediums: Tuple[str, ...] = pd.Field( + uniform_grid_mediums: tuple[str, ...] = pd.Field( (), title="Mediums With Uniform Refinement", description="List of mediums for which ``dl_interface`` will be enforced everywhere " "in the volume.", ) - non_refined_structures: Tuple[str, ...] = pd.Field( + non_refined_structures: tuple[str, ...] = pd.Field( (), title="Structures Without Refinement", description="List of structures for which ``dl_interface`` will not be enforced. " "``dl_bulk`` is used instead.", ) - mesh_refinements: Tuple[annotate_type(Union[GridRefinementRegion, GridRefinementLine]), ...] = ( + mesh_refinements: tuple[annotate_type(Union[GridRefinementRegion, GridRefinementLine]), ...] = ( pd.Field( (), title="Mesh refinement structures", diff --git a/tidy3d/components/tcad/mobility.py b/tidy3d/components/tcad/mobility.py index 127d3cca47..0d3cc22042 100644 --- a/tidy3d/components/tcad/mobility.py +++ b/tidy3d/components/tcad/mobility.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pydantic.v1 as pd from tidy3d.components.base import Tidy3dBaseModel diff --git a/tidy3d/components/tcad/monitors/abstract.py b/tidy3d/components/tcad/monitors/abstract.py index 6628079429..8c3fa91bbe 100644 --- a/tidy3d/components/tcad/monitors/abstract.py +++ b/tidy3d/components/tcad/monitors/abstract.py @@ -1,5 +1,7 @@ """Objects that define how data is recorded from simulation.""" +from __future__ import annotations + from abc import ABC import pydantic.v1 as pd diff --git a/tidy3d/components/tcad/monitors/charge.py b/tidy3d/components/tcad/monitors/charge.py index e575e30a86..df479897fc 100644 --- a/tidy3d/components/tcad/monitors/charge.py +++ b/tidy3d/components/tcad/monitors/charge.py @@ -1,5 +1,7 @@ """Objects that define how data is recorded from simulation.""" +from __future__ import annotations + from typing import Literal import pydantic.v1 as pd diff --git a/tidy3d/components/tcad/monitors/heat.py b/tidy3d/components/tcad/monitors/heat.py index be6f3c65a0..3d8fff722b 100644 --- a/tidy3d/components/tcad/monitors/heat.py +++ b/tidy3d/components/tcad/monitors/heat.py @@ -1,6 +1,8 @@ """Objects that define how data is recorded from simulation.""" -import pydantic.v1 as pd +from __future__ import annotations + +from pydantic.v1 import Field, PositiveInt from tidy3d.components.tcad.monitors.abstract import HeatChargeMonitor @@ -8,7 +10,7 @@ class TemperatureMonitor(HeatChargeMonitor): """Temperature monitor.""" - interval: pd.PositiveInt = pd.Field( + interval: PositiveInt = Field( 1, title="Interval", description="Sampling rate of the monitor: number of time steps between each measurement. " diff --git a/tidy3d/components/tcad/simulation/heat.py b/tidy3d/components/tcad/simulation/heat.py index 6304ed8630..ace1d8a855 100644 --- a/tidy3d/components/tcad/simulation/heat.py +++ b/tidy3d/components/tcad/simulation/heat.py @@ -3,7 +3,7 @@ from __future__ import annotations -from typing import Tuple +from typing import Optional import pydantic.v1 as pd @@ -60,16 +60,16 @@ def issue_warning_deprecated(cls, values): @add_ax_if_none def plot_heat_conductivity( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - alpha: float = None, - source_alpha: float = None, - monitor_alpha: float = None, + alpha: Optional[float] = None, + source_alpha: Optional[float] = None, + monitor_alpha: Optional[float] = None, colorbar: str = "conductivity", - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ) -> Ax: """Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate. diff --git a/tidy3d/components/tcad/simulation/heat_charge.py b/tidy3d/components/tcad/simulation/heat_charge.py index 7fee8523b6..2e2f78ba8e 100644 --- a/tidy3d/components/tcad/simulation/heat_charge.py +++ b/tidy3d/components/tcad/simulation/heat_charge.py @@ -4,7 +4,7 @@ from __future__ import annotations from enum import Enum -from typing import Dict, List, Tuple, Union +from typing import Optional, Union import numpy as np import pydantic.v1 as pd @@ -37,6 +37,7 @@ from tidy3d.components.spice.sources.dc import DCVoltageSource from tidy3d.components.spice.types import ElectricalAnalysisType from tidy3d.components.structure import Structure +from tidy3d.components.tcad.analysis.heat_simulation_type import UnsteadyHeatAnalysis from tidy3d.components.tcad.boundary.specification import ( HeatBoundarySpec, HeatChargeBoundarySpec, @@ -85,8 +86,6 @@ from tidy3d.exceptions import SetupError from tidy3d.log import log -from ..analysis.heat_simulation_type import UnsteadyHeatAnalysis - HEAT_CHARGE_BACK_STRUCTURE_STR = "<<>>" HeatBCTypes = (TemperatureBC, HeatFluxBC, ConvectionBC) @@ -265,19 +264,19 @@ class HeatChargeSimulation(AbstractSimulation): Background medium of simulation, defaults to a standard dispersion-less :class:`Medium` if not specified. """ - sources: Tuple[annotate_type(HeatChargeSourceType), ...] = pd.Field( + sources: tuple[annotate_type(HeatChargeSourceType), ...] = pd.Field( (), title="Heat and Charge sources", description="List of heat and/or charge sources.", ) - monitors: Tuple[annotate_type(HeatChargeMonitorType), ...] = pd.Field( + monitors: tuple[annotate_type(HeatChargeMonitorType), ...] = pd.Field( (), title="Monitors", description="Monitors in the simulation.", ) - boundary_spec: Tuple[annotate_type(Union[HeatChargeBoundarySpec, HeatBoundarySpec]), ...] = ( + boundary_spec: tuple[annotate_type(Union[HeatChargeBoundarySpec, HeatBoundarySpec]), ...] = ( pd.Field( (), title="Boundary Condition Specifications", @@ -292,7 +291,7 @@ class HeatChargeSimulation(AbstractSimulation): discriminator=TYPE_TAG_STR, ) - symmetry: Tuple[ScalarSymmetry, ScalarSymmetry, ScalarSymmetry] = pd.Field( + symmetry: tuple[ScalarSymmetry, ScalarSymmetry, ScalarSymmetry] = pd.Field( (0, 0, 0), title="Symmetries", description="Tuple of integers defining reflection symmetry across a plane " @@ -320,7 +319,7 @@ def check_unsupported_geometries(cls, val): return val @staticmethod - def _check_cross_solids(objs: Tuple[Box, ...], values: Dict) -> Tuple[int, ...]: + def _check_cross_solids(objs: tuple[Box, ...], values: dict) -> tuple[int, ...]: """Given model dictionary ``values``, check whether objects in list ``objs`` cross a ``SolidSpec`` medium. """ @@ -340,7 +339,7 @@ def _check_cross_solids(objs: Tuple[Box, ...], values: Dict) -> Tuple[int, ...]: "'size', 'center', 'medium', and 'structures'. Thus, it should only be used in " "validators with @skip_if_fields_missing(['medium', 'center', 'size', 'structures']) " "or root validators with option 'skip_on_failure=True'." - ) + ) from None # list of structures including background as a Box() structure_bg = Structure( @@ -351,7 +350,7 @@ def _check_cross_solids(objs: Tuple[Box, ...], values: Dict) -> Tuple[int, ...]: medium=medium, ) - total_structures = [structure_bg] + list(structures) + total_structures = [structure_bg, *list(structures)] obj_do_not_cross_solid_idx = [] obj_do_not_cross_cond_idx = [] @@ -719,7 +718,7 @@ def _check_if_semiconductor_present(structures) -> bool: @staticmethod def _check_simulation_types( - values: Dict, + values: dict, HeatBCTypes=HeatBCTypes, ElectricBCTypes=ElectricBCTypes, HeatSourceTypes=HeatSourceTypes, @@ -898,16 +897,16 @@ def check_transient_heat(cls, values): @add_ax_if_none def plot_property( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - alpha: float = None, - source_alpha: float = None, - monitor_alpha: float = None, + alpha: Optional[float] = None, + source_alpha: Optional[float] = None, + monitor_alpha: Optional[float] = None, property: str = "heat_conductivity", - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ) -> Ax: """Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate. @@ -997,16 +996,16 @@ def plot_property( @add_ax_if_none def plot_heat_conductivity( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - alpha: float = None, - source_alpha: float = None, - monitor_alpha: float = None, + alpha: Optional[float] = None, + source_alpha: Optional[float] = None, + monitor_alpha: Optional[float] = None, colorbar: str = "conductivity", - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, **kwargs, ) -> Ax: """ @@ -1072,9 +1071,9 @@ def plot_heat_conductivity( @add_ax_if_none def plot_boundaries( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, property: str = "heat_conductivity", ax: Ax = None, ) -> Ax: @@ -1167,9 +1166,9 @@ def _plot_boundary_condition( @staticmethod def _structure_to_bc_spec_map( plane: Box, - structures: Tuple[Structure, ...], - boundary_spec: Tuple[HeatChargeBoundarySpec, ...], - ) -> Dict[str, HeatChargeBoundarySpec]: + structures: tuple[Structure, ...], + boundary_spec: tuple[HeatChargeBoundarySpec, ...], + ) -> dict[str, HeatChargeBoundarySpec]: """Construct structure name to bc spec inverse mapping. One structure may correspond to multiple boundary conditions.""" @@ -1203,9 +1202,9 @@ def _structure_to_bc_spec_map( @staticmethod def _medium_to_bc_spec_map( plane: Box, - structures: Tuple[Structure, ...], - boundary_spec: Tuple[HeatChargeBoundarySpec, ...], - ) -> Dict[str, HeatChargeBoundarySpec]: + structures: tuple[Structure, ...], + boundary_spec: tuple[HeatChargeBoundarySpec, ...], + ) -> dict[str, HeatChargeBoundarySpec]: """Construct medium name to bc spec inverse mapping. One medium may correspond to multiple boundary conditions.""" @@ -1228,11 +1227,11 @@ def _medium_to_bc_spec_map( @staticmethod def _construct_forward_boundaries( - shapes: Tuple[Tuple[str, str, Shapely, Tuple[float, float, float, float]], ...], - struct_to_bc_spec: Dict[str, HeatChargeBoundarySpec], - med_to_bc_spec: Dict[str, HeatChargeBoundarySpec], + shapes: tuple[tuple[str, str, Shapely, tuple[float, float, float, float]], ...], + struct_to_bc_spec: dict[str, HeatChargeBoundarySpec], + med_to_bc_spec: dict[str, HeatChargeBoundarySpec], background_structure_shape: Shapely, - ) -> Tuple[Tuple[HeatChargeBoundarySpec, Shapely], ...]: + ) -> tuple[tuple[HeatChargeBoundarySpec, Shapely], ...]: """Construct Simulation, StructureSimulation, Structure, and MediumMedium boundaries.""" # forward foop to take care of Simulation, StructureSimulation, Structure, @@ -1320,10 +1319,10 @@ def _construct_forward_boundaries( @staticmethod def _construct_reverse_boundaries( - shapes: Tuple[Tuple[str, str, Shapely, Bound], ...], - struct_to_bc_spec: Dict[str, HeatChargeBoundarySpec], + shapes: tuple[tuple[str, str, Shapely, Bound], ...], + struct_to_bc_spec: dict[str, HeatChargeBoundarySpec], background_structure_shape: Shapely, - ) -> Tuple[Tuple[HeatChargeBoundarySpec, Shapely], ...]: + ) -> tuple[tuple[HeatChargeBoundarySpec, Shapely], ...]: """Construct StructureStructure boundaries.""" # backward foop to take care of StructureStructure @@ -1388,10 +1387,10 @@ def _construct_reverse_boundaries( @staticmethod def _construct_heat_charge_boundaries( - structures: List[Structure], + structures: list[Structure], plane: Box, - boundary_spec: List[HeatChargeBoundarySpec], - ) -> List[Tuple[HeatChargeBoundarySpec, Shapely]]: + boundary_spec: list[HeatChargeBoundarySpec], + ) -> list[tuple[HeatChargeBoundarySpec, Shapely]]: """Compute list of boundary lines to plot on plane. Parameters @@ -1455,13 +1454,13 @@ def _construct_heat_charge_boundaries( @add_ax_if_none def plot_sources( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, property: str = "heat_conductivity", - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, - alpha: float = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, + alpha: Optional[float] = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's sources on a plane defined by one nonzero x,y,z coordinate. @@ -1558,7 +1557,7 @@ def _add_source_cbar(self, ax: Ax, property: str = "heat_conductivity"): ax=ax, ) - def source_bounds(self, property: str = "heat_conductivity") -> Tuple[float, float]: + def source_bounds(self, property: str = "heat_conductivity") -> tuple[float, float]: """Compute range of heat sources present in the simulation.""" if property == "heat_conductivity" or property == "source": @@ -1580,7 +1579,7 @@ def _get_structure_source_plot_params( source: HeatChargeSourceType, source_min: float, source_max: float, - alpha: float = None, + alpha: Optional[float] = None, ) -> PlotParams: """Constructs the plot parameters for a given medium in simulation.plot_eps().""" @@ -1607,7 +1606,7 @@ def _plot_shape_structure_source( source_min: float, source_max: float, ax: Ax, - alpha: float = None, + alpha: Optional[float] = None, ) -> Ax: """Plot a structure's cross section shape for a given medium, grayscale for permittivity.""" plot_params = self._get_structure_source_plot_params( @@ -1691,7 +1690,7 @@ def _get_simulation_types(self) -> list[TCADAnalysisTypes]: if heat_source_present and not heat_BCs_present: raise SetupError("Heat sources defined but no heat BCs present.") - elif heat_BCs_present or heat_source_present: + if heat_BCs_present or heat_source_present: simulation_types.append(TCADAnalysisTypes.HEAT) # check for conduction simulation @@ -1710,7 +1709,7 @@ def _get_simulation_types(self) -> list[TCADAnalysisTypes]: "'.medium.charge=None' are treated as insulators, thus, " "the solution domain is empty." ) - elif electric_BCs_present and electric_spec_present: + if electric_BCs_present and electric_spec_present: simulation_types.append(TCADAnalysisTypes.CONDUCTION) return simulation_types diff --git a/tidy3d/components/tcad/source/abstract.py b/tidy3d/components/tcad/source/abstract.py index 9e1b7ffa71..d5891207e8 100644 --- a/tidy3d/components/tcad/source/abstract.py +++ b/tidy3d/components/tcad/source/abstract.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC -from typing import Tuple import pydantic.v1 as pd @@ -28,7 +27,7 @@ class StructureBasedHeatChargeSource(AbstractHeatChargeSource): """Abstract class associated with structures. Sources associated to structures must derive from this class""" - structures: Tuple[str, ...] = pd.Field( + structures: tuple[str, ...] = pd.Field( title="Target Structures", description="Names of structures where to apply heat source.", ) diff --git a/tidy3d/components/tcad/types.py b/tidy3d/components/tcad/types.py index c3520d8a86..002bce16bf 100644 --- a/tidy3d/components/tcad/types.py +++ b/tidy3d/components/tcad/types.py @@ -1,5 +1,7 @@ """File containing classes required for the setup of a DEVSIM case.""" +from __future__ import annotations + from tidy3d.components.tcad.bandgap import SlotboomBandGapNarrowing from tidy3d.components.tcad.boundary.charge import CurrentBC, InsulatingBC, VoltageBC from tidy3d.components.tcad.boundary.heat import ConvectionBC, HeatFluxBC, TemperatureBC diff --git a/tidy3d/components/tcad/viz.py b/tidy3d/components/tcad/viz.py index 4de6fd825b..e305e0c317 100644 --- a/tidy3d/components/tcad/viz.py +++ b/tidy3d/components/tcad/viz.py @@ -1,5 +1,7 @@ """utilities for heat solver plotting""" +from __future__ import annotations + from tidy3d.components.viz import PlotParams """ Constants """ diff --git a/tidy3d/components/time.py b/tidy3d/components/time.py index c26ebf88ba..93bd91bd91 100644 --- a/tidy3d/components/time.py +++ b/tidy3d/components/time.py @@ -7,8 +7,9 @@ import numpy as np import pydantic.v1 as pydantic -from ..constants import RADIAN -from ..exceptions import SetupError +from tidy3d.constants import RADIAN +from tidy3d.exceptions import SetupError + from .base import Tidy3dBaseModel from .types import ArrayFloat1D, Ax, PlotVal from .viz import add_ax_if_none diff --git a/tidy3d/components/time_modulation.py b/tidy3d/components/time_modulation.py index 93b55cee6b..a4d82d7384 100644 --- a/tidy3d/components/time_modulation.py +++ b/tidy3d/components/time_modulation.py @@ -9,8 +9,9 @@ import numpy as np import pydantic.v1 as pd -from ..constants import HERTZ, RADIAN -from ..exceptions import ValidationError +from tidy3d.constants import HERTZ, RADIAN +from tidy3d.exceptions import ValidationError + from .base import Tidy3dBaseModel, cached_property, skip_if_fields_missing from .data.data_array import SpatialDataArray from .data.validators import validate_no_nans diff --git a/tidy3d/components/transformation.py b/tidy3d/components/transformation.py index ea7c1ee494..4e2643a9ae 100644 --- a/tidy3d/components/transformation.py +++ b/tidy3d/components/transformation.py @@ -8,8 +8,9 @@ import numpy as np import pydantic.v1 as pd -from ..constants import RADIAN -from ..exceptions import ValidationError +from tidy3d.constants import RADIAN +from tidy3d.exceptions import ValidationError + from .autograd import TracedFloat from .base import Tidy3dBaseModel, cached_property from .types import ArrayFloat2D, Axis, Coordinate, TensorReal diff --git a/tidy3d/components/type_util.py b/tidy3d/components/type_util.py index 5bbb64f975..983f623829 100644 --- a/tidy3d/components/type_util.py +++ b/tidy3d/components/type_util.py @@ -1,5 +1,7 @@ """Utilities for type & schema creation.""" +from __future__ import annotations + def _add_schema(arbitrary_type: type, title: str, field_type_str: str) -> None: """Adds a schema to the ``arbitrary_type`` class without subclassing.""" @@ -7,6 +9,6 @@ def _add_schema(arbitrary_type: type, title: str, field_type_str: str) -> None: @classmethod def mod_schema_fn(cls, field_schema: dict) -> None: """Function that gets set to ``arbitrary_type.__modify_schema__``.""" - field_schema.update(dict(title=title, type=field_type_str)) + field_schema.update({"title": title, "type": field_type_str}) arbitrary_type.__modify_schema__ = mod_schema_fn diff --git a/tidy3d/components/types.py b/tidy3d/components/types.py index c83a1baeb2..0489d56797 100644 --- a/tidy3d/components/types.py +++ b/tidy3d/components/types.py @@ -1,11 +1,8 @@ """Defines 'types' that various fields can be""" -from typing import ( - Literal, # We support py3.9+, so direct typing import is fine. - Optional, - Tuple, - Union, -) +from __future__ import annotations + +from typing import Literal, Optional, Union import autograd.numpy as np import pydantic.v1 as pydantic @@ -14,10 +11,11 @@ from matplotlib.axes import Axes except ImportError: Axes = None +from typing import Annotated + from shapely.geometry.base import BaseGeometry -from typing_extensions import Annotated -from ..exceptions import ValidationError +from tidy3d.exceptions import ValidationError # type tag default name TYPE_TAG_STR = "type" @@ -99,14 +97,16 @@ def assert_non_null(cls, val): def __modify_schema__(cls, field_schema): """Sets the schema of DataArray object.""" - schema = dict( - type="ArrayLike", - ) + schema = { + "type": "ArrayLike", + } field_schema.update(schema) def constrained_array( - dtype: type = None, ndim: int = None, shape: Tuple[pydantic.NonNegativeInt, ...] = None + dtype: Optional[type] = None, + ndim: Optional[int] = None, + shape: Optional[tuple[pydantic.NonNegativeInt, ...]] = None, ) -> type: """Generate an ArrayLike sub-type with constraints built in.""" @@ -122,7 +122,7 @@ def constrained_array( meta_args.append(f"shape={shape}") type_name += "[" + ", ".join(meta_args) + "]" - return type(type_name, (ArrayLike,), dict(dtype=dtype, ndim=ndim, shape=shape)) + return type(type_name, (ArrayLike,), {"dtype": dtype, "ndim": ndim, "shape": shape}) # pre-define a set of commonly used array like instances for import and use in type hints @@ -187,12 +187,12 @@ def __modify_schema__(cls, field_schema): """ geometric """ Size1D = pydantic.NonNegativeFloat -Size = Tuple[Size1D, Size1D, Size1D] -Coordinate = Tuple[float, float, float] -CoordinateOptional = Tuple[Optional[float], Optional[float], Optional[float]] -Coordinate2D = Tuple[float, float] -Bound = Tuple[Coordinate, Coordinate] -GridSize = Union[pydantic.PositiveFloat, Tuple[pydantic.PositiveFloat, ...]] +Size = tuple[Size1D, Size1D, Size1D] +Coordinate = tuple[float, float, float] +CoordinateOptional = tuple[Optional[float], Optional[float], Optional[float]] +Coordinate2D = tuple[float, float] +Bound = tuple[Coordinate, Coordinate] +GridSize = Union[pydantic.PositiveFloat, tuple[pydantic.PositiveFloat, ...]] Axis = Literal[0, 1, 2] Axis2D = Literal[0, 1] Shapely = BaseGeometry @@ -209,12 +209,12 @@ def __modify_schema__(cls, field_schema): # Complex = Union[complex, ComplexNumber] Complex = Union[tidycomplex, ComplexNumber] -PoleAndResidue = Tuple[Complex, Complex] +PoleAndResidue = tuple[Complex, Complex] # PoleAndResidue = Tuple[Tuple[float, float], Tuple[float, float]] FreqBoundMax = float FreqBoundMin = float -FreqBound = Tuple[FreqBoundMin, FreqBoundMax] +FreqBound = tuple[FreqBoundMin, FreqBoundMax] PermittivityComponent = Literal["xx", "xy", "xz", "yx", "yy", "yz", "zx", "zy", "zz"] @@ -227,8 +227,8 @@ def __modify_schema__(cls, field_schema): EMField = Literal["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"] FieldType = Literal["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"] -FreqArray = Union[Tuple[float, ...], ArrayFloat1D] -ObsGridArray = Union[Tuple[float, ...], ArrayFloat1D] +FreqArray = Union[tuple[float, ...], ArrayFloat1D] +ObsGridArray = Union[tuple[float, ...], ArrayFloat1D] PolarizationBasis = Literal["linear", "circular"] AuxField = Literal["Nfx", "Nfy", "Nfz"] @@ -258,3 +258,6 @@ def __modify_schema__(cls, field_schema): xyz = Literal["x", "y", "z"] UnitsZBF = Literal["mm", "cm", "in", "m"] + +""" sentinel """ +Undefined = object() diff --git a/tidy3d/components/types_extra.py b/tidy3d/components/types_extra.py index 92a6342ade..4fe305ce68 100644 --- a/tidy3d/components/types_extra.py +++ b/tidy3d/components/types_extra.py @@ -1,6 +1,8 @@ +from __future__ import annotations + from typing import Any -from ..packaging import check_import +from tidy3d.packaging import check_import # TODO Complicated as trimesh should be a core package unless decoupled implementation types in functional location. # We need to restructure. diff --git a/tidy3d/components/validators.py b/tidy3d/components/validators.py index 339134289c..4bb4a8b365 100644 --- a/tidy3d/components/validators.py +++ b/tidy3d/components/validators.py @@ -1,17 +1,21 @@ """Defines various validation functions that get used to ensure inputs are legit""" +from __future__ import annotations + +from typing import Optional + import numpy as np import pydantic.v1 as pydantic from autograd.tracer import isbox -from ..exceptions import SetupError, ValidationError -from ..log import log +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.log import log + from .autograd.utils import get_static from .base import DATA_ARRAY_MAP, skip_if_fields_missing from .data.dataset import Dataset, FieldDataset from .geometry.base import Box from .mode_spec import ModeSpec -from .types import Tuple """ Explanation of pydantic validators: @@ -333,9 +337,9 @@ def _single_frequency_in_range(cls, val: FieldDataset, values: dict) -> FieldDat def _warn_potential_error( field_name: str, base_value: float, - val_change_range: Tuple[float, float], - allowed_real_range: Tuple[float, float], - allowed_imag_range: Tuple[float, float], + val_change_range: tuple[float, float], + allowed_real_range: tuple[float, float], + allowed_imag_range: tuple[float, float], ): """Basic validation that perturbations do not drive a parameter out of physical bounds.""" @@ -368,8 +372,8 @@ def _warn_potential_error( def validate_parameter_perturbation( field_name: str, base_field_name: str, - allowed_real_range: Tuple[Tuple[float, float], ...], - allowed_imag_range: Tuple[Tuple[float, float], ...] = None, + allowed_real_range: tuple[tuple[float, float], ...], + allowed_imag_range: Optional[tuple[tuple[float, float], ...]] = None, allowed_complex: bool = True, ): """Assert perturbations do not drive a parameter out of physical bounds.""" diff --git a/tidy3d/components/viz/__init__.py b/tidy3d/components/viz/__init__.py index ca1911f9dd..1f3b5fb2ca 100644 --- a/tidy3d/components/viz/__init__.py +++ b/tidy3d/components/viz/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .axes_utils import add_ax_if_none, equal_aspect, make_ax, set_default_labels_and_title from .descartes import Polygon, polygon_patch, polygon_path from .flex_style import apply_tidy3d_params, restore_matplotlib_rcparams @@ -39,32 +41,27 @@ apply_tidy3d_params() __all__ = [ - "arrow_style", "ARROW_ALPHA", - "ARROW_LENGTH", - "PLOT_BUFFER", - "VisualizationSpec", - "MATPLOTLIB_IMPORTED", - "plot_sim_3d", - "FLEXCOMPUTE_COLORS", - "ARROW_COLOR_SOURCE", "ARROW_COLOR_MONITOR", "ARROW_COLOR_POLARIZATION", + "ARROW_COLOR_SOURCE", + "ARROW_LENGTH", + "FLEXCOMPUTE_COLORS", + "MATPLOTLIB_IMPORTED", "MEDIUM_CMAP", + "PLOT_BUFFER", "STRUCTURE_EPS_CMAP", "STRUCTURE_EPS_CMAP_R", "STRUCTURE_HEAT_COND_CMAP", - "restore_matplotlib_rcparams", - "make_ax", - "add_ax_if_none", - "equal_aspect", - "set_default_labels_and_title", - "polygon_patch", - "polygon_path", - "Polygon", "AbstractPlotParams", "PathPlotParams", "PlotParams", + "Polygon", + "VisualizationSpec", + "add_ax_if_none", + "arrow_style", + "equal_aspect", + "make_ax", "plot_params_bloch", "plot_params_fluid", "plot_params_geometry", @@ -78,4 +75,9 @@ "plot_params_source", "plot_params_structure", "plot_params_symmetry", + "plot_sim_3d", + "polygon_patch", + "polygon_path", + "restore_matplotlib_rcparams", + "set_default_labels_and_title", ] diff --git a/tidy3d/components/viz/axes_utils.py b/tidy3d/components/viz/axes_utils.py index 0209ce9daa..6f96e17f66 100644 --- a/tidy3d/components/viz/axes_utils.py +++ b/tidy3d/components/viz/axes_utils.py @@ -6,9 +6,9 @@ import matplotlib.pyplot as plt import matplotlib.ticker as ticker -from ...constants import UnitScaling -from ...exceptions import Tidy3dKeyError -from ..types import Ax, Axis, LengthUnit +from tidy3d.components.types import Ax, Axis, LengthUnit +from tidy3d.constants import UnitScaling +from tidy3d.exceptions import Tidy3dKeyError def make_ax() -> Ax: diff --git a/tidy3d/components/viz/flex_color_palettes.py b/tidy3d/components/viz/flex_color_palettes.py index 1cc1682953..7fc1454a0b 100644 --- a/tidy3d/components/viz/flex_color_palettes.py +++ b/tidy3d/components/viz/flex_color_palettes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + SEQUENTIAL_PALETTES_HEX = { "flex_turquoise_seq": [ "#ffffff", diff --git a/tidy3d/components/viz/flex_style.py b/tidy3d/components/viz/flex_style.py index 8971234758..f2ced3a407 100644 --- a/tidy3d/components/viz/flex_style.py +++ b/tidy3d/components/viz/flex_style.py @@ -1,4 +1,6 @@ -from ...log import log +from __future__ import annotations + +from tidy3d.log import log _ORIGINAL_PARAMS = None diff --git a/tidy3d/components/viz/plot_params.py b/tidy3d/components/viz/plot_params.py index beb655495e..76b22b0ef6 100644 --- a/tidy3d/components/viz/plot_params.py +++ b/tidy3d/components/viz/plot_params.py @@ -5,7 +5,7 @@ import pydantic.v1 as pd from numpy import inf -from ..base import Tidy3dBaseModel +from tidy3d.components.base import Tidy3dBaseModel class AbstractPlotParams(Tidy3dBaseModel): diff --git a/tidy3d/components/viz/plot_sim_3d.py b/tidy3d/components/viz/plot_sim_3d.py index 846d0c96d7..46cf50c256 100644 --- a/tidy3d/components/viz/plot_sim_3d.py +++ b/tidy3d/components/viz/plot_sim_3d.py @@ -2,7 +2,7 @@ from html import escape -from ...exceptions import SetupError +from tidy3d.exceptions import SetupError def plot_sim_3d(sim, width=800, height=800) -> None: diff --git a/tidy3d/components/viz/styles.py b/tidy3d/components/viz/styles.py index 90b5557055..5fd806e65f 100644 --- a/tidy3d/components/viz/styles.py +++ b/tidy3d/components/viz/styles.py @@ -1,3 +1,5 @@ +from __future__ import annotations + try: from matplotlib.patches import ArrowStyle diff --git a/tidy3d/components/viz/visualization_spec.py b/tidy3d/components/viz/visualization_spec.py index ffd93b346d..abe22ed7cf 100644 --- a/tidy3d/components/viz/visualization_spec.py +++ b/tidy3d/components/viz/visualization_spec.py @@ -1,11 +1,11 @@ from __future__ import annotations -from typing import Any, Dict, Optional +from typing import Any, Optional import pydantic.v1 as pd -from ...log import log -from ..base import Tidy3dBaseModel +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.log import log MATPLOTLIB_IMPORTED = True try: @@ -55,7 +55,7 @@ def validate_color(value: str) -> str: return is_valid_color(value) @pd.validator("edgecolor", always=True) - def validate_and_copy_color(value: str, values: Dict[str, Any]) -> str: + def validate_and_copy_color(value: str, values: dict[str, Any]) -> str: if (value == "") and "facecolor" in values: return is_valid_color(values["facecolor"]) diff --git a/tidy3d/config.py b/tidy3d/config.py index 3598bc27a7..af61bb5f01 100644 --- a/tidy3d/config.py +++ b/tidy3d/config.py @@ -1,5 +1,7 @@ """Sets the configuration of the script, can be changed with `td.config.config_name = new_val`.""" +from __future__ import annotations + import pydantic.v1 as pd from .log import DEFAULT_LEVEL, LogLevel, set_log_suppression, set_logging_level diff --git a/tidy3d/constants.py b/tidy3d/constants.py index 5514e71090..7edcf22278 100644 --- a/tidy3d/constants.py +++ b/tidy3d/constants.py @@ -10,6 +10,8 @@ Q_e (float): funamental charge [C] """ +from __future__ import annotations + from types import MappingProxyType import numpy as np diff --git a/tidy3d/exceptions.py b/tidy3d/exceptions.py index 32ccc88e49..96b137d749 100644 --- a/tidy3d/exceptions.py +++ b/tidy3d/exceptions.py @@ -1,12 +1,16 @@ """Custom Tidy3D exceptions""" +from __future__ import annotations + +from typing import Optional + from .log import log class Tidy3dError(ValueError): """Any error in tidy3d""" - def __init__(self, message: str = None): + def __init__(self, message: Optional[str] = None): """Log just the error message and then raise the Exception.""" super().__init__(message) log.error(message) diff --git a/tidy3d/log.py b/tidy3d/log.py index 9bbb0e04be..fb9dcec17a 100644 --- a/tidy3d/log.py +++ b/tidy3d/log.py @@ -1,8 +1,10 @@ """Logging for Tidy3d.""" +from __future__ import annotations + import inspect from datetime import datetime -from typing import Callable, List, Tuple, Union +from typing import Callable, Optional, Union from rich.console import Console from rich.text import Text @@ -42,7 +44,7 @@ CONSOLE_WIDTH = 80 -def _default_log_level_format(level: str, message: str) -> Tuple[str, str]: +def _default_log_level_format(level: str, message: str) -> tuple[str, str]: """By default just return unformatted prefix and message.""" return level, message @@ -214,7 +216,7 @@ def _parse_warning_capture(self, current_loc, stack_item): new_loc = current_loc + list(field) else: # single field - new_loc = current_loc + [field] + new_loc = [*current_loc, field] # process current level warnings for level, msg, custom_loc in stack_item["messages"]: @@ -242,7 +244,7 @@ def _log( message: str, *args, log_once: bool = False, - custom_loc: List = None, + custom_loc: Optional[list] = None, capture: bool = True, ) -> None: """Distribute log messages to all handlers""" @@ -311,7 +313,7 @@ def warning( message: str, *args, log_once: bool = False, - custom_loc: List = None, + custom_loc: Optional[list] = None, capture: bool = True, ) -> None: """Log (message) % (args) at warning level""" diff --git a/tidy3d/material_library/material_library.py b/tidy3d/material_library/material_library.py index 18550e04cf..ef962d191d 100644 --- a/tidy3d/material_library/material_library.py +++ b/tidy3d/material_library/material_library.py @@ -1,12 +1,16 @@ """Holds dispersive models for several commonly used optical materials.""" +from __future__ import annotations + import json -from typing import Dict, List, Union +from typing import Union import pydantic.v1 as pd +from tidy3d.components.base import Tidy3dBaseModel from tidy3d.components.material.multi_physics import MultiPhysicsMedium from tidy3d.components.material.tcad.charge import SemiconductorMedium +from tidy3d.components.medium import AnisotropicMedium, Medium2D, PoleResidue, Sellmeier from tidy3d.components.tcad.types import ( AugerRecombination, CaugheyThomasMobility, @@ -14,12 +18,10 @@ ShockleyReedHallRecombination, SlotboomBandGapNarrowing, ) +from tidy3d.components.types import Axis +from tidy3d.exceptions import SetupError +from tidy3d.log import log -from ..components.base import Tidy3dBaseModel -from ..components.medium import AnisotropicMedium, Medium2D, PoleResidue, Sellmeier -from ..components.types import Axis -from ..exceptions import SetupError -from ..log import log from .material_reference import ReferenceData, material_refs from .parametric_materials import Graphene from .util import ( @@ -66,7 +68,7 @@ def export_matlib_to_file(fname: str = "matlib.json") -> None: class AbstractVariantItem(Tidy3dBaseModel): """Reference, and data_source for a variant of a material.""" - reference: List[ReferenceData] = pd.Field( + reference: list[ReferenceData] = pd.Field( None, title="Reference information", description="A list of references related to this variant model.", @@ -80,7 +82,7 @@ class AbstractVariantItem(Tidy3dBaseModel): ) @property - def summarize_mediums(self) -> Dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: + def summarize_mediums(self) -> dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: return {} def __str__(self): @@ -103,7 +105,7 @@ class VariantItem(AbstractVariantItem): ) @property - def summarize_mediums(self) -> Dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: + def summarize_mediums(self) -> dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: return {"medium": self.medium} @@ -111,7 +113,7 @@ class MaterialItem(Tidy3dBaseModel): """A material that includes several variants.""" name: str = pd.Field(..., title="Name", description="Unique name for the medium.") - variants: Dict[str, VariantItem] = pd.Field( + variants: dict[str, VariantItem] = pd.Field( ..., title="Dictionary of available variants for this material", description="A dictionary of available variants for this material " @@ -167,14 +169,14 @@ class VariantItem2D(AbstractVariantItem): ) @property - def summarize_mediums(self) -> Dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: + def summarize_mediums(self) -> dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: return {"medium": self.medium} class MaterialItem2D(MaterialItem): """A 2D material that includes several variants.""" - variants: Dict[str, VariantItem2D] = pd.Field( + variants: dict[str, VariantItem2D] = pd.Field( ..., title="Dictionary of available variants for this material", description="A dictionary of available variants for this material " @@ -213,19 +215,19 @@ def medium(self, optical_axis: Axis) -> AnisotropicMedium: """ components = ["xx", "yy", "zz"] - mat_dict = {comp: self.ordinary for comp in components} + mat_dict = dict.fromkeys(components, self.ordinary) mat_dict.update({components[optical_axis]: self.extraordinary}) return AnisotropicMedium.parse_obj(mat_dict) @property - def summarize_mediums(self) -> Dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: + def summarize_mediums(self) -> dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: return {"ordinary": self.ordinary, "extraordinary": self.extraordinary} class MaterialItemUniaxial(MaterialItem): """A material that includes several variants.""" - variants: Dict[str, VariantItemUniaxial] = pd.Field( + variants: dict[str, VariantItemUniaxial] = pd.Field( ..., title="Dictionary of available variants for this material", description="A dictionary of available variants for this material " @@ -352,8 +354,7 @@ def medium(self, optical_axis: Axis): frequency_range=(154771532566312.25, 1595489401708072.2), ), reference=[material_refs["Yang2015"]], - data_url="https://refractiveindex.info/data_csv.php?datafile=database/data-nk/" - "main/Ag/Yang.yml", + data_url="https://refractiveindex.info/data_csv.php?datafile=database/data-nk/main/Ag/Yang.yml", ) Al_Rakic1995 = VariantItem( @@ -2083,447 +2084,447 @@ def _repr_pretty_(self, p, cycle): material_library = MaterialLibrary( Ag=MaterialItem( name="Silver", - variants=dict( - Rakic1998BB=Ag_Rakic1998BB, - JohnsonChristy1972=Ag_JohnsonChristy1972, - RakicLorentzDrude1998=Ag_RakicLorentzDrude1998, - Yang2015Drude=Ag_Yang2015Drude, - ), + variants={ + "Rakic1998BB": Ag_Rakic1998BB, + "JohnsonChristy1972": Ag_JohnsonChristy1972, + "RakicLorentzDrude1998": Ag_RakicLorentzDrude1998, + "Yang2015Drude": Ag_Yang2015Drude, + }, default="Rakic1998BB", ), Al=MaterialItem( name="Aluminum", - variants=dict( - Rakic1995=Al_Rakic1995, - RakicLorentzDrude1998=Al_RakicLorentzDrude1998, - ), + variants={ + "Rakic1995": Al_Rakic1995, + "RakicLorentzDrude1998": Al_RakicLorentzDrude1998, + }, default="Rakic1995", ), Al2O3=MaterialItem( name="Alumina", - variants=dict( - Horiba=Al2O3_Horiba, - ), + variants={ + "Horiba": Al2O3_Horiba, + }, default="Horiba", ), AlAs=MaterialItem( name="Aluminum Arsenide", - variants=dict( - Horiba=AlAs_Horiba, - FernOnton1971=AlAs_FernOnton1971, - ), + variants={ + "Horiba": AlAs_Horiba, + "FernOnton1971": AlAs_FernOnton1971, + }, default="Horiba", ), AlGaN=MaterialItem( name="Aluminum Gallium Nitride", - variants=dict( - Horiba=AlGaN_Horiba, - ), + variants={ + "Horiba": AlGaN_Horiba, + }, default="Horiba", ), AlN=MaterialItem( name="Aluminum Nitride", - variants=dict( - Horiba=AlN_Horiba, - ), + variants={ + "Horiba": AlN_Horiba, + }, default="Horiba", ), AlxOy=MaterialItem( name="Aluminum Oxide", - variants=dict( - Horiba=AlxOy_Horiba, - ), + variants={ + "Horiba": AlxOy_Horiba, + }, default="Horiba", ), Aminoacid=MaterialItem( name="Amino Acid", - variants=dict( - Horiba=Aminoacid_Horiba, - ), + variants={ + "Horiba": Aminoacid_Horiba, + }, default="Horiba", ), Au=MaterialItem( name="Gold", - variants=dict( - Olmon2012crystal=Au_Olmon2012crystal, - Olmon2012stripped=Au_Olmon2012stripped, - Olmon2012evaporated=Au_Olmon2012evaporated, - Olmon2012Drude=Au_Olmon2012Drude, - JohnsonChristy1972=Au_JohnsonChristy1972, - RakicLorentzDrude1998=Au_RakicLorentzDrude1998, - ), + variants={ + "Olmon2012crystal": Au_Olmon2012crystal, + "Olmon2012stripped": Au_Olmon2012stripped, + "Olmon2012evaporated": Au_Olmon2012evaporated, + "Olmon2012Drude": Au_Olmon2012Drude, + "JohnsonChristy1972": Au_JohnsonChristy1972, + "RakicLorentzDrude1998": Au_RakicLorentzDrude1998, + }, default="Olmon2012evaporated", ), BK7=MaterialItem( name="N-BK7 Borosilicate Glass", - variants=dict( - Zemax=BK7_Zemax, - ), + variants={ + "Zemax": BK7_Zemax, + }, default="Zemax", ), Be=MaterialItem( name="Beryllium", - variants=dict( - Rakic1998BB=Be_Rakic1998BB, - RakicLorentzDrude1998=Be_RakicLorentzDrude1998, - ), + variants={ + "Rakic1998BB": Be_Rakic1998BB, + "RakicLorentzDrude1998": Be_RakicLorentzDrude1998, + }, default="Rakic1998BB", ), CaF2=MaterialItem( name="Calcium Fluoride", - variants=dict( - Horiba=CaF2_Horiba, - ), + variants={ + "Horiba": CaF2_Horiba, + }, default="Horiba", ), Cellulose=MaterialItem( name="Cellulose", - variants=dict( - Sultanova2009=Cellulose_Sultanova2009, - ), + variants={ + "Sultanova2009": Cellulose_Sultanova2009, + }, default="Sultanova2009", ), Cr=MaterialItem( name="Chromium", - variants=dict( - Rakic1998BB=Cr_Rakic1998BB, - RakicLorentzDrude1998=Cr_RakicLorentzDrude1998, - ), + variants={ + "Rakic1998BB": Cr_Rakic1998BB, + "RakicLorentzDrude1998": Cr_RakicLorentzDrude1998, + }, default="Rakic1998BB", ), Cu=MaterialItem( name="Copper", - variants=dict( - JohnsonChristy1972=Cu_JohnsonChristy1972, - RakicLorentzDrude1998=Cu_RakicLorentzDrude1998, - ), + variants={ + "JohnsonChristy1972": Cu_JohnsonChristy1972, + "RakicLorentzDrude1998": Cu_RakicLorentzDrude1998, + }, default="JohnsonChristy1972", ), FusedSilica=MaterialItem( name="Fused Silica", - variants=dict( - ZemaxSellmeier=FusedSilica_Zemax, - ZemaxVisiblePMLStable=FusedSilica_Zemax_Visible_PMLStable, - ZemaxPMLStable=FusedSilica_Zemax_PMLStable, - ), + variants={ + "ZemaxSellmeier": FusedSilica_Zemax, + "ZemaxVisiblePMLStable": FusedSilica_Zemax_Visible_PMLStable, + "ZemaxPMLStable": FusedSilica_Zemax_PMLStable, + }, default="ZemaxPMLStable", ), GaAs=MaterialItem( name="Gallium Arsenide", - variants=dict( - Palik_Lossless=GaAs_Palik_Lossless, - Palik_Lossy=GaAs_Palik_Lossy, - Skauli2003=GaAs_Skauli2003, - ), + variants={ + "Palik_Lossless": GaAs_Palik_Lossless, + "Palik_Lossy": GaAs_Palik_Lossy, + "Skauli2003": GaAs_Skauli2003, + }, default="Skauli2003", ), Ge=MaterialItem( name="Germanium", - variants=dict( - Palik_Lossless=Ge_Palik_Lossless, - Palik_Lossy=Ge_Palik_Lossy, - Icenogle1976=Ge_Icenogle1976, - ), + variants={ + "Palik_Lossless": Ge_Palik_Lossless, + "Palik_Lossy": Ge_Palik_Lossy, + "Icenogle1976": Ge_Icenogle1976, + }, default="Icenogle1976", ), GeOx=MaterialItem( name="Germanium Oxide", - variants=dict( - Horiba=GeOx_Horiba, - ), + variants={ + "Horiba": GeOx_Horiba, + }, default="Horiba", ), H2O=MaterialItem( name="Water", - variants=dict( - Horiba=H2O_Horiba, - ), + variants={ + "Horiba": H2O_Horiba, + }, default="Horiba", ), HMDS=MaterialItem( name="Hexamethyldisilazane, or Bis(trimethylsilyl)amine", - variants=dict( - Horiba=HMDS_Horiba, - ), + variants={ + "Horiba": HMDS_Horiba, + }, default="Horiba", ), HfO2=MaterialItem( name="Hafnium Oxide", - variants=dict( - Horiba=HfO2_Horiba, - ), + variants={ + "Horiba": HfO2_Horiba, + }, default="Horiba", ), ITO=MaterialItem( name="Indium Tin Oxide", - variants=dict( - Horiba=ITO_Horiba, - ), + variants={ + "Horiba": ITO_Horiba, + }, default="Horiba", ), InAs=MaterialItem( name="Indium Arsenide", - variants=dict( - Palik=InAs_Palik, - ), + variants={ + "Palik": InAs_Palik, + }, default="Palik", ), InP=MaterialItem( name="Indium Phosphide", - variants=dict( - Palik_Lossless=InP_Palik_Lossless, - Palik_Lossy=InP_Palik_Lossy, - Pettit1965=InP_Pettit1965, - ), + variants={ + "Palik_Lossless": InP_Palik_Lossless, + "Palik_Lossy": InP_Palik_Lossy, + "Pettit1965": InP_Pettit1965, + }, default="Pettit1965", ), MgF2=MaterialItem( name="Magnesium Fluoride", - variants=dict( - Horiba=MgF2_Horiba, - ), + variants={ + "Horiba": MgF2_Horiba, + }, default="Horiba", ), MgO=MaterialItem( name="Magnesium Oxide", - variants=dict( - StephensMalitson1952=MgO_StephensMalitson1952, - ), + variants={ + "StephensMalitson1952": MgO_StephensMalitson1952, + }, default="StephensMalitson1952", ), MoS2=MaterialItem2D( name="Molybdenum Disulfide", - variants=dict( - Li2014=MoS2_Li2014, - ), + variants={ + "Li2014": MoS2_Li2014, + }, default="Li2014", ), MoSe2=MaterialItem2D( name="Molybdenum Diselenide", - variants=dict( - Li2014=MoSe2_Li2014, - ), + variants={ + "Li2014": MoSe2_Li2014, + }, default="Li2014", ), Ni=MaterialItem( name="Nickel", - variants=dict( - JohnsonChristy1972=Ni_JohnsonChristy1972, - RakicLorentzDrude1998=Ni_RakicLorentzDrude1998, - ), + variants={ + "JohnsonChristy1972": Ni_JohnsonChristy1972, + "RakicLorentzDrude1998": Ni_RakicLorentzDrude1998, + }, default="JohnsonChristy1972", ), PEI=MaterialItem( name="Polyetherimide", - variants=dict( - Horiba=PEI_Horiba, - ), + variants={ + "Horiba": PEI_Horiba, + }, default="Horiba", ), PEN=MaterialItem( name="Polyethylene Naphthalate", - variants=dict( - Horiba=PEN_Horiba, - ), + variants={ + "Horiba": PEN_Horiba, + }, default="Horiba", ), PET=MaterialItem( name="Polyethylene Terephthalate", - variants=dict( - Horiba=PET_Horiba, - ), + variants={ + "Horiba": PET_Horiba, + }, default="Horiba", ), PMMA=MaterialItem( name="Poly(methyl Methacrylate)", - variants=dict( - Horiba=PMMA_Horiba, - Sultanova2009=PMMA_Sultanova2009, - ), + variants={ + "Horiba": PMMA_Horiba, + "Sultanova2009": PMMA_Sultanova2009, + }, default="Sultanova2009", ), PTFE=MaterialItem( name="Polytetrafluoroethylene, or Teflon", - variants=dict( - Horiba=PTFE_Horiba, - ), + variants={ + "Horiba": PTFE_Horiba, + }, default="Horiba", ), PVC=MaterialItem( name="Polyvinyl Chloride", - variants=dict( - Horiba=PVC_Horiba, - ), + variants={ + "Horiba": PVC_Horiba, + }, default="Horiba", ), Pd=MaterialItem( name="Palladium", - variants=dict( - JohnsonChristy1972=Pd_JohnsonChristy1972, - RakicLorentzDrude1998=Pd_RakicLorentzDrude1998, - ), + variants={ + "JohnsonChristy1972": Pd_JohnsonChristy1972, + "RakicLorentzDrude1998": Pd_RakicLorentzDrude1998, + }, default="JohnsonChristy1972", ), Polycarbonate=MaterialItem( name="Polycarbonate", - variants=dict( - Horiba=Polycarbonate_Horiba, - Sultanova2009=Polycarbonate_Sultanova2009, - ), + variants={ + "Horiba": Polycarbonate_Horiba, + "Sultanova2009": Polycarbonate_Sultanova2009, + }, default="Sultanova2009", ), Polystyrene=MaterialItem( name="Polystyrene", - variants=dict( - Sultanova2009=Polystyrene_Sultanova2009, - ), + variants={ + "Sultanova2009": Polystyrene_Sultanova2009, + }, default="Sultanova2009", ), Pt=MaterialItem( name="Platinum", - variants=dict( - Werner2009=Pt_Werner2009, - RakicLorentzDrude1998=Pt_RakicLorentzDrude1998, - ), + variants={ + "Werner2009": Pt_Werner2009, + "RakicLorentzDrude1998": Pt_RakicLorentzDrude1998, + }, default="Werner2009", ), Sapphire=MaterialItem( name="Sapphire", - variants=dict( - Horiba=Sapphire_Horiba, - ), + variants={ + "Horiba": Sapphire_Horiba, + }, default="Horiba", ), Si3N4=MaterialItem( name="Silicon Nitride", - variants=dict( - Horiba=Si3N4_Horiba, - Luke2015Sellmeier=Si3N4_Luke2015, - Luke2015PMLStable=Si3N4_Luke2015_PMLStable, - Philipp1973Sellmeier=Si3N4_Philipp1973, - ), + variants={ + "Horiba": Si3N4_Horiba, + "Luke2015Sellmeier": Si3N4_Luke2015, + "Luke2015PMLStable": Si3N4_Luke2015_PMLStable, + "Philipp1973Sellmeier": Si3N4_Philipp1973, + }, default="Horiba", ), SiC=MaterialItem( name="Silicon Carbide", - variants=dict( - Horiba=SiC_Horiba, - ), + variants={ + "Horiba": SiC_Horiba, + }, default="Horiba", ), SiN=MaterialItem( name="Silicon Mononitride", - variants=dict( - Horiba=SiN_Horiba, - ), + variants={ + "Horiba": SiN_Horiba, + }, default="Horiba", ), SiO2=MaterialItem( name="Silicon Dioxide", - variants=dict( - Palik_Lossless=SiO2_Palik_Lossless, - Palik_Lossy=SiO2_Palik_Lossy, - Horiba=SiO2_Horiba, - ), + variants={ + "Palik_Lossless": SiO2_Palik_Lossless, + "Palik_Lossy": SiO2_Palik_Lossy, + "Horiba": SiO2_Horiba, + }, default="Palik_Lossless", ), SiON=MaterialItem( name="Silicon Oxynitride", - variants=dict( - Horiba=SiON_Horiba, - ), + variants={ + "Horiba": SiON_Horiba, + }, default="Horiba", ), Ta2O5=MaterialItem( name="Tantalum Pentoxide", - variants=dict( - Horiba=Ta2O5_Horiba, - ), + variants={ + "Horiba": Ta2O5_Horiba, + }, default="Horiba", ), Ti=MaterialItem( name="Titanium", - variants=dict( - Werner2009=Ti_Werner2009, - RakicLorentzDrude1998=Ti_RakicLorentzDrude1998, - ), + variants={ + "Werner2009": Ti_Werner2009, + "RakicLorentzDrude1998": Ti_RakicLorentzDrude1998, + }, default="Werner2009", ), TiOx=MaterialItem( name="Titanium Oxide", - variants=dict( - Horiba=TiOx_Horiba, - HorbiaStable=TiOx_HoribaStable, - ), + variants={ + "Horiba": TiOx_Horiba, + "HorbiaStable": TiOx_HoribaStable, + }, default="Horiba", ), W=MaterialItem( name="Tungsten", - variants=dict( - Werner2009=W_Werner2009, - RakicLorentzDrude1998=W_RakicLorentzDrude1998, - ), + variants={ + "Werner2009": W_Werner2009, + "RakicLorentzDrude1998": W_RakicLorentzDrude1998, + }, default="Werner2009", ), WS2=MaterialItem2D( name="Tungsten Disulfide", - variants=dict( - Li2014=WS2_Li2014, - ), + variants={ + "Li2014": WS2_Li2014, + }, default="Li2014", ), WSe2=MaterialItem2D( name="Tungsten Diselenide", - variants=dict( - Li2014=WSe2_Li2014, - ), + variants={ + "Li2014": WSe2_Li2014, + }, default="Li2014", ), Y2O3=MaterialItem( name="Yttrium Oxide", - variants=dict( - Horiba=Y2O3_Horiba, - Nigara1968=Y2O3_Nigara1968, - ), + variants={ + "Horiba": Y2O3_Horiba, + "Nigara1968": Y2O3_Nigara1968, + }, default="Horiba", ), YAG=MaterialItem( name="Yttrium Aluminium Garnet", - variants=dict( - Zelmon1998=YAG_Zelmon1998, - ), + variants={ + "Zelmon1998": YAG_Zelmon1998, + }, default="Zelmon1998", ), ZrO2=MaterialItem( name="Zirconium Oxide", - variants=dict( - Horiba=ZrO2_Horiba, - ), + variants={ + "Horiba": ZrO2_Horiba, + }, default="Horiba", ), aSi=MaterialItem( name="Silicon (Amorphous)", - variants=dict( - Horiba=aSi_Horiba, - ), + variants={ + "Horiba": aSi_Horiba, + }, default="Horiba", ), cSi=MaterialItem( name="Silicon (Crystalline)", - variants=dict( - Palik_Lossless=cSi_PalikLossless, - Palik_Lossy=cSi_PalikLossy, - SalzbergVilla1957=cSi_SalzbergVilla1957, - Li1993_293K=cSi_Li1993_293K, - Green2008=cSi_Green2008, - Green2008_Lossless=cSi_Green2008Lossless, - Si_MultiPhysics=cSi_MultiPhysics, - ), + variants={ + "Palik_Lossless": cSi_PalikLossless, + "Palik_Lossy": cSi_PalikLossy, + "SalzbergVilla1957": cSi_SalzbergVilla1957, + "Li1993_293K": cSi_Li1993_293K, + "Green2008": cSi_Green2008, + "Green2008_Lossless": cSi_Green2008Lossless, + "Si_MultiPhysics": cSi_MultiPhysics, + }, default="Green2008", ), LiNbO3=MaterialItemUniaxial( name="Lithium niobate", - variants=dict(Zelmon1997=LiNbO3_Zelmon1997), + variants={"Zelmon1997": LiNbO3_Zelmon1997}, default="Zelmon1997", ), graphene=Graphene, diff --git a/tidy3d/material_library/material_reference.py b/tidy3d/material_library/material_reference.py index ac27867dfe..bba6e7baa4 100644 --- a/tidy3d/material_library/material_reference.py +++ b/tidy3d/material_library/material_reference.py @@ -1,8 +1,10 @@ """Holds the reference materials for Tidy3D material library.""" +from __future__ import annotations + import pydantic.v1 as pd -from ..components.base import Tidy3dBaseModel +from tidy3d.components.base import Tidy3dBaseModel class ReferenceData(Tidy3dBaseModel): @@ -31,188 +33,188 @@ class ReferenceData(Tidy3dBaseModel): ) -material_refs = dict( - Li2014=ReferenceData( +material_refs = { + "Li2014": ReferenceData( journal="Y. Li, A. Chernikov, X. Zhang, A. Rigosi, H. M. Hill, A. M. van der Zande, " "D. A. Chenet, E. Shih, J. Hone, and T. F. Heinz. Measurement of the optical dielectric " "function of monolayer transition-metal dichalcogenides: MoS2, MoSe2, WS2, and WSe2, " "Phys. Rev. B 90, 205422 (2014)", doi="https://doi.org/10.1103/PhysRevB.90.205422", ), - Yang2015=ReferenceData( + "Yang2015": ReferenceData( journal="H. U. Yang, J. D'Archangel, M. L. Sundheimer, E. Tucker, G. D. Boreman, " "M. B. Raschke. Optical dielectric function of silver, Phys. Rev. B 91, 235137 (2015)", doi="https://journals.aps.org/prb/abstract/10.1103/PhysRevB.91.235137", ), - Olmon2012=ReferenceData( + "Olmon2012": ReferenceData( journal="R. L. Olmon, B. Slovick, T. W. Johnson, D. Shelton, S.-H. Oh, " "G. D. Boreman, and M. B. Raschke. Optical dielectric function of " "gold, Phys. Rev. B 86, 235147 (2012)", doi="https://doi.org/10.1103/PhysRevB.86.235147", ), - Rakic1995=ReferenceData( + "Rakic1995": ReferenceData( journal="A. D. Rakic. Algorithm for the determination of intrinsic optical " "constants of metal films: application to aluminum, Appl. Opt. 34, 4755-4767 (1995)", doi="https://doi.org/10.1364/AO.34.004755", ), - Rakic1998=ReferenceData( + "Rakic1998": ReferenceData( journal="A. D. Rakic, A. B. Djurisic, J. M. Elazar, and M. L. Majewski. " "Optical properties of metallic films for vertical-cavity optoelectronic " "devices, Appl. Opt. 37, 5271-5283 (1998)", doi="https://doi.org/10.1364/AO.37.005271", ), - JohnsonChristy1972=ReferenceData( + "JohnsonChristy1972": ReferenceData( journal="P. B. Johnson and R. W. Christy. Optical constants of the noble " "metals, Phys. Rev. B 6, 4370-4379 (1972)", doi="https://doi.org/10.1103/PhysRevB.6.4370", ), - Horiba=ReferenceData( + "Horiba": ReferenceData( journal="Horiba Technical Note 08: Lorentz Dispersion Model", url="http://www.horiba.com/fileadmin/uploads/Scientific/Downloads" "/OpticalSchool_CN/TN/ellipsometer/Lorentz_Dispersion_Model.pdf", ), - FernOnton1971=ReferenceData( + "FernOnton1971": ReferenceData( journal="R. E. Fern and A. Onton. Refractive index of AlAs, " "J. Appl. Phys. 42, 3499-3500 (1971)", doi="https://doi.org/10.1063/1.1660760", ), - Sultanova2009=ReferenceData( + "Sultanova2009": ReferenceData( journal="N. Sultanova, S. Kasarova and I. Nikolov. Dispersion properties " "of optical polymers, Acta Physica Polonica A 116, 585-587 (2009)", doi="https://doi.org/10.12693/aphyspola.116.585", ), - Malitson1965=ReferenceData( + "Malitson1965": ReferenceData( journal="I. H. Malitson. Interspecimen comparison of the refractive " "index of fused silica, J. Opt. Soc. Am. 55, 1205-1208 (1965)", doi="https://doi.org/10.1364/JOSA.55.001205", ), - Tan1998=ReferenceData( + "Tan1998": ReferenceData( journal="C. Z. Tan. Determination of refractive index of silica glass " "for infrared wavelengths by IR spectroscopy, J. Non-Cryst. Solids 223, 158-163 (1998)", doi="https://doi.org/10.1016/S0022-3093(97)00438-9", ), - Skauli2003=ReferenceData( + "Skauli2003": ReferenceData( journal="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)", doi="https://doi.org/10.1063/1.1621740", ), - Icenogle1976=ReferenceData( + "Icenogle1976": ReferenceData( journal="H. W. Icenogle, Ben C. Platt, and William L. Wolfe. " "Refractive indexes and temperature coefficients of germanium " "and silicon Appl. Opt. 15 2348-2351 (1976)", doi="https://doi.org/10.1364/AO.15.002348", ), - Barnes1979=ReferenceData( + "Barnes1979": ReferenceData( journal="N. P. Barnes and M. S. Piltch. Temperature-dependent " "Sellmeier coefficients and nonlinear optics average power limit " "for germanium J. Opt. Soc. Am. 69 178-180 (1979)", doi="https://doi.org/10.1364/JOSA.69.000178", ), - Pettit1965=ReferenceData( + "Pettit1965": ReferenceData( journal="G. D. Pettit and W. J. Turner. Refractive index of InP, " "J. Appl. Phys. 36, 2081 (1965)", doi="https://doi.org/10.1063/1.1714410", ), - Pikhtin1978=ReferenceData( + "Pikhtin1978": ReferenceData( journal="A. N. Pikhtin and A. D. Yas'kov. Disperson of the " "refractive index of semiconductors with diamond and zinc-blende " "structures, Sov. Phys. Semicond. 12, 622-626 (1978)", ), - HandbookOptics=ReferenceData( + "HandbookOptics": ReferenceData( journal="Handbook of Optics, 2nd edition, Vol. 2. McGraw-Hill 1994 (ISBN 9780070479746)", ), - StephensMalitson1952=ReferenceData( + "StephensMalitson1952": ReferenceData( journal="R. E. Stephens and I. H. Malitson. Index of refraction of " "magnesium oxide, J. Res. Natl. Bur. Stand. 49 249-252 (1952)", doi="https://doi.org/10.6028/jres.049.025", ), - Werner2009=ReferenceData( + "Werner2009": ReferenceData( journal="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)", doi="https://doi.org/10.1063/1.3243762", ), - Luke2015=ReferenceData( + "Luke2015": ReferenceData( journal="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)", doi="https://doi.org/10.1364/OL.40.004823", ), - Philipp1973=ReferenceData( + "Philipp1973": ReferenceData( journal="H. R. Philipp. Optical properties of silicon nitride, " "J. Electrochim. Soc. 120, 295-300 (1973)", doi="https://doi.org/10.1149/1.2403440", ), - Baak1982=ReferenceData( + "Baak1982": ReferenceData( journal="T. Baak. Silicon oxynitride; a material for GRIN optics, " "Appl. Optics 21, 1069-1072 (1982)", doi="https://doi.org/10.1364/AO.21.001069", ), - Nigara1968=ReferenceData( + "Nigara1968": ReferenceData( journal="Y. Nigara. Measurement of the optical constants of yttrium oxide, " "Jpn. J. Appl. Phys. 7, 404-408 (1968)", doi="https://doi.org/10.1143/JJAP.7.404", ), - Zelmon1997=ReferenceData( + "Zelmon1997": ReferenceData( journal="D. E. Zelmon, D. L. Small and D. Jundt. Infrared corrected Sellmeier " "coefficients for congruently grown lithium niobate and 5 mol.% magnesium oxide-doped " "lithium niobate, J. Opt. Soc. Am. B 14, 3319-3322 (1997)", doi="https://doi.org/10.1364/JOSAB.14.003319", ), - Zelmon1998=ReferenceData( + "Zelmon1998": ReferenceData( journal="D. E. Zelmon, D. L. Small and R. Page. Refractive-index measurements " "of undoped yttrium aluminum garnet from 0.4 to 5.0 μm, Appl. Opt. 37, 4933-4935 (1998)", doi="https://doi.org/10.1364/AO.37.004933", ), - SalzbergVilla1957=ReferenceData( + "SalzbergVilla1957": ReferenceData( journal="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)", doi="https://doi.org/10.1364/JOSA.47.000244", ), - Tatian1984=ReferenceData( + "Tatian1984": ReferenceData( journal="B. Tatian. Fitting refractive-index data with the Sellmeier " "dispersion formula, Appl. Opt. 23, 4477-4485 (1984)", doi="https://doi.org/10.1364/AO.23.004477", ), - Li1993_293K=ReferenceData( + "Li1993_293K": ReferenceData( journal="H. H. Li. Refractive index of silicon and germanium and its wavelength " "and temperature derivatives, J. Phys. Chem. Ref. Data 9, 561-658 (1993)", doi="https://doi.org/10.1063/1.555624", ), - Green2008=ReferenceData( + "Green2008": ReferenceData( journal="M. A. Green. Self-consistent optical parameters of intrinsic silicon " "at 300K including temperature coefficients, Sol. Energ. Mat. " "Sol. Cells 92, 1305–1310 (2008)", doi="https://doi.org/10.1016/j.solmat.2008.06.009", ), - Zemax=ReferenceData( + "Zemax": ReferenceData( journal="SCHOTT Zemax catalog 2017-01-20b", url="https://refractiveindex.info/download/data/2017/schott_2017-01-20.pdf", ), - Hanson2008=ReferenceData( + "Hanson2008": ReferenceData( journal="George W. Hanson. Dyadic Green's Functions for an Anisotropic, " "Non-Local Model of Biased Graphene, IEEE Trans. Antennas Propag. 56, 3, 747-757 (2008)", doi="https://doi.org/10.1109/TAP.2008.917005", ), - Burnett2016=ReferenceData( + "Burnett2016": ReferenceData( journal="John H. Burnett, Simon G. Kaplan, Eric Stover, and Adam Phenis, " "Refractive index measurements of Ge, " "Proc. SPIE 9974, Infrared Sensors, Devices, and Applications VI, 99740X " "(20 September 2016)", doi="https://doi.org/10.1117/12.2237978", ), - Palik=ReferenceData( - journal="E. D. Palik. Handbook of Optical Constants of Solids, " "Academic Press (1998)", + "Palik": ReferenceData( + journal="E. D. Palik. Handbook of Optical Constants of Solids, Academic Press (1998)", doi="https://doi.org/10.1016/B978-0-08-055630-7.50001-8", ), - Palik_Lossy=ReferenceData( - journal="E. D. Palik. Handbook of Optical Constants of Solids, " "Academic Press (1998)", + "Palik_Lossy": ReferenceData( + journal="E. D. Palik. Handbook of Optical Constants of Solids, Academic Press (1998)", doi="https://doi.org/10.1016/B978-0-08-055630-7.50001-8", ), - Palik_Lossless=ReferenceData( - journal="E. D. Palik. Handbook of Optical Constants of Solids, " "Academic Press (1998)", + "Palik_Lossless": ReferenceData( + journal="E. D. Palik. Handbook of Optical Constants of Solids, Academic Press (1998)", doi="https://doi.org/10.1016/B978-0-08-055630-7.50001-8", ), -) +} diff --git a/tidy3d/material_library/parametric_materials.py b/tidy3d/material_library/parametric_materials.py index 02608aca9d..3a1df7fcce 100644 --- a/tidy3d/material_library/parametric_materials.py +++ b/tidy3d/material_library/parametric_materials.py @@ -1,16 +1,17 @@ """Parametric material models.""" +from __future__ import annotations + import warnings from abc import ABC, abstractmethod -from typing import List, Tuple import numpy as np import pydantic.v1 as pd -from ..components.base import Tidy3dBaseModel -from ..components.medium import Drude, Medium2D, PoleResidue -from ..constants import ELECTRON_VOLT, EPSILON_0, HBAR, K_B, KELVIN, Q_e -from ..log import log +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.components.medium import Drude, Medium2D, PoleResidue +from tidy3d.constants import ELECTRON_VOLT, EPSILON_0, HBAR, K_B, KELVIN, Q_e +from tidy3d.log import log try: from scipy import integrate @@ -102,7 +103,7 @@ class Graphene(ParametricVariantItem2D): "Otherwise, the intraband terms only give a simpler Drude-type model relevant " "only at low frequency (THz).", ) - interband_fit_freq_nodes: List[Tuple[float, float]] = pd.Field( + interband_fit_freq_nodes: list[tuple[float, float]] = pd.Field( None, title="Interband fitting frequency nodes", description="Frequency nodes for fitting interband term. " @@ -199,7 +200,7 @@ def interband_pole_residue(self) -> PoleResidue: ) return pole_residue_filtered - def numerical_conductivity(self, freqs: List[float]) -> List[complex]: + def numerical_conductivity(self, freqs: list[float]) -> list[complex]: """Numerically calculate the conductivity. If this differs from the conductivity of the :class:`.Medium2D`, it is due to error while fitting the interband term, and you may try values of ``interband_fit_freq_nodes`` @@ -219,7 +220,7 @@ def numerical_conductivity(self, freqs: List[float]) -> List[complex]: inter_sigma = self.interband_conductivity(freqs) return intra_sigma + inter_sigma - def interband_conductivity(self, freqs: List[float]) -> List[complex]: + def interband_conductivity(self, freqs: list[float]) -> list[complex]: """Numerically integrate interband term. Parameters @@ -271,9 +272,9 @@ def integrand(E: float, omega: float) -> float: def _fit_interband_conductivity( self, - freqs: List[float], - sigma: List[complex], - indslist: List[Tuple[int, int]], + freqs: list[float], + sigma: list[complex], + indslist: list[tuple[int, int]], ): """Fit the interband conductivity with a Pade approximation, as described in @@ -296,7 +297,7 @@ def _fit_interband_conductivity( A pole-residue model approximating the interband conductivity. """ - def evaluate_coeffslist(omega: List[float], coeffslist: List[List[float]]) -> List[float]: + def evaluate_coeffslist(omega: list[float], coeffslist: list[list[float]]) -> list[float]: """Evaluate the Pade approximants given by ``coeffslist` to ``omega``. Each item in ``coeffslist`` is a list of four coefficients corresponding to a single Pade term.""" @@ -308,8 +309,8 @@ def evaluate_coeffslist(omega: List[float], coeffslist: List[List[float]]) -> Li return res def fit_single( - omega: List[float], sigma: List[complex], inds: Tuple[int, int] - ) -> List[float]: + omega: list[float], sigma: list[complex], inds: tuple[int, int] + ) -> list[float]: """Fit a single Pade approximant of degree (1, 2) to ``sigma`` as a real function of i ``omega``. The method is described in @@ -331,11 +332,11 @@ def fit_single( return np.linalg.pinv(matrix) @ np.array([gamma[0], eta[0], gamma[1], eta[1]]) def optimize( - omega: List[float], - sigma: List[complex], - indslist: List[Tuple[int, int]], - coeffslist: List[List[float]], - ) -> List[float]: + omega: list[float], + sigma: list[complex], + indslist: list[tuple[int, int]], + coeffslist: list[list[float]], + ) -> list[float]: """Optimize the coefficients in ``coeffslist`` by sampling ``omega`` and ``sigma`` at the indices in ``indslist``.""" for _ in range(self.interband_fit_num_iters): @@ -346,7 +347,7 @@ def optimize( coeffslist[j] = fit_single(omega, curr_res, indslist[j]) return coeffslist - def get_pole_residue(coeffslist: List[List[float]]) -> PoleResidue: + def get_pole_residue(coeffslist: list[list[float]]) -> PoleResidue: """Convert a list of Pade coefficients into a :class:`.PoleResidue` model.""" poles = [] for alpha0, alpha1, beta1, beta2 in coeffslist: @@ -398,6 +399,6 @@ def _filter_poles(self, medium: PoleResidue) -> PoleResidue: else: poles += [(a, c)] return PoleResidue( - poles=poles + [(0, zero_res)], + poles=[*poles, (0, zero_res)], frequency_range=(0, GRAPHENE_FIT_FREQ_MAX), ) diff --git a/tidy3d/material_library/util.py b/tidy3d/material_library/util.py index 62090f0c4f..206cbdf4a7 100644 --- a/tidy3d/material_library/util.py +++ b/tidy3d/material_library/util.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from io import StringIO -from typing import List from rich.console import Console from rich.panel import Panel @@ -7,7 +8,7 @@ from rich.text import Text from rich.tree import Tree -from ..components.viz import FLEXCOMPUTE_COLORS +from tidy3d.components.viz import FLEXCOMPUTE_COLORS MAX_POLES_TO_DISPLAY = 3 @@ -55,7 +56,7 @@ def variant_name(v): return name -def summarize_medium(med) -> List[str]: +def summarize_medium(med) -> list[str]: """Returns relevant medium information for display.""" lines = [] diff --git a/tidy3d/packaging.py b/tidy3d/packaging.py index 43aeb8d4d5..ebec4446c3 100644 --- a/tidy3d/packaging.py +++ b/tidy3d/packaging.py @@ -4,6 +4,8 @@ This section should only depend on the standard core installation in the pyproject.toml, and should not depend on any other part of the codebase optional imports. """ +from __future__ import annotations + import functools from importlib import import_module from typing import Literal @@ -89,7 +91,7 @@ def checks_modules_import(*args, **kwargs): f"Please install the '{module}' dependencies using, for example, " f"'pip install tidy3d[]" ) - elif required == "any": + if required == "any": # Means we need to verify that at least one of the modules is available if ( not any(available_modules_status) @@ -140,12 +142,12 @@ def _fn(*args, **kwargs): if vtk["mod"].vtkIdTypeArray().GetDataTypeSize() == 4: vtk["id_type"] = np.int32 - except ImportError: + except ImportError as exc: raise Tidy3dImportError( "The package 'vtk' is required for this operation, but it was not found. " "Please install the 'vtk' dependencies using, for example, " "'pip install .[vtk]'." - ) + ) from exc return fn(*args, **kwargs) diff --git a/tidy3d/plugins/adjoint/__init__.py b/tidy3d/plugins/adjoint/__init__.py index 6cf3f04f84..24f2097c15 100644 --- a/tidy3d/plugins/adjoint/__init__.py +++ b/tidy3d/plugins/adjoint/__init__.py @@ -1,6 +1,8 @@ """Imports for adjoint plugin.""" # import the jax version of tidy3d components +from __future__ import annotations + try: import jax @@ -27,21 +29,21 @@ from .web import run, run_async __all__ = [ + "JaxAnisotropicMedium", "JaxBox", - "JaxPolySlab", "JaxComplexPolySlab", + "JaxCustomMedium", + "JaxDataArray", "JaxGeometryGroup", "JaxMedium", - "JaxAnisotropicMedium", - "JaxCustomMedium", - "JaxStructure", - "JaxStructureStaticMedium", - "JaxStructureStaticGeometry", - "JaxSimulation", - "JaxSimulationData", "JaxModeData", "JaxPermittivityDataset", - "JaxDataArray", + "JaxPolySlab", + "JaxSimulation", + "JaxSimulationData", + "JaxStructure", + "JaxStructureStaticGeometry", + "JaxStructureStaticMedium", "run", "run_async", ] diff --git a/tidy3d/plugins/adjoint/components/__init__.py b/tidy3d/plugins/adjoint/components/__init__.py index c9d9cdb5cc..bbd71e0a7d 100644 --- a/tidy3d/plugins/adjoint/components/__init__.py +++ b/tidy3d/plugins/adjoint/components/__init__.py @@ -1,6 +1,8 @@ """Component imports for adjoint plugin. from tidy3d.plugins.adjoint.components import *""" # import the jax version of tidy3d components +from __future__ import annotations + from .data.data_array import JaxDataArray from .data.dataset import JaxPermittivityDataset from .data.monitor_data import JaxModeData @@ -11,19 +13,19 @@ from .structure import JaxStructure, JaxStructureStaticGeometry, JaxStructureStaticMedium __all__ = [ + "JaxAnisotropicMedium", "JaxBox", - "JaxPolySlab", "JaxComplexPolySlab", + "JaxCustomMedium", + "JaxDataArray", "JaxGeometryGroup", "JaxMedium", - "JaxAnisotropicMedium", - "JaxCustomMedium", - "JaxStructure", - "JaxStructureStaticMedium", - "JaxStructureStaticGeometry", - "JaxSimulation", - "JaxSimulationData", "JaxModeData", "JaxPermittivityDataset", - "JaxDataArray", + "JaxPolySlab", + "JaxSimulation", + "JaxSimulationData", + "JaxStructure", + "JaxStructureStaticGeometry", + "JaxStructureStaticMedium", ] diff --git a/tidy3d/plugins/adjoint/components/base.py b/tidy3d/plugins/adjoint/components/base.py index 1fcd6baaa7..943295ef98 100644 --- a/tidy3d/plugins/adjoint/components/base.py +++ b/tidy3d/plugins/adjoint/components/base.py @@ -3,7 +3,7 @@ from __future__ import annotations import json -from typing import Any, Callable, List, Tuple +from typing import Any, Callable, Optional import jax import numpy as np @@ -11,7 +11,8 @@ from jax.tree_util import tree_flatten as jax_tree_flatten from jax.tree_util import tree_unflatten as jax_tree_unflatten -from ....components.base import Tidy3dBaseModel +from tidy3d.components.base import Tidy3dBaseModel + from .data.data_array import JAX_DATA_ARRAY_TAG, JaxDataArray # end of the error message when a ``_validate_web_adjoint`` exception is raised @@ -36,7 +37,7 @@ class JaxObject(Tidy3dBaseModel): """Shortcut to get names of fields with certain properties.""" @classmethod - def _get_field_names(cls, field_key: str) -> List[str]: + def _get_field_names(cls, field_key: str) -> list[str]: """Get all fields where ``field_key`` defined in the ``pydantic.Field``.""" fields = [] for field_name, model_field in cls.__fields__.items(): @@ -46,17 +47,17 @@ def _get_field_names(cls, field_key: str) -> List[str]: return fields @classmethod - def get_jax_field_names(cls) -> List[str]: + def get_jax_field_names(cls) -> list[str]: """Returns list of field names where ``jax_field=True``.""" return cls._get_field_names("jax_field") @classmethod - def get_jax_leaf_names(cls) -> List[str]: + def get_jax_leaf_names(cls) -> list[str]: """Returns list of field names where ``stores_jax_for`` defined.""" return cls._get_field_names("stores_jax_for") @classmethod - def get_jax_field_names_all(cls) -> List[str]: + def get_jax_field_names_all(cls) -> list[str]: """Returns list of field names where ``jax_field=True`` or ``stores_jax_for`` defined.""" jax_field_names = cls.get_jax_field_names() jax_leaf_names = cls.get_jax_leaf_names() @@ -72,11 +73,10 @@ def jax_fields(self) -> dict: def _validate_web_adjoint(self) -> None: """Run validators for this component, only if using ``tda.web.run()``.""" - pass """Methods needed for jax to register arbitrary classes.""" - def tree_flatten(self) -> Tuple[list, dict]: + def tree_flatten(self) -> tuple[list, dict]: """How to flatten a :class:`.JaxObject` instance into a ``pytree``.""" children = [] aux_data = self.dict() @@ -95,8 +95,7 @@ def fix_numpy(value: Any) -> Any: return value.tolist() if isinstance(value, dict): return {key: fix_numpy(val) for key, val in value.items()} - else: - return value + return value aux_data = fix_numpy(aux_data) @@ -153,7 +152,7 @@ def from_tidy3d(cls, tidy3d_obj: Tidy3dBaseModel) -> JaxObject: @property def exclude_fields_leafs_only(self) -> set: """Fields to exclude from ``self.dict()``, ``"type"`` and all ``jax`` leafs.""" - return set(["type"] + self.get_jax_leaf_names()) + return {"type", *self.get_jax_leaf_names()} """Accounting with jax and regular fields.""" @@ -208,7 +207,7 @@ def strip_data_array(val: Any) -> Any: return JAX_DATA_ARRAY_TAG return {k: strip_data_array(v) for k, v in val.items()} - elif isinstance(val, (tuple, list)): + if isinstance(val, (tuple, list)): return [strip_data_array(v) for v in val] return val @@ -218,7 +217,7 @@ def strip_data_array(val: Any) -> Any: # TODO: replace with implementing these in DataArray - def to_hdf5(self, fname: str, custom_encoders: List[Callable] = None) -> None: + def to_hdf5(self, fname: str, custom_encoders: Optional[list[Callable]] = None) -> None: """Exports :class:`JaxObject` instance to .hdf5 file. Parameters @@ -249,7 +248,7 @@ def data_array_encoder(fname: str, group_path: str, value: Any) -> None: @classmethod def dict_from_hdf5( - cls, fname: str, group_path: str = "", custom_decoders: List[Callable] = None + cls, fname: str, group_path: str = "", custom_decoders: Optional[list[Callable]] = None ) -> dict: """Loads a dictionary containing the model contents from a .hdf5 file. diff --git a/tidy3d/plugins/adjoint/components/data/data_array.py b/tidy3d/plugins/adjoint/components/data/data_array.py index 083dd9d2e9..0fdcc61284 100644 --- a/tidy3d/plugins/adjoint/components/data/data_array.py +++ b/tidy3d/plugins/adjoint/components/data/data_array.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Any, Dict, List, Literal, Sequence, Tuple, Union +from collections.abc import Sequence +from typing import Any, Literal, Optional, Union import h5py import jax @@ -12,8 +13,8 @@ import xarray as xr from jax.tree_util import register_pytree_node_class -from .....components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing -from .....exceptions import AdjointError, DataError, Tidy3dKeyError +from tidy3d.components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing +from tidy3d.exceptions import AdjointError, DataError, Tidy3dKeyError # condition setting when to set value in DataArray to zero: # if abs(val) <= VALUE_FILTER_THRESHOLD * max(abs(val)) @@ -34,7 +35,7 @@ class JaxDataArray(Tidy3dBaseModel): jax_field=True, ) - coords: Dict[str, list] = pd.Field( + coords: dict[str, list] = pd.Field( ..., title="Coords", description="Dictionary storing the coordinates, namely ``(direction, f, mode_index)``.", @@ -152,18 +153,18 @@ def as_list(self) -> list: def real(self) -> np.ndarray: """Real part of self.""" new_values = jnp.real(self.as_jnp_array) - return self.copy(update=dict(values=new_values)) + return self.copy(update={"values": new_values}) @cached_property def imag(self) -> np.ndarray: """Imaginary part of self.""" new_values = jnp.imag(self.as_jnp_array) - return self.copy(update=dict(values=new_values)) + return self.copy(update={"values": new_values}) def conj(self) -> JaxDataArray: """Complex conjugate of self.""" new_values = jnp.conj(self.as_jnp_array) - return self.copy(update=dict(values=new_values)) + return self.copy(update={"values": new_values}) def __abs__(self) -> JaxDataArray: """Absolute value of self's values.""" @@ -219,7 +220,7 @@ def __rmul__(self, other) -> JaxDataArray: """Multiply self with something else.""" return self * other - def sum(self, dim: str = None): + def sum(self, dim: Optional[str] = None): """Sum (optionally along a single or multiple dimensions).""" if dim is None: @@ -239,7 +240,7 @@ def sum(self, dim: str = None): ret = ret.sum(dim=dim_i) return ret - def squeeze(self, dim: str = None, drop: bool = True) -> JaxDataArray: + def squeeze(self, dim: Optional[str] = None, drop: bool = True) -> JaxDataArray: """Remove any non-zero dims.""" if dim is None: @@ -294,7 +295,7 @@ def isel_single(self, coord_name: str, coord_index: int) -> JaxDataArray: return new_values # otherwise, return another JaxDataArray with the values and coords selected out - return self.copy(update=dict(values=new_values, coords=new_coords)) + return self.copy(update={"values": new_values, "coords": new_coords}) def isel(self, **isel_kwargs) -> JaxDataArray: """Select a value from the :class:`.JaxDataArray` by indexing into coordinates by index.""" @@ -313,7 +314,7 @@ def isel(self, **isel_kwargs) -> JaxDataArray: return self_sel def sel( - self, indexers: dict = None, method: Literal[None, "nearest"] = None, **sel_kwargs + self, indexers: Optional[dict] = None, method: Literal[None, "nearest"] = None, **sel_kwargs ) -> JaxDataArray: """Select a value from the :class:`.JaxDataArray` by indexing into coordinates by value. @@ -392,7 +393,7 @@ def _indices_literal(self, coord_list: list, values: Union[Any, Sequence[Any]]) return indices - def assign_coords(self, coords: dict = None, **coords_kwargs) -> JaxDataArray: + def assign_coords(self, coords: Optional[dict] = None, **coords_kwargs) -> JaxDataArray: """Assign new coordinates to this object.""" update_kwargs = self.coords.copy() @@ -407,7 +408,7 @@ def assign_coords(self, coords: dict = None, **coords_kwargs) -> JaxDataArray: update_kwargs = {key: np.array(value).tolist() for key, value in update_kwargs.items()} return self.updated_copy(coords=update_kwargs) - def multiply_at(self, value: complex, coord_name: str, indices: List[int]) -> JaxDataArray: + def multiply_at(self, value: complex, coord_name: str, indices: list[int]) -> JaxDataArray: """Multiply self by value at indices into .""" axis = list(self.coords.keys()).index(coord_name) scalar_data_arr = self.as_jnp_array @@ -498,7 +499,7 @@ def interp(self, kwargs=None, assume_sorted=None, **interp_kwargs) -> JaxDataArr return ret_value @cached_property - def nonzero_val_coords(self) -> Tuple[List[complex], Dict[str, Any]]: + def nonzero_val_coords(self) -> tuple[list[complex], dict[str, Any]]: """The value and coordinate associated with the only non-zero element of ``self.values``.""" values = np.nan_to_num(self.as_ndarray) @@ -519,7 +520,7 @@ def nonzero_val_coords(self) -> Tuple[List[complex], Dict[str, Any]]: return nonzero_values, nonzero_coords - def tree_flatten(self) -> Tuple[list, dict]: + def tree_flatten(self) -> tuple[list, dict]: """Jax works on the values, stash the coords for reconstruction.""" return self.values, self.coords diff --git a/tidy3d/plugins/adjoint/components/data/dataset.py b/tidy3d/plugins/adjoint/components/data/dataset.py index 6e4006816b..f8e357bbcb 100644 --- a/tidy3d/plugins/adjoint/components/data/dataset.py +++ b/tidy3d/plugins/adjoint/components/data/dataset.py @@ -1,10 +1,13 @@ """Defines jax-compatible datasets.""" +from __future__ import annotations + import pydantic.v1 as pd from jax.tree_util import register_pytree_node_class -from .....components.data.dataset import PermittivityDataset -from ..base import JaxObject +from tidy3d.components.data.dataset import PermittivityDataset +from tidy3d.plugins.adjoint.components.base import JaxObject + from .data_array import JaxDataArray diff --git a/tidy3d/plugins/adjoint/components/data/monitor_data.py b/tidy3d/plugins/adjoint/components/data/monitor_data.py index 23ed786958..62ccbbf639 100644 --- a/tidy3d/plugins/adjoint/components/data/monitor_data.py +++ b/tidy3d/plugins/adjoint/components/data/monitor_data.py @@ -3,36 +3,37 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Dict, List, Union +from typing import Any, Union import jax.numpy as jnp import numpy as np import pydantic.v1 as pd from jax.tree_util import register_pytree_node_class -from .....components.base import cached_property -from .....components.data.data_array import ( +from tidy3d.components.base import cached_property +from tidy3d.components.data.data_array import ( FreqModeDataArray, MixedModeDataArray, ModeAmpsDataArray, ScalarFieldDataArray, ) -from .....components.data.dataset import FieldDataset -from .....components.data.monitor_data import ( +from tidy3d.components.data.dataset import FieldDataset +from tidy3d.components.data.monitor_data import ( DiffractionData, FieldData, ModeData, ModeSolverData, MonitorData, ) -from .....components.geometry.base import Box -from .....components.source.base import Source -from .....components.source.current import CustomCurrentSource, PointDipole -from .....components.source.field import CustomFieldSource, ModeSource, PlaneWave -from .....components.source.time import GaussianPulse -from .....constants import C_0, ETA_0, MU_0 -from .....exceptions import AdjointError -from ..base import JaxObject +from tidy3d.components.geometry.base import Box +from tidy3d.components.source.base import Source +from tidy3d.components.source.current import CustomCurrentSource, PointDipole +from tidy3d.components.source.field import CustomFieldSource, ModeSource, PlaneWave +from tidy3d.components.source.time import GaussianPulse +from tidy3d.constants import C_0, ETA_0, MU_0 +from tidy3d.exceptions import AdjointError +from tidy3d.plugins.adjoint.components.base import JaxObject + from .data_array import JaxDataArray @@ -54,7 +55,7 @@ def from_monitor_data(cls, mnt_data: MonitorData) -> JaxMonitorData: return cls.parse_obj(self_dict) @abstractmethod - def to_adjoint_sources(self, fwidth: float) -> List[Source]: + def to_adjoint_sources(self, fwidth: float) -> list[Source]: """Construct a list of adjoint sources from this :class:`.JaxMonitorData`.""" @staticmethod @@ -87,7 +88,7 @@ class JaxModeData(JaxMonitorData, ModeData): jax_field=True, ) - def to_adjoint_sources(self, fwidth: float) -> List[ModeSource]: + def to_adjoint_sources(self, fwidth: float) -> list[ModeSource]: """Converts a :class:`.ModeData` to a list of adjoint :class:`.ModeSource`.""" amps, sel_coords = self.amps.nonzero_val_coords @@ -167,7 +168,7 @@ def __contains__(self, item: str) -> bool: def __getitem__(self, item: str) -> bool: return self.field_components[item] - def package_colocate_results(self, centered_fields: Dict[str, ScalarFieldDataArray]) -> Any: + def package_colocate_results(self, centered_fields: dict[str, ScalarFieldDataArray]) -> Any: """How to package the dictionary of fields computed via self.colocate().""" return self.updated_copy(**centered_fields) @@ -243,7 +244,7 @@ def time_reversed_copy(self) -> FieldData: "'time_reversed_copy' is not yet supported in the adjoint plugin." ) - def to_adjoint_sources(self, fwidth: float) -> List[CustomFieldSource]: + def to_adjoint_sources(self, fwidth: float) -> list[CustomFieldSource]: """Converts a :class:`.JaxFieldData` to a list of adjoint :class:`.CustomFieldSource.""" interpolate_source = True @@ -408,7 +409,7 @@ def power(self) -> JaxDataArray: return JaxDataArray(values=power_values, coords=power_coords) - def to_adjoint_sources(self, fwidth: float) -> List[PlaneWave]: + def to_adjoint_sources(self, fwidth: float) -> list[PlaneWave]: """Converts a :class:`.DiffractionData` to a list of adjoint :class:`.PlaneWave`.""" # extract the values coordinates of the non-zero amplitudes @@ -428,7 +429,11 @@ def to_adjoint_sources(self, fwidth: float) -> List[PlaneWave]: continue # select the propagation angles from the data - angle_sel_kwargs = dict(orders_x=int(order_x), orders_y=int(order_y), f=float(freq)) + angle_sel_kwargs = { + "orders_x": int(order_x), + "orders_y": int(order_y), + "f": float(freq), + } angle_theta = float(theta_data.sel(**angle_sel_kwargs)) angle_phi = float(phi_data.sel(**angle_sel_kwargs)) diff --git a/tidy3d/plugins/adjoint/components/data/sim_data.py b/tidy3d/plugins/adjoint/components/data/sim_data.py index 7568c9135e..d9f9cff5b0 100644 --- a/tidy3d/plugins/adjoint/components/data/sim_data.py +++ b/tidy3d/plugins/adjoint/components/data/sim_data.py @@ -2,20 +2,21 @@ from __future__ import annotations -from typing import Dict, List, Tuple, Union +from typing import Optional, Union import numpy as np import pydantic.v1 as pd import xarray as xr from jax.tree_util import register_pytree_node_class -from .....components.data.monitor_data import FieldData, MonitorDataType, PermittivityData -from .....components.data.sim_data import SimulationData -from .....components.source.current import PointDipole -from .....components.source.time import GaussianPulse -from .....log import log -from ..base import JaxObject -from ..simulation import JaxInfo, JaxSimulation +from tidy3d.components.data.monitor_data import FieldData, MonitorDataType, PermittivityData +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.source.current import PointDipole +from tidy3d.components.source.time import GaussianPulse +from tidy3d.log import log +from tidy3d.plugins.adjoint.components.base import JaxObject +from tidy3d.plugins.adjoint.components.simulation import JaxInfo, JaxSimulation + from .monitor_data import JAX_MONITOR_DATA_MAP, JaxMonitorDataType @@ -23,20 +24,20 @@ class JaxSimulationData(SimulationData, JaxObject): """A :class:`.SimulationData` registered with jax.""" - output_data: Tuple[JaxMonitorDataType, ...] = pd.Field( + output_data: tuple[JaxMonitorDataType, ...] = pd.Field( (), title="Jax Data", description="Tuple of Jax-compatible data associated with output monitors.", jax_field=True, ) - grad_data: Tuple[FieldData, ...] = pd.Field( + grad_data: tuple[FieldData, ...] = pd.Field( (), title="Gradient Field Data", description="Tuple of monitor data storing fields associated with the input structures.", ) - grad_eps_data: Tuple[PermittivityData, ...] = pd.Field( + grad_eps_data: tuple[PermittivityData, ...] = pd.Field( (), title="Gradient Permittivity Data", description="Tuple of monitor data storing epsilon associated with the input structures.", @@ -83,22 +84,22 @@ def get_poynting_vector(self, field_monitor_name: str) -> xr.Dataset: return super().get_poynting_vector(field_monitor_name) @property - def grad_data_symmetry(self) -> Tuple[FieldData, ...]: + def grad_data_symmetry(self) -> tuple[FieldData, ...]: """``self.grad_data`` but with ``symmetry_expanded_copy`` applied.""" return tuple(data.symmetry_expanded_copy for data in self.grad_data) @property - def grad_eps_data_symmetry(self) -> Tuple[FieldData, ...]: + def grad_eps_data_symmetry(self) -> tuple[FieldData, ...]: """``self.grad_eps_data`` but with ``symmetry_expanded_copy`` applied.""" return tuple(data.symmetry_expanded_copy for data in self.grad_eps_data) @property - def output_monitor_data(self) -> Dict[str, JaxMonitorDataType]: + def output_monitor_data(self) -> dict[str, JaxMonitorDataType]: """Dictionary of ``.output_data`` monitor ``.name`` to the corresponding data.""" return {monitor_data.monitor.name: monitor_data for monitor_data in self.output_data} @property - def monitor_data(self) -> Dict[str, Union[JaxMonitorDataType, MonitorDataType]]: + def monitor_data(self) -> dict[str, Union[JaxMonitorDataType, MonitorDataType]]: """Dictionary of ``.output_data`` monitor ``.name`` to the corresponding data.""" reg_mnt_data = {monitor_data.monitor.name: monitor_data for monitor_data in self.data} reg_mnt_data.update(self.output_monitor_data) @@ -106,8 +107,8 @@ def monitor_data(self) -> Dict[str, Union[JaxMonitorDataType, MonitorDataType]]: @staticmethod def split_data( - mnt_data: List[MonitorDataType], jax_info: JaxInfo - ) -> Dict[str, List[MonitorDataType]]: + mnt_data: list[MonitorDataType], jax_info: JaxInfo + ) -> dict[str, list[MonitorDataType]]: """Split list of monitor data into data, output_data, grad_data, and grad_eps_data.""" # Get information needed to split the full data list len_output_data = jax_info.num_output_monitors @@ -124,13 +125,16 @@ def split_data( ] grad_eps_data = all_data[len_data + len_output_data + len_grad_data :] - return dict( - data=data, output_data=output_data, grad_data=grad_data, grad_eps_data=grad_eps_data - ) + return { + "data": data, + "output_data": output_data, + "grad_data": grad_data, + "grad_eps_data": grad_eps_data, + } @classmethod def from_sim_data( - cls, sim_data: SimulationData, jax_info: JaxInfo, task_id: str = None + cls, sim_data: SimulationData, jax_info: JaxInfo, task_id: Optional[str] = None ) -> JaxSimulationData: """Construct a :class:`.JaxSimulationData` instance from a :class:`.SimulationData`.""" @@ -159,14 +163,14 @@ def from_sim_data( output_data_list.append(jax_mnt_data) data_dict["output_data"] = output_data_list self_dict.update(data_dict) - self_dict.update(dict(task_id=task_id)) + self_dict.update({"task_id": task_id}) return cls.parse_obj(self_dict) @classmethod def split_fwd_sim_data( cls, sim_data: SimulationData, jax_info: JaxInfo - ) -> Tuple[SimulationData, SimulationData]: + ) -> tuple[SimulationData, SimulationData]: """Split a :class:`.SimulationData` into two parts, containing user and gradient data.""" sim = sim_data.simulation @@ -230,14 +234,14 @@ def make_adjoint_simulation(self, fwidth: float, run_time: float) -> JaxSimulati # set a very short run time relative to the fwidth run_time = 2 / fwidth - update_dict = dict( - boundary_spec=bc_adj, - sources=adj_srcs, - monitors=(), - output_monitors=(), - run_time=run_time, - normalize_index=None, # normalize later, frequency-by-frequency - ) + update_dict = { + "boundary_spec": bc_adj, + "sources": adj_srcs, + "monitors": (), + "output_monitors": (), + "run_time": run_time, + "normalize_index": None, # normalize later, frequency-by-frequency + } update_dict.update( sim_fwd.get_grad_monitors( @@ -252,7 +256,7 @@ def make_adjoint_simulation(self, fwidth: float, run_time: float) -> JaxSimulati if len(sim_fwd.sources) and grid_spec_fwd.wavelength is None: wavelength_fwd = grid_spec_fwd.wavelength_from_sources(sim_fwd.sources) grid_spec_adj = grid_spec_fwd.updated_copy(wavelength=wavelength_fwd) - update_dict.update(dict(grid_spec=grid_spec_adj)) + update_dict.update({"grid_spec": grid_spec_adj}) return sim_fwd.updated_copy(**update_dict) @@ -272,7 +276,7 @@ def normalize_adjoint_fields(self) -> JaxSimulationData: spectrum_fn = self.source_spectrum(source_index) norm_factor_f[i] = complex(spectrum_fn([freq])[0]) - norm_factor_f_darr = xr.DataArray(norm_factor_f, coords=dict(f=freqs)) + norm_factor_f_darr = xr.DataArray(norm_factor_f, coords={"f": freqs}) field_component_norm = field_component / norm_factor_f_darr field_components_norm[field_name] = field_component_norm diff --git a/tidy3d/plugins/adjoint/components/geometry.py b/tidy3d/plugins/adjoint/components/geometry.py index 578d8f53aa..44f0424078 100644 --- a/tidy3d/plugins/adjoint/components/geometry.py +++ b/tidy3d/plugins/adjoint/components/geometry.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC -from typing import Dict, List, Tuple, Union +from typing import Union import jax import jax.numpy as jnp @@ -14,21 +14,22 @@ from jax.tree_util import register_pytree_node_class from joblib import Parallel, delayed -from ....components.base import cached_property -from ....components.data.data_array import ScalarFieldDataArray -from ....components.data.monitor_data import FieldData, PermittivityData -from ....components.geometry.base import Box, Geometry, GeometryGroup -from ....components.geometry.polyslab import ( +from tidy3d.components.base import cached_property +from tidy3d.components.data.data_array import ScalarFieldDataArray +from tidy3d.components.data.monitor_data import FieldData, PermittivityData +from tidy3d.components.geometry.base import Box, Geometry, GeometryGroup +from tidy3d.components.geometry.polyslab import ( _COMPLEX_POLYSLAB_DIVISIONS_WARN, _IS_CLOSE_RTOL, PolySlab, ) -from ....components.monitor import FieldMonitor, PermittivityMonitor -from ....components.types import ArrayFloat2D, Bound, Coordinate2D # , annotate_type -from ....constants import MICROMETER, fp_eps -from ....exceptions import AdjointError -from ....log import log -from ...polyslab import ComplexPolySlab +from tidy3d.components.monitor import FieldMonitor, PermittivityMonitor +from tidy3d.components.types import ArrayFloat2D, Bound, Coordinate2D # , annotate_type +from tidy3d.constants import MICROMETER, fp_eps +from tidy3d.exceptions import AdjointError +from tidy3d.log import log +from tidy3d.plugins.polyslab import ComplexPolySlab + from .base import WEB_ADJOINT_MESSAGE, JaxObject from .types import JaxFloat @@ -46,13 +47,13 @@ class JaxGeometry(Geometry, ABC): """Abstract :class:`.Geometry` with methods useful for all Jax subclasses.""" @property - def bound_size(self) -> Tuple[float, float, float]: + def bound_size(self) -> tuple[float, float, float]: """Size of the bounding box of this geometry.""" rmin, rmax = self.bounds return tuple(abs(pt_max - pt_min) for (pt_min, pt_max) in zip(rmin, rmax)) @property - def bound_center(self) -> Tuple[float, float, float]: + def bound_center(self) -> tuple[float, float, float]: """Size of the bounding box of this geometry.""" rmin, rmax = self.bounds @@ -76,8 +77,8 @@ def bounding_box(self): return JaxBox.from_bounds(*self.bounds) def make_grad_monitors( - self, freqs: List[float], name: str - ) -> Tuple[FieldMonitor, PermittivityMonitor]: + self, freqs: list[float], name: str + ) -> tuple[FieldMonitor, PermittivityMonitor]: """Return gradient monitor associated with this object.""" size_enlarged = tuple(s + 2 * GRAD_MONITOR_EXPANSION for s in self.bound_size) field_mnt = FieldMonitor( @@ -100,7 +101,7 @@ def make_grad_monitors( @staticmethod def compute_dotted_e_d_fields( grad_data_fwd: FieldData, grad_data_adj: FieldData, grad_data_eps: PermittivityData - ) -> Tuple[Dict[str, ScalarFieldDataArray], Dict[str, ScalarFieldDataArray]]: + ) -> tuple[dict[str, ScalarFieldDataArray], dict[str, ScalarFieldDataArray]]: """Get the (x,y,z) components of E_fwd * E_adj and D_fwd * D_adj fields in the domain.""" e_mult_xyz = {} @@ -133,7 +134,7 @@ class JaxBox(JaxGeometry, Box, JaxObject): _tidy3d_class = Box - center_jax: Tuple[JaxFloat, JaxFloat, JaxFloat] = pd.Field( + center_jax: tuple[JaxFloat, JaxFloat, JaxFloat] = pd.Field( (0.0, 0.0, 0.0), title="Center (Jax)", description="Jax traced value for the center of the box in (x, y, z).", @@ -141,7 +142,7 @@ class JaxBox(JaxGeometry, Box, JaxObject): stores_jax_for="center", ) - size_jax: Tuple[JaxFloat, JaxFloat, JaxFloat] = pd.Field( + size_jax: tuple[JaxFloat, JaxFloat, JaxFloat] = pd.Field( ..., title="Size (Jax)", description="Jax-traced value for the size of the box in (x, y, z).", @@ -266,7 +267,7 @@ def store_vjp( # convert surface vjps to center, size vjps. Note, convert these to jax types w/ np.sum() vjp_center = tuple(np.sum(vjp_surfs[dim][1] - vjp_surfs[dim][0]) for dim in "xyz") vjp_size = tuple(np.sum(0.5 * (vjp_surfs[dim][1] + vjp_surfs[dim][0])) for dim in "xyz") - return self.copy(update=dict(center_jax=vjp_center, size_jax=vjp_size)) + return self.copy(update={"center_jax": vjp_center, "size_jax": vjp_size}) @register_pytree_node_class @@ -275,7 +276,7 @@ class JaxPolySlab(JaxGeometry, PolySlab, JaxObject): _tidy3d_class = PolySlab - vertices_jax: Tuple[Tuple[JaxFloat, JaxFloat], ...] = pd.Field( + vertices_jax: tuple[tuple[JaxFloat, JaxFloat], ...] = pd.Field( ..., title="Vertices (Jax)", description="Jax-traced list of (d1, d2) defining the 2 dimensional positions of the " @@ -286,7 +287,7 @@ class JaxPolySlab(JaxGeometry, PolySlab, JaxObject): stores_jax_for="vertices", ) - slab_bounds_jax: Tuple[JaxFloat, JaxFloat] = pd.Field( + slab_bounds_jax: tuple[JaxFloat, JaxFloat] = pd.Field( ..., title="Slab bounds (Jax)", description="Jax-traced list of (h1, h2) defining the minimum and maximum positions " @@ -383,7 +384,7 @@ def _area(vertices: jnp.ndarray) -> float: @staticmethod def _shift_vertices( vertices: jnp.ndarray, dist - ) -> Tuple[jnp.ndarray, jnp.ndarray, Tuple[jnp.ndarray, jnp.ndarray]]: + ) -> tuple[jnp.ndarray, jnp.ndarray, tuple[jnp.ndarray, jnp.ndarray]]: """Shifts the vertices of a polygon outward uniformly by distances `dists`. @@ -468,7 +469,7 @@ def _neighbor_vertices_crossing_detection( return None @staticmethod - def _edge_length_and_reduction_rate(vertices: jnp.ndarray) -> Tuple[jnp.ndarray, jnp.ndarray]: + def _edge_length_and_reduction_rate(vertices: jnp.ndarray) -> tuple[jnp.ndarray, jnp.ndarray]: """Edge length of reduction rate of each edge with unit offset length. Parameters @@ -565,8 +566,8 @@ def edge_contrib( vertex_grad: Coordinate2D, vertex_stat: Coordinate2D, is_next: bool, - e_mult_xyz: Tuple[Dict[str, ScalarFieldDataArray]], - d_mult_xyz: Tuple[Dict[str, ScalarFieldDataArray]], + e_mult_xyz: tuple[dict[str, ScalarFieldDataArray]], + d_mult_xyz: tuple[dict[str, ScalarFieldDataArray]], sim_bounds: Bound, wvl_mat: float, eps_out: complex, @@ -606,8 +607,8 @@ def edge_position(s: np.array) -> np.array: return (1 - s) * vertex_stat[:, None] + s * vertex_grad[:, None] def edge_basis( - xyz_components: Tuple[FieldData, FieldData, FieldData], - ) -> Tuple[FieldData, FieldData, FieldData]: + xyz_components: tuple[FieldData, FieldData, FieldData], + ) -> tuple[FieldData, FieldData, FieldData]: """Puts a field component from the (x, y, z) basis to the (t, n, z) basis.""" cmp_z, (cmp_x_edge, cmp_y_edge) = self.pop_axis(xyz_components, axis=self.axis) @@ -623,7 +624,7 @@ def compute_integrand(s: np.array, z: np.array) -> np.array: x, y = edge_position(s=s) x = xr.DataArray(x, coords={"s": s}) y = xr.DataArray(y, coords={"s": s}) - coords_interp = dict(x=x, y=y, z=z) + coords_interp = {"x": x, "y": y, "z": z} def evaluate(scalar_field: ScalarFieldDataArray) -> float: """Evaluate a scalar field at a coordinate along the edge.""" @@ -696,8 +697,8 @@ def evaluate(scalar_field: ScalarFieldDataArray) -> float: def vertex_vjp( self, i_vertex, - e_mult_xyz: Tuple[Dict[str, ScalarFieldDataArray]], - d_mult_xyz: Tuple[Dict[str, ScalarFieldDataArray]], + e_mult_xyz: tuple[dict[str, ScalarFieldDataArray]], + d_mult_xyz: tuple[dict[str, ScalarFieldDataArray]], sim_bounds: Bound, wvl_mat: float, eps_out: complex, @@ -791,7 +792,7 @@ def _make_vertex_args( arg_list = [] for i in range(num_verts): - args_i = [i] + [e_mult_xyz, d_mult_xyz, sim_bounds, wvl_mat, eps_out, eps_in] + args_i = [i, e_mult_xyz, d_mult_xyz, sim_bounds, wvl_mat, eps_out, eps_in] arg_list.append(args_i) return arg_list @@ -870,7 +871,7 @@ def _dilation_value_at_reference_to_coord(self, dilation: float) -> float: return z_coord @property - def sub_polyslabs(self) -> List[JaxPolySlab]: + def sub_polyslabs(self) -> list[JaxPolySlab]: """Divide a complex polyslab into a list of simple polyslabs. Only neighboring vertex-vertex crossing events are treated in this version. @@ -992,7 +993,7 @@ class JaxGeometryGroup(JaxGeometry, GeometryGroup, JaxObject): _tidy3d_class = GeometryGroup - geometries: Tuple[JaxPolySlab, ...] = pd.Field( + geometries: tuple[JaxPolySlab, ...] = pd.Field( ..., title="Geometries", description="Tuple of jax geometries in a single grouping. " diff --git a/tidy3d/plugins/adjoint/components/medium.py b/tidy3d/plugins/adjoint/components/medium.py index f87a3ec9a2..75f506a8d4 100644 --- a/tidy3d/plugins/adjoint/components/medium.py +++ b/tidy3d/plugins/adjoint/components/medium.py @@ -3,19 +3,20 @@ from __future__ import annotations from abc import ABC -from typing import Callable, Dict, Optional, Tuple, Union +from typing import Callable, Literal, Optional, Union import numpy as np import pydantic.v1 as pd import xarray as xr from jax.tree_util import register_pytree_node_class -from ....components.data.monitor_data import FieldData -from ....components.geometry.base import Geometry -from ....components.medium import AnisotropicMedium, CustomMedium, Medium -from ....components.types import Bound, Literal -from ....constants import CONDUCTIVITY -from ....exceptions import SetupError +from tidy3d.components.data.monitor_data import FieldData +from tidy3d.components.geometry.base import Geometry +from tidy3d.components.medium import AnisotropicMedium, CustomMedium, Medium +from tidy3d.components.types import Bound +from tidy3d.constants import CONDUCTIVITY +from tidy3d.exceptions import SetupError + from .base import WEB_ADJOINT_MESSAGE, JaxObject from .data.data_array import JaxDataArray from .data.dataset import JaxPermittivityDataset @@ -33,7 +34,7 @@ class AbstractJaxMedium(ABC, JaxObject): def _get_volume_disc( self, grad_data: FieldData, sim_bounds: Bound, wvl_mat: float - ) -> Tuple[Dict[str, np.ndarray], float]: + ) -> tuple[dict[str, np.ndarray], float]: """Get the coordinates and volume element for the inside of the corresponding structure.""" # find intersecting volume between structure and simulation @@ -63,7 +64,7 @@ def _get_volume_disc( return vol_coords, d_vol @staticmethod - def make_inside_mask(vol_coords: Dict[str, np.ndarray], inside_fn: Callable) -> xr.DataArray: + def make_inside_mask(vol_coords: dict[str, np.ndarray], inside_fn: Callable) -> xr.DataArray: """Make a 3D mask of where the volume coordinates are inside a supplied function.""" meshgrid_args = [vol_coords[dim] for dim in "xyz" if dim in vol_coords] @@ -77,7 +78,7 @@ def e_mult_volume( field: Literal["Ex", "Ey", "Ez"], grad_data_fwd: FieldData, grad_data_adj: FieldData, - vol_coords: Dict[str, np.ndarray], + vol_coords: dict[str, np.ndarray], d_vol: float, inside_fn: Callable, ) -> xr.DataArray: @@ -188,10 +189,10 @@ def store_vjp( vjp_sigma += _vjp_sigma return self.copy( - update=dict( - permittivity_jax=vjp_eps, - conductivity_jax=vjp_sigma, - ) + update={ + "permittivity_jax": vjp_eps, + "conductivity_jax": vjp_sigma, + } ) @@ -444,7 +445,7 @@ def store_vjp( # package everything into dataset vjp_eps_dataset = JaxPermittivityDataset(**vjp_field_components) - return self.copy(update=dict(eps_dataset=vjp_eps_dataset)) + return self.copy(update={"eps_dataset": vjp_eps_dataset}) JaxMediumType = Union[JaxMedium, JaxAnisotropicMedium, JaxCustomMedium] diff --git a/tidy3d/plugins/adjoint/components/simulation.py b/tidy3d/plugins/adjoint/components/simulation.py index 04f623f37d..3fcfdf0066 100644 --- a/tidy3d/plugins/adjoint/components/simulation.py +++ b/tidy3d/plugins/adjoint/components/simulation.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, List, Literal, Tuple, Union +from typing import Literal, Optional, Union import numpy as np import pydantic.v1 as pd @@ -10,24 +10,25 @@ from jax.tree_util import register_pytree_node_class from joblib import Parallel, delayed -from ....components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing -from ....components.data.monitor_data import FieldData, PermittivityData -from ....components.geometry.base import Box -from ....components.medium import AbstractMedium -from ....components.monitor import ( +from tidy3d.components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing +from tidy3d.components.data.monitor_data import FieldData, PermittivityData +from tidy3d.components.geometry.base import Box +from tidy3d.components.medium import AbstractMedium +from tidy3d.components.monitor import ( DiffractionMonitor, FieldMonitor, ModeMonitor, Monitor, PermittivityMonitor, ) -from ....components.simulation import Simulation -from ....components.structure import Structure -from ....components.subpixel_spec import Staircasing, SubpixelSpec -from ....components.types import Ax, annotate_type -from ....constants import HERTZ, SECOND -from ....exceptions import AdjointError -from ....log import log +from tidy3d.components.simulation import Simulation +from tidy3d.components.structure import Structure +from tidy3d.components.subpixel_spec import Staircasing, SubpixelSpec +from tidy3d.components.types import Ax, annotate_type +from tidy3d.constants import HERTZ, SECOND +from tidy3d.exceptions import AdjointError +from tidy3d.log import log + from .base import WEB_ADJOINT_MESSAGE, JaxObject from .geometry import JaxGeometryGroup, JaxPolySlab from .structure import ( @@ -61,7 +62,7 @@ ) OutputMonitorTypes = (DiffractionMonitor, FieldMonitor, ModeMonitor) -OutputMonitorType = Tuple[annotate_type(Union[OutputMonitorTypes]), ...] +OutputMonitorType = tuple[annotate_type(Union[OutputMonitorTypes]), ...] class JaxInfo(Tidy3dBaseModel): @@ -105,7 +106,7 @@ class JaxInfo(Tidy3dBaseModel): units=SECOND, ) - input_structure_types: Tuple[ + input_structure_types: tuple[ Literal["JaxStructure", "JaxStructureStaticMedium", "JaxStructureStaticGeometry"], ... ] = pd.Field( (), @@ -118,7 +119,7 @@ class JaxInfo(Tidy3dBaseModel): class JaxSimulation(Simulation, JaxObject): """A :class:`.Simulation` registered with jax.""" - input_structures: Tuple[annotate_type(JaxStructureType), ...] = pd.Field( + input_structures: tuple[annotate_type(JaxStructureType), ...] = pd.Field( (), title="Input Structures", description="Tuple of jax-compatible structures" @@ -132,13 +133,13 @@ class JaxSimulation(Simulation, JaxObject): description="Tuple of monitors whose data the differentiable output depends on.", ) - grad_monitors: Tuple[FieldMonitor, ...] = pd.Field( + grad_monitors: tuple[FieldMonitor, ...] = pd.Field( (), title="Gradient Field Monitors", description="Tuple of monitors used for storing fields, used internally for gradients.", ) - grad_eps_monitors: Tuple[PermittivityMonitor, ...] = pd.Field( + grad_eps_monitors: tuple[PermittivityMonitor, ...] = pd.Field( (), title="Gradient Permittivity Monitors", description="Tuple of monitors used for storing epsilon, used internally for gradients.", @@ -296,7 +297,7 @@ def _validate_web_adjoint(self) -> None: structure._validate_web_adjoint() @staticmethod - def get_freqs_adjoint(output_monitors: List[Monitor]) -> List[float]: + def get_freqs_adjoint(output_monitors: list[Monitor]) -> list[float]: """Return sorted list of unique frequencies stripped from a collection of monitors.""" if len(output_monitors) == 0: @@ -310,7 +311,7 @@ def get_freqs_adjoint(output_monitors: List[Monitor]) -> List[float]: return np.unique(output_freqs).tolist() @cached_property - def freqs_adjoint(self) -> List[float]: + def freqs_adjoint(self) -> list[float]: """Return sorted list of frequencies stripped from the output monitors.""" return self.get_freqs_adjoint(output_monitors=self.output_monitors) @@ -400,7 +401,7 @@ def num_time_steps_adjoint(self) -> int: """Number of time steps in the adjoint simulation.""" return len(self.tmesh_adjoint) - def to_simulation(self) -> Tuple[Simulation, JaxInfo]: + def to_simulation(self) -> tuple[Simulation, JaxInfo]: """Convert :class:`.JaxSimulation` instance to :class:`.Simulation` with an info dict.""" sim_dict = self.dict( @@ -447,13 +448,13 @@ def to_simulation(self) -> Tuple[Simulation, JaxInfo]: def to_gds( self, cell, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, permittivity_threshold: pd.NonNegativeFloat = 1, frequency: pd.PositiveFloat = 0, - gds_layer_dtype_map: Dict[ - AbstractMedium, Tuple[pd.NonNegativeInt, pd.NonNegativeInt] + gds_layer_dtype_map: Optional[ + dict[AbstractMedium, tuple[pd.NonNegativeInt, pd.NonNegativeInt]] ] = None, ) -> None: """Append the simulation structures to a .gds cell. @@ -488,15 +489,15 @@ def to_gds( def to_gdstk( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, permittivity_threshold: pd.NonNegativeFloat = 1, frequency: pd.PositiveFloat = 0, - gds_layer_dtype_map: Dict[ - AbstractMedium, Tuple[pd.NonNegativeInt, pd.NonNegativeInt] + gds_layer_dtype_map: Optional[ + dict[AbstractMedium, tuple[pd.NonNegativeInt, pd.NonNegativeInt]] ] = None, - ) -> List: + ) -> list: """Convert a simulation's planar slice to a .gds type polygon list. Parameters ---------- @@ -530,14 +531,14 @@ def to_gdstk( def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - source_alpha: float = None, - monitor_alpha: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + source_alpha: Optional[float] = None, + monitor_alpha: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, **patch_kwargs, ) -> Ax: """Wrapper around regular :class:`.Simulation` structure plotting.""" @@ -556,15 +557,15 @@ def plot( def plot_eps( self, - x: float = None, - y: float = None, - z: float = None, - freq: float = None, - alpha: float = None, - source_alpha: float = None, - monitor_alpha: float = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, + source_alpha: Optional[float] = None, + monitor_alpha: Optional[float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ax: Ax = None, ) -> Ax: """Wrapper around regular :class:`.Simulation` permittivity plotting.""" @@ -582,12 +583,12 @@ def plot_eps( def plot_structures( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ) -> Ax: """Plot each of simulation's structures on a plane defined by one nonzero x,y,z coordinate. @@ -623,16 +624,16 @@ def plot_structures( def plot_structures_eps( self, - x: float = None, - y: float = None, - z: float = None, - freq: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, - hlim: Tuple[float, float] = None, - vlim: Tuple[float, float] = None, + hlim: Optional[tuple[float, float]] = None, + vlim: Optional[tuple[float, float]] = None, ) -> Ax: """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. @@ -686,7 +687,7 @@ def epsilon( self, box: Box, coord_key: str = "centers", - freq: float = None, + freq: Optional[float] = None, ) -> xr.DataArray: """Get array of permittivity at volume specified by box and freq. @@ -727,7 +728,7 @@ def __eq__(self, other: JaxSimulation) -> bool: return self.to_simulation()[0] == other.to_simulation()[0] @classmethod - def split_monitors(cls, monitors: List[Monitor], jax_info: JaxInfo) -> Dict[str, Monitor]: + def split_monitors(cls, monitors: list[Monitor], jax_info: JaxInfo) -> dict[str, Monitor]: """Split monitors into user and adjoint required based on jax info.""" all_monitors = list(monitors) @@ -750,17 +751,17 @@ def split_monitors(cls, monitors: List[Monitor], jax_info: JaxInfo) -> Dict[str, grad_eps_monitors = all_monitors[num_mnts + num_output_monitors + num_grad_monitors :] # load into a dictionary - return dict( - monitors=monitors, - output_monitors=output_monitors, - grad_monitors=grad_monitors, - grad_eps_monitors=grad_eps_monitors, - ) + return { + "monitors": monitors, + "output_monitors": output_monitors, + "grad_monitors": grad_monitors, + "grad_eps_monitors": grad_eps_monitors, + } @classmethod def split_structures( - cls, structures: List[Structure], jax_info: JaxInfo - ) -> Dict[str, Structure]: + cls, structures: list[Structure], jax_info: JaxInfo + ) -> dict[str, Structure]: """Split structures into regular and input based on jax info.""" all_structures = list(structures) @@ -771,11 +772,11 @@ def split_structures( # split the list based on these numbers structures = all_structures[:num_structs] - structure_type_map = dict( - JaxStructure=JaxStructure, - JaxStructureStaticMedium=JaxStructureStaticMedium, - JaxStructureStaticGeometry=JaxStructureStaticGeometry, - ) + structure_type_map = { + "JaxStructure": JaxStructure, + "JaxStructureStaticMedium": JaxStructureStaticMedium, + "JaxStructureStaticGeometry": JaxStructureStaticGeometry, + } input_structures = [] for struct_type_str, struct in zip( @@ -786,7 +787,7 @@ def split_structures( input_structures.append(new_structure) # return a dictionary containing these split structures - return dict(structures=structures, input_structures=input_structures) + return {"structures": structures, "input_structures": input_structures} @classmethod def from_simulation(cls, simulation: Simulation, jax_info: JaxInfo) -> JaxSimulation: @@ -802,17 +803,17 @@ def from_simulation(cls, simulation: Simulation, jax_info: JaxInfo) -> JaxSimula sim_dict.update(**structures) sim_dict.update(**monitors) sim_dict.update( - dict( - fwidth_adjoint=jax_info.fwidth_adjoint, - run_time_adjoint=jax_info.run_time_adjoint, - ) + { + "fwidth_adjoint": jax_info.fwidth_adjoint, + "run_time_adjoint": jax_info.run_time_adjoint, + } ) # load JaxSimulation from the dictionary return cls.parse_obj(sim_dict) @classmethod - def make_sim_fwd(cls, simulation: Simulation, jax_info: JaxInfo) -> Tuple[Simulation, JaxInfo]: + def make_sim_fwd(cls, simulation: Simulation, jax_info: JaxInfo) -> tuple[Simulation, JaxInfo]: """Make the forward :class:`.JaxSimulation` from the supplied :class:`.Simulation`.""" mnt_dict = JaxSimulation.split_monitors(monitors=simulation.monitors, jax_info=jax_info) @@ -845,7 +846,7 @@ def make_sim_fwd(cls, simulation: Simulation, jax_info: JaxInfo) -> Tuple[Simula return sim_fwd, jax_info - def to_simulation_fwd(self) -> Tuple[Simulation, JaxInfo, JaxInfo]: + def to_simulation_fwd(self) -> tuple[Simulation, JaxInfo, JaxInfo]: """Like ``to_simulation()`` but the gradient monitors are included.""" simulation, jax_info = self.to_simulation() sim_fwd, jax_info_fwd = self.make_sim_fwd(simulation=simulation, jax_info=jax_info) @@ -853,7 +854,7 @@ def to_simulation_fwd(self) -> Tuple[Simulation, JaxInfo, JaxInfo]: @staticmethod def get_grad_monitors( - input_structures: List[Structure], freqs_adjoint: List[float], include_eps_mnts: bool = True + input_structures: list[Structure], freqs_adjoint: list[float], include_eps_mnts: bool = True ) -> dict: """Return dictionary of gradient monitors for simulation.""" grad_mnts = [] @@ -865,7 +866,7 @@ def get_grad_monitors( grad_mnts.append(grad_mnt) if include_eps_mnts: grad_eps_mnts.append(grad_eps_mnt) - return dict(grad_monitors=grad_mnts, grad_eps_monitors=grad_eps_mnts) + return {"grad_monitors": grad_mnts, "grad_eps_monitors": grad_eps_mnts} def _store_vjp_structure( self, @@ -890,9 +891,9 @@ def _store_vjp_structure( def store_vjp( self, - grad_data_fwd: Tuple[FieldData], - grad_data_adj: Tuple[FieldData], - grad_eps_data: Tuple[PermittivityData], + grad_data_fwd: tuple[FieldData], + grad_data_adj: tuple[FieldData], + grad_eps_data: tuple[PermittivityData], num_proc: int = NUM_PROC_LOCAL, ) -> JaxSimulation: """Store the vjp w.r.t. each input_structure as a sim using fwd and adj grad_data.""" @@ -913,25 +914,27 @@ def store_vjp( def store_vjp_sequential( self, - grad_data_fwd: Tuple[FieldData], - grad_data_adj: Tuple[FieldData], - grad_eps_data: Tuple[PermittivityData], + grad_data_fwd: tuple[FieldData], + grad_data_adj: tuple[FieldData], + grad_eps_data: tuple[PermittivityData], ) -> JaxSimulation: """Store the vjp w.r.t. each input_structure without multiprocessing.""" map_args = [self.input_structures, grad_data_fwd, grad_data_adj, grad_eps_data] input_structures_vjp = list(map(self._store_vjp_structure, *map_args)) return self.copy( - update=dict( - input_structures=input_structures_vjp, grad_monitors=(), grad_eps_monitors=() - ) + update={ + "input_structures": input_structures_vjp, + "grad_monitors": (), + "grad_eps_monitors": (), + } ) def store_vjp_parallel( self, - grad_data_fwd: Tuple[FieldData], - grad_data_adj: Tuple[FieldData], - grad_eps_data: Tuple[PermittivityData], + grad_data_fwd: tuple[FieldData], + grad_data_adj: tuple[FieldData], + grad_eps_data: tuple[PermittivityData], num_proc: int, ) -> JaxSimulation: """Store the vjp w.r.t. each input_structure as a sim using fwd and adj grad_data, and @@ -988,7 +991,9 @@ def make_args(indexes, num_proc_internal) -> list: input_structures_vjp[index] = vjp return self.copy( - update=dict( - input_structures=input_structures_vjp, grad_monitors=(), grad_eps_monitors=() - ) + update={ + "input_structures": input_structures_vjp, + "grad_monitors": (), + "grad_eps_monitors": (), + } ) diff --git a/tidy3d/plugins/adjoint/components/structure.py b/tidy3d/plugins/adjoint/components/structure.py index d562816722..11c2276ca1 100644 --- a/tidy3d/plugins/adjoint/components/structure.py +++ b/tidy3d/plugins/adjoint/components/structure.py @@ -2,24 +2,25 @@ from __future__ import annotations -from typing import Dict, List, Union +from typing import Union import numpy as np import pydantic.v1 as pd from jax.tree_util import register_pytree_node_class -from ....components.data.monitor_data import FieldData, PermittivityData -from ....components.geometry.utils import GeometryType -from ....components.medium import MediumType -from ....components.monitor import FieldMonitor -from ....components.structure import Structure -from ....components.types import TYPE_TAG_STR, Bound -from ....constants import C_0 +from tidy3d.components.data.monitor_data import FieldData, PermittivityData +from tidy3d.components.geometry.utils import GeometryType +from tidy3d.components.medium import MediumType +from tidy3d.components.monitor import FieldMonitor +from tidy3d.components.structure import Structure +from tidy3d.components.types import TYPE_TAG_STR, Bound +from tidy3d.constants import C_0 + from .base import JaxObject from .geometry import JAX_GEOMETRY_MAP, JaxBox, JaxGeometryType from .medium import JAX_MEDIUM_MAP, JaxMediumType -GEO_MED_MAPPINGS = dict(geometry=JAX_GEOMETRY_MAP, medium=JAX_MEDIUM_MAP) +GEO_MED_MAPPINGS = {"geometry": JAX_GEOMETRY_MAP, "medium": JAX_MEDIUM_MAP} class AbstractJaxStructure(Structure, JaxObject): @@ -48,12 +49,12 @@ def _validate_web_adjoint(self) -> None: @property def jax_fields(self): """The fields that are jax-traced for this class.""" - return dict(geometry=self.geometry, medium=self.medium) + return {"geometry": self.geometry, "medium": self.medium} @property def exclude_fields(self): """Fields to exclude from the self dict.""" - return set(["type"] + list(self.jax_fields.keys())) + return {"type", *list(self.jax_fields.keys())} def to_structure(self) -> Structure: """Convert :class:`.JaxStructure` instance to :class:`.Structure`""" @@ -71,7 +72,7 @@ def from_structure(cls, structure: Structure) -> JaxStructure: struct_dict = structure.dict(exclude={"type"}) - jax_fields = dict(geometry=structure.geometry, medium=structure.medium) + jax_fields = {"geometry": structure.geometry, "medium": structure.medium} for key, component in jax_fields.items(): if key in cls._differentiable_fields: @@ -83,7 +84,7 @@ def from_structure(cls, structure: Structure) -> JaxStructure: return cls.parse_obj(struct_dict) - def make_grad_monitors(self, freqs: List[float], name: str) -> FieldMonitor: + def make_grad_monitors(self, freqs: list[float], name: str) -> FieldMonitor: """Return gradient monitor associated with this object.""" if "geometry" not in self._differentiable_fields: # make a fake JaxBox to be able to call .make_grad_monitors @@ -96,7 +97,7 @@ def make_grad_monitors(self, freqs: List[float], name: str) -> FieldMonitor: def _get_medium_params( self, grad_data_eps: PermittivityData, - ) -> Dict[str, float]: + ) -> dict[str, float]: """Compute params in the material of this structure.""" freq_max = float(max(grad_data_eps.eps_xx.f)) eps_in = self.medium.eps_model(frequency=freq_max) @@ -104,7 +105,7 @@ def _get_medium_params( ref_ind = max([1.0, abs(ref_ind)]) wvl_free_space = C_0 / freq_max wvl_mat = wvl_free_space / ref_ind - return dict(wvl_mat=wvl_mat, eps_in=eps_in) + return {"wvl_mat": wvl_mat, "eps_in": eps_in} def geometry_vjp( self, diff --git a/tidy3d/plugins/adjoint/components/types.py b/tidy3d/plugins/adjoint/components/types.py index fb1f01b580..9f191b4431 100644 --- a/tidy3d/plugins/adjoint/components/types.py +++ b/tidy3d/plugins/adjoint/components/types.py @@ -1,5 +1,7 @@ """Special types and validators used by adjoint plugin.""" +from __future__ import annotations + from typing import Any, Union import numpy as np @@ -32,10 +34,10 @@ class NumpyArrayType(np.ndarray): def __modify_schema__(cls, field_schema): """Sets the schema of np.ndarray object.""" - schema = dict( - title="npdarray", - type="numpy.ndarray", - ) + schema = { + "title": "npdarray", + "type": "numpy.ndarray", + } field_schema.update(schema) diff --git a/tidy3d/plugins/adjoint/utils/filter.py b/tidy3d/plugins/adjoint/utils/filter.py index 139aa5678b..29de7d9254 100644 --- a/tidy3d/plugins/adjoint/utils/filter.py +++ b/tidy3d/plugins/adjoint/utils/filter.py @@ -1,5 +1,7 @@ """Spatial filtering Functions for adjoint plugin.""" +from __future__ import annotations + from abc import ABC, abstractmethod import jax.numpy as jnp @@ -7,9 +9,9 @@ import numpy as np import pydantic.v1 as pd -from ....components.base import Tidy3dBaseModel -from ....constants import MICROMETER -from ....log import log +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.constants import MICROMETER +from tidy3d.log import log class Filter(Tidy3dBaseModel, ABC): diff --git a/tidy3d/plugins/adjoint/utils/penalty.py b/tidy3d/plugins/adjoint/utils/penalty.py index ae4892a3bf..6b736eec2a 100644 --- a/tidy3d/plugins/adjoint/utils/penalty.py +++ b/tidy3d/plugins/adjoint/utils/penalty.py @@ -1,14 +1,18 @@ """Penalty Functions for adjoint plugin.""" +from __future__ import annotations + from abc import ABC, abstractmethod +from typing import Optional import jax.numpy as jnp import pydantic.v1 as pd -from ....components.base import Tidy3dBaseModel -from ....components.types import ArrayFloat2D -from ....constants import MICROMETER -from ....log import log +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.components.types import ArrayFloat2D +from tidy3d.constants import MICROMETER +from tidy3d.log import log + from .filter import BinaryProjector, ConicFilter # Radius of Curvature Calculation @@ -227,7 +231,7 @@ def conic_filter(self) -> ConicFilter: """:class:`ConicFilter` associated with this object.""" return ConicFilter(radius=self.length_scale, design_region_dl=self.pixel_size) - def binary_projector(self, eta: float = None) -> BinaryProjector: + def binary_projector(self, eta: Optional[float] = None) -> BinaryProjector: """:class:`BinaryProjector` associated with this object.""" if eta is None: @@ -235,11 +239,11 @@ def binary_projector(self, eta: float = None) -> BinaryProjector: return BinaryProjector(eta=eta, beta=self.beta, vmin=0.0, vmax=1.0, strict_binarize=False) - def tanh_projection(self, x: jnp.ndarray, eta: float = None) -> jnp.ndarray: + def tanh_projection(self, x: jnp.ndarray, eta: Optional[float] = None) -> jnp.ndarray: """Project an array ``x`` once using ``self.beta`` and ``self.eta0``.""" return self.binary_projector(eta=eta).evaluate(x) - def filter_project(self, x: jnp.ndarray, eta: float = None) -> jnp.ndarray: + def filter_project(self, x: jnp.ndarray, eta: Optional[float] = None) -> jnp.ndarray: """Filter an array ``x`` using length scale and dL and then apply a projection.""" filter = self.conic_filter() projector = self.binary_projector(eta=eta) diff --git a/tidy3d/plugins/adjoint/web.py b/tidy3d/plugins/adjoint/web.py index f1031ed211..78182d9a69 100644 --- a/tidy3d/plugins/adjoint/web.py +++ b/tidy3d/plugins/adjoint/web.py @@ -1,24 +1,26 @@ """Adjoint-specific webapi.""" +from __future__ import annotations + import os import tempfile from functools import partial -from typing import Dict, List, Tuple +from typing import Optional import pydantic.v1 as pd from jax import custom_vjp from jax.tree_util import register_pytree_node_class import tidy3d as td +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.simulation import Simulation +from tidy3d.components.types import Literal from tidy3d.web.api.asynchronous import run_async as web_run_async +from tidy3d.web.api.container import DEFAULT_DATA_DIR, Batch, BatchData, Job from tidy3d.web.api.webapi import run as web_run from tidy3d.web.api.webapi import wait_for_connection from tidy3d.web.core.s3utils import download_file, upload_file -from ...components.data.sim_data import SimulationData -from ...components.simulation import Simulation -from ...components.types import Literal -from ...web.api.container import DEFAULT_DATA_DIR, Batch, BatchData, Job from .components.base import JaxObject from .components.data.sim_data import JaxSimulationData from .components.simulation import NUM_PROC_LOCAL, JaxInfo, JaxSimulation @@ -41,7 +43,7 @@ class RunResidual(JaxObject): class RunResidualBatch(JaxObject): """Class to store extra data needed to pass between the forward and backward adjoint run.""" - fwd_task_ids: Tuple[str, ...] = pd.Field( + fwd_task_ids: tuple[str, ...] = pd.Field( ..., title="Forward task_ids", description="task_ids of the forward simulations." ) @@ -50,7 +52,7 @@ class RunResidualBatch(JaxObject): class RunResidualAsync(JaxObject): """Class to store extra data needed to pass between the forward and backward adjoint run.""" - fwd_task_ids: Dict[str, str] = pd.Field( + fwd_task_ids: dict[str, str] = pd.Field( ..., title="Forward task_ids", description="task_ids of the forward simulation for async." ) @@ -70,7 +72,7 @@ def tidy3d_run_fn(simulation: Simulation, task_name: str, **kwargs) -> Simulatio return web_run(simulation=simulation, task_name=task_name, **kwargs) -def tidy3d_run_async_fn(simulations: Dict[str, Simulation], **kwargs) -> BatchData: +def tidy3d_run_async_fn(simulations: dict[str, Simulation], **kwargs) -> BatchData: """Run a set of regular :class:`.Simulation` objects after conversion from jax type.""" return web_run_async(simulations=simulations, **kwargs) @@ -83,7 +85,7 @@ def _run( task_name: str, folder_name: str = "default", path: str = "simulation_data.hdf5", - callback_url: str = None, + callback_url: Optional[str] = None, verbose: bool = True, ) -> JaxSimulationData: """Split the provided ``JaxSimulation`` into a regular ``Simulation`` and a ``JaxInfo`` part, @@ -109,7 +111,7 @@ def run( task_name: str, folder_name: str = "default", path: str = "simulation_data.hdf5", - callback_url: str = None, + callback_url: Optional[str] = None, verbose: bool = True, ) -> JaxSimulationData: """Submits a :class:`.JaxSimulation` to server, starts running, monitors progress, downloads, @@ -158,7 +160,7 @@ def run_fwd( path: str, callback_url: str, verbose: bool, -) -> Tuple[JaxSimulationData, Tuple[RunResidual]]: +) -> tuple[JaxSimulationData, tuple[RunResidual]]: """Run forward pass and stash extra objects for the backwards pass.""" simulation._validate_web_adjoint() @@ -191,7 +193,7 @@ def run_bwd( verbose: bool, res: tuple, sim_data_vjp: JaxSimulationData, -) -> Tuple[JaxSimulation]: +) -> tuple[JaxSimulation]: """Run backward pass and return simulation storing vjp of the objective w.r.t. the sim.""" fwd_task_id = res[0].fwd_task_id @@ -292,13 +294,13 @@ class AdjointBatch(Batch): description="Type of simulation, used internally only.", ) - jax_infos: Dict[str, JaxInfo] = pd.Field( + jax_infos: dict[str, JaxInfo] = pd.Field( ..., title="Jax Info Dict", description="Containers of information needed to reconstruct JaxSimulation for each item.", ) - jobs_cached: Dict[str, AdjointJob] = pd.Field( + jobs_cached: dict[str, AdjointJob] = pd.Field( None, title="Jobs (Cached)", description="Optional field to specify ``jobs``. Only used as a workaround internally " @@ -330,7 +332,7 @@ def webapi_run_adjoint_fwd( path: str, callback_url: str, verbose: bool, -) -> Dict[str, float]: +) -> dict[str, float]: """Runs the forward simulation on our servers, stores the gradient data for later.""" job = AdjointJob( @@ -390,13 +392,13 @@ def _task_name_orig(index: int): @partial(custom_vjp, nondiff_argnums=tuple(range(1, 6))) def run_async( - simulations: Tuple[JaxSimulation, ...], + simulations: tuple[JaxSimulation, ...], folder_name: str = "default", path_dir: str = DEFAULT_DATA_DIR, - callback_url: str = None, + callback_url: Optional[str] = None, verbose: bool = True, - num_workers: int = None, -) -> Tuple[JaxSimulationData, ...]: + num_workers: Optional[int] = None, +) -> tuple[JaxSimulationData, ...]: """Submits a set of :class:`.JaxSimulation` objects to server, starts running, monitors progress, downloads, and loads results as a tuple of :class:`.JaxSimulationData` objects. @@ -464,13 +466,13 @@ def run_async( def run_async_fwd( - simulations: Tuple[JaxSimulation, ...], + simulations: tuple[JaxSimulation, ...], folder_name: str, path_dir: str, callback_url: str, verbose: bool, num_workers: int, -) -> Tuple[Tuple[JaxSimulationData, ...], RunResidualBatch]: +) -> tuple[tuple[JaxSimulationData, ...], RunResidualBatch]: """Run forward pass and stash extra objects for the backwards pass.""" for simulation in simulations: @@ -514,8 +516,8 @@ def run_async_bwd( verbose: bool, num_workers: int, res: tuple, - batch_data_vjp: Tuple[JaxSimulationData, ...], -) -> Tuple[Dict[str, JaxSimulation]]: + batch_data_vjp: tuple[JaxSimulationData, ...], +) -> tuple[dict[str, JaxSimulation]]: """Run backward pass and return simulation storing vjp of the objective w.r.t. the sim.""" fwd_task_ids = res[0].fwd_task_ids @@ -553,13 +555,13 @@ def run_async_bwd( def webapi_run_async_adjoint_fwd( - simulations: Tuple[Simulation, ...], - jax_infos: Tuple[JaxInfo, ...], + simulations: tuple[Simulation, ...], + jax_infos: tuple[JaxInfo, ...], folder_name: str, path_dir: str, callback_url: str, verbose: bool, -) -> Tuple[BatchData, Dict[str, str]]: +) -> tuple[BatchData, dict[str, str]]: """Runs the forward simulations on our servers, stores the gradient data for later.""" task_names = [str(_task_name_orig(i)) for i in range(len(simulations))] @@ -581,14 +583,14 @@ def webapi_run_async_adjoint_fwd( def webapi_run_async_adjoint_bwd( - simulations: Tuple[Simulation, ...], - jax_infos: Tuple[JaxInfo, ...], + simulations: tuple[Simulation, ...], + jax_infos: tuple[JaxInfo, ...], folder_name: str, path_dir: str, callback_url: str, verbose: bool, - parent_tasks: List[List[str]], -) -> List[JaxSimulation]: + parent_tasks: list[list[str]], +) -> list[JaxSimulation]: """Runs the forward simulations on our servers, stores the gradient data for later.""" task_names = [str(i) for i in range(len(simulations))] @@ -632,7 +634,7 @@ def run_local( task_name: str, folder_name: str = "default", path: str = "simulation_data.hdf5", - callback_url: str = None, + callback_url: Optional[str] = None, verbose: bool = True, num_proc: int = NUM_PROC_LOCAL, ) -> JaxSimulationData: @@ -690,7 +692,7 @@ def run_local_fwd( callback_url: str, verbose: bool, num_proc: int, -) -> Tuple[JaxSimulationData, tuple]: +) -> tuple[JaxSimulationData, tuple]: """Run forward pass and stash extra objects for the backwards pass.""" # add the gradient monitors and run the forward simulation @@ -708,7 +710,7 @@ def run_local_fwd( ) # remove the gradient data from the returned version (not needed) - sim_data_orig = sim_data_fwd.copy(update=dict(grad_data=(), simulation=simulation)) + sim_data_orig = sim_data_fwd.copy(update={"grad_data": (), "simulation": simulation}) return sim_data_orig, (sim_data_fwd,) @@ -721,7 +723,7 @@ def run_local_bwd( num_proc: int, res: tuple, sim_data_vjp: JaxSimulationData, -) -> Tuple[JaxSimulation]: +) -> tuple[JaxSimulation]: """Run backward pass and return simulation storing vjp of the objective w.r.t. the sim.""" # grab the forward simulation and its gradient monitor data @@ -764,7 +766,7 @@ def run_local_bwd( """ Running a batch of simulations using web.run_async. """ -def _task_name_orig_local(index: int, task_name_suffix: str = None): +def _task_name_orig_local(index: int, task_name_suffix: Optional[str] = None): """Task name as function of index into simulations. Note: for original must be int.""" if task_name_suffix is not None: return f"{index}{task_name_suffix}" @@ -773,14 +775,14 @@ def _task_name_orig_local(index: int, task_name_suffix: str = None): @partial(custom_vjp, nondiff_argnums=tuple(range(1, 7))) def run_async_local( - simulations: Tuple[JaxSimulation, ...], + simulations: tuple[JaxSimulation, ...], folder_name: str = "default", path_dir: str = DEFAULT_DATA_DIR, - callback_url: str = None, + callback_url: Optional[str] = None, verbose: bool = True, - num_workers: int = None, - task_name_suffix: str = None, -) -> Tuple[JaxSimulationData, ...]: + num_workers: Optional[int] = None, + task_name_suffix: Optional[str] = None, +) -> tuple[JaxSimulationData, ...]: """Submits a set of :class:`.JaxSimulation` objects to server, starts running, monitors progress, downloads, and loads results as a tuple of :class:`.JaxSimulationData` objects. @@ -847,14 +849,14 @@ def run_async_local( def run_async_local_fwd( - simulations: Tuple[JaxSimulation, ...], + simulations: tuple[JaxSimulation, ...], folder_name: str, path_dir: str, callback_url: str, verbose: bool, num_workers: int, task_name_suffix: str, -) -> Tuple[Dict[str, JaxSimulationData], tuple]: +) -> tuple[dict[str, JaxSimulationData], tuple]: """Run forward pass and stash extra objects for the backwards pass.""" task_name_suffix_fwd = _task_name_fwd("") @@ -881,7 +883,7 @@ def run_async_local_fwd( batch_data_orig = [] for i, sim_data_fwd in enumerate(batch_data_fwd): sim_orig = simulations[i] - sim_data_orig = sim_data_fwd.copy(update=dict(grad_data=(), simulation=sim_orig)) + sim_data_orig = sim_data_fwd.copy(update={"grad_data": (), "simulation": sim_orig}) batch_data_orig.append(sim_data_orig) return batch_data_orig, (batch_data_fwd,) @@ -895,8 +897,8 @@ def run_async_local_bwd( num_workers: int, task_name_suffix: str, res: tuple, - batch_data_vjp: Tuple[JaxSimulationData, ...], -) -> Tuple[Dict[str, JaxSimulation]]: + batch_data_vjp: tuple[JaxSimulationData, ...], +) -> tuple[dict[str, JaxSimulation]]: """Run backward pass and return simulation storing vjp of the objective w.r.t. the sim.""" # grab the forward simulation and its gradient monitor data diff --git a/tidy3d/plugins/autograd/__init__.py b/tidy3d/plugins/autograd/__init__.py index 6f304ad43c..c4b9de15e4 100644 --- a/tidy3d/plugins/autograd/__init__.py +++ b/tidy3d/plugins/autograd/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .differential_operators import grad, value_and_grad from .functions import ( add_at, @@ -41,38 +43,38 @@ "ConicFilter", "ErosionDilationPenalty", "FilterAndProject", - "make_filter", - "make_conic_filter", - "make_circular_filter", - "grey_indicator", + "add_at", + "chain", "convolve", - "pad", - "ramp_projection", - "tanh_projection", - "make_erosion_dilation_penalty", - "make_curvature_penalty", - "make_filter_and_project", "gaussian_filter", - "interpolate_spline", - "make_kernel", "get_kernel_size_px", - "chain", + "grad", "grey_closing", "grey_dilation", "grey_erosion", + "grey_indicator", "grey_opening", + "interpn", + "interpolate_spline", + "least_squares", + "make_circular_filter", + "make_conic_filter", + "make_curvature_penalty", + "make_erosion_dilation_penalty", + "make_filter", + "make_filter_and_project", + "make_kernel", "morphological_gradient", "morphological_gradient_external", "morphological_gradient_internal", + "pad", + "ramp_projection", "rescale", - "threshold", - "value_and_grad", - "smooth_min", - "smooth_max", - "add_at", - "interpn", - "least_squares", - "grad", "scalar_objective", + "smooth_max", + "smooth_min", + "tanh_projection", + "threshold", "trapz", + "value_and_grad", ] diff --git a/tidy3d/plugins/autograd/constants.py b/tidy3d/plugins/autograd/constants.py index 062be8e5c5..9d4539d18c 100644 --- a/tidy3d/plugins/autograd/constants.py +++ b/tidy3d/plugins/autograd/constants.py @@ -1,2 +1,4 @@ +from __future__ import annotations + BETA_DEFAULT = 1.0 ETA_DEFAULT = 0.5 diff --git a/tidy3d/plugins/autograd/differential_operators.py b/tidy3d/plugins/autograd/differential_operators.py index f61ea58945..3bd92356eb 100644 --- a/tidy3d/plugins/autograd/differential_operators.py +++ b/tidy3d/plugins/autograd/differential_operators.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Callable from autograd.builtins import tuple as atuple @@ -9,8 +11,8 @@ from .utilities import scalar_objective __all__ = [ - "value_and_grad", "grad", + "value_and_grad", ] diff --git a/tidy3d/plugins/autograd/functions.py b/tidy3d/plugins/autograd/functions.py index 0cdb3c952b..fd0a3d1557 100644 --- a/tidy3d/plugins/autograd/functions.py +++ b/tidy3d/plugins/autograd/functions.py @@ -1,4 +1,7 @@ -from typing import Callable, Iterable, List, Literal, Tuple, Union +from __future__ import annotations + +from collections.abc import Iterable +from typing import Callable, Literal, Union import autograd.numpy as np from autograd import jacobian @@ -11,26 +14,26 @@ from .types import PaddingType __all__ = [ - "interpn", - "trapz", "add_at", - "pad", "convolve", + "grey_closing", "grey_dilation", "grey_erosion", "grey_opening", - "grey_closing", + "interpn", "morphological_gradient", - "morphological_gradient_internal", "morphological_gradient_external", + "morphological_gradient_internal", + "pad", "rescale", - "threshold", - "smooth_min", "smooth_max", + "smooth_min", + "threshold", + "trapz", ] -def _pad_indices(n: int, pad_width: Tuple[int, int], *, mode: PaddingType) -> NDArray: +def _pad_indices(n: int, pad_width: tuple[int, int], *, mode: PaddingType) -> NDArray: """Compute the indices to pad an array along a single axis based on the padding mode. Parameters @@ -78,7 +81,7 @@ def _pad_indices(n: int, pad_width: Tuple[int, int], *, mode: PaddingType) -> ND def _pad_axis( array: NDArray, - pad_width: Tuple[int, int], + pad_width: tuple[int, int], axis: int, *, mode: PaddingType = "constant", @@ -117,7 +120,7 @@ def _pad_axis( def pad( array: NDArray, - pad_width: Union[int, Tuple[int, int]], + pad_width: Union[int, tuple[int, int]], *, mode: PaddingType = "constant", axis: Union[int, Iterable[int], None] = None, @@ -188,7 +191,7 @@ def convolve( kernel: NDArray, *, padding: PaddingType = "constant", - axes: Union[Tuple[List[int], List[int]], None] = None, + axes: Union[tuple[list[int], list[int]], None] = None, mode: Literal["full", "valid", "same"] = "same", ) -> NDArray: """Convolve an array with a given kernel. @@ -237,7 +240,7 @@ def convolve( def grey_dilation( array: NDArray, - size: Union[Union[int, Tuple[int, int]], None] = None, + size: Union[Union[int, tuple[int, int]], None] = None, structure: Union[NDArray, None] = None, *, mode: PaddingType = "reflect", @@ -291,7 +294,7 @@ def grey_dilation( def grey_erosion( array: NDArray, - size: Union[Union[int, Tuple[int, int]], None] = None, + size: Union[Union[int, tuple[int, int]], None] = None, structure: Union[NDArray, None] = None, *, mode: PaddingType = "reflect", @@ -345,7 +348,7 @@ def grey_erosion( def grey_opening( array: NDArray, - size: Union[Union[int, Tuple[int, int]], None] = None, + size: Union[Union[int, tuple[int, int]], None] = None, structure: Union[NDArray, None] = None, *, mode: PaddingType = "reflect", @@ -378,7 +381,7 @@ def grey_opening( def grey_closing( array: NDArray, - size: Union[Union[int, Tuple[int, int]], None] = None, + size: Union[Union[int, tuple[int, int]], None] = None, structure: Union[NDArray, None] = None, *, mode: PaddingType = "reflect", @@ -411,7 +414,7 @@ def grey_closing( def morphological_gradient( array: NDArray, - size: Union[Union[int, Tuple[int, int]], None] = None, + size: Union[Union[int, tuple[int, int]], None] = None, structure: Union[NDArray, None] = None, *, mode: PaddingType = "reflect", @@ -444,7 +447,7 @@ def morphological_gradient( def morphological_gradient_internal( array: NDArray, - size: Union[Union[int, Tuple[int, int]], None] = None, + size: Union[Union[int, tuple[int, int]], None] = None, structure: Union[NDArray, None] = None, *, mode: PaddingType = "reflect", @@ -475,7 +478,7 @@ def morphological_gradient_internal( def morphological_gradient_external( array: NDArray, - size: Union[Union[int, Tuple[int, int]], None] = None, + size: Union[Union[int, tuple[int, int]], None] = None, structure: Union[NDArray, None] = None, *, mode: PaddingType = "reflect", @@ -581,7 +584,7 @@ def threshold( def smooth_max( - x: NDArray, tau: float = 1.0, axis: Union[int, Tuple[int, ...], None] = None + x: NDArray, tau: float = 1.0, axis: Union[int, tuple[int, ...], None] = None ) -> float: """Compute the smooth maximum of an array using temperature parameter tau. @@ -603,7 +606,7 @@ def smooth_max( def smooth_min( - x: NDArray, tau: float = 1.0, axis: Union[int, Tuple[int, ...], None] = None + x: NDArray, tau: float = 1.0, axis: Union[int, tuple[int, ...], None] = None ) -> float: """Compute the smooth minimum of an array using temperature parameter tau. @@ -628,7 +631,7 @@ def least_squares( func: Callable[[NDArray, float], NDArray], x: NDArray, y: NDArray, - initial_guess: Tuple[float, ...], + initial_guess: tuple[float, ...], max_iterations: int = 100, tol: float = 1e-6, ) -> NDArray: diff --git a/tidy3d/plugins/autograd/invdes/__init__.py b/tidy3d/plugins/autograd/invdes/__init__.py index 080f7dba82..b3a312ef0e 100644 --- a/tidy3d/plugins/autograd/invdes/__init__.py +++ b/tidy3d/plugins/autograd/invdes/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .filters import ( CircularFilter, ConicFilter, @@ -11,17 +13,17 @@ from .projections import ramp_projection, tanh_projection __all__ = [ - "grey_indicator", "CircularFilter", "ConicFilter", + "ErosionDilationPenalty", + "FilterAndProject", + "grey_indicator", "make_circular_filter", "make_conic_filter", "make_curvature_penalty", "make_erosion_dilation_penalty", - "ErosionDilationPenalty", "make_filter", "make_filter_and_project", - "FilterAndProject", "ramp_projection", "tanh_projection", ] diff --git a/tidy3d/plugins/autograd/invdes/filters.py b/tidy3d/plugins/autograd/invdes/filters.py index 2bdf45e2bf..b8bb019563 100644 --- a/tidy3d/plugins/autograd/invdes/filters.py +++ b/tidy3d/plugins/autograd/invdes/filters.py @@ -1,8 +1,9 @@ from __future__ import annotations import abc +from collections.abc import Iterable from functools import lru_cache, partial -from typing import Annotated, Callable, Iterable, Tuple, Union +from typing import Annotated, Callable, Optional, Union import numpy as np import pydantic.v1 as pd @@ -11,16 +12,15 @@ import tidy3d as td from tidy3d.components.base import Tidy3dBaseModel from tidy3d.components.types import TYPE_TAG_STR - -from ..functions import convolve -from ..types import KernelType, PaddingType -from ..utilities import get_kernel_size_px, make_kernel +from tidy3d.plugins.autograd.functions import convolve +from tidy3d.plugins.autograd.types import KernelType, PaddingType +from tidy3d.plugins.autograd.utilities import get_kernel_size_px, make_kernel class AbstractFilter(Tidy3dBaseModel, abc.ABC): """An abstract class for creating and applying convolution filters.""" - kernel_size: Union[pd.PositiveInt, Tuple[pd.PositiveInt, ...]] = pd.Field( + kernel_size: Union[pd.PositiveInt, tuple[pd.PositiveInt, ...]] = pd.Field( ..., title="Kernel Size", description="Size of the kernel in pixels for each dimension." ) normalize: bool = pd.Field( @@ -32,7 +32,7 @@ class AbstractFilter(Tidy3dBaseModel, abc.ABC): @classmethod def from_radius_dl( - cls, radius: Union[float, Tuple[float, ...]], dl: Union[float, Tuple[float, ...]], **kwargs + cls, radius: Union[float, tuple[float, ...]], dl: Union[float, tuple[float, ...]], **kwargs ) -> AbstractFilter: """Create a filter from radius and grid spacing. @@ -125,10 +125,10 @@ def get_kernel(size_px: Iterable[int], normalize: bool) -> NDArray: def _get_kernel_size( - radius: Union[float, Tuple[float, ...]], - dl: Union[float, Tuple[float, ...]], - size_px: Union[int, Tuple[int, ...]], -) -> Tuple[int, ...]: + radius: Union[float, tuple[float, ...]], + dl: Union[float, tuple[float, ...]], + size_px: Union[int, tuple[int, ...]], +) -> tuple[int, ...]: """Determine the kernel size based on the provided radius, grid spacing, or size in pixels. Parameters @@ -156,18 +156,17 @@ def _get_kernel_size( "Both 'size_px' and 'radius' and 'dl' are provided. 'size_px' will take precedence." ) return (size_px,) if np.isscalar(size_px) else tuple(size_px) - elif radius is not None and dl is not None: + if radius is not None and dl is not None: kernel_size = get_kernel_size_px(radius=radius, dl=dl) return (kernel_size,) if np.isscalar(kernel_size) else tuple(kernel_size) - else: - raise ValueError("Either 'size_px' or both 'radius' and 'dl' must be provided.") + raise ValueError("Either 'size_px' or both 'radius' and 'dl' must be provided.") def make_filter( - radius: Union[float, Tuple[float, ...]] = None, - dl: Union[float, Tuple[float, ...]] = None, + radius: Optional[Union[float, tuple[float, ...]]] = None, + dl: Optional[Union[float, tuple[float, ...]]] = None, *, - size_px: Union[int, Tuple[int, ...]] = None, + size_px: Optional[Union[int, tuple[int, ...]]] = None, normalize: bool = True, padding: PaddingType = "reflect", filter_type: KernelType, diff --git a/tidy3d/plugins/autograd/invdes/misc.py b/tidy3d/plugins/autograd/invdes/misc.py index ea92c7828d..0221d35b3e 100644 --- a/tidy3d/plugins/autograd/invdes/misc.py +++ b/tidy3d/plugins/autograd/invdes/misc.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import autograd.numpy as np from numpy.typing import NDArray diff --git a/tidy3d/plugins/autograd/invdes/parametrizations.py b/tidy3d/plugins/autograd/invdes/parametrizations.py index 22ef6d6f61..a853fb0053 100644 --- a/tidy3d/plugins/autograd/invdes/parametrizations.py +++ b/tidy3d/plugins/autograd/invdes/parametrizations.py @@ -1,14 +1,14 @@ from __future__ import annotations -from typing import Callable, Tuple, Union +from typing import Callable, Optional, Union import pydantic.v1 as pd from numpy.typing import NDArray from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.plugins.autograd.constants import BETA_DEFAULT, ETA_DEFAULT +from tidy3d.plugins.autograd.types import KernelType, PaddingType -from ..constants import BETA_DEFAULT, ETA_DEFAULT -from ..types import KernelType, PaddingType from .filters import make_filter from .projections import tanh_projection @@ -16,13 +16,13 @@ class FilterAndProject(Tidy3dBaseModel): """A class that combines filtering and projection operations.""" - radius: Union[float, Tuple[float, ...]] = pd.Field( + radius: Union[float, tuple[float, ...]] = pd.Field( ..., title="Radius", description="The radius of the kernel." ) - dl: Union[float, Tuple[float, ...]] = pd.Field( + dl: Union[float, tuple[float, ...]] = pd.Field( ..., title="Grid Spacing", description="The grid spacing." ) - size_px: Union[int, Tuple[int, ...]] = pd.Field( + size_px: Union[int, tuple[int, ...]] = pd.Field( None, title="Size in Pixels", description="The size of the kernel in pixels." ) beta: pd.NonNegativeFloat = pd.Field( @@ -38,7 +38,9 @@ class FilterAndProject(Tidy3dBaseModel): "reflect", title="Padding", description="The padding mode to use." ) - def __call__(self, array: NDArray, beta: float = None, eta: float = None) -> NDArray: + def __call__( + self, array: NDArray, beta: Optional[float] = None, eta: Optional[float] = None + ) -> NDArray: """Apply the filter and projection to an input array. Parameters @@ -70,10 +72,10 @@ def __call__(self, array: NDArray, beta: float = None, eta: float = None) -> NDA def make_filter_and_project( - radius: Union[float, Tuple[float, ...]] = None, - dl: Union[float, Tuple[float, ...]] = None, + radius: Optional[Union[float, tuple[float, ...]]] = None, + dl: Optional[Union[float, tuple[float, ...]]] = None, *, - size_px: Union[int, Tuple[int, ...]] = None, + size_px: Optional[Union[int, tuple[int, ...]]] = None, beta: float = BETA_DEFAULT, eta: float = ETA_DEFAULT, filter_type: KernelType = "conic", diff --git a/tidy3d/plugins/autograd/invdes/penalties.py b/tidy3d/plugins/autograd/invdes/penalties.py index 9fc27d40ae..92eaac6e79 100644 --- a/tidy3d/plugins/autograd/invdes/penalties.py +++ b/tidy3d/plugins/autograd/invdes/penalties.py @@ -1,4 +1,6 @@ -from typing import Callable, Tuple, Union +from __future__ import annotations + +from typing import Callable, Optional, Union import autograd.numpy as np import pydantic.v1 as pd @@ -6,21 +8,21 @@ from tidy3d.components.base import Tidy3dBaseModel from tidy3d.components.types import ArrayFloat2D +from tidy3d.plugins.autograd.types import PaddingType -from ..types import PaddingType from .parametrizations import FilterAndProject class ErosionDilationPenalty(Tidy3dBaseModel): """A class that computes a penalty for erosion/dilation of a parameter map not being unity.""" - radius: Union[float, Tuple[float, ...]] = pd.Field( + radius: Union[float, tuple[float, ...]] = pd.Field( ..., title="Radius", description="The radius of the kernel." ) - dl: Union[float, Tuple[float, ...]] = pd.Field( + dl: Union[float, tuple[float, ...]] = pd.Field( ..., title="Grid Spacing", description="The grid spacing." ) - size_px: Union[int, Tuple[int, ...]] = pd.Field( + size_px: Union[int, tuple[int, ...]] = pd.Field( None, title="Size in Pixels", description="The size of the kernel in pixels." ) beta: pd.NonNegativeFloat = pd.Field( @@ -88,10 +90,10 @@ def _close(arr: NDArray): def make_erosion_dilation_penalty( - radius: Union[float, Tuple[float, ...]], - dl: Union[float, Tuple[float, ...]], + radius: Union[float, tuple[float, ...]], + dl: Union[float, tuple[float, ...]], *, - size_px: Union[int, Tuple[int, ...]] = None, + size_px: Optional[Union[int, tuple[int, ...]]] = None, beta: float = 20.0, eta: float = 0.5, delta_eta: float = 0.01, diff --git a/tidy3d/plugins/autograd/invdes/projections.py b/tidy3d/plugins/autograd/invdes/projections.py index 51ba088db9..91da5d8866 100644 --- a/tidy3d/plugins/autograd/invdes/projections.py +++ b/tidy3d/plugins/autograd/invdes/projections.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import autograd.numpy as np from numpy.typing import NDArray -from ..constants import BETA_DEFAULT, ETA_DEFAULT +from tidy3d.plugins.autograd.constants import BETA_DEFAULT, ETA_DEFAULT def ramp_projection(array: NDArray, width: float = 0.1, center: float = 0.5) -> NDArray: diff --git a/tidy3d/plugins/autograd/primitives/__init__.py b/tidy3d/plugins/autograd/primitives/__init__.py index 7a04eb0993..1306e8b512 100644 --- a/tidy3d/plugins/autograd/primitives/__init__.py +++ b/tidy3d/plugins/autograd/primitives/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .interpolate import interpolate_spline from .misc import gaussian_filter diff --git a/tidy3d/plugins/autograd/primitives/interpolate.py b/tidy3d/plugins/autograd/primitives/interpolate.py index e348a2c53a..2e80e36b12 100644 --- a/tidy3d/plugins/autograd/primitives/interpolate.py +++ b/tidy3d/plugins/autograd/primitives/interpolate.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Optional import numpy as np @@ -606,13 +608,12 @@ def compute_spline_coeffs( """ if order == 1: return compute_linear_coefficients(x_points, y_points) - elif order == 2: + if order == 2: left_deriv = endpoint_derivatives[0] return compute_quadratic_coefficients(x_points, y_points, left_deriv) - elif order == 3: + if order == 3: return compute_spline_coefficients(x_points, y_points, endpoint_derivatives) - else: - raise NotImplementedError(f"Spline order '{order}' not implemented.") + raise NotImplementedError(f"Spline order '{order}' not implemented.") def evaluate_spline(x_points: NDArray, coeffs: tuple, x_eval: NDArray) -> NDArray: @@ -636,12 +637,11 @@ def evaluate_spline(x_points: NDArray, coeffs: tuple, x_eval: NDArray) -> NDArra if order == 1: return evaluate_linear_spline(x_points, coeffs, x_eval) - elif order == 2: + if order == 2: return evaluate_quadratic_spline(x_points, coeffs, x_eval) - elif order == 3: + if order == 3: return evaluate_cubic_spline(x_points, coeffs, x_eval) - else: - raise NotImplementedError(f"Spline order '{order}' not implemented.") + raise NotImplementedError(f"Spline order '{order}' not implemented.") def get_spline_derivatives_wrt_y( @@ -672,13 +672,12 @@ def get_spline_derivatives_wrt_y( """ if order == 1: return get_linear_derivative_wrt_y(x_points, y_points) - elif order == 2: + if order == 2: left_deriv = endpoint_derivatives[0] return get_quadratic_derivative_wrt_y(x_points, y_points, left_deriv) - elif order == 3: + if order == 3: return get_cubic_derivative_wrt_y(x_points, y_points, endpoint_derivatives) - else: - raise NotImplementedError(f"Derivatives for spline order '{order}' not implemented.") + raise NotImplementedError(f"Derivatives for spline order '{order}' not implemented.") @primitive diff --git a/tidy3d/plugins/autograd/primitives/misc.py b/tidy3d/plugins/autograd/primitives/misc.py index c6f5bbfdfa..8b19b21b58 100644 --- a/tidy3d/plugins/autograd/primitives/misc.py +++ b/tidy3d/plugins/autograd/primitives/misc.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import scipy.ndimage from autograd.extend import defvjp, primitive diff --git a/tidy3d/plugins/autograd/types.py b/tidy3d/plugins/autograd/types.py index 1f98ebe077..3b842e2313 100644 --- a/tidy3d/plugins/autograd/types.py +++ b/tidy3d/plugins/autograd/types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Literal PaddingType = Literal["constant", "edge", "reflect", "symmetric", "wrap"] diff --git a/tidy3d/plugins/autograd/utilities.py b/tidy3d/plugins/autograd/utilities.py index 1303f9e1b6..7a7b5f83a8 100644 --- a/tidy3d/plugins/autograd/utilities.py +++ b/tidy3d/plugins/autograd/utilities.py @@ -1,5 +1,8 @@ +from __future__ import annotations + +from collections.abc import Iterable from functools import reduce, wraps -from typing import Any, Callable, Iterable, List, Union +from typing import Any, Callable, Optional, Union import autograd.numpy as anp import numpy as np @@ -83,8 +86,9 @@ def make_kernel(kernel_type: KernelType, size: Iterable[int], normalize: bool = def get_kernel_size_px( - radius: Union[float, Iterable[float]] = None, dl: Union[float, Iterable[float]] = None -) -> Union[int, List[int]]: + radius: Optional[Union[float, Iterable[float]]] = None, + dl: Optional[Union[float, Iterable[float]]] = None, +) -> Union[int, list[int]]: """Calculate the kernel size in pixels based on the provided radius and grid spacing. Parameters @@ -164,7 +168,7 @@ def chained(array: NDArray): return chained -def scalar_objective(func: Callable = None, *, has_aux: bool = False) -> Callable: +def scalar_objective(func: Optional[Callable] = None, *, has_aux: bool = False) -> Callable: """Decorator to ensure the objective function returns a real scalar value. This decorator wraps an objective function to ensure that its return value is a real scalar. @@ -202,8 +206,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: if has_aux: if not isinstance(result, tuple) or len(result) != 2: raise Tidy3dError( - "If 'has_aux' is True, the objective function must return " - "a tuple of length 2." + "If 'has_aux' is True, the objective function must return a tuple of length 2." ) result, aux_data = result @@ -223,7 +226,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: raise Tidy3dError( "An objective function's return value must be a scalar, " "a Python float/int, or an array containing a single element." - ) + ) from None except ValueError as e: # Result contains more than one element raise Tidy3dError( diff --git a/tidy3d/plugins/design/__init__.py b/tidy3d/plugins/design/__init__.py index 66b1da7ea5..e8bf9cc2cb 100644 --- a/tidy3d/plugins/design/__init__.py +++ b/tidy3d/plugins/design/__init__.py @@ -1,5 +1,7 @@ """Imports for parameter sweep.""" +from __future__ import annotations + from .design import DesignSpace from .method import ( MethodBayOpt, @@ -13,13 +15,13 @@ __all__ = [ "DesignSpace", - "ParameterInt", - "ParameterFloat", - "ParameterAny", - "Result", - "MethodMonteCarlo", - "MethodGrid", "MethodBayOpt", "MethodGenAlg", + "MethodGrid", + "MethodMonteCarlo", "MethodParticleSwarm", + "ParameterAny", + "ParameterFloat", + "ParameterInt", + "Result", ] diff --git a/tidy3d/plugins/design/design.py b/tidy3d/plugins/design/design.py index cf3333fcb4..2ea536e268 100644 --- a/tidy3d/plugins/design/design.py +++ b/tidy3d/plugins/design/design.py @@ -3,15 +3,16 @@ from __future__ import annotations import inspect -from typing import Any, Callable, Dict, List, Tuple, Union +from typing import Any, Callable, Optional, Union import pydantic.v1 as pd -from ...components.base import TYPE_TAG_STR, Tidy3dBaseModel, cached_property -from ...components.data.sim_data import SimulationData -from ...components.simulation import Simulation -from ...log import Console, get_logging_console, log -from ...web.api.container import Batch, BatchData, Job +from tidy3d.components.base import TYPE_TAG_STR, Tidy3dBaseModel, cached_property +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.simulation import Simulation +from tidy3d.log import Console, get_logging_console, log +from tidy3d.web.api.container import Batch, BatchData, Job + from .method import ( MethodBayOpt, MethodGenAlg, @@ -64,7 +65,7 @@ class DesignSpace(Tidy3dBaseModel): """ - parameters: Tuple[ParameterType, ...] = pd.Field( + parameters: tuple[ParameterType, ...] = pd.Field( (), title="Parameters", description="Set of parameters defining the dimensions and allowed values for the design space.", @@ -101,18 +102,18 @@ class DesignSpace(Tidy3dBaseModel): ) @cached_property - def dims(self) -> Tuple[str]: + def dims(self) -> tuple[str]: """dimensions defined by the design parameter names.""" return tuple(param.name for param in self.parameters) def _package_run_results( self, fn_args: list[dict[str, Any]], - fn_values: List[Any], + fn_values: list[Any], fn_source: str, - task_names: Tuple[str] = None, - task_paths: list = None, - aux_values: List[Any] = None, + task_names: Optional[tuple[str]] = None, + task_paths: Optional[list] = None, + aux_values: Optional[list[Any]] = None, opt_output: Any = None, ) -> Result: """How to package results from ``method.run`` and ``method.run_batch``""" @@ -142,7 +143,7 @@ def get_fn_source(function: Callable) -> str: except (TypeError, OSError): return None - def run(self, fn: Callable, fn_post: Callable = None, verbose: bool = True) -> Result: + def run(self, fn: Callable, fn_post: Optional[Callable] = None, verbose: bool = True) -> Result: """Explore a parameter space with a supplied method using the user supplied function. Supplied functions are used to evaluate the design space and are called within the method. For optimization methods these functions act as the fitness function. A single function can be @@ -245,12 +246,12 @@ def run(self, fn: Callable, fn_post: Callable = None, verbose: bool = True) -> R opt_output=opt_output, ) - def run_single(self, fn: Callable, console: Console) -> Tuple(list[dict], list, list[Any]): + def run_single(self, fn: Callable, console: Console) -> tuple(list[dict], list, list[Any]): """Run a single function of parameter inputs.""" evaluate_fn = self._get_evaluate_fn_single(fn=fn) return self.method._run(run_fn=evaluate_fn, parameters=self.parameters, console=console) - def run_pre_post(self, fn_pre: Callable, fn_post: Callable, console: Console) -> Tuple( + def run_pre_post(self, fn_pre: Callable, fn_post: Callable, console: Console) -> tuple( list[dict], list[dict], list[Any] ): """Run a function with Tidy3D implicitly called in between.""" @@ -356,8 +357,8 @@ def _find_and_map( _find_and_map(pre_out, Batch, batches, naming_keys) # Exit fn_mid here if no td computation is required - if not len(simulations) and not len(batches): - return original_pre_out, list(), list(), sim_counter + if not simulations and not batches: + return original_pre_out, [], [], sim_counter # Create task names for simulations named_sims = {} @@ -454,9 +455,9 @@ def _remove_or_replace(search_dict: dict, attr_name: str) -> dict: def run_batch( self, - fn_pre: Callable[Any, Union[Simulation, List[Simulation], Dict[str, Simulation]]], + fn_pre: Callable[Any, Union[Simulation, list[Simulation], dict[str, Simulation]]], fn_post: Callable[ - Union[SimulationData, List[SimulationData], Dict[str, SimulationData]], Any + Union[SimulationData, list[SimulationData], dict[str, SimulationData]], Any ], path_dir: str = ".", **batch_kwargs, @@ -560,10 +561,9 @@ def _estimate_sim_cost(sim): # For if tidy3d server cannot determine the estimate if per_run_estimate is None: return None - else: - return round(per_run_estimate * run_count, 3) + return round(per_run_estimate * run_count, 3) - def summarize(self, fn_pre: Callable = None, verbose: bool = True) -> dict[str, Any]: + def summarize(self, fn_pre: Optional[Callable] = None, verbose: bool = True) -> dict[str, Any]: """Summarize the setup of the DesignSpace Prints a summary of the DesignSpace including the method and associated args, the parameters, diff --git a/tidy3d/plugins/design/method.py b/tidy3d/plugins/design/method.py index 23fcafd217..ada58533db 100644 --- a/tidy3d/plugins/design/method.py +++ b/tidy3d/plugins/design/method.py @@ -1,14 +1,17 @@ """Defines the methods used for parameter sweep.""" +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Any, Callable, Dict, Literal, Tuple, Union +from typing import Any, Callable, Literal, Optional, Union import numpy as np import pydantic.v1 as pd import scipy.stats.qmc as qmc -from ...components.base import Tidy3dBaseModel -from ...constants import inf +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.constants import inf + from .parameter import ParameterAny, ParameterFloat, ParameterInt, ParameterType DEFAULT_MONTE_CARLO_SAMPLER_TYPE = qmc.LatinHypercube @@ -20,11 +23,11 @@ class Method(Tidy3dBaseModel, ABC): name: str = pd.Field(None, title="Name", description="Optional name for the sweep method.") @abstractmethod - def _run(self, parameters: Tuple[ParameterType, ...], run_fn: Callable) -> Tuple[Any]: + def _run(self, parameters: tuple[ParameterType, ...], run_fn: Callable) -> tuple[Any]: """Defines the search algorithm.""" @abstractmethod - def _get_run_count(self, parameters: list = None) -> int: + def _get_run_count(self, parameters: Optional[list] = None) -> int: """Return the maximum number of runs for the method based on current method arguments.""" def _force_int(self, next_point: dict, parameters: list) -> None: @@ -36,7 +39,7 @@ def _force_int(self, next_point: dict, parameters: list) -> None: next_point[param.name] = int(round(next_point[param.name], 0)) @staticmethod - def _extract_output(output: list, sampler: bool = False) -> Tuple: + def _extract_output(output: list, sampler: bool = False) -> tuple: """Format the user function output for further optimization and result storage.""" # Light check if all the outputs are the same type @@ -57,7 +60,7 @@ def _extract_output(output: list, sampler: bool = False) -> Tuple: none_aux = [None for _ in range(len(output))] return (output, none_aux) - if all(isinstance(val, (list, Tuple)) for val in output): + if all(isinstance(val, (list, tuple)) for val in output): if all(isinstance(val[0], (float, int)) for val in output): float_out = [] aux_out = [] @@ -73,16 +76,14 @@ def _extract_output(output: list, sampler: bool = False) -> Tuple: # Float with aux_out return (float_out, aux_out) - else: - raise ValueError( - "Unrecognized output from supplied post function. The first element in the iterable object should be a 'float'." - ) - - else: raise ValueError( - "Unrecognized output from supplied post function. Output should be a 'float' or an iterable object." + "Unrecognized output from supplied post function. The first element in the iterable object should be a 'float'." ) + raise ValueError( + "Unrecognized output from supplied post function. Output should be a 'float' or an iterable object." + ) + @staticmethod def _flatten_and_append(list_of_lists: list[list], append_target: list) -> None: """Flatten a list of lists and append the sublist to a new list.""" @@ -95,13 +96,13 @@ class MethodSample(Method, ABC): """A sweep method where all points are independently computed in one iteration.""" @abstractmethod - def sample(self, parameters: Tuple[ParameterType, ...], **kwargs) -> Dict[str, Any]: + def sample(self, parameters: tuple[ParameterType, ...], **kwargs) -> dict[str, Any]: """Defines how the design parameters are sampled.""" def _assemble_args( self, - parameters: Tuple[ParameterType, ...], - ) -> Tuple[dict, int]: + parameters: tuple[ParameterType, ...], + ) -> tuple[dict, int]: """Sample design parameters, check the args are hashable and compute number of points.""" fn_args = self.sample(parameters) @@ -109,7 +110,7 @@ def _assemble_args( self._force_int(arg_dict, parameters) return fn_args - def _run(self, parameters: Tuple[ParameterType, ...], run_fn: Callable, console) -> Tuple[Any]: + def _run(self, parameters: tuple[ParameterType, ...], run_fn: Callable, console) -> tuple[Any]: """Defines the search algorithm.""" # get all function inputs @@ -139,7 +140,7 @@ def _get_run_count(self, parameters: list) -> int: return len(self.sample(parameters)) @staticmethod - def sample(parameters: Tuple[ParameterType, ...]) -> Dict[str, Any]: + def sample(parameters: tuple[ParameterType, ...]) -> dict[str, Any]: """Defines how the design parameters are sampled on the grid.""" # sample each dimension individually @@ -230,11 +231,11 @@ class MethodBayOpt(MethodOptimize, ABC): description="The Xi coefficient used by the ``ei`` and ``poi`` acquisition functions. More detail available in the `package docs `_.", ) - def _get_run_count(self, parameters: list = None) -> int: + def _get_run_count(self, parameters: Optional[list] = None) -> int: """Return the maximum number of runs for the method based on current method arguments.""" return self.initial_iter + self.n_iter - def _run(self, parameters: Tuple[ParameterType, ...], run_fn: Callable, console) -> Tuple[Any]: + def _run(self, parameters: tuple[ParameterType, ...], run_fn: Callable, console) -> tuple[Any]: """Defines the Bayesian optimization search algorithm for the method. Uses the ``bayes_opt`` package to carry out a Bayesian optimization. Utilizes the ``.suggest`` and ``.register`` methods instead of @@ -247,7 +248,7 @@ def _run(self, parameters: Tuple[ParameterType, ...], run_fn: Callable, console) raise ImportError( "Cannot run Bayesian optimization as 'bayes_opt' module not found. " "Please check installation or run 'pip install bayesian-optimization==1.5.1'." - ) + ) from None # Identify non-numeric params and define boundaries for Bay-opt param_converter = {} @@ -429,13 +430,13 @@ class MethodGenAlg(MethodOptimize, ABC): # TODO: See if anyone is interested in having the full suite of PyGAD options - there's a lot! - def _get_run_count(self, parameters: list = None) -> int: + def _get_run_count(self, parameters: Optional[list] = None) -> int: """Return the maximum number of runs for the method based on current method arguments.""" # +1 to generations as pygad creates an initial population which is effectively "Generation 0" run_count = self.solutions_per_pop * (self.n_generations + 1) return run_count - def _run(self, parameters: Tuple[ParameterType, ...], run_fn: Callable, console) -> Tuple[Any]: + def _run(self, parameters: tuple[ParameterType, ...], run_fn: Callable, console) -> tuple[Any]: """Defines the genetic algorithm for the method. Uses the ``pygad`` package to carry out a particle search optimization. Additional development has ensured that @@ -447,7 +448,7 @@ def _run(self, parameters: Tuple[ParameterType, ...], run_fn: Callable, console) except ImportError: raise ImportError( "Cannot run genetic algorithm optimization as 'pygad' module not found. Please check installation or run 'pip install pygad'." - ) + ) from None # Make param names available to the fitness function param_keys = [param.name for param in parameters] @@ -475,7 +476,7 @@ def _run(self, parameters: Tuple[ParameterType, ...], run_fn: Callable, console) # Designed for str in ParameterAny but may work for anything param_converter[param.name] = self.any_to_int_param(param) - gene_spaces.append(range(0, len(param.allowed_values))) + gene_spaces.append(range(len(param.allowed_values))) gene_types.append(int) def capture_aux(sol_dict_list: list[dict]) -> None: @@ -680,11 +681,11 @@ class MethodParticleSwarm(MethodOptimize, ABC): description="Set the initial positions of the swarm using a numpy array of appropriate size.", ) - def _get_run_count(self, parameters: list = None) -> int: + def _get_run_count(self, parameters: Optional[list] = None) -> int: """Return the maximum number of runs for the method based on current method arguments.""" return self.n_particles * self.n_iter - def _run(self, parameters: Tuple[ParameterType, ...], run_fn: Callable, console) -> Tuple[Any]: + def _run(self, parameters: tuple[ParameterType, ...], run_fn: Callable, console) -> tuple[Any]: """Defines the particle search optimization algorithm for the method. Uses the ``pyswarms`` package to carry out a particle search optimization. @@ -695,7 +696,7 @@ def _run(self, parameters: Tuple[ParameterType, ...], run_fn: Callable, console) except ImportError: raise ImportError( "Cannot run particle swarm optimization as 'pyswarms' module not found. Please check installation or run 'pip install pyswarms'." - ) + ) from None # Pyswarms doesn't have a seed set outside of numpy std method if self.seed is not None: @@ -785,14 +786,14 @@ class AbstractMethodRandom(MethodSample, ABC): ) @abstractmethod - def _get_sampler(self, parameters: Tuple[ParameterType, ...]) -> qmc.QMCEngine: + def _get_sampler(self, parameters: tuple[ParameterType, ...]) -> qmc.QMCEngine: """Sampler for this ``Method`` class. If ``None``, sets a default.""" - def _get_run_count(self, parameters: list = None) -> int: + def _get_run_count(self, parameters: Optional[list] = None) -> int: """Return the maximum number of runs for the method based on current method arguments.""" return self.num_points - def sample(self, parameters: Tuple[ParameterType, ...], **kwargs) -> Dict[str, Any]: + def sample(self, parameters: tuple[ParameterType, ...], **kwargs) -> dict[str, Any]: """Defines how the design parameters are sampled on grid.""" sampler = self._get_sampler(parameters) @@ -822,7 +823,7 @@ class MethodMonteCarlo(AbstractMethodRandom): >>> method = tdd.MethodMonteCarlo(num_points=20) """ - def _get_sampler(self, parameters: Tuple[ParameterType, ...]) -> qmc.QMCEngine: + def _get_sampler(self, parameters: tuple[ParameterType, ...]) -> qmc.QMCEngine: """Sampler for this ``Method`` class.""" d = len(parameters) diff --git a/tidy3d/plugins/design/parameter.py b/tidy3d/plugins/design/parameter.py index 77e5cf3c70..4d96646c2e 100644 --- a/tidy3d/plugins/design/parameter.py +++ b/tidy3d/plugins/design/parameter.py @@ -3,12 +3,12 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, List, Tuple, Union +from typing import Any, Union import numpy as np import pydantic.v1 as pd -from ...components.base import Tidy3dBaseModel +from tidy3d.components.base import Tidy3dBaseModel class Parameter(Tidy3dBaseModel, ABC): @@ -20,7 +20,7 @@ class Parameter(Tidy3dBaseModel, ABC): description="Unique name for the variable. Used as a key into the parameter sweep results.", ) - values: Tuple[Any, ...] = pd.Field( + values: tuple[Any, ...] = pd.Field( None, title="Custom Values", description="If specified, the parameter scan uses these values for grid search methods.", @@ -33,22 +33,22 @@ def _values_unique(cls, val): raise ValueError("Supplied 'values' were not unique.") return val - def sample_grid(self) -> List[Any]: + def sample_grid(self) -> list[Any]: """Sample design variable on grid, checking for custom values.""" if self.values is not None: return self.values return self._sample_grid() @abstractmethod - def sample_random(self, num_samples: int) -> List[Any]: + def sample_random(self, num_samples: int) -> list[Any]: """Sample this design variable randomly 'num_samples' times.""" @abstractmethod - def _sample_grid(self) -> List[Any]: + def _sample_grid(self) -> list[Any]: """Sample this design variable on a grid.""" @abstractmethod - def select_from_01(self, pts_01: np.ndarray) -> List[Any]: + def select_from_01(self, pts_01: np.ndarray) -> list[Any]: """Select values given a set of points between 0, 1.""" @abstractmethod @@ -59,7 +59,7 @@ def sample_first(self) -> Any: class ParameterNumeric(Parameter, ABC): """A variable with numeric values.""" - span: Tuple[Union[float, int], Union[float, int]] = pd.Field( + span: tuple[Union[float, int], Union[float, int]] = pd.Field( ..., title="Span", description="(min, max) range within which are allowed values for the variable. Is inclusive of max value.", @@ -109,21 +109,19 @@ def _span_is_float(cls, val): low, high = val return float(low), float(high) - def sample_random(self, num_samples: int) -> List[float]: + def sample_random(self, num_samples: int) -> list[float]: """Sample this design variable randomly 'num_samples' times.""" low, high = self.span return np.random.uniform(low=low, high=high, size=num_samples).tolist() - def _sample_grid(self) -> List[float]: + def _sample_grid(self) -> list[float]: """Sample this design variable on a grid.""" if self.num_points is None: - raise ValueError( - "'ParameterFloat' sampled on a grid must have '.num_points' defined." "" - ) + raise ValueError("'ParameterFloat' sampled on a grid must have '.num_points' defined.") low, high = self.span return np.linspace(low, high, self.num_points).tolist() - def select_from_01(self, pts_01: np.ndarray) -> List[Any]: + def select_from_01(self, pts_01: np.ndarray) -> list[Any]: """Select values given a set of points between 0, 1.""" return (min(self.span) + pts_01 * self.span_size).tolist() @@ -138,7 +136,7 @@ class ParameterInt(ParameterNumeric): >>> var = tdd.ParameterInt(name="x", span=(1, 4)) """ - span: Tuple[int, int] = pd.Field( + span: tuple[int, int] = pd.Field( ..., title="Span", description="``(min, max)`` range within which are allowed values for the variable. " @@ -152,17 +150,17 @@ def _span_is_int(cls, val): low, high = val return int(low), int(high) - def sample_random(self, num_samples: int) -> List[int]: + def sample_random(self, num_samples: int) -> list[int]: """Sample this design variable randomly 'num_samples' times.""" low, high = self.span return np.random.randint(low=low, high=high, size=num_samples).tolist() - def _sample_grid(self) -> List[float]: + def _sample_grid(self) -> list[float]: """Sample this design variable on a grid.""" low, high = self.span return np.arange(low, high).tolist() - def select_from_01(self, pts_01: np.ndarray) -> List[Any]: + def select_from_01(self, pts_01: np.ndarray) -> list[Any]: """Select values given a set of points between 0, 1.""" pts_continuous = min(self.span) + pts_01 * self.span_size return np.floor(pts_continuous).astype(int).tolist() @@ -177,7 +175,7 @@ class ParameterAny(Parameter): >>> var = tdd.ParameterAny(name="x", allowed_values=("a", "b", "c")) """ - allowed_values: Tuple[Any, ...] = pd.Field( + allowed_values: tuple[Any, ...] = pd.Field( ..., title="Allowed Values", description="The discrete set of values that this variable can take on.", @@ -197,15 +195,15 @@ def _no_duplicate_allowed_values(cls, val): raise ValueError("'allowed_values' has duplicate entries, must be unique.") return val - def sample_random(self, num_samples: int) -> List[Any]: + def sample_random(self, num_samples: int) -> list[Any]: """Sample this design variable randomly 'num_samples' times.""" return np.random.choice(self.allowed_values, size=int(num_samples)).tolist() - def _sample_grid(self) -> List[Any]: + def _sample_grid(self) -> list[Any]: """Sample this design variable uniformly, ie just take all allowed values.""" return list(self.allowed_values) - def select_from_01(self, pts_01: np.ndarray) -> List[Any]: + def select_from_01(self, pts_01: np.ndarray) -> list[Any]: """Select values given a set of points between 0, 1.""" pts_continuous = pts_01 * len(self.allowed_values) indices = np.floor(pts_continuous).astype(int) diff --git a/tidy3d/plugins/design/result.py b/tidy3d/plugins/design/result.py index 5f703cecfc..abf843183d 100644 --- a/tidy3d/plugins/design/result.py +++ b/tidy3d/plugins/design/result.py @@ -2,13 +2,13 @@ from __future__ import annotations -from typing import Any, Dict, List, Tuple +from typing import Any, Optional import numpy as np import pandas import pydantic.v1 as pd -from ...components.base import Tidy3dBaseModel, cached_property +from tidy3d.components.base import Tidy3dBaseModel, cached_property # NOTE: Coords are args_dict from method and design. This may be changed in future to unify naming @@ -30,26 +30,26 @@ class Result(Tidy3dBaseModel): >>> # df.head() # print out first 5 elements of data """ - dims: Tuple[str, ...] = pd.Field( + dims: tuple[str, ...] = pd.Field( (), title="Dimensions", description="The dimensions of the design variables (indexed by 'name').", ) - values: Tuple[Any, ...] = pd.Field( + values: tuple[Any, ...] = pd.Field( (), title="Values", description="The return values from the design problem function.", ) - coords: Tuple[Tuple[Any, ...], ...] = pd.Field( + coords: tuple[tuple[Any, ...], ...] = pd.Field( (), title="Coordinates", description="The values of the coordinates corresponding to each of the dims." "Note: shaped (D, N) where D is the ``len(dims)`` and N is the ``len(values)``", ) - output_names: Tuple[str, ...] = pd.Field( + output_names: tuple[str, ...] = pd.Field( None, title="Output Names", description="Names for each of the outputs stored in ``values``. If not specified, default " @@ -78,7 +78,7 @@ class Result(Tidy3dBaseModel): "Stored in the same format as the output of fn_pre i.e. if pre outputs a dict, this output is a dict with the keys preserved.", ) - aux_values: Tuple[Any, ...] = pd.Field( + aux_values: tuple[Any, ...] = pd.Field( None, title="Auxiliary values output from the user function", description="The auxiliary return values from the design problem function. This is the collection of objects returned " @@ -99,7 +99,7 @@ def _coords_and_dims_shape(cls, val, values): dims = values.get("dims") if val is None or dims is None: - return + return None num_dims = len(dims) for i, _val in enumerate(val): @@ -118,7 +118,7 @@ def _coords_and_values_shape(cls, val, values): _values = values.get("values") if val is None or _values is None: - return + return None num_values = len(_values) num_coords = len(val) @@ -131,7 +131,7 @@ def _coords_and_values_shape(cls, val, values): return val - def value_as_dict(self, value) -> Dict[str, Any]: + def value_as_dict(self, value) -> dict[str, Any]: """How to convert an output function value as a dictionary.""" if isinstance(value, dict): return value @@ -141,7 +141,7 @@ def value_as_dict(self, value) -> Dict[str, Any]: return dict(zip(keys, value)) @staticmethod - def default_value_keys(value) -> Tuple[str, ...]: + def default_value_keys(value) -> tuple[str, ...]: """The default keys for a given value.""" # if a dict already, just use the existing keys as labels @@ -155,7 +155,7 @@ def default_value_keys(value) -> Tuple[str, ...]: # if simply single value (float, int, bool, etc) just label "output" return ("output",) - def items(self) -> Tuple[dict, Any]: + def items(self) -> tuple[dict, Any]: """Iterate through coordinates (args) and values (outputs) one by one.""" for coord_tuple, val in zip(self.coords, self.values): @@ -163,7 +163,7 @@ def items(self) -> Tuple[dict, Any]: yield coord_dict, val @cached_property - def data(self) -> Dict[tuple, Any]: + def data(self) -> dict[tuple, Any]: """Dict mapping tuple of fn args to their value.""" result = {} @@ -235,18 +235,18 @@ def to_dataframe(self, include_aux: bool = False) -> pandas.DataFrame: df = pandas.DataFrame(data=data, columns=columns) - attrs = dict( - task_names=self.task_names, - output_names=self.output_names, - fn_source=self.fn_source, - dims=self.dims, - ) + attrs = { + "task_names": self.task_names, + "output_names": self.output_names, + "fn_source": self.fn_source, + "dims": self.dims, + } df.attrs = attrs return df @classmethod - def from_dataframe(cls, df: pandas.DataFrame, dims: List[str] = None) -> Result: + def from_dataframe(cls, df: pandas.DataFrame, dims: Optional[list[str]] = None) -> Result: """Load a result directly from a `pandas.DataFrame` object. Parameters @@ -346,14 +346,14 @@ def __add__(self, other): """Special syntax for design_result1 + design_result2.""" return self.combine(other) - def get_index(self, fn_args: Dict[str, float]) -> int: + def get_index(self, fn_args: dict[str, float]) -> int: """Get index into the data for a specific set of arguments.""" key_list = list(self.coords) arg_key = tuple(fn_args[dim] for dim in self.dims) return key_list.index(arg_key) - def delete(self, fn_args: Dict[str, float]) -> Result: + def delete(self, fn_args: dict[str, float]) -> Result: """Delete a specific set of arguments from the result. Parameters @@ -392,7 +392,7 @@ def delete(self, fn_args: Dict[str, float]) -> Result: return self.updated_copy(values=new_values, coords=new_coords) - def add(self, fn_args: Dict[str, float], value: Any) -> Result: + def add(self, fn_args: dict[str, float], value: Any) -> Result: """Add a specific argument and value the result. Parameters @@ -408,8 +408,8 @@ def add(self, fn_args: Dict[str, float], value: Any) -> Result: Copy of the result with that element added. """ - new_values = list(self.values) + [value] - new_coords = list(self.coords) + [tuple(fn_args[dim] for dim in self.dims)] + new_values = [*list(self.values), value] + new_coords = [*list(self.coords), tuple(fn_args[dim] for dim in self.dims)] # ParticleSwarm optimizer doesn't work with updated_copy # Creating new result with updated values and coords instead diff --git a/tidy3d/plugins/dispersion/__init__.py b/tidy3d/plugins/dispersion/__init__.py index 2c6f10b3e5..33a76b4ee8 100644 --- a/tidy3d/plugins/dispersion/__init__.py +++ b/tidy3d/plugins/dispersion/__init__.py @@ -1,13 +1,15 @@ """Imports from dispersion fitter plugin.""" +from __future__ import annotations + from .fit import DispersionFitter from .fit_fast import AdvancedFastFitterParam, FastDispersionFitter from .web import AdvancedFitterParam, StableDispersionFitter __all__ = [ - "DispersionFitter", + "AdvancedFastFitterParam", "AdvancedFitterParam", - "StableDispersionFitter", + "DispersionFitter", "FastDispersionFitter", - "AdvancedFastFitterParam", + "StableDispersionFitter", ] diff --git a/tidy3d/plugins/dispersion/fit.py b/tidy3d/plugins/dispersion/fit.py index 24d318fa12..67bb6e5843 100644 --- a/tidy3d/plugins/dispersion/fit.py +++ b/tidy3d/plugins/dispersion/fit.py @@ -4,7 +4,7 @@ import codecs import csv -from typing import List, Optional, Tuple +from typing import Optional import numpy as np import requests @@ -12,16 +12,15 @@ from pydantic.v1 import Field, validator from rich.progress import Progress +from tidy3d.components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing +from tidy3d.components.medium import AbstractMedium, PoleResidue +from tidy3d.components.types import ArrayFloat1D, Ax +from tidy3d.components.viz import add_ax_if_none +from tidy3d.constants import C_0, HBAR, MICROMETER +from tidy3d.exceptions import SetupError, ValidationError, WebError +from tidy3d.log import get_logging_console, log from tidy3d.web.core.environment import Env -from ...components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing -from ...components.medium import AbstractMedium, PoleResidue -from ...components.types import ArrayFloat1D, Ax -from ...components.viz import add_ax_if_none -from ...constants import C_0, HBAR, MICROMETER -from ...exceptions import SetupError, ValidationError, WebError -from ...log import get_logging_console, log - class DispersionFitter(Tidy3dBaseModel): """Tool for fitting refractive index data to get a @@ -46,7 +45,7 @@ class DispersionFitter(Tidy3dBaseModel): description="Imaginary part of the complex index of refraction.", ) - wvl_range: Tuple[Optional[float], Optional[float]] = Field( + wvl_range: tuple[Optional[float], Optional[float]] = Field( (None, None), title="Wavelength range [wvl_min,wvl_max] for fitting", description="Truncate the wavelength, n and k data to the wavelength range '[wvl_min, " @@ -82,7 +81,7 @@ def _kdata_setup_and_length_match(cls, val, values): return val @cached_property - def data_in_range(self) -> Tuple[ArrayFloat1D, ArrayFloat1D, ArrayFloat1D]: + def data_in_range(self) -> tuple[ArrayFloat1D, ArrayFloat1D, ArrayFloat1D]: """Filter the wavelength-nk data to wavelength range for fitting. Returns @@ -129,7 +128,7 @@ def eps_data(self) -> complex: return AbstractMedium.nk_to_eps_complex(n=n_data, k=k_data) @property - def freqs(self) -> Tuple[float, ...]: + def freqs(self) -> tuple[float, ...]: """Convert filtered input wavelength data to frequency. Returns @@ -142,7 +141,7 @@ def freqs(self) -> Tuple[float, ...]: return C_0 / wvl_um @property - def frequency_range(self) -> Tuple[float, float]: + def frequency_range(self) -> tuple[float, float]: """Frequency range of filtered input data Returns @@ -262,7 +261,7 @@ def fit( num_tries: int = 50, tolerance_rms: float = 1e-2, guess: PoleResidue = None, - ) -> Tuple[PoleResidue, float]: + ) -> tuple[PoleResidue, float]: """Fit data a number of times and returns best results. Parameters @@ -347,7 +346,7 @@ def _fit_single( self, num_poles: int = 3, guess: PoleResidue = None, - ) -> Tuple[PoleResidue, float]: + ) -> tuple[PoleResidue, float]: """Perform a single fit to the data and return optimization result. Parameters @@ -460,7 +459,7 @@ def objective(coeffs, _grad=None): constraints=(scipy_constraint,), tol=1e-7, callback=None, - options=dict(maxiter=10000), + options={"maxiter": 10000}, ) coeffs = res.x @@ -557,7 +556,7 @@ def plot( return ax @staticmethod - def _validate_url_load(data_load: List): + def _validate_url_load(data_load: list): """Validate if the loaded data from URL is valid The data list should be in this format: [["wl", "n"], @@ -753,7 +752,7 @@ def from_complex_permittivity( wvl_um: ArrayFloat1D, eps_real: ArrayFloat1D, eps_imag: ArrayFloat1D = None, - wvl_range: Tuple[Optional[float], Optional[float]] = (None, None), + wvl_range: tuple[Optional[float], Optional[float]] = (None, None), ) -> DispersionFitter: """Loads :class:`DispersionFitter` from wavelength and complex relative permittivity data @@ -785,7 +784,7 @@ def from_loss_tangent( wvl_um: ArrayFloat1D, eps_real: ArrayFloat1D, loss_tangent: ArrayFloat1D, - wvl_range: Tuple[Optional[float], Optional[float]] = (None, None), + wvl_range: tuple[Optional[float], Optional[float]] = (None, None), ) -> DispersionFitter: """Loads :class:`DispersionFitter` from wavelength and loss tangent data. diff --git a/tidy3d/plugins/dispersion/fit_fast.py b/tidy3d/plugins/dispersion/fit_fast.py index 10564093ea..17d02fae82 100644 --- a/tidy3d/plugins/dispersion/fit_fast.py +++ b/tidy3d/plugins/dispersion/fit_fast.py @@ -2,14 +2,15 @@ from __future__ import annotations -from typing import Tuple +from typing import Optional import numpy as np from pydantic.v1 import NonNegativeFloat, PositiveInt -from ...components.dispersion_fitter import AdvancedFastFitterParam, fit -from ...components.medium import PoleResidue -from ...constants import C_0, HBAR +from tidy3d.components.dispersion_fitter import AdvancedFastFitterParam, fit +from tidy3d.components.medium import PoleResidue +from tidy3d.constants import C_0, HBAR + from .fit import DispersionFitter # numerical tolerance for pole relocation for fast fitter @@ -41,10 +42,10 @@ def fit( self, min_num_poles: PositiveInt = 1, max_num_poles: PositiveInt = DEFAULT_MAX_POLES, - eps_inf: float = None, + eps_inf: Optional[float] = None, tolerance_rms: NonNegativeFloat = DEFAULT_TOLERANCE_RMS, advanced_param: AdvancedFastFitterParam = None, - ) -> Tuple[PoleResidue, float]: + ) -> tuple[PoleResidue, float]: """Fit data using a fast fitting algorithm. Note @@ -117,7 +118,7 @@ def constant_loss_tangent_model( cls, eps_real: float, loss_tangent: float, - frequency_range: Tuple[float, float], + frequency_range: tuple[float, float], max_num_poles: PositiveInt = DEFAULT_MAX_POLES, number_sampling_frequency: PositiveInt = 10, tolerance_rms: NonNegativeFloat = DEFAULT_TOLERANCE_RMS, diff --git a/tidy3d/plugins/dispersion/fit_web.py b/tidy3d/plugins/dispersion/fit_web.py index 4eac610734..d8061ddbfd 100644 --- a/tidy3d/plugins/dispersion/fit_web.py +++ b/tidy3d/plugins/dispersion/fit_web.py @@ -1,6 +1,8 @@ """Deprecated module""" -from ...log import log +from __future__ import annotations + +from tidy3d.log import log log.warning( "The module 'plugins.dispersion.fit_web' has been deprecated in favor of " diff --git a/tidy3d/plugins/dispersion/web.py b/tidy3d/plugins/dispersion/web.py index 275ff076e7..186eb0f782 100644 --- a/tidy3d/plugins/dispersion/web.py +++ b/tidy3d/plugins/dispersion/web.py @@ -4,21 +4,21 @@ import ssl from enum import Enum -from typing import Optional, Tuple +from typing import Literal, Optional import pydantic.v1 as pydantic import requests from pydantic.v1 import Field, NonNegativeFloat, PositiveFloat, PositiveInt, validator +from tidy3d.components.base import Tidy3dBaseModel, skip_if_fields_missing +from tidy3d.components.medium import PoleResidue +from tidy3d.components.types import Undefined +from tidy3d.constants import HERTZ, MICROMETER +from tidy3d.exceptions import SetupError, Tidy3dError, WebError +from tidy3d.log import log from tidy3d.web.core.environment import Env from tidy3d.web.core.http_util import get_headers -from ...components.base import Tidy3dBaseModel, skip_if_fields_missing -from ...components.medium import PoleResidue -from ...components.types import Literal -from ...constants import HERTZ, MICROMETER -from ...exceptions import SetupError, Tidy3dError, WebError -from ...log import log from .fit import DispersionFitter BOUND_MAX_FACTOR = 10 @@ -102,8 +102,7 @@ def _validate_lower_frequency_bound(cls, val, values): """bound_f_lower cannot be larger than bound_f.""" if values["bound_f"] is not None and val > values["bound_f"]: raise SetupError( - "The upper bound 'bound_f' cannot be smaller " - "than the lower bound 'bound_f_lower'." + "The upper bound 'bound_f' cannot be smaller than the lower bound 'bound_f_lower'." ) return val @@ -111,18 +110,18 @@ def _validate_lower_frequency_bound(cls, val, values): class FitterData(AdvancedFitterParam): """Data class for request body of Fitter where dipsersion data is input through tuple.""" - wvl_um: Tuple[float, ...] = Field( + wvl_um: tuple[float, ...] = Field( ..., title="Wavelengths", description="A set of wavelengths for dispersion data.", units=MICROMETER, ) - n_data: Tuple[float, ...] = Field( + n_data: tuple[float, ...] = Field( ..., title="Index of refraction", description="Real part of the complex index of refraction at each wavelength.", ) - k_data: Tuple[float, ...] = Field( + k_data: tuple[float, ...] = Field( None, title="Extinction coefficient", description="Imaginary part of the complex index of refraction at each wavelength.", @@ -255,7 +254,7 @@ def _setup_server(url_server: str): return get_headers() - def run(self) -> Tuple[PoleResidue, float]: + def run(self) -> tuple[PoleResidue, float]: """Execute the data fit using the stable fitter in the server. Returns @@ -316,8 +315,8 @@ def run( num_poles: PositiveInt = 1, num_tries: PositiveInt = 50, tolerance_rms: NonNegativeFloat = 1e-2, - advanced_param: AdvancedFitterParam = AdvancedFitterParam(), -) -> Tuple[PoleResidue, float]: + advanced_param: AdvancedFitterParam = Undefined, +) -> tuple[PoleResidue, float]: """Execute the data fit using the stable fitter in the server. Parameters @@ -338,6 +337,8 @@ def run( Tuple[:class:`.PoleResidue`, float] Best results of multiple fits: (dispersive medium, RMS error). """ + if advanced_param is Undefined: + advanced_param = AdvancedFitterParam() task = FitterData.create(fitter, num_poles, num_tries, tolerance_rms, advanced_param) return task.run() @@ -359,7 +360,9 @@ def fit( num_tries: PositiveInt = 50, tolerance_rms: NonNegativeFloat = 1e-2, guess: PoleResidue = None, - advanced_param: AdvancedFitterParam = AdvancedFitterParam(), - ) -> Tuple[PoleResidue, float]: + advanced_param: AdvancedFitterParam = Undefined, + ) -> tuple[PoleResidue, float]: """Deprecated.""" + if advanced_param is Undefined: + advanced_param = AdvancedFitterParam() return run(self, num_poles, num_tries, tolerance_rms, advanced_param) diff --git a/tidy3d/plugins/expressions/__init__.py b/tidy3d/plugins/expressions/__init__.py index 1f8f733f3a..616aac08e8 100644 --- a/tidy3d/plugins/expressions/__init__.py +++ b/tidy3d/plugins/expressions/__init__.py @@ -1,22 +1,24 @@ +from __future__ import annotations + from .base import Expression from .functions import Cos, Exp, Log, Log10, Sin, Sqrt, Tan from .metrics import ModeAmp, ModePower, generate_validation_data from .variables import Constant, Variable __all__ = [ - "Expression", "Constant", - "Variable", - "ModeAmp", - "ModePower", - "generate_validation_data", - "Sin", "Cos", - "Tan", "Exp", + "Expression", "Log", "Log10", + "ModeAmp", + "ModePower", + "Sin", "Sqrt", + "Tan", + "Variable", + "generate_validation_data", ] # The following code dynamically collects all classes that are subclasses of Expression diff --git a/tidy3d/plugins/expressions/base.py b/tidy3d/plugins/expressions/base.py index f24cd57cc3..ff52b648ef 100644 --- a/tidy3d/plugins/expressions/base.py +++ b/tidy3d/plugins/expressions/base.py @@ -1,7 +1,8 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Generator, Optional, Type +from collections.abc import Generator +from typing import TYPE_CHECKING, Any, Optional from tidy3d.components.base import Tidy3dBaseModel from tidy3d.components.types import TYPE_TAG_STR @@ -62,7 +63,7 @@ def parse_obj(cls, obj: dict[str, Any]) -> ExpressionType: return subclass(**obj) def filter( - self, target_type: Type[Expression], target_field: Optional[str] = None + self, target_type: type[Expression], target_field: Optional[str] = None ) -> Generator[Expression, None, None]: """ Find all instances of a given type or field in the expression. @@ -107,12 +108,11 @@ def _find_instances(expr: Expression): def _to_expression(other: NumberOrExpression | dict[str, Any]) -> ExpressionType: if isinstance(other, Expression): return other - elif isinstance(other, dict): + if isinstance(other, dict): return Expression.parse_obj(other) - else: - from .variables import Constant + from .variables import Constant - return Constant(other) + return Constant(other) def __neg__(self) -> Negate: from .operators import Negate diff --git a/tidy3d/plugins/expressions/functions.py b/tidy3d/plugins/expressions/functions.py index 5bcfcf5c1a..e5460fe028 100644 --- a/tidy3d/plugins/expressions/functions.py +++ b/tidy3d/plugins/expressions/functions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Any import autograd.numpy as anp diff --git a/tidy3d/plugins/expressions/metrics.py b/tidy3d/plugins/expressions/metrics.py index 04aa79c405..277085bfe7 100644 --- a/tidy3d/plugins/expressions/metrics.py +++ b/tidy3d/plugins/expressions/metrics.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from abc import ABC, abstractmethod from typing import Any, Optional, Union diff --git a/tidy3d/plugins/expressions/types.py b/tidy3d/plugins/expressions/types.py index 861e86353b..1f6a12e9fe 100644 --- a/tidy3d/plugins/expressions/types.py +++ b/tidy3d/plugins/expressions/types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TYPE_CHECKING, Annotated, Union from pydantic.v1 import Field diff --git a/tidy3d/plugins/expressions/variables.py b/tidy3d/plugins/expressions/variables.py index ae32591c1e..13cd5534b8 100644 --- a/tidy3d/plugins/expressions/variables.py +++ b/tidy3d/plugins/expressions/variables.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Any, Optional import pydantic.v1 as pd @@ -48,12 +50,11 @@ def evaluate(self, *args: Any, **kwargs: Any) -> NumberType: if self.name not in kwargs: raise ValueError(f"Variable '{self.name}' not provided.") return kwargs[self.name] - else: - if not args: - raise ValueError("No positional argument provided for unnamed variable.") - if len(args) > 1: - raise ValueError("Multiple positional arguments provided for unnamed variable.") - return args[0] + if not args: + raise ValueError("No positional argument provided for unnamed variable.") + if len(args) > 1: + raise ValueError("Multiple positional arguments provided for unnamed variable.") + return args[0] def __repr__(self) -> str: return self.name if self.name else "Variable()" diff --git a/tidy3d/plugins/invdes/__init__.py b/tidy3d/plugins/invdes/__init__.py index 8bb3d5e961..86f819248e 100644 --- a/tidy3d/plugins/invdes/__init__.py +++ b/tidy3d/plugins/invdes/__init__.py @@ -1,4 +1,5 @@ # imports from tidy3d.plugins.invdes as tdi +from __future__ import annotations from . import utils from .design import InverseDesign, InverseDesignMulti @@ -14,15 +15,15 @@ from .transformation import FilterProject __all__ = ( + "AdamOptimizer", + "CustomInitializationSpec", + "ErosionDilationPenalty", + "FilterProject", "InverseDesign", "InverseDesignMulti", - "FilterProject", - "ErosionDilationPenalty", - "TopologyDesignRegion", - "AdamOptimizer", "InverseDesignResult", "RandomInitializationSpec", + "TopologyDesignRegion", "UniformInitializationSpec", - "CustomInitializationSpec", "utils", ) diff --git a/tidy3d/plugins/invdes/design.py b/tidy3d/plugins/invdes/design.py index f8a4487b07..42ea65c108 100644 --- a/tidy3d/plugins/invdes/design.py +++ b/tidy3d/plugins/invdes/design.py @@ -62,7 +62,7 @@ def make_objective_fn( direction_multiplier = 1 if maximize else -1 - def objective_fn(params: anp.ndarray, aux_data: dict = None) -> float: + def objective_fn(params: anp.ndarray, aux_data: typing.Optional[dict] = None) -> float: """Full objective function.""" data = self.to_simulation_data(params=params) @@ -124,7 +124,7 @@ class InverseDesign(AbstractInverseDesign): description="Simulation without the design regions or monitors used in the objective fn.", ) - output_monitor_names: typing.Tuple[str, ...] = pd.Field( + output_monitor_names: tuple[str, ...] = pd.Field( None, title="Output Monitor Names", description="Optional names of monitors whose data the differentiable output depends on." @@ -201,7 +201,7 @@ def _validate_metric_data(expr: ExpressionType, simulation: td.Simulation) -> No try: result = expr(data) except Exception as e: - raise ValidationError(f"Failed to evaluate the metric expression: {str(e)}") from e + raise ValidationError(f"Failed to evaluate the metric expression: {e!s}") from e if len(np.ravel(result)) > 1: raise ValidationError( f"The expression must return a scalar value or an array of length 1 (got {result})." @@ -221,10 +221,10 @@ def is_output_monitor(self, monitor: td.Monitor) -> bool: return monitor.name in self.output_monitor_names - def separate_output_monitors(self, monitors: typing.Tuple[td.Monitor]) -> dict: + def separate_output_monitors(self, monitors: tuple[td.Monitor]) -> dict: """Separate monitors into output_monitors and regular monitors.""" - monitor_fields = dict(monitors=[], output_monitors=[]) + monitor_fields = {"monitors": [], "output_monitors": []} for monitor in monitors: key = "output_monitors" if self.is_output_monitor(monitor) else "monitors" @@ -247,7 +247,7 @@ def to_simulation(self, params: anp.ndarray) -> td.Simulation: grid_spec = grid_spec.updated_copy(override_structures=override_structures) return self.simulation.updated_copy( - structures=list(self.simulation.structures) + [design_region_structure], + structures=[*list(self.simulation.structures), design_region_structure], grid_spec=grid_spec, ) @@ -260,13 +260,13 @@ def to_simulation_data(self, params: anp.ndarray, **kwargs) -> td.SimulationData class InverseDesignMulti(AbstractInverseDesign): """``InverseDesign`` with multiple simulations and corresponding postprocess functions.""" - simulations: typing.Tuple[td.Simulation, ...] = pd.Field( + simulations: tuple[td.Simulation, ...] = pd.Field( ..., title="Base Simulations", description="Set of simulation without the design regions or monitors used in the objective fn.", ) - output_monitor_names: typing.Tuple[typing.Union[typing.Tuple[str, ...], None], ...] = pd.Field( + output_monitor_names: tuple[typing.Union[tuple[str, ...], None], ...] = pd.Field( None, title="Output Monitor Names", description="Optional names of monitors whose data the differentiable output depends on." @@ -301,7 +301,7 @@ def task_names(self) -> list[str]: return [f"{self.task_name}_{i}" for i in range(len(self.simulations))] @property - def designs(self) -> typing.List[InverseDesign]: + def designs(self) -> list[InverseDesign]: """List of individual ``InverseDesign`` objects corresponding to this instance.""" designs_list = [] diff --git a/tidy3d/plugins/invdes/initialization.py b/tidy3d/plugins/invdes/initialization.py index eb75a940c4..3acd297d25 100644 --- a/tidy3d/plugins/invdes/initialization.py +++ b/tidy3d/plugins/invdes/initialization.py @@ -21,7 +21,6 @@ class AbstractInitializationSpec(Tidy3dBaseModel, ABC): @abstractmethod def create_parameters(self, shape: tuple[int, ...]) -> NDArray: """Generate the parameter array based on the specification.""" - pass class RandomInitializationSpec(AbstractInitializationSpec): @@ -102,8 +101,7 @@ def _validate_params_dtype(cls, value, values): """Ensure that params is real-valued.""" if np.issubdtype(value.dtype, np.bool_): td.log.warning( - "Got a boolean array for 'params'. " - "This will be treated as a floating point array." + "Got a boolean array for 'params'. This will be treated as a floating point array." ) value = value.astype(float) elif not np.issubdtype(value.dtype, np.floating): diff --git a/tidy3d/plugins/invdes/optimizer.py b/tidy3d/plugins/invdes/optimizer.py index 08758f12b6..a43ba47e23 100644 --- a/tidy3d/plugins/invdes/optimizer.py +++ b/tidy3d/plugins/invdes/optimizer.py @@ -1,4 +1,5 @@ # specification for running the optimizer +from __future__ import annotations import abc import typing @@ -139,7 +140,7 @@ def run( def continue_run( self, result: InverseDesignResult, - num_steps: int = None, + num_steps: typing.Optional[int] = None, post_process_fn: typing.Optional[typing.Callable] = None, callback: typing.Optional[typing.Callable] = None, ) -> InverseDesignResult: @@ -229,7 +230,7 @@ def continue_run( def continue_run_from_file( self, fname: str, - num_steps: int = None, + num_steps: typing.Optional[int] = None, post_process_fn: typing.Optional[typing.Callable] = None, callback: typing.Optional[typing.Callable] = None, ) -> InverseDesignResult: @@ -244,7 +245,7 @@ def continue_run_from_file( def continue_run_from_history( self, - num_steps: int = None, + num_steps: typing.Optional[int] = None, post_process_fn: typing.Optional[typing.Callable] = None, callback: typing.Optional[typing.Callable] = None, ) -> InverseDesignResult: @@ -285,10 +286,10 @@ class AdamOptimizer(AbstractOptimizer): def initial_state(self, parameters: np.ndarray) -> dict: """initial state of the optimizer""" zeros = np.zeros_like(parameters) - return dict(m=zeros, v=zeros, t=0) + return {"m": zeros, "v": zeros, "t": 0} def update( - self, parameters: np.ndarray, gradient: np.ndarray, state: dict = None + self, parameters: np.ndarray, gradient: np.ndarray, state: typing.Optional[dict] = None ) -> tuple[np.ndarray, dict]: if state is None: state = self.initial_state(parameters) @@ -311,5 +312,5 @@ def update( # update parameters and state parameters -= self.learning_rate * m_ / (np.sqrt(v_) + self.eps) - state = dict(m=m, v=v, t=t) + state = {"m": m, "v": v, "t": t} return parameters, state diff --git a/tidy3d/plugins/invdes/penalty.py b/tidy3d/plugins/invdes/penalty.py index 621cbfb9e4..1683f577fd 100644 --- a/tidy3d/plugins/invdes/penalty.py +++ b/tidy3d/plugins/invdes/penalty.py @@ -1,4 +1,5 @@ # define penalties applied to parameters from design region +from __future__ import annotations import abc import typing diff --git a/tidy3d/plugins/invdes/region.py b/tidy3d/plugins/invdes/region.py index b94eaf8682..17fa5009c4 100644 --- a/tidy3d/plugins/invdes/region.py +++ b/tidy3d/plugins/invdes/region.py @@ -1,4 +1,5 @@ # container for specification fully defining the inverse design problem +from __future__ import annotations import abc import typing @@ -38,14 +39,14 @@ class DesignRegion(InvdesBaseModel, abc.ABC): units=td.constants.MICROMETER, ) - eps_bounds: typing.Tuple[float, float] = pd.Field( + eps_bounds: tuple[float, float] = pd.Field( ..., ge=1.0, title="Relative Permittivity Bounds", description="Minimum and maximum relative permittivity expressed to the design region.", ) - transformations: typing.Tuple[TransformationType, ...] = pd.Field( + transformations: tuple[TransformationType, ...] = pd.Field( (), title="Transformations", description="Transformations that get applied from first to last on the parameter array." @@ -55,7 +56,7 @@ class DesignRegion(InvdesBaseModel, abc.ABC): "Specific permittivity values given the density array are determined by ``eps_bounds``.", ) - penalties: typing.Tuple[PenaltyType, ...] = pd.Field( + penalties: tuple[PenaltyType, ...] = pd.Field( (), title="Penalties", description="Set of penalties that get evaluated on the material density. Note that the " @@ -149,7 +150,7 @@ class TopologyDesignRegion(DesignRegion): "is assumed to be uniform, i.e. invariant, in the z direction.", ) - transformations: typing.Tuple[TransformationType, ...] = pd.Field( + transformations: tuple[TransformationType, ...] = pd.Field( (), title="Transformations", description="Transformations that get applied from first to last on the parameter array." @@ -158,7 +159,7 @@ class TopologyDesignRegion(DesignRegion): "permittivity and 1 corresponds to the maximum relative permittivity. " "Specific permittivity values given the density array are determined by ``eps_bounds``.", ) - penalties: typing.Tuple[PenaltyType, ...] = pd.Field( + penalties: tuple[PenaltyType, ...] = pd.Field( (), title="Penalties", description="Set of penalties that get evaluated on the material density. Note that the " @@ -183,7 +184,7 @@ def _validate_eps_values(self): x = self.initial_parameters self.eps_values(x) except Exception as e: - raise ValidationError(f"Could not evaluate transformations: {str(e)}") from e + raise ValidationError(f"Could not evaluate transformations: {e!s}") from e def _validate_penalty_value(self): """Validate the penalty values by evaluating the penalties.""" @@ -191,7 +192,7 @@ def _validate_penalty_value(self): x = self.initial_parameters self.penalty_value(x) except Exception as e: - raise ValidationError(f"Could not evaluate penalties: {str(e)}") from e + raise ValidationError(f"Could not evaluate penalties: {e!s}") from e def _validate_gradients(self): """Validate the gradients of the penalties and transformations.""" @@ -219,7 +220,7 @@ def _validate_gradients(self): "This indicates that the optimization will not function correctly. " "Please double-check the definitions of both the penalties and transformations." ) - elif penalty_independent: + if penalty_independent: td.log.warning( "Penalty gradient seems independent of input, meaning that it " "will not contribute to the objective gradient during optimization. " @@ -244,7 +245,7 @@ def _check_params(params: anp.ndarray = None): ) @property - def params_shape(self) -> typing.Tuple[int, int, int]: + def params_shape(self) -> tuple[int, int, int]: """Shape of the parameters array in (x, y, z), given the ``pixel_size`` and bounds.""" side_lengths = np.array(self.size) num_pixels = np.ceil(side_lengths / self.pixel_size) @@ -289,7 +290,7 @@ def params_ones(self): return self.params_uniform(1.0) @property - def coords(self) -> typing.Dict[str, typing.List[float]]: + def coords(self) -> dict[str, list[float]]: """Coordinates for the custom medium corresponding to this design region.""" lengths = np.array(self.size) @@ -297,7 +298,7 @@ def coords(self) -> typing.Dict[str, typing.List[float]]: rmin, rmax = self.geometry.bounds params_shape = self.params_shape - coords = dict() + coords = {} for dim, ptmin, ptmax, length, num_pts in zip("xyz", rmin, rmax, lengths, params_shape): step_size = length / num_pts if np.isinf(length): diff --git a/tidy3d/plugins/invdes/result.py b/tidy3d/plugins/invdes/result.py index 86e5fcd015..c05aace157 100644 --- a/tidy3d/plugins/invdes/result.py +++ b/tidy3d/plugins/invdes/result.py @@ -1,4 +1,5 @@ # convenient container for the output of the inverse design (specifically the history) +from __future__ import annotations import typing @@ -24,67 +25,67 @@ class InverseDesignResult(InvdesBaseModel): description="Specification describing the inverse design problem we wish to optimize.", ) - params: typing.Tuple[ArrayLike, ...] = pd.Field( + params: tuple[ArrayLike, ...] = pd.Field( (), title="Parameter History", description="History of parameter arrays throughout the optimization.", ) - objective_fn_val: typing.Tuple[float, ...] = pd.Field( + objective_fn_val: tuple[float, ...] = pd.Field( (), title="Objective Function History", description="History of objective function values throughout the optimization.", ) - grad: typing.Tuple[ArrayLike, ...] = pd.Field( + grad: tuple[ArrayLike, ...] = pd.Field( (), title="Gradient History", description="History of objective function gradient arrays throughout the optimization.", ) - penalty: typing.Tuple[float, ...] = pd.Field( + penalty: tuple[float, ...] = pd.Field( (), title="Penalty History", description="History of weighted sum of penalties throughout the optimization.", ) - post_process_val: typing.Tuple[float, ...] = pd.Field( + post_process_val: tuple[float, ...] = pd.Field( (), title="Post-Process Function History", description="History of return values from ``post_process_fn`` throughout the optimization.", ) - simulation: typing.Tuple[td.Simulation, ...] = pd.Field( + simulation: tuple[td.Simulation, ...] = pd.Field( (), title="Simulation History", description="History of ``td.Simulation`` instances throughout the optimization.", ) - opt_state: typing.Tuple[dict, ...] = pd.Field( + opt_state: tuple[dict, ...] = pd.Field( (), title="Optimizer State History", description="History of optimizer states throughout the optimization.", ) @property - def history(self) -> typing.Dict[str, list]: + def history(self) -> dict[str, list]: """The history-containing fields as a dictionary of lists.""" - return dict( - params=list(self.params), - objective_fn_val=list(self.objective_fn_val), - grad=list(self.grad), - penalty=list(self.penalty), - post_process_val=list(self.post_process_val), - opt_state=list(self.opt_state), - ) + return { + "params": list(self.params), + "objective_fn_val": list(self.objective_fn_val), + "grad": list(self.grad), + "penalty": list(self.penalty), + "post_process_val": list(self.post_process_val), + "opt_state": list(self.opt_state), + } @property - def keys(self) -> typing.List[str]: + def keys(self) -> list[str]: """Keys stored in the history.""" return list(self.history.keys()) @property - def last(self) -> typing.Dict[str, typing.Any]: + def last(self) -> dict[str, typing.Any]: """Dictionary of last values in ``self.history``.""" return {key: value[-1] for key, value in self.history.items()} @@ -101,20 +102,20 @@ def get_last(self, key: str) -> typing.Any: """Get the last value from the history.""" return self.get(key=key, index=-1) - def get_sim(self, index: int = -1) -> typing.Union[td.Simulation, typing.List[td.Simulation]]: + def get_sim(self, index: int = -1) -> typing.Union[td.Simulation, list[td.Simulation]]: """Get the simulation at a specific index in the history (list of sims if multi).""" params = np.array(self.get(key="params", index=index)) return self.design.to_simulation(params=params) def get_sim_data( self, index: int = -1, **kwargs - ) -> typing.Union[td.SimulationData, typing.List[td.SimulationData]]: + ) -> typing.Union[td.SimulationData, list[td.SimulationData]]: """Get the simulation data at a specific index in the history (list of simdata if multi).""" params = np.array(self.get(key="params", index=index)) return self.design.to_simulation_data(params=params, **kwargs) @property - def sim_last(self) -> typing.Union[td.Simulation, typing.List[td.Simulation]]: + def sim_last(self) -> typing.Union[td.Simulation, list[td.Simulation]]: """The last simulation.""" return self.get_sim(index=-1) diff --git a/tidy3d/plugins/invdes/transformation.py b/tidy3d/plugins/invdes/transformation.py index cb9e343ceb..c5060209cf 100644 --- a/tidy3d/plugins/invdes/transformation.py +++ b/tidy3d/plugins/invdes/transformation.py @@ -1,4 +1,5 @@ # transformations applied to design region +from __future__ import annotations import abc import typing diff --git a/tidy3d/plugins/invdes/utils.py b/tidy3d/plugins/invdes/utils.py index 3e3e1fd0af..eeb3fff79f 100644 --- a/tidy3d/plugins/invdes/utils.py +++ b/tidy3d/plugins/invdes/utils.py @@ -1,6 +1,7 @@ """Functional utilities that help define postprocessing functions more simply in ``invdes``.""" # TODO: improve these? +from __future__ import annotations import typing diff --git a/tidy3d/plugins/invdes/validators.py b/tidy3d/plugins/invdes/validators.py index cfdaa6ad18..07a522b42e 100644 --- a/tidy3d/plugins/invdes/validators.py +++ b/tidy3d/plugins/invdes/validators.py @@ -1,4 +1,5 @@ # validator utilities for invdes plugin +from __future__ import annotations import typing @@ -23,7 +24,7 @@ def _ignore_field(cls, val): "set this field internally using the design region specifications. " "The supplied value will be ignored. " ) - return None + return return _ignore_field @@ -31,7 +32,9 @@ def _ignore_field(cls, val): def check_pixel_size(sim_field_name: str): """make validator to check the pixel size of sim or list of sims in an ``InverseDesign``.""" - def check_pixel_size_sim(sim: td.Simulation, pixel_size: float, index: int = None) -> None: + def check_pixel_size_sim( + sim: td.Simulation, pixel_size: float, index: typing.Optional[int] = None + ) -> None: """Check a pixel size compared to the simulation min wvl in material.""" if not sim.sources: td.log.warning( diff --git a/tidy3d/plugins/microwave/__init__.py b/tidy3d/plugins/microwave/__init__.py index b0d6f87c8b..bc47c58c21 100644 --- a/tidy3d/plugins/microwave/__init__.py +++ b/tidy3d/plugins/microwave/__init__.py @@ -1,5 +1,7 @@ """Imports from microwave plugin.""" +from __future__ import annotations + from . import models from .array_factor import ( RectangularAntennaArrayCalculator, @@ -21,17 +23,17 @@ __all__ = [ "AxisAlignedPathIntegral", - "CustomPathIntegral2D", - "VoltageIntegralAxisAligned", "CurrentIntegralAxisAligned", - "CustomVoltageIntegral2D", - "CustomCurrentIntegral2D", - "VoltageIntegralTypes", "CurrentIntegralTypes", + "CustomCurrentIntegral2D", + "CustomPathIntegral2D", + "CustomVoltageIntegral2D", "ImpedanceCalculator", + "LobeMeasurer", + "RectangularAntennaArrayCalculator", + "VoltageIntegralAxisAligned", + "VoltageIntegralTypes", "models", "path_integrals_from_lumped_element", "rf_material_library", - "RectangularAntennaArrayCalculator", - "LobeMeasurer", ] diff --git a/tidy3d/plugins/microwave/array_factor.py b/tidy3d/plugins/microwave/array_factor.py index 5becb44414..ca688b7ee5 100644 --- a/tidy3d/plugins/microwave/array_factor.py +++ b/tidy3d/plugins/microwave/array_factor.py @@ -1,28 +1,29 @@ """Convenience functions for estimating antenna radiation by applying array factor.""" +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Optional, Tuple, Union +from typing import Optional, Union import numpy as np import pydantic.v1 as pd from pydantic.v1 import NonNegativeFloat, PositiveInt +from tidy3d.components.base import Tidy3dBaseModel, skip_if_fields_missing +from tidy3d.components.data.monitor_data import AbstractFieldProjectionData, DirectivityData +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.geometry.base import Box, Geometry +from tidy3d.components.grid.grid_spec import GridSpec, LayerRefinementSpec +from tidy3d.components.lumped_element import LumpedElement +from tidy3d.components.medium import Medium, MediumType3D +from tidy3d.components.monitor import AbstractFieldProjectionMonitor, MonitorType +from tidy3d.components.simulation import Simulation +from tidy3d.components.source.utils import SourceType +from tidy3d.components.structure import MeshOverrideStructure, Structure +from tidy3d.components.types import ArrayLike, Axis, Bound, Undefined +from tidy3d.constants import C_0, inf from tidy3d.log import log -from ...components.base import Tidy3dBaseModel, skip_if_fields_missing -from ...components.data.monitor_data import AbstractFieldProjectionData, DirectivityData -from ...components.data.sim_data import SimulationData -from ...components.geometry.base import Box, Geometry -from ...components.grid.grid_spec import GridSpec, LayerRefinementSpec -from ...components.lumped_element import LumpedElement -from ...components.medium import Medium, MediumType3D -from ...components.monitor import AbstractFieldProjectionMonitor, MonitorType -from ...components.simulation import Simulation -from ...components.source.utils import SourceType -from ...components.structure import MeshOverrideStructure, Structure -from ...components.types import ArrayLike, Axis, Bound -from ...constants import C_0, inf - class AbstractAntennaArrayCalculator(Tidy3dBaseModel, ABC): """Abstract base for phased array calculators.""" @@ -159,7 +160,7 @@ def _try_to_expand_geometry( def _duplicate_or_expand_list_of_objects( self, - objects: Tuple[ + objects: tuple[ Union[Structure, MeshOverrideStructure, LayerRefinementSpec, LumpedElement], ... ], old_sim_bounds: Bound, @@ -228,7 +229,7 @@ def _duplicate_or_expand_list_of_objects( def _expand_monitors( self, - monitors: Tuple[MonitorType, ...], + monitors: tuple[MonitorType, ...], antenna_bounds: Bound, new_sim_bounds: Bound, old_sim_bounds: Bound, @@ -300,7 +301,7 @@ def _expand_monitors( return array_monitors def _duplicate_structures( - self, structures: Tuple[Structure, ...], new_sim_bounds: Bound, old_sim_bounds: Bound + self, structures: tuple[Structure, ...], new_sim_bounds: Bound, old_sim_bounds: Bound ): """Duplicate structures.""" @@ -310,8 +311,8 @@ def _duplicate_structures( def _duplicate_sources( self, - sources: Tuple[SourceType, ...], - lumped_elements: Tuple[LumpedElement, ...], + sources: tuple[SourceType, ...], + lumped_elements: tuple[LumpedElement, ...], old_sim_bounds: Bound, new_sim_bounds: Bound, ): @@ -619,23 +620,23 @@ class RectangularAntennaArrayCalculator(AbstractAntennaArrayCalculator): ... ) # doctest: +SKIP """ - array_size: Tuple[PositiveInt, PositiveInt, PositiveInt] = pd.Field( + array_size: tuple[PositiveInt, PositiveInt, PositiveInt] = pd.Field( title="Array Size", description="Number of antennas along x, y, and z directions.", ) - spacings: Tuple[NonNegativeFloat, NonNegativeFloat, NonNegativeFloat] = pd.Field( + spacings: tuple[NonNegativeFloat, NonNegativeFloat, NonNegativeFloat] = pd.Field( title="Antenna Spacings", description="Center-to-center spacings between antennas along x, y, and z directions.", ) - phase_shifts: Tuple[float, float, float] = pd.Field( + phase_shifts: tuple[float, float, float] = pd.Field( (0, 0, 0), title="Phase Shifts", description="Phase-shifts between antennas along x, y, and z directions.", ) - amp_multipliers: Tuple[Optional[ArrayLike], Optional[ArrayLike], Optional[ArrayLike]] = ( + amp_multipliers: tuple[Optional[ArrayLike], Optional[ArrayLike], Optional[ArrayLike]] = ( pd.Field( (None, None, None), title="Amplitude Multipliers", @@ -702,7 +703,7 @@ def _antenna_phases(self) -> ArrayLike: return np.ravel(sum(p for p in phase_shifts_grid)) @property - def _extend_dims(self) -> Tuple[Axis, ...]: + def _extend_dims(self) -> tuple[Axis, ...]: """Dimensions along which antennas will be duplicated.""" return [ind for ind, size in enumerate(self.array_size) if size > 1] @@ -711,7 +712,7 @@ def array_factor( theta: Union[float, ArrayLike], phi: Union[float, ArrayLike], frequency: Union[NonNegativeFloat, ArrayLike], - medium: MediumType3D = Medium(), + medium: MediumType3D = Undefined, ) -> ArrayLike: """ Compute the array factor for a 3D antenna array. @@ -730,6 +731,8 @@ def array_factor( ArrayLike Array factor values for each combination of theta and phi. """ + if medium is Undefined: + medium = Medium() # Convert all inputs to numpy arrays theta_array = np.atleast_1d(theta) diff --git a/tidy3d/plugins/microwave/auto_path_integrals.py b/tidy3d/plugins/microwave/auto_path_integrals.py index d9140b7a57..b93e611f77 100644 --- a/tidy3d/plugins/microwave/auto_path_integrals.py +++ b/tidy3d/plugins/microwave/auto_path_integrals.py @@ -1,10 +1,18 @@ """Helpers for automatic setup of path integrals.""" -from ...components.geometry.base import Box -from ...components.geometry.utils import SnapBehavior, SnapLocation, SnappingSpec, snap_box_to_grid -from ...components.grid.grid import Grid -from ...components.lumped_element import LinearLumpedElement -from ...components.types import Direction +from __future__ import annotations + +from tidy3d.components.geometry.base import Box +from tidy3d.components.geometry.utils import ( + SnapBehavior, + SnapLocation, + SnappingSpec, + snap_box_to_grid, +) +from tidy3d.components.grid.grid import Grid +from tidy3d.components.lumped_element import LinearLumpedElement +from tidy3d.components.types import Direction + from .path_integrals import ( CurrentIntegralAxisAligned, VoltageIntegralAxisAligned, diff --git a/tidy3d/plugins/microwave/custom_path_integrals.py b/tidy3d/plugins/microwave/custom_path_integrals.py index 6b6c48469a..d1e2c89a19 100644 --- a/tidy3d/plugins/microwave/custom_path_integrals.py +++ b/tidy3d/plugins/microwave/custom_path_integrals.py @@ -2,19 +2,20 @@ from __future__ import annotations -from typing import Literal +from typing import Literal, Optional import numpy as np import pydantic.v1 as pd import shapely import xarray as xr -from ...components.base import cached_property -from ...components.geometry.base import Geometry -from ...components.types import ArrayFloat2D, Ax, Axis, Bound, Coordinate, Direction -from ...components.viz import add_ax_if_none -from ...constants import MICROMETER, fp_eps -from ...exceptions import SetupError +from tidy3d.components.base import cached_property +from tidy3d.components.geometry.base import Geometry +from tidy3d.components.types import ArrayFloat2D, Ax, Axis, Bound, Coordinate, Direction +from tidy3d.components.viz import add_ax_if_none +from tidy3d.constants import MICROMETER, fp_eps +from tidy3d.exceptions import SetupError + from .path_integrals import ( AbstractAxesRH, AxisAlignedPathIntegral, @@ -263,9 +264,9 @@ def compute_voltage(self, em_field: MonitorDataTypes) -> IntegralResultTypes: @add_ax_if_none def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, **path_kwargs, ) -> Ax: @@ -336,9 +337,9 @@ def compute_current(self, em_field: MonitorDataTypes) -> IntegralResultTypes: @add_ax_if_none def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, **path_kwargs, ) -> Ax: @@ -393,5 +394,4 @@ def sign(self) -> Direction: is_ccw = not is_ccw if is_ccw: return "+" - else: - return "-" + return "-" diff --git a/tidy3d/plugins/microwave/impedance_calculator.py b/tidy3d/plugins/microwave/impedance_calculator.py index 488fbcd9b1..aeb0de8e37 100644 --- a/tidy3d/plugins/microwave/impedance_calculator.py +++ b/tidy3d/plugins/microwave/impedance_calculator.py @@ -7,11 +7,12 @@ import numpy as np import pydantic.v1 as pd -from ...components.base import Tidy3dBaseModel -from ...components.data.monitor_data import FieldTimeData -from ...constants import OHM -from ...exceptions import ValidationError -from ...log import log +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.components.data.monitor_data import FieldTimeData +from tidy3d.constants import OHM +from tidy3d.exceptions import ValidationError +from tidy3d.log import log + from .custom_path_integrals import CustomCurrentIntegral2D, CustomVoltageIntegral2D from .path_integrals import ( AxisAlignedPathIntegral, diff --git a/tidy3d/plugins/microwave/lobe_measurer.py b/tidy3d/plugins/microwave/lobe_measurer.py index 9bf0f61ddc..b55bf3ac3c 100644 --- a/tidy3d/plugins/microwave/lobe_measurer.py +++ b/tidy3d/plugins/microwave/lobe_measurer.py @@ -1,5 +1,7 @@ """Tool for finding and characterizing lobes in antenna radiation patterns.""" +from __future__ import annotations + from math import isclose, isnan from typing import Optional @@ -8,11 +10,12 @@ from pandas import DataFrame from scipy.signal import find_peaks, peak_widths -from ...components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing -from ...components.types import ArrayFloat1D, ArrayLike, Ax -from ...constants import fp_eps -from ...exceptions import ValidationError -from ...log import log +from tidy3d.components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing +from tidy3d.components.types import ArrayFloat1D, ArrayLike, Ax +from tidy3d.constants import fp_eps +from tidy3d.exceptions import ValidationError +from tidy3d.log import log + from .viz import plot_params_lobe_FNBW, plot_params_lobe_peak, plot_params_lobe_width # The minimum plateau size for peak finding, which is set to 0 to ensure that all peaks are found. diff --git a/tidy3d/plugins/microwave/models/__init__.py b/tidy3d/plugins/microwave/models/__init__.py index 458fed0c15..211c81c8f3 100644 --- a/tidy3d/plugins/microwave/models/__init__.py +++ b/tidy3d/plugins/microwave/models/__init__.py @@ -1,8 +1,10 @@ """Imports for transmission line models.""" +from __future__ import annotations + from . import coupled_microstrip, microstrip __all__ = [ - microstrip, - coupled_microstrip, + "coupled_microstrip", + "microstrip", ] diff --git a/tidy3d/plugins/microwave/models/coupled_microstrip.py b/tidy3d/plugins/microwave/models/coupled_microstrip.py index 85de223cfd..a6dd4d1c11 100644 --- a/tidy3d/plugins/microwave/models/coupled_microstrip.py +++ b/tidy3d/plugins/microwave/models/coupled_microstrip.py @@ -7,6 +7,8 @@ Transactions on Microwave Theory and Techniques, 32(1), 83-90. """ +from __future__ import annotations + import numpy as np from . import microstrip diff --git a/tidy3d/plugins/microwave/models/microstrip.py b/tidy3d/plugins/microwave/models/microstrip.py index aad29fd7d6..6545be1852 100644 --- a/tidy3d/plugins/microwave/models/microstrip.py +++ b/tidy3d/plugins/microwave/models/microstrip.py @@ -11,9 +11,11 @@ for open end effect of microstrip lines.” Electronics Letters 17 (1981): 123-125. """ +from __future__ import annotations + import numpy as np -from ....constants import ETA_0 +from tidy3d.constants import ETA_0 def _f(normalized_width: float) -> float: diff --git a/tidy3d/plugins/microwave/path_integrals.py b/tidy3d/plugins/microwave/path_integrals.py index 5d8d48324c..802f5b0d92 100644 --- a/tidy3d/plugins/microwave/path_integrals.py +++ b/tidy3d/plugins/microwave/path_integrals.py @@ -3,15 +3,14 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Union +from typing import Optional, Union import numpy as np import pydantic.v1 as pd -import shapely as shapely import xarray as xr -from ...components.base import Tidy3dBaseModel, cached_property -from ...components.data.data_array import ( +from tidy3d.components.base import Tidy3dBaseModel, cached_property +from tidy3d.components.data.data_array import ( FreqDataArray, FreqModeDataArray, ScalarFieldDataArray, @@ -19,14 +18,15 @@ ScalarModeFieldDataArray, TimeDataArray, ) -from ...components.data.monitor_data import FieldData, FieldTimeData, ModeData, ModeSolverData -from ...components.geometry.base import Box, Geometry -from ...components.types import Ax, Axis, Coordinate2D, Direction -from ...components.validators import assert_line, assert_plane -from ...components.viz import add_ax_if_none -from ...constants import AMP, VOLT, fp_eps -from ...exceptions import DataError, Tidy3dError -from ...log import log +from tidy3d.components.data.monitor_data import FieldData, FieldTimeData, ModeData, ModeSolverData +from tidy3d.components.geometry.base import Box, Geometry +from tidy3d.components.types import Ax, Axis, Coordinate2D, Direction +from tidy3d.components.validators import assert_line, assert_plane +from tidy3d.components.viz import add_ax_if_none +from tidy3d.constants import AMP, VOLT, fp_eps +from tidy3d.exceptions import DataError, Tidy3dError +from tidy3d.log import log + from .viz import ( ARROW_CURRENT, plot_params_current_path, @@ -57,8 +57,7 @@ def remaining_axes(self) -> tuple[Axis, Axis]: axes.pop(self.main_axis) if self.main_axis == 1: return (axes[1], axes[0]) - else: - return (axes[0], axes[1]) + return (axes[0], axes[1]) @cached_property def remaining_dims(self) -> tuple[str, str]: @@ -211,10 +210,9 @@ def _make_result_data_array(result: xr.DataArray) -> IntegralResultTypes: """Helper for creating the proper result type.""" if "t" in result.coords: return TimeDataArray(data=result.data, coords=result.coords) - elif "f" in result.coords and "mode_index" in result.coords: + if "f" in result.coords and "mode_index" in result.coords: return FreqModeDataArray(data=result.data, coords=result.coords) - else: - return FreqDataArray(data=result.data, coords=result.coords) + return FreqDataArray(data=result.data, coords=result.coords) class VoltageIntegralAxisAligned(AxisAlignedPathIntegral): @@ -254,9 +252,9 @@ def _set_data_array_attributes(data_array: IntegralResultTypes) -> IntegralResul def from_terminal_positions( plus_terminal: float, minus_terminal: float, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, extrapolate_to_endpoints: bool = True, snap_path_to_grid: bool = True, ) -> VoltageIntegralAxisAligned: @@ -310,9 +308,9 @@ def from_terminal_positions( @add_ax_if_none def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, **path_kwargs, ) -> Ax: @@ -509,9 +507,9 @@ def _set_data_array_attributes(data_array: IntegralResultTypes) -> IntegralResul @add_ax_if_none def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, **path_kwargs, ) -> Ax: diff --git a/tidy3d/plugins/microwave/rf_material_library.py b/tidy3d/plugins/microwave/rf_material_library.py index 690333bce1..7cb784dd87 100644 --- a/tidy3d/plugins/microwave/rf_material_library.py +++ b/tidy3d/plugins/microwave/rf_material_library.py @@ -1,8 +1,11 @@ """Holds dispersive models for several commonly used RF materials.""" # from ...components.base import Tidy3dBaseModel -from ...components.medium import PoleResidue -from ...material_library.material_library import MaterialItem, VariantItem +from __future__ import annotations + +from tidy3d.components.medium import PoleResidue +from tidy3d.material_library.material_library import MaterialItem, VariantItem + from .rf_material_reference import rf_material_refs Rogers3003_design = VariantItem( @@ -201,53 +204,53 @@ reference=[rf_material_refs["FR4_lowloss"]], ) -rf_material_library = dict( - RO3003=MaterialItem( +rf_material_library = { + "RO3003": MaterialItem( name="Rogers3003", - variants=dict( - design=Rogers3003_design, - process=Rogers3003_process, - ), + variants={ + "design": Rogers3003_design, + "process": Rogers3003_process, + }, default="design", ), - RO3010=MaterialItem( + "RO3010": MaterialItem( name="Rogers3010", - variants=dict( - design=Rogers3010_design, - process=Rogers3010_process, - ), + variants={ + "design": Rogers3010_design, + "process": Rogers3010_process, + }, default="design", ), - RO4003C=MaterialItem( + "RO4003C": MaterialItem( name="Rogers4003C", - variants=dict( - design=Rogers4003C_design, - process=Rogers4003C_process, - ), + variants={ + "design": Rogers4003C_design, + "process": Rogers4003C_process, + }, default="design", ), - RO4350B=MaterialItem( + "RO4350B": MaterialItem( name="Rogers4350B", - variants=dict( - design=Rogers4350B_design, - process=Rogers4350B_process, - ), + variants={ + "design": Rogers4350B_design, + "process": Rogers4350B_process, + }, default="design", ), - AD255C=MaterialItem( + "AD255C": MaterialItem( name="ArlonAD255C", - variants=dict( - design=ArlonAD255C_design, - process=ArlonAD255C_process, - ), + variants={ + "design": ArlonAD255C_design, + "process": ArlonAD255C_process, + }, default="design", ), - FR4=MaterialItem( + "FR4": MaterialItem( name="FR4", - variants=dict( - standard=FR4_standard, - lowloss=FR4_lowloss, - ), + variants={ + "standard": FR4_standard, + "lowloss": FR4_lowloss, + }, default="standard", ), -) +} diff --git a/tidy3d/plugins/microwave/rf_material_reference.py b/tidy3d/plugins/microwave/rf_material_reference.py index 0767687dda..895fb6e91b 100644 --- a/tidy3d/plugins/microwave/rf_material_reference.py +++ b/tidy3d/plugins/microwave/rf_material_reference.py @@ -1,41 +1,43 @@ """Holds the reference materials for Tidy3D material library.""" -from ...material_library.material_reference import ReferenceData +from __future__ import annotations -rf_material_refs = dict( - Rogers3003=ReferenceData( +from tidy3d.material_library.material_reference import ReferenceData + +rf_material_refs = { + "Rogers3003": ReferenceData( manufacturer="Rogers Corporation", datasheet_title="RO3003™ Laminates", url="https://www.rogerscorp.com/advanced-electronics-solutions/ro3000-series-laminates/ro3003-laminates", ), - Rogers3010=ReferenceData( + "Rogers3010": ReferenceData( manufacturer="Rogers Corporation", datasheet_title="RO3010™ Laminates", url="https://www.rogerscorp.com/advanced-electronics-solutions/ro3000-series-laminates/ro3010-laminates", ), - Rogers4003C=ReferenceData( + "Rogers4003C": ReferenceData( manufacturer="Rogers Corporation", datasheet_title="RO4003C™ Laminates", url="https://www.rogerscorp.com/advanced-electronics-solutions/ro4000-series-laminates/ro4350b-laminates", ), - Rogers4350B=ReferenceData( + "Rogers4350B": ReferenceData( manufacturer="Rogers Corporation", datasheet_title="RO4350B™ Laminates", url="https://www.rogerscorp.com/advanced-electronics-solutions/ro4000-series-laminates/ro4350b-laminates", ), - ArlonAD255C=ReferenceData( + "ArlonAD255C": ReferenceData( manufacturer="Rogers Corporation", datasheet_title="AD255C High Performance Polyimide Laminates", url="https://www.rogerscorp.com/advanced-electronics-solutions/ad-series-laminates/ad255c-laminates", ), - FR4_standard=ReferenceData( + "FR4_standard": ReferenceData( manufacturer="Isola", datasheet_title="Standard FR-4 Epoxy Glass Cloth Laminate", url="https://www.isola-group.com/pcb-laminates-prepreg/is410-fr-4-epoxy-laminate-and-prepreg/", ), - FR4_lowloss=ReferenceData( + "FR4_lowloss": ReferenceData( manufacturer="Isola", datasheet_title="Low loss FR-4 Epoxy Glass Cloth Laminate", url="https://www.isola-group.com/pcb-laminates-prepreg/is410-fr-4-epoxy-laminate-and-prepreg/", ), -) +} diff --git a/tidy3d/plugins/microwave/viz.py b/tidy3d/plugins/microwave/viz.py index 5a8b3dd582..72f4570c86 100644 --- a/tidy3d/plugins/microwave/viz.py +++ b/tidy3d/plugins/microwave/viz.py @@ -1,8 +1,10 @@ """Utilities for plotting microwave components""" +from __future__ import annotations + from numpy import inf -from ...components.viz import PathPlotParams +from tidy3d.components.viz import PathPlotParams """ Constants """ VOLTAGE_COLOR = "red" @@ -11,13 +13,13 @@ LOBE_WIDTH_COLOR = "tab:orange" LOBE_FNBW_COLOR = "tab:blue" PATH_LINEWIDTH = 2 -ARROW_CURRENT = dict( - arrowstyle="-|>", - mutation_scale=32, - linestyle="", - lw=PATH_LINEWIDTH, - color=CURRENT_COLOR, -) +ARROW_CURRENT = { + "arrowstyle": "-|>", + "mutation_scale": 32, + "linestyle": "", + "lw": PATH_LINEWIDTH, + "color": CURRENT_COLOR, +} plot_params_voltage_path = PathPlotParams( alpha=1.0, diff --git a/tidy3d/plugins/mode/__init__.py b/tidy3d/plugins/mode/__init__.py index 3a37ced052..6cff69cefc 100644 --- a/tidy3d/plugins/mode/__init__.py +++ b/tidy3d/plugins/mode/__init__.py @@ -1,5 +1,7 @@ """Imports from mode solver plugin.""" +from __future__ import annotations + from .mode_solver import ModeSolver, ModeSolverData __all__ = ["ModeSolver", "ModeSolverData"] diff --git a/tidy3d/plugins/mode/mode_solver.py b/tidy3d/plugins/mode/mode_solver.py index 12716b0daf..691ca88e45 100644 --- a/tidy3d/plugins/mode/mode_solver.py +++ b/tidy3d/plugins/mode/mode_solver.py @@ -4,8 +4,8 @@ from __future__ import annotations -from ...components.data.monitor_data import ModeSolverData -from ...components.mode.mode_solver import MODE_MONITOR_NAME, MODE_PLANE_TYPE, ModeSolver +from tidy3d.components.data.monitor_data import ModeSolverData +from tidy3d.components.mode.mode_solver import MODE_MONITOR_NAME, MODE_PLANE_TYPE, ModeSolver _ = ModeSolver _ = ModeSolverData diff --git a/tidy3d/plugins/mode/web.py b/tidy3d/plugins/mode/web.py index edbbbeccac..c6ddf19f4a 100644 --- a/tidy3d/plugins/mode/web.py +++ b/tidy3d/plugins/mode/web.py @@ -1,5 +1,7 @@ """Web API for mode solver""" -from ...web.api.mode import run, run_batch +from __future__ import annotations + +from tidy3d.web.api.mode import run, run_batch __all__ = ["run", "run_batch"] diff --git a/tidy3d/plugins/polyslab/__init__.py b/tidy3d/plugins/polyslab/__init__.py index 0f89f97513..31f4fbce4f 100644 --- a/tidy3d/plugins/polyslab/__init__.py +++ b/tidy3d/plugins/polyslab/__init__.py @@ -1,5 +1,7 @@ """Imports from complex polyslab plugin.""" +from __future__ import annotations + from .polyslab import ComplexPolySlab __all__ = ["ComplexPolySlab"] diff --git a/tidy3d/plugins/polyslab/polyslab.py b/tidy3d/plugins/polyslab/polyslab.py index 1fb496bd09..a5dfa3cefd 100644 --- a/tidy3d/plugins/polyslab/polyslab.py +++ b/tidy3d/plugins/polyslab/polyslab.py @@ -1,8 +1,10 @@ """Divide a complex polyslab where self-intersecting polygon can occur during extrusion.""" -from ...components.geometry.polyslab import ComplexPolySlabBase -from ...components.medium import MediumType -from ...components.structure import Structure +from __future__ import annotations + +from tidy3d.components.geometry.polyslab import ComplexPolySlabBase +from tidy3d.components.medium import MediumType +from tidy3d.components.structure import Structure class ComplexPolySlab(ComplexPolySlabBase): diff --git a/tidy3d/plugins/pytorch/__init__.py b/tidy3d/plugins/pytorch/__init__.py index e3bf690e24..8ea92eb9ec 100644 --- a/tidy3d/plugins/pytorch/__init__.py +++ b/tidy3d/plugins/pytorch/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .wrapper import to_torch __all__ = ["to_torch"] diff --git a/tidy3d/plugins/pytorch/wrapper.py b/tidy3d/plugins/pytorch/wrapper.py index ffdabf0bd1..3759922a2d 100644 --- a/tidy3d/plugins/pytorch/wrapper.py +++ b/tidy3d/plugins/pytorch/wrapper.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import inspect import torch diff --git a/tidy3d/plugins/resonance/__init__.py b/tidy3d/plugins/resonance/__init__.py index 2da284f233..9fa193fffc 100644 --- a/tidy3d/plugins/resonance/__init__.py +++ b/tidy3d/plugins/resonance/__init__.py @@ -1,5 +1,7 @@ """Imports from resonance fitter plugin.""" +from __future__ import annotations + from .resonance import ResonanceFinder __all__ = ["ResonanceFinder"] diff --git a/tidy3d/plugins/resonance/resonance.py b/tidy3d/plugins/resonance/resonance.py index 924a24e409..25ba8d5deb 100644 --- a/tidy3d/plugins/resonance/resonance.py +++ b/tidy3d/plugins/resonance/resonance.py @@ -1,20 +1,22 @@ """Find resonances in time series data""" +from __future__ import annotations + from functools import partial -from typing import List, Tuple, Union +from typing import Union import numpy as np import scipy.linalg import xarray as xr from pydantic.v1 import Field, NonNegativeFloat, PositiveInt, validator -from ...components.base import Tidy3dBaseModel -from ...components.data.data_array import ScalarFieldTimeDataArray -from ...components.data.monitor_data import FieldTimeData -from ...components.types import ArrayComplex1D, ArrayComplex2D, ArrayComplex3D, ArrayFloat1D -from ...constants import HERTZ -from ...exceptions import SetupError, ValidationError -from ...log import log +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.components.data.data_array import ScalarFieldTimeDataArray +from tidy3d.components.data.monitor_data import FieldTimeData +from tidy3d.components.types import ArrayComplex1D, ArrayComplex2D, ArrayComplex3D, ArrayFloat1D +from tidy3d.constants import HERTZ +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.log import log INIT_NUM_FREQS = 200 @@ -69,7 +71,7 @@ class ResonanceFinder(Tidy3dBaseModel): ... # A given dataframe """ - freq_window: Tuple[float, float] = Field( + freq_window: tuple[float, float] = Field( ..., title="Window ``[fmin, fmax]``", description="Window ``[fmin, fmax]`` for the initial frequencies. " @@ -110,7 +112,7 @@ def _check_freq_window(cls, val): ) return val - def run(self, signals: Union[FieldTimeData, Tuple[FieldTimeData, ...]]) -> xr.Dataset: + def run(self, signals: Union[FieldTimeData, tuple[FieldTimeData, ...]]) -> xr.Dataset: """Finds resonances in a :class:`.FieldTimeData` or a Tuple of such. The time coordinates must be uniformly spaced, and the spacing must be the same across all supplied data. The resonance finder runs on the sum of the @@ -158,7 +160,7 @@ def run_scalar_field_time(self, signal: ScalarFieldTimeDataArray) -> xr.Dataset: signal, dt = self._validate_scalar_field_time(signal) return self.run_raw_signal(signal, dt) - def run_raw_signal(self, signal: List[complex], time_step: float) -> xr.Dataset: + def run_raw_signal(self, signal: list[complex], time_step: float) -> xr.Dataset: """Finds resonances in a time series. Note that the signal should start after the sources have turned off. @@ -207,7 +209,7 @@ def run_raw_signal(self, signal: List[complex], time_step: float) -> xr.Dataset: def _validate_scalar_field_time( self, signal: ScalarFieldTimeDataArray - ) -> Tuple[ArrayComplex1D, float]: + ) -> tuple[ArrayComplex1D, float]: """Validates a :class:`.ScalarFieldTimeDataArray` and returns the time step as well as underlying data array.""" dts = np.diff(signal.t) @@ -227,12 +229,12 @@ def _validate_scalar_field_time( return np.squeeze(signal.data), dt def _aggregate_field_time_comps( - self, signals: Tuple[FieldTimeData, ...], comps + self, signals: tuple[FieldTimeData, ...], comps ) -> ScalarFieldTimeDataArray: """Aggregates the given components from several :class:`.FieldTimeData`.""" total_signal = None dt = -1 - coords = dict(x=[0], y=[0], z=[0], t=[0]) + coords = {"x": [0], "y": [0], "z": [0], "t": [0]} for sig_field in signals: for comp in comps: @@ -261,7 +263,7 @@ def _aggregate_field_time_comps( ) def _aggregate_field_time( - self, signals: Union[FieldTimeData, Tuple[FieldTimeData, ...]] + self, signals: Union[FieldTimeData, tuple[FieldTimeData, ...]] ) -> ScalarFieldTimeDataArray: """Aggregates several :class:`.FieldTimeData` into a single :class:`.ScalarFieldTimeDataArray`.""" @@ -345,7 +347,7 @@ def _gram_schmidt(self, a_matrix: ArrayComplex2D) -> ArrayComplex2D: def _solve_gen_eig_prob( self, a_matrix: ArrayComplex2D, b_matrix: ArrayComplex2D, rcond: float - ) -> Tuple[ArrayComplex1D, ArrayComplex2D]: + ) -> tuple[ArrayComplex1D, ArrayComplex2D]: """Solve a generalized eigenvalue problem of the form .. math:: diff --git a/tidy3d/plugins/smatrix/__init__.py b/tidy3d/plugins/smatrix/__init__.py index 8152d0d748..73591039a7 100644 --- a/tidy3d/plugins/smatrix/__init__.py +++ b/tidy3d/plugins/smatrix/__init__.py @@ -1,5 +1,7 @@ """Imports from scattering matrix plugin.""" +from __future__ import annotations + import warnings from .component_modelers.modal import AbstractComponentModeler, ComponentModeler, ModalPortDataArray @@ -20,13 +22,13 @@ __all__ = [ "AbstractComponentModeler", + "CoaxialLumpedPort", "ComponentModeler", - "Port", + "LumpedPort", "ModalPortDataArray", + "Port", + "PortDataArray", "TerminalComponentModeler", - "CoaxialLumpedPort", - "LumpedPort", - "WavePort", "TerminalPortDataArray", - "PortDataArray", + "WavePort", ] diff --git a/tidy3d/plugins/smatrix/component_modelers/base.py b/tidy3d/plugins/smatrix/component_modelers/base.py index e8467f2799..24a737a384 100644 --- a/tidy3d/plugins/smatrix/component_modelers/base.py +++ b/tidy3d/plugins/smatrix/component_modelers/base.py @@ -4,25 +4,25 @@ import os from abc import ABC, abstractmethod -from typing import Dict, Tuple, Union, get_args +from typing import Optional, Union, get_args import numpy as np import pydantic.v1 as pd -from ....components.base import Tidy3dBaseModel, cached_property -from ....components.data.data_array import DataArray -from ....components.data.sim_data import SimulationData -from ....components.simulation import Simulation -from ....components.types import FreqArray -from ....config import config -from ....constants import HERTZ -from ....exceptions import SetupError, Tidy3dKeyError -from ....log import log -from ....web.api.container import Batch, BatchData -from ..ports.coaxial_lumped import CoaxialLumpedPort -from ..ports.modal import Port -from ..ports.rectangular_lumped import LumpedPort -from ..ports.wave import WavePort +from tidy3d.components.base import Tidy3dBaseModel, cached_property +from tidy3d.components.data.data_array import DataArray +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.simulation import Simulation +from tidy3d.components.types import FreqArray +from tidy3d.config import config +from tidy3d.constants import HERTZ +from tidy3d.exceptions import SetupError, Tidy3dKeyError +from tidy3d.log import log +from tidy3d.plugins.smatrix.ports.coaxial_lumped import CoaxialLumpedPort +from tidy3d.plugins.smatrix.ports.modal import Port +from tidy3d.plugins.smatrix.ports.rectangular_lumped import LumpedPort +from tidy3d.plugins.smatrix.ports.wave import WavePort +from tidy3d.web.api.container import Batch, BatchData # fwidth of gaussian pulse in units of central frequency FWIDTH_FRAC = 1.0 / 10 @@ -41,7 +41,7 @@ class AbstractComponentModeler(ABC, Tidy3dBaseModel): description="Simulation describing the device without any sources present.", ) - ports: Tuple[Union[Port, TerminalPortType], ...] = pd.Field( + ports: tuple[Union[Port, TerminalPortType], ...] = pd.Field( (), title="Ports", description="Collection of ports describing the scattering matrix elements. " @@ -132,14 +132,14 @@ def _warn_rf_license(cls, val): return val @staticmethod - def _task_name(port: Port, mode_index: int = None) -> str: + def _task_name(port: Port, mode_index: Optional[int] = None) -> str: """The name of a task, determined by the port of the source and mode index, if given.""" if mode_index is not None: return f"smatrix_{port.name}_{mode_index}" return f"smatrix_{port.name}" @cached_property - def sim_dict(self) -> Dict[str, Simulation]: + def sim_dict(self) -> dict[str, Simulation]: """Generate all the :class:`.Simulation` objects for the S matrix calculation.""" def to_file(self, fname: str) -> None: diff --git a/tidy3d/plugins/smatrix/component_modelers/modal.py b/tidy3d/plugins/smatrix/component_modelers/modal.py index c0acdf7fae..251d48252a 100644 --- a/tidy3d/plugins/smatrix/component_modelers/modal.py +++ b/tidy3d/plugins/smatrix/component_modelers/modal.py @@ -4,26 +4,27 @@ # "ModalPort" to explicitly differentiate these from "TerminalComponentModeler" and "LumpedPort". from __future__ import annotations -from typing import Dict, List, Optional, Tuple +from typing import Optional import numpy as np import pydantic.v1 as pd -from ....components.base import cached_property -from ....components.data.sim_data import SimulationData -from ....components.monitor import ModeMonitor -from ....components.simulation import Simulation -from ....components.source.field import ModeSource -from ....components.source.time import GaussianPulse -from ....components.types import Ax, Complex -from ....components.viz import add_ax_if_none, equal_aspect -from ....exceptions import SetupError -from ....web.api.container import BatchData -from ..ports.modal import ModalPortDataArray, Port +from tidy3d.components.base import cached_property +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.monitor import ModeMonitor +from tidy3d.components.simulation import Simulation +from tidy3d.components.source.field import ModeSource +from tidy3d.components.source.time import GaussianPulse +from tidy3d.components.types import Ax, Complex +from tidy3d.components.viz import add_ax_if_none, equal_aspect +from tidy3d.exceptions import SetupError +from tidy3d.plugins.smatrix.ports.modal import ModalPortDataArray, Port +from tidy3d.web.api.container import BatchData + from .base import FWIDTH_FRAC, AbstractComponentModeler -MatrixIndex = Tuple[str, pd.NonNegativeInt] # the 'i' in S_ij -Element = Tuple[MatrixIndex, MatrixIndex] # the 'ij' in S_ij +MatrixIndex = tuple[str, pd.NonNegativeInt] # the 'i' in S_ij +Element = tuple[MatrixIndex, MatrixIndex] # the 'ij' in S_ij class ComponentModeler(AbstractComponentModeler): @@ -39,14 +40,14 @@ class ComponentModeler(AbstractComponentModeler): * `Computing the scattering matrix of a device <../../notebooks/SMatrix.html>`_ """ - ports: Tuple[Port, ...] = pd.Field( + ports: tuple[Port, ...] = pd.Field( (), title="Ports", description="Collection of ports describing the scattering matrix elements. " "For each input mode, one simulation will be run with a modal source.", ) - element_mappings: Tuple[Tuple[Element, Element, Complex], ...] = pd.Field( + element_mappings: tuple[tuple[Element, Element, Complex], ...] = pd.Field( (), title="Element Mappings", description="Mapping between elements of the scattering matrix, " @@ -59,7 +60,7 @@ class ComponentModeler(AbstractComponentModeler): "is skipped automatically.", ) - run_only: Optional[Tuple[MatrixIndex, ...]] = pd.Field( + run_only: Optional[tuple[MatrixIndex, ...]] = pd.Field( None, title="Run Only", description="If given, a tuple of matrix indices, specified by (:class:`.Port`, ``int``)," @@ -93,7 +94,7 @@ def _sim_has_no_sources(cls, val): return val @cached_property - def sim_dict(self) -> Dict[str, Simulation]: + def sim_dict(self) -> dict[str, Simulation]: """Generate all the :class:`.Simulation` objects for the S matrix calculation.""" sim_dict = {} @@ -106,13 +107,13 @@ def sim_dict(self) -> Dict[str, Simulation]: mode_source = self.to_source(port=port_source, mode_index=mode_index) new_mnts = list(self.simulation.monitors) + mode_monitors - sim_copy = self.simulation.copy(update=dict(sources=[mode_source], monitors=new_mnts)) + sim_copy = self.simulation.copy(update={"sources": [mode_source], "monitors": new_mnts}) task_name = self._task_name(port=port, mode_index=mode_index) sim_dict[task_name] = sim_copy return sim_dict @cached_property - def matrix_indices_monitor(self) -> Tuple[MatrixIndex, ...]: + def matrix_indices_monitor(self) -> tuple[MatrixIndex, ...]: """Tuple of all the possible matrix indices (port, mode_index) in the Component Modeler.""" matrix_indices = [] for port in self.ports: @@ -121,14 +122,14 @@ def matrix_indices_monitor(self) -> Tuple[MatrixIndex, ...]: return tuple(matrix_indices) @cached_property - def matrix_indices_source(self) -> Tuple[MatrixIndex, ...]: + def matrix_indices_source(self) -> tuple[MatrixIndex, ...]: """Tuple of all the source matrix indices (port, mode_index) in the Component Modeler.""" if self.run_only is not None: return self.run_only return self.matrix_indices_monitor @cached_property - def matrix_indices_run_sim(self) -> Tuple[MatrixIndex, ...]: + def matrix_indices_run_sim(self) -> tuple[MatrixIndex, ...]: """Tuple of all the source matrix indices (port, mode_index) in the Component Modeler.""" if self.element_mappings is None or self.element_mappings == {}: @@ -154,10 +155,10 @@ def matrix_indices_run_sim(self) -> Tuple[MatrixIndex, ...]: return source_indices_needed @cached_property - def port_names(self) -> Tuple[List[str], List[str]]: + def port_names(self) -> tuple[list[str], list[str]]: """List of port names for inputs and outputs, respectively.""" - def get_port_names(matrix_elements: Tuple[str, int]) -> List[str]: + def get_port_names(matrix_elements: tuple[str, int]) -> list[str]: """Get the port names from a list of (port name, mode index).""" port_names = [] for port_name, _ in matrix_elements: @@ -182,7 +183,7 @@ def to_monitor(self, port: Port) -> ModeMonitor: def to_source( self, port: Port, mode_index: int, num_freqs: int = 1, **kwargs - ) -> List[ModeSource]: + ) -> list[ModeSource]: """Creates a list of mode sources from a given port.""" freq0 = np.mean(self.freqs) fdiff = max(self.freqs) - min(self.freqs) @@ -205,25 +206,36 @@ def shift_port(self, port: Port) -> Port: shift_value = self._shift_value_signed(port=port) center_shifted = list(port.center) center_shifted[port.size.index(0.0)] += shift_value - port_shifted = port.copy(update=dict(center=center_shifted)) + port_shifted = port.copy(update={"center": center_shifted}) return port_shifted @equal_aspect @add_ax_if_none - def plot_sim(self, x: float = None, y: float = None, z: float = None, ax: Ax = None) -> Ax: + def plot_sim( + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, + ) -> Ax: """Plot a :class:`.Simulation` with all sources added for each port, for troubleshooting.""" plot_sources = [] for port_source in self.ports: mode_source_0 = self.to_source(port=port_source, mode_index=0) plot_sources.append(mode_source_0) - sim_plot = self.simulation.copy(update=dict(sources=plot_sources)) + sim_plot = self.simulation.copy(update={"sources": plot_sources}) return sim_plot.plot(x=x, y=y, z=z, ax=ax) @equal_aspect @add_ax_if_none def plot_sim_eps( - self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **kwargs + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, + **kwargs, ) -> Ax: """Plot permittivity of the :class:`.Simulation` with all sources added for each port.""" @@ -231,7 +243,7 @@ def plot_sim_eps( for port_source in self.ports: mode_source_0 = self.to_source(port=port_source, mode_index=0) plot_sources.append(mode_source_0) - sim_plot = self.simulation.copy(update=dict(sources=plot_sources)) + sim_plot = self.simulation.copy(update={"sources": plot_sources}) return sim_plot.plot_eps(x=x, y=y, z=z, ax=ax, **kwargs) def _normalization_factor(self, port_source: Port, sim_data: SimulationData) -> complex: @@ -249,10 +261,10 @@ def _normalization_factor(self, port_source: Port, sim_data: SimulationData) -> return normalize_amps.values @cached_property - def max_mode_index(self) -> Tuple[int, int]: + def max_mode_index(self) -> tuple[int, int]: """maximum mode indices for the smatrix dataset for the in and out ports, respectively.""" - def get_max_mode_indices(matrix_elements: Tuple[str, int]) -> int: + def get_max_mode_indices(matrix_elements: tuple[str, int]) -> int: """Get the maximum mode index for a list of (port name, mode index).""" return max(mode_index for _, mode_index in matrix_elements) @@ -277,13 +289,13 @@ def _internal_construct_smatrix(self, batch_data: BatchData) -> ModalPortDataArr (len(port_names_out), len(port_names_in), num_modes_out, num_modes_in, len(self.freqs)), dtype=complex, ) - coords = dict( - port_out=port_names_out, - port_in=port_names_in, - mode_index_out=range(num_modes_out), - mode_index_in=range(num_modes_in), - f=np.array(self.freqs), - ) + coords = { + "port_out": port_names_out, + "port_in": port_names_in, + "mode_index_out": range(num_modes_out), + "mode_index_in": range(num_modes_in), + "f": np.array(self.freqs), + } s_matrix = ModalPortDataArray(values, coords=coords) # loop through source ports @@ -306,33 +318,33 @@ def _internal_construct_smatrix(self, batch_data: BatchData) -> ModalPortDataArr source_norm = self._normalization_factor(port_in, sim_data) s_matrix_elements = np.array(amp.data) / np.array(source_norm) s_matrix.loc[ - dict( - port_in=port_name_in, - mode_index_in=mode_index_in, - port_out=port_name_out, - mode_index_out=mode_index_out, - ) + { + "port_in": port_name_in, + "mode_index_in": mode_index_in, + "port_out": port_name_out, + "mode_index_out": mode_index_out, + } ] = s_matrix_elements # element can be determined by user-defined mapping for (row_in, col_in), (row_out, col_out), mult_by in self.element_mappings: port_out_from, mode_index_out_from = row_in port_in_from, mode_index_in_from = col_in - coords_from = dict( - port_in=port_in_from, - mode_index_in=mode_index_in_from, - port_out=port_out_from, - mode_index_out=mode_index_out_from, - ) + coords_from = { + "port_in": port_in_from, + "mode_index_in": mode_index_in_from, + "port_out": port_out_from, + "mode_index_out": mode_index_out_from, + } port_out_to, mode_index_out_to = row_out port_in_to, mode_index_in_to = col_out - coords_to = dict( - port_in=port_in_to, - mode_index_in=mode_index_in_to, - port_out=port_out_to, - mode_index_out=mode_index_out_to, - ) + coords_to = { + "port_in": port_in_to, + "mode_index_in": mode_index_in_to, + "port_out": port_out_to, + "mode_index_out": mode_index_out_to, + } s_matrix.loc[coords_to] = mult_by * s_matrix.loc[coords_from].values return s_matrix diff --git a/tidy3d/plugins/smatrix/component_modelers/terminal.py b/tidy3d/plugins/smatrix/component_modelers/terminal.py index ebd17b5046..37f1d68cc0 100644 --- a/tidy3d/plugins/smatrix/component_modelers/terminal.py +++ b/tidy3d/plugins/smatrix/component_modelers/terminal.py @@ -2,36 +2,32 @@ from __future__ import annotations -from typing import Dict, Tuple, Union +from typing import Optional, Union import numpy as np import pydantic.v1 as pd -from ....components.base import cached_property -from ....components.data.data_array import ( - DataArray, - FreqDataArray, -) -from ....components.data.monitor_data import ( - MonitorData, -) -from ....components.data.sim_data import SimulationData -from ....components.geometry.utils_2d import snap_coordinate_to_grid -from ....components.microwave.data.monitor_data import AntennaMetricsData -from ....components.monitor import DirectivityMonitor -from ....components.simulation import Simulation -from ....components.source.time import GaussianPulse -from ....components.types import Ax -from ....components.viz import add_ax_if_none, equal_aspect -from ....constants import C_0, OHM -from ....exceptions import Tidy3dError, Tidy3dKeyError, ValidationError -from ....log import log -from ....web.api.container import BatchData -from ..data.terminal import PortDataArray, TerminalPortDataArray -from ..ports.base_lumped import AbstractLumpedPort -from ..ports.coaxial_lumped import CoaxialLumpedPort -from ..ports.rectangular_lumped import LumpedPort -from ..ports.wave import WavePort +from tidy3d.components.base import cached_property +from tidy3d.components.data.data_array import DataArray, FreqDataArray +from tidy3d.components.data.monitor_data import MonitorData +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.geometry.utils_2d import snap_coordinate_to_grid +from tidy3d.components.microwave.data.monitor_data import AntennaMetricsData +from tidy3d.components.monitor import DirectivityMonitor +from tidy3d.components.simulation import Simulation +from tidy3d.components.source.time import GaussianPulse +from tidy3d.components.types import Ax +from tidy3d.components.viz import add_ax_if_none, equal_aspect +from tidy3d.constants import C_0, OHM +from tidy3d.exceptions import Tidy3dError, Tidy3dKeyError, ValidationError +from tidy3d.log import log +from tidy3d.plugins.smatrix.data.terminal import PortDataArray, TerminalPortDataArray +from tidy3d.plugins.smatrix.ports.base_lumped import AbstractLumpedPort +from tidy3d.plugins.smatrix.ports.coaxial_lumped import CoaxialLumpedPort +from tidy3d.plugins.smatrix.ports.rectangular_lumped import LumpedPort +from tidy3d.plugins.smatrix.ports.wave import WavePort +from tidy3d.web.api.container import BatchData + from .base import AbstractComponentModeler, TerminalPortType @@ -39,7 +35,7 @@ class TerminalComponentModeler(AbstractComponentModeler): """Tool for modeling two-terminal multiport devices and computing port parameters with lumped and wave ports.""" - ports: Tuple[TerminalPortType, ...] = pd.Field( + ports: tuple[TerminalPortType, ...] = pd.Field( (), title="Terminal Ports", description="Collection of lumped and wave ports associated with the network. " @@ -64,7 +60,12 @@ def _warn_rf_license(cls, values): @equal_aspect @add_ax_if_none def plot_sim( - self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **kwargs + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, + **kwargs, ) -> Ax: """Plot a :class:`.Simulation` with all sources added for each port, for troubleshooting.""" @@ -72,13 +73,18 @@ def plot_sim( for port_source in self.ports: source_0 = port_source.to_source(self._source_time) plot_sources.append(source_0) - sim_plot = self.simulation.copy(update=dict(sources=plot_sources)) + sim_plot = self.simulation.copy(update={"sources": plot_sources}) return sim_plot.plot(x=x, y=y, z=z, ax=ax, **kwargs) @equal_aspect @add_ax_if_none def plot_sim_eps( - self, x: float = None, y: float = None, z: float = None, ax: Ax = None, **kwargs + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, + **kwargs, ) -> Ax: """Plot permittivity of the :class:`.Simulation` with all sources added for each port.""" @@ -86,11 +92,11 @@ def plot_sim_eps( for port_source in self.ports: source_0 = port_source.to_source(self._source_time) plot_sources.append(source_0) - sim_plot = self.simulation.copy(update=dict(sources=plot_sources)) + sim_plot = self.simulation.copy(update={"sources": plot_sources}) return sim_plot.plot_eps(x=x, y=y, z=z, ax=ax, **kwargs) @cached_property - def sim_dict(self) -> Dict[str, Simulation]: + def sim_dict(self) -> dict[str, Simulation]: """Generate all the :class:`.Simulation` objects for the port parameter calculation.""" sim_dict = {} @@ -110,7 +116,7 @@ def sim_dict(self) -> Dict[str, Simulation]: sim_wo_source = self.simulation.updated_copy( grid_spec=grid_spec, lumped_elements=lumped_resistors ) - snap_centers = dict() + snap_centers = {} for port in self._lumped_ports: port_center_on_axis = port.center[port.injection_axis] new_port_center = snap_coordinate_to_grid( @@ -136,10 +142,10 @@ def sim_dict(self) -> Dict[str, Simulation]: port.to_load(snap_center=snap_centers[port.name]) for port in self._lumped_ports ] - update_dict = dict( - monitors=new_mnts, - lumped_elements=new_lumped_elements, - ) + update_dict = { + "monitors": new_mnts, + "lumped_elements": new_lumped_elements, + } # This is the new default simulation will all shared components added sim_wo_source = sim_wo_source.copy(update=update_dict) @@ -164,7 +170,7 @@ def sim_dict(self) -> Dict[str, Simulation]: ) port_source = wave_port.to_source(self._source_time, snap_center=mode_src_pos) - update_dict = dict(sources=[port_source]) + update_dict = {"sources": [port_source]} task_name = self._task_name(port=wave_port) sim_dict[task_name] = sim_wo_source.copy(update=update_dict) @@ -190,11 +196,11 @@ def _internal_construct_smatrix(self, batch_data: BatchData) -> TerminalPortData (len(self.freqs), len(port_names), len(port_names)), dtype=complex, ) - coords = dict( - f=np.array(self.freqs), - port_out=port_names, - port_in=port_names, - ) + coords = { + "f": np.array(self.freqs), + "port_out": port_names, + "port_in": port_names, + } a_matrix = TerminalPortDataArray(values, coords=coords) b_matrix = a_matrix.copy(deep=True) @@ -205,7 +211,7 @@ def _internal_construct_smatrix(self, batch_data: BatchData) -> TerminalPortData for port_in in self.ports: sim_data = batch_data[self._task_name(port=port_in)] a, b = self.compute_power_wave_amplitudes_at_each_port(port_impedances, sim_data) - indexer = dict(f=a.f, port_in=port_in.name, port_out=a.port) + indexer = {"f": a.f, "port_in": port_in.name, "port_out": a.port} a_matrix.loc[indexer] = a b_matrix.loc[indexer] = b @@ -265,10 +271,10 @@ def compute_power_wave_amplitudes_at_each_port( (len(self.freqs), len(port_names)), dtype=complex, ) - coords = dict( - f=np.array(self.freqs), - port=port_names, - ) + coords = { + "f": np.array(self.freqs), + "port": port_names, + } V_matrix = PortDataArray(values, coords=coords) I_matrix = V_matrix.copy(deep=True) @@ -277,7 +283,7 @@ def compute_power_wave_amplitudes_at_each_port( for port_out in self.ports: V_out, I_out = self.compute_port_VI(port_out, sim_data) - indexer = dict(port=port_out.name) + indexer = {"port": port_out.name} V_matrix.loc[indexer] = V_out I_matrix.loc[indexer] = I_out @@ -434,7 +440,7 @@ def _port_reference_impedances(self, batch_data: BatchData) -> PortDataArray: (len(self.freqs), len(port_names)), dtype=complex, ) - coords = dict(f=np.array(self.freqs), port=port_names) + coords = {"f": np.array(self.freqs), "port": port_names} port_impedances = PortDataArray(values, coords=coords) for port in self.ports: if isinstance(port, WavePort): @@ -443,10 +449,10 @@ def _port_reference_impedances(self, batch_data: BatchData) -> PortDataArray: # WavePorts have a port impedance calculated from its associated modal field distribution # and is frequency dependent. impedances = port.compute_port_impedance(sim_data_port).values - port_impedances.loc[dict(port=port.name)] = impedances.squeeze() + port_impedances.loc[{"port": port.name}] = impedances.squeeze() else: # LumpedPorts have a constant reference impedance - port_impedances.loc[dict(port=port.name)] = np.full(len(self.freqs), port.impedance) + port_impedances.loc[{"port": port.name}] = np.full(len(self.freqs), port.impedance) port_impedances = TerminalComponentModeler._set_port_data_array_attributes(port_impedances) return port_impedances @@ -527,12 +533,14 @@ def _monitor_data_at_port_amplitude( if not isinstance(a_port, FreqDataArray): freqs = list(monitor_data.monitor.freqs) array_vals = a_port * np.ones(len(freqs)) - a_port = FreqDataArray(array_vals, coords=dict(f=freqs)) + a_port = FreqDataArray(array_vals, coords={"f": freqs}) scale_array = a_port / a_raw_port return monitor_data.scale_fields_by_freq_array(scale_array, method="nearest") def get_antenna_metrics_data( - self, port_amplitudes: dict[str, complex] = None, monitor_name: str = None + self, + port_amplitudes: Optional[dict[str, complex]] = None, + monitor_name: Optional[str] = None, ) -> AntennaMetricsData: """Calculate antenna parameters using superposition of fields from multiple port excitations. @@ -574,7 +582,7 @@ def get_antenna_metrics_data( # Create data arrays for holding the superposition of all port power wave amplitudes f = list(rad_mon.freqs) - coords = dict(f=f, port=port_names) + coords = {"f": f, "port": port_names} a_sum = PortDataArray(np.zeros((len(f), len(port_names)), dtype=complex), coords=coords) b_sum = a_sum.copy() # Retrieve associated simulation data diff --git a/tidy3d/plugins/smatrix/data/terminal.py b/tidy3d/plugins/smatrix/data/terminal.py index 6a240a1dd0..39917d6a46 100644 --- a/tidy3d/plugins/smatrix/data/terminal.py +++ b/tidy3d/plugins/smatrix/data/terminal.py @@ -4,12 +4,9 @@ import pydantic.v1 as pd +from tidy3d.components.data.data_array import DataArray from tidy3d.log import log -from ....components.data.data_array import ( - DataArray, -) - class PortDataArray(DataArray): """Array of values over dimensions of frequency and port name. diff --git a/tidy3d/plugins/smatrix/ports/base_lumped.py b/tidy3d/plugins/smatrix/ports/base_lumped.py index e0bc3b0147..2871da304a 100644 --- a/tidy3d/plugins/smatrix/ports/base_lumped.py +++ b/tidy3d/plugins/smatrix/ports/base_lumped.py @@ -1,17 +1,20 @@ """Class and custom data array for representing a scattering matrix port based on lumped circuit elements.""" +from __future__ import annotations + from abc import abstractmethod from typing import Optional import pydantic.v1 as pd -from ....components.base import cached_property -from ....components.geometry.utils_2d import snap_coordinate_to_grid -from ....components.grid.grid import Grid, YeeGrid -from ....components.lumped_element import LumpedElementType -from ....components.monitor import FieldMonitor -from ....components.types import Complex, Coordinate, FreqArray -from ....constants import OHM +from tidy3d.components.base import cached_property +from tidy3d.components.geometry.utils_2d import snap_coordinate_to_grid +from tidy3d.components.grid.grid import Grid, YeeGrid +from tidy3d.components.lumped_element import LumpedElementType +from tidy3d.components.monitor import FieldMonitor +from tidy3d.components.types import Complex, Coordinate, FreqArray +from tidy3d.constants import OHM + from .base_terminal import AbstractTerminalPort DEFAULT_PORT_NUM_CELLS = 3 @@ -64,19 +67,23 @@ def snapped_center(self, grid: Grid) -> Coordinate: @cached_property @abstractmethod - def to_load(self, snap_center: float = None) -> LumpedElementType: + def to_load(self, snap_center: Optional[float] = None) -> LumpedElementType: """Create a load from the lumped port.""" @abstractmethod - def to_voltage_monitor(self, freqs: FreqArray, snap_center: float = None) -> FieldMonitor: + def to_voltage_monitor( + self, freqs: FreqArray, snap_center: Optional[float] = None + ) -> FieldMonitor: """Field monitor to compute port voltage.""" @abstractmethod - def to_current_monitor(self, freqs: FreqArray, snap_center: float = None) -> FieldMonitor: + def to_current_monitor( + self, freqs: FreqArray, snap_center: Optional[float] = None + ) -> FieldMonitor: """Field monitor to compute port current.""" def to_monitors( - self, freqs: FreqArray, snap_center: float = None, grid: Grid = None + self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None ) -> list[FieldMonitor]: """Field monitors to compute port voltage and current.""" return [ diff --git a/tidy3d/plugins/smatrix/ports/base_terminal.py b/tidy3d/plugins/smatrix/ports/base_terminal.py index 88504856ce..24594e76b1 100644 --- a/tidy3d/plugins/smatrix/ports/base_terminal.py +++ b/tidy3d/plugins/smatrix/ports/base_terminal.py @@ -1,21 +1,22 @@ """Class and custom data array for representing a scattering-matrix port, which is defined by a pair of terminals.""" +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Union +from typing import Optional, Union import pydantic.v1 as pd +from tidy3d.components.base import Tidy3dBaseModel, cached_property +from tidy3d.components.data.data_array import FreqDataArray +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.grid.grid import Grid +from tidy3d.components.monitor import FieldMonitor, ModeMonitor +from tidy3d.components.source.base import Source +from tidy3d.components.source.time import GaussianPulse +from tidy3d.components.types import FreqArray from tidy3d.log import log -from ....components.base import Tidy3dBaseModel, cached_property -from ....components.data.data_array import FreqDataArray -from ....components.data.sim_data import SimulationData -from ....components.grid.grid import Grid -from ....components.monitor import FieldMonitor, ModeMonitor -from ....components.source.base import Source -from ....components.source.time import GaussianPulse -from ....components.types import FreqArray - class AbstractTerminalPort(Tidy3dBaseModel, ABC): """Class representing a single terminal-based port. All terminal ports must provide methods @@ -37,12 +38,12 @@ def injection_axis(self): @abstractmethod def to_source( - self, source_time: GaussianPulse, snap_center: float = None, grid: Grid = None + self, source_time: GaussianPulse, snap_center: Optional[float] = None, grid: Grid = None ) -> Source: """Create a current source from a terminal-based port.""" def to_field_monitors( - self, freqs: FreqArray, snap_center: float = None, grid: Grid = None + self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None ) -> Union[list[FieldMonitor], list[ModeMonitor]]: """DEPRECATED: Monitors used to compute the port voltage and current.""" log.warning( @@ -53,7 +54,7 @@ def to_field_monitors( @abstractmethod def to_monitors( - self, freqs: FreqArray, snap_center: float = None, grid: Grid = None + self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None ) -> Union[list[FieldMonitor], list[ModeMonitor]]: """Monitors used to compute the port voltage and current.""" diff --git a/tidy3d/plugins/smatrix/ports/coaxial_lumped.py b/tidy3d/plugins/smatrix/ports/coaxial_lumped.py index 51a0faef31..ac09cdb464 100644 --- a/tidy3d/plugins/smatrix/ports/coaxial_lumped.py +++ b/tidy3d/plugins/smatrix/ports/coaxial_lumped.py @@ -1,25 +1,30 @@ """Lumped port specialization with an annular geometry for exciting coaxial ports.""" +from __future__ import annotations + +from typing import Optional + import numpy as np import pydantic.v1 as pd -from ....components.base import cached_property -from ....components.data.data_array import FreqDataArray, ScalarFieldDataArray -from ....components.data.dataset import FieldDataset -from ....components.data.sim_data import SimulationData -from ....components.geometry.base import Box, Geometry -from ....components.geometry.utils_2d import increment_float -from ....components.grid.grid import Grid, YeeGrid -from ....components.lumped_element import CoaxialLumpedResistor -from ....components.monitor import FieldMonitor -from ....components.source.current import CustomCurrentSource -from ....components.source.time import GaussianPulse -from ....components.types import Axis, Coordinate, Direction, FreqArray, Size -from ....components.validators import skip_if_fields_missing -from ....constants import MICROMETER -from ....exceptions import SetupError, ValidationError -from ...microwave import CustomCurrentIntegral2D, VoltageIntegralAxisAligned -from ...microwave.path_integrals import AbstractAxesRH +from tidy3d.components.base import cached_property +from tidy3d.components.data.data_array import FreqDataArray, ScalarFieldDataArray +from tidy3d.components.data.dataset import FieldDataset +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.geometry.base import Box, Geometry +from tidy3d.components.geometry.utils_2d import increment_float +from tidy3d.components.grid.grid import Grid, YeeGrid +from tidy3d.components.lumped_element import CoaxialLumpedResistor +from tidy3d.components.monitor import FieldMonitor +from tidy3d.components.source.current import CustomCurrentSource +from tidy3d.components.source.time import GaussianPulse +from tidy3d.components.types import Axis, Coordinate, Direction, FreqArray, Size +from tidy3d.components.validators import skip_if_fields_missing +from tidy3d.constants import MICROMETER +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.plugins.microwave import CustomCurrentIntegral2D, VoltageIntegralAxisAligned +from tidy3d.plugins.microwave.path_integrals import AbstractAxesRH + from .base_lumped import AbstractLumpedPort DEFAULT_COAX_SOURCE_NUM_POINTS = 11 @@ -106,7 +111,7 @@ def _ensure_inner_diameter_is_smaller(cls, val, values): return val def to_source( - self, source_time: GaussianPulse, snap_center: float = None, grid: Grid = None + self, source_time: GaussianPulse, snap_center: Optional[float] = None, grid: Grid = None ) -> CustomCurrentSource: """Create a current source from the lumped port.""" # Discretized source amps are manually zeroed out later if they @@ -195,7 +200,7 @@ def compute_coax_current(rin, rout, x, y): current_dataset=dataset_E, ) - def to_load(self, snap_center: float = None) -> CoaxialLumpedResistor: + def to_load(self, snap_center: Optional[float] = None) -> CoaxialLumpedResistor: """Create a load resistor from the lumped port.""" # 2D materials are currently snapped to the grid, so snapping here is not needed. # Snapping is done here so plots of the simulation will more accurately portray the setup. @@ -214,7 +219,7 @@ def to_load(self, snap_center: float = None) -> CoaxialLumpedResistor: ) def to_voltage_monitor( - self, freqs: FreqArray, snap_center: float = None, grid: Grid = None + self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None ) -> FieldMonitor: """Field monitor to compute port voltage.""" center = list(self.center) @@ -238,7 +243,7 @@ def to_voltage_monitor( ) def to_current_monitor( - self, freqs: FreqArray, snap_center: float = None, grid: Grid = None + self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None ) -> FieldMonitor: """Field monitor to compute port current.""" center = list(self.center) @@ -351,8 +356,7 @@ def _determine_current_integral_pos( # We need to choose which side of the port to place the path integral, if direction == "+": return normal_coords[upper_bound] - else: - return normal_coords[lower_bound] + return normal_coords[lower_bound] @cached_property def _voltage_axis(self) -> Axis: diff --git a/tidy3d/plugins/smatrix/ports/modal.py b/tidy3d/plugins/smatrix/ports/modal.py index eb747d482c..eec551a80b 100644 --- a/tidy3d/plugins/smatrix/ports/modal.py +++ b/tidy3d/plugins/smatrix/ports/modal.py @@ -1,11 +1,13 @@ """Class and custom data array for representing a scattering matrix port based on waveguide modes.""" +from __future__ import annotations + import pydantic.v1 as pd -from ....components.data.data_array import DataArray -from ....components.geometry.base import Box -from ....components.mode_spec import ModeSpec -from ....components.types import Direction +from tidy3d.components.data.data_array import DataArray +from tidy3d.components.geometry.base import Box +from tidy3d.components.mode_spec import ModeSpec +from tidy3d.components.types import Direction class ModalPortDataArray(DataArray): diff --git a/tidy3d/plugins/smatrix/ports/rectangular_lumped.py b/tidy3d/plugins/smatrix/ports/rectangular_lumped.py index 507a80f1c4..68aeb979f9 100644 --- a/tidy3d/plugins/smatrix/ports/rectangular_lumped.py +++ b/tidy3d/plugins/smatrix/ports/rectangular_lumped.py @@ -1,35 +1,33 @@ """Lumped port specialization with a rectangular geometry.""" +from __future__ import annotations + +from typing import Optional + import numpy as np import pydantic.v1 as pd -from ....components.base import cached_property -from ....components.data.data_array import FreqDataArray -from ....components.data.sim_data import SimulationData -from ....components.geometry.base import Box -from ....components.geometry.utils import ( +from tidy3d.components.base import cached_property +from tidy3d.components.data.data_array import FreqDataArray +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.geometry.base import Box +from tidy3d.components.geometry.utils import ( SnapBehavior, SnapLocation, SnappingSpec, snap_box_to_grid, ) -from ....components.geometry.utils_2d import increment_float -from ....components.grid.grid import Grid, YeeGrid -from ....components.lumped_element import ( - LinearLumpedElement, - LumpedResistor, - RLCNetwork, -) -from ....components.monitor import FieldMonitor -from ....components.source.current import UniformCurrentSource -from ....components.source.time import GaussianPulse -from ....components.types import Axis, FreqArray, LumpDistType -from ....components.validators import assert_line_or_plane -from ....exceptions import SetupError, ValidationError -from ...microwave import ( - CurrentIntegralAxisAligned, - VoltageIntegralAxisAligned, -) +from tidy3d.components.geometry.utils_2d import increment_float +from tidy3d.components.grid.grid import Grid, YeeGrid +from tidy3d.components.lumped_element import LinearLumpedElement, LumpedResistor, RLCNetwork +from tidy3d.components.monitor import FieldMonitor +from tidy3d.components.source.current import UniformCurrentSource +from tidy3d.components.source.time import GaussianPulse +from tidy3d.components.types import Axis, FreqArray, LumpDistType +from tidy3d.components.validators import assert_line_or_plane +from tidy3d.exceptions import SetupError, ValidationError +from tidy3d.plugins.microwave import CurrentIntegralAxisAligned, VoltageIntegralAxisAligned + from .base_lumped import AbstractLumpedPort @@ -99,7 +97,7 @@ def current_axis(self) -> Axis: return 3 - self.injection_axis - self.voltage_axis def to_source( - self, source_time: GaussianPulse, snap_center: float = None, grid: Grid = None + self, source_time: GaussianPulse, snap_center: Optional[float] = None, grid: Grid = None ) -> UniformCurrentSource: """Create a current source from the lumped port.""" if grid: @@ -126,7 +124,7 @@ def to_source( confine_to_bounds=True, ) - def to_load(self, snap_center: float = None) -> LumpedResistor: + def to_load(self, snap_center: Optional[float] = None) -> LumpedResistor: """Create a load resistor from the lumped port.""" # 2D materials are currently snapped to the grid, so snapping here is not needed. # It is done here so plots of the simulation will more accurately portray the setup @@ -148,7 +146,7 @@ def to_load(self, snap_center: float = None) -> LumpedResistor: ) def to_voltage_monitor( - self, freqs: FreqArray, snap_center: float = None, grid: Grid = None + self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None ) -> FieldMonitor: """Field monitor to compute port voltage.""" if grid: @@ -176,7 +174,7 @@ def to_voltage_monitor( ) def to_current_monitor( - self, freqs: FreqArray, snap_center: float = None, grid: Grid = None + self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None ) -> FieldMonitor: """Field monitor to compute port current.""" if grid: diff --git a/tidy3d/plugins/smatrix/ports/wave.py b/tidy3d/plugins/smatrix/ports/wave.py index 1bbeb29b04..f43bbbaf3c 100644 --- a/tidy3d/plugins/smatrix/ports/wave.py +++ b/tidy3d/plugins/smatrix/ports/wave.py @@ -1,30 +1,29 @@ """Class and custom data array for representing a scattering matrix wave port.""" +from __future__ import annotations + from typing import Optional, Union import numpy as np import pydantic.v1 as pd -from ....components.base import cached_property, skip_if_fields_missing -from ....components.data.data_array import FreqDataArray, FreqModeDataArray -from ....components.data.monitor_data import ModeData -from ....components.data.sim_data import SimulationData -from ....components.geometry.base import Box -from ....components.geometry.bound_ops import bounds_contains -from ....components.grid.grid import Grid -from ....components.monitor import ModeMonitor -from ....components.simulation import Simulation -from ....components.source.field import ModeSource, ModeSpec -from ....components.source.time import GaussianPulse -from ....components.types import Direction, FreqArray -from ....constants import fp_eps -from ....exceptions import ValidationError -from ...microwave import ( - CurrentIntegralTypes, - ImpedanceCalculator, - VoltageIntegralTypes, -) -from ...mode import ModeSolver +from tidy3d.components.base import cached_property, skip_if_fields_missing +from tidy3d.components.data.data_array import FreqDataArray, FreqModeDataArray +from tidy3d.components.data.monitor_data import ModeData +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.geometry.base import Box +from tidy3d.components.geometry.bound_ops import bounds_contains +from tidy3d.components.grid.grid import Grid +from tidy3d.components.monitor import ModeMonitor +from tidy3d.components.simulation import Simulation +from tidy3d.components.source.field import ModeSource, ModeSpec +from tidy3d.components.source.time import GaussianPulse +from tidy3d.components.types import Direction, FreqArray +from tidy3d.constants import fp_eps +from tidy3d.exceptions import ValidationError +from tidy3d.plugins.microwave import CurrentIntegralTypes, ImpedanceCalculator, VoltageIntegralTypes +from tidy3d.plugins.mode import ModeSolver + from .base_terminal import AbstractTerminalPort @@ -98,7 +97,9 @@ def _mode_monitor_name(self) -> str: """Return the name of the :class:`.ModeMonitor` associated with this port.""" return f"{self.name}_mode" - def to_source(self, source_time: GaussianPulse, snap_center: float = None) -> ModeSource: + def to_source( + self, source_time: GaussianPulse, snap_center: Optional[float] = None + ) -> ModeSource: """Create a mode source from the wave port.""" center = list(self.center) if snap_center: @@ -114,7 +115,7 @@ def to_source(self, source_time: GaussianPulse, snap_center: float = None) -> Mo ) def to_monitors( - self, freqs: FreqArray, snap_center: float = None, grid: Grid = None + self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None ) -> list[ModeMonitor]: """The wave port uses a :class:`.ModeMonitor` to compute the characteristic impedance and the port voltages and currents.""" diff --git a/tidy3d/plugins/smatrix/smatrix.py b/tidy3d/plugins/smatrix/smatrix.py index bb2e6d576a..8d244171a4 100644 --- a/tidy3d/plugins/smatrix/smatrix.py +++ b/tidy3d/plugins/smatrix/smatrix.py @@ -1,6 +1,7 @@ # backwards compatibility support for ``from tidy3d.plugins.smatrix.smatrix import `` +from __future__ import annotations from .component_modelers.modal import ComponentModeler from .ports.modal import Port -__all__ = ["Port", "ComponentModeler"] +__all__ = ["ComponentModeler", "Port"] diff --git a/tidy3d/plugins/waveguide/__init__.py b/tidy3d/plugins/waveguide/__init__.py index 8e468f2a33..cb43db72b6 100644 --- a/tidy3d/plugins/waveguide/__init__.py +++ b/tidy3d/plugins/waveguide/__init__.py @@ -1,5 +1,7 @@ """Waveguide utilities module""" +from __future__ import annotations + from .rectangular_dielectric import RectangularDielectric __all__ = ["RectangularDielectric"] diff --git a/tidy3d/plugins/waveguide/rectangular_dielectric.py b/tidy3d/plugins/waveguide/rectangular_dielectric.py index 78891e17bf..757f07bfd4 100644 --- a/tidy3d/plugins/waveguide/rectangular_dielectric.py +++ b/tidy3d/plugins/waveguide/rectangular_dielectric.py @@ -1,30 +1,31 @@ """Rectangular dielectric waveguide utilities.""" -from typing import Any, List, Tuple, Union +from __future__ import annotations + +from typing import Annotated, Any, Literal, Optional, Union import numpy import pydantic.v1 as pydantic from matplotlib import pyplot -from typing_extensions import Annotated - -from ...components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing -from ...components.boundary import BoundarySpec, Periodic -from ...components.data.data_array import FreqModeDataArray, ModeIndexDataArray -from ...components.geometry.base import Box -from ...components.geometry.polyslab import PolySlab -from ...components.grid.grid_spec import GridSpec -from ...components.medium import Medium, MediumType -from ...components.mode_spec import ModeSpec -from ...components.simulation import Simulation -from ...components.source.field import ModeSource -from ...components.source.time import GaussianPulse -from ...components.structure import Structure -from ...components.types import TYPE_TAG_STR, ArrayFloat1D, Ax, Axis, Coordinate, Literal, Size1D -from ...components.viz import add_ax_if_none -from ...constants import C_0, MICROMETER, RADIAN, inf -from ...exceptions import Tidy3dError, ValidationError -from ...log import log -from ..mode.mode_solver import ModeSolver + +from tidy3d.components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing +from tidy3d.components.boundary import BoundarySpec, Periodic +from tidy3d.components.data.data_array import FreqModeDataArray, ModeIndexDataArray +from tidy3d.components.geometry.base import Box +from tidy3d.components.geometry.polyslab import PolySlab +from tidy3d.components.grid.grid_spec import GridSpec +from tidy3d.components.medium import Medium, MediumType +from tidy3d.components.mode_spec import ModeSpec +from tidy3d.components.simulation import Simulation +from tidy3d.components.source.field import ModeSource +from tidy3d.components.source.time import GaussianPulse +from tidy3d.components.structure import Structure +from tidy3d.components.types import TYPE_TAG_STR, ArrayFloat1D, Ax, Axis, Coordinate, Size1D +from tidy3d.components.viz import add_ax_if_none +from tidy3d.constants import C_0, MICROMETER, RADIAN, inf +from tidy3d.exceptions import Tidy3dError, ValidationError +from tidy3d.log import log +from tidy3d.plugins.mode.mode_solver import ModeSolver AnnotatedMedium = Annotated[MediumType, pydantic.Field(discriminator=TYPE_TAG_STR)] @@ -72,14 +73,14 @@ class RectangularDielectric(Tidy3dBaseModel): discriminator=TYPE_TAG_STR, ) - clad_medium: Union[AnnotatedMedium, Tuple[AnnotatedMedium, ...]] = pydantic.Field( + clad_medium: Union[AnnotatedMedium, tuple[AnnotatedMedium, ...]] = pydantic.Field( ..., title="Clad Medium", description="Medium associated with the upper cladding layer. A sequence of mediums can " "be used to create a layered clad.", ) - box_medium: Union[AnnotatedMedium, Tuple[AnnotatedMedium, ...]] = pydantic.Field( + box_medium: Union[AnnotatedMedium, tuple[AnnotatedMedium, ...]] = pydantic.Field( None, title="Box Medium", description="Medium associated with the lower cladding layer. A sequence of mediums can " @@ -339,14 +340,14 @@ def _ensure_consistency(cls, values): return values @property - def _clad_medium(self) -> Tuple[MediumType, ...]: + def _clad_medium(self) -> tuple[MediumType, ...]: """Normalize data type to tuple.""" if not isinstance(self.clad_medium, tuple): return (self.clad_medium,) return self.clad_medium @property - def _box_medium(self) -> Tuple[MediumType, ...]: + def _box_medium(self) -> tuple[MediumType, ...]: """Normalize data type to tuple.""" if not isinstance(self.box_medium, tuple): return (self.box_medium,) @@ -373,7 +374,7 @@ def lateral_axis(self) -> Axis: def _swap_axis( self, lateral_coord: Any, normal_coord: Any, propagation_coord: Any - ) -> List[Any]: + ) -> list[Any]: """Swap the model coordinates to desired axes.""" result = [None, None, None] result[self.lateral_axis] = lateral_coord @@ -383,13 +384,13 @@ def _swap_axis( def _translate( self, lateral_coord: float, normal_coord: float, propagation_coord: float - ) -> List[float]: + ) -> list[float]: """Swap the model coordinates to desired axes and translate to origin.""" coordinates = self._swap_axis(lateral_coord, normal_coord, propagation_coord) result = [a + b for a, b in zip(self.origin, coordinates)] return result - def _transform_in_plane(self, lateral_coord: float, propagation_coord: float) -> List[float]: + def _transform_in_plane(self, lateral_coord: float, propagation_coord: float) -> list[float]: """Swap the model coordinates to desired axes in the substrate plane.""" result = self._translate(lateral_coord, 0, propagation_coord) _, result = Box.pop_axis(result, self.normal_axis) @@ -409,14 +410,14 @@ def width(self) -> Size1D: return w @property - def _core_starts(self) -> List[float]: + def _core_starts(self) -> list[float]: """Starting positions of each waveguide (x is the position in the lateral direction).""" core_x = [-0.5 * (self.core_width.sum() + self.gap.sum())] core_x.extend(core_x[0] + numpy.cumsum(self.core_width[:-1]) + numpy.cumsum(self.gap)) return core_x @property - def _override_structures(self) -> List[Structure]: + def _override_structures(self) -> list[Structure]: """Build override structures to define the simulation grid.""" # Grid resolution factor applied to the materials (increase for waveguide corners @@ -547,7 +548,7 @@ def grid_spec(self) -> GridSpec: return grid_spec @cached_property - def structures(self) -> List[Structure]: + def structures(self) -> list[Structure]: """Waveguide structures for simulation, including the core(s), slabs (if any), and bottom cladding, if different from the top. For bend modes, the structure is a 270 degree bend regardless of :attr:`length`.""" @@ -815,12 +816,12 @@ def mode_area(self) -> FreqModeDataArray: def plot( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, - source_alpha: float = None, - monitor_alpha: float = None, + source_alpha: Optional[float] = None, + monitor_alpha: Optional[float] = None, **patch_kwargs, ) -> Ax: """Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate. @@ -857,13 +858,13 @@ def plot( def plot_eps( self, - x: float = None, - y: float = None, - z: float = None, - freq: float = None, - alpha: float = None, - source_alpha: float = None, - monitor_alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, + source_alpha: Optional[float] = None, + monitor_alpha: Optional[float] = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate. @@ -907,7 +908,11 @@ def plot_eps( ) def plot_structures( - self, x: float = None, y: float = None, z: float = None, ax: Ax = None + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, ) -> Ax: """Plot each of simulation's structures on a plane defined by one nonzero x,y,z coordinate. @@ -936,11 +941,11 @@ def plot_structures( def plot_structures_eps( self, - x: float = None, - y: float = None, - z: float = None, - freq: float = None, - alpha: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + freq: Optional[float] = None, + alpha: Optional[float] = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, @@ -988,9 +993,9 @@ def plot_structures_eps( def plot_grid( self, - x: float = None, - y: float = None, - z: float = None, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, ax: Ax = None, **kwargs, ) -> Ax: @@ -1090,10 +1095,10 @@ def plot_field( val: Literal["real", "imag", "abs"] = "real", eps_alpha: float = 0.2, robust: bool = True, - vmin: float = None, - vmax: float = None, + vmin: Optional[float] = None, + vmax: Optional[float] = None, ax: Ax = None, - geometry_edges: str = None, + geometry_edges: Optional[str] = None, **sel_kwargs, ) -> Ax: """Plot the field for a :class:`.ModeSolverData` with :class:`.Simulation` plot overlaid. diff --git a/tidy3d/updater.py b/tidy3d/updater.py index 6e6228e3a9..bc7f017fab 100644 --- a/tidy3d/updater.py +++ b/tidy3d/updater.py @@ -4,7 +4,7 @@ import functools import json -from typing import Callable, Dict +from typing import Callable, Optional import pydantic.v1 as pd import yaml @@ -24,7 +24,7 @@ class Version(pd.BaseModel): minor: int @classmethod - def from_string(cls, string: str = None) -> Version: + def from_string(cls, string: Optional[str] = None) -> Version: """Return Version from a version string.""" if string is None: return cls.from_string(string=__version__) @@ -186,7 +186,7 @@ def new_update_function(sim_dict: dict) -> dict: return decorator -def iterate_update_dict(update_dict: Dict, update_types: Dict[str, Callable]): +def iterate_update_dict(update_dict: dict, update_types: dict[str, Callable]): """Recursively iterate nested ``update_dict``. For any nested ``nested_dict`` found, apply an update function if its ``nested_dict["type"]`` is in the keys of the ``update_types`` dictionary. Also iterates lists and tuples. @@ -194,7 +194,7 @@ def iterate_update_dict(update_dict: Dict, update_types: Dict[str, Callable]): if isinstance(update_dict, dict): # Update if we need to, and iterate recursively all items - if update_dict.get("type") in update_types.keys(): + if update_dict.get("type") in update_types: update_types[update_dict["type"]](update_dict) for item in update_dict.values(): iterate_update_dict(item, update_types) diff --git a/tidy3d/version.py b/tidy3d/version.py index eb0cb69796..6547964db9 100644 --- a/tidy3d/version.py +++ b/tidy3d/version.py @@ -1,3 +1,5 @@ """DO NOT EDIT: Modified automatically with .bump2version.cfg""" +from __future__ import annotations + __version__ = "2.8.4" diff --git a/tidy3d/web/__init__.py b/tidy3d/web/__init__.py index 423e4dc18f..ff51cf9f3b 100644 --- a/tidy3d/web/__init__.py +++ b/tidy3d/web/__init__.py @@ -1,8 +1,11 @@ # ruff: noqa: E402 """imports interfaces for interacting with server""" -from ..log import get_logging_console, log -from ..version import __version__ +from __future__ import annotations + +from tidy3d.log import get_logging_console, log +from tidy3d.version import __version__ + from .core import core_config # set logger to tidy3d.log before it's invoked in other imports @@ -39,28 +42,28 @@ migrate() __all__ = [ - "run", - "upload", - "get_info", - "start", - "monitor", - "delete", + "Batch", + "BatchData", + "Job", "abort", - "download", - "load", - "estimate_cost", - "get_tasks", + "account", + "configure", + "delete", "delete_old", + "download", "download_json", "download_log", + "estimate_cost", + "get_info", + "get_tasks", + "load", "load_simulation", + "monitor", "real_cost", - "Job", - "Batch", - "BatchData", - "tidy3d_cli", - "configure", + "run", "run_async", + "start", "test", - "account", + "tidy3d_cli", + "upload", ] diff --git a/tidy3d/web/api/asynchronous.py b/tidy3d/web/api/asynchronous.py index fc50b1d349..91eacb5f18 100644 --- a/tidy3d/web/api/asynchronous.py +++ b/tidy3d/web/api/asynchronous.py @@ -1,22 +1,25 @@ """Interface to run several jobs in batch using simplified syntax.""" -from typing import Dict, List, Literal, Union +from __future__ import annotations + +from typing import Literal, Optional, Union + +from tidy3d.log import log +from tidy3d.web.core.types import PayType -from ...log import log -from ..core.types import PayType from .container import DEFAULT_DATA_DIR, Batch, BatchData from .tidy3d_stub import SimulationType def run_async( - simulations: Dict[str, SimulationType], + simulations: dict[str, SimulationType], folder_name: str = "default", path_dir: str = DEFAULT_DATA_DIR, - callback_url: str = None, - num_workers: int = None, + callback_url: Optional[str] = None, + num_workers: Optional[int] = None, verbose: bool = True, simulation_type: str = "tidy3d", - parent_tasks: Dict[str, List[str]] = None, + parent_tasks: Optional[dict[str, list[str]]] = None, reduce_simulation: Literal["auto", True, False] = "auto", pay_type: Union[PayType, str] = PayType.AUTO, ) -> BatchData: diff --git a/tidy3d/web/api/autograd/autograd.py b/tidy3d/web/api/autograd/autograd.py index 18a32b8ca5..a0dfac8bec 100644 --- a/tidy3d/web/api/autograd/autograd.py +++ b/tidy3d/web/api/autograd/autograd.py @@ -1,4 +1,5 @@ # autograd wrapper for web functions +from __future__ import annotations import os import tempfile @@ -14,16 +15,15 @@ import tidy3d as td from tidy3d.components.autograd import AutogradFieldMap, get_static from tidy3d.components.autograd.derivative_utils import DerivativeInfo -from tidy3d.components.types import Literal - -from ....exceptions import AdjointError -from ...core.s3utils import download_file, upload_file -from ...core.types import PayType -from ..asynchronous import DEFAULT_DATA_DIR -from ..asynchronous import run_async as run_async_webapi -from ..container import DEFAULT_DATA_PATH, Batch, BatchData, Job -from ..tidy3d_stub import SimulationDataType, SimulationType -from ..webapi import run as run_webapi +from tidy3d.exceptions import AdjointError +from tidy3d.web.api.asynchronous import DEFAULT_DATA_DIR +from tidy3d.web.api.asynchronous import run_async as run_async_webapi +from tidy3d.web.api.container import DEFAULT_DATA_PATH, Batch, BatchData, Job +from tidy3d.web.api.tidy3d_stub import SimulationDataType, SimulationType +from tidy3d.web.api.webapi import run as run_webapi +from tidy3d.web.core.s3utils import download_file, upload_file +from tidy3d.web.core.types import PayType + from .utils import E_to_D, FieldMap, TracerKeys, get_derivative_maps # keys for data into auxiliary dictionary @@ -96,17 +96,17 @@ def run( task_name: str, folder_name: str = "default", path: str = "simulation_data.hdf5", - callback_url: str = None, + callback_url: typing.Optional[str] = None, verbose: bool = True, - progress_callback_upload: typing.Callable[[float], None] = None, - progress_callback_download: typing.Callable[[float], None] = None, - solver_version: str = None, - worker_group: str = None, + progress_callback_upload: typing.Optional[typing.Callable[[float], None]] = None, + progress_callback_download: typing.Optional[typing.Callable[[float], None]] = None, + solver_version: typing.Optional[str] = None, + worker_group: typing.Optional[str] = None, simulation_type: str = "tidy3d", - parent_tasks: list[str] = None, + parent_tasks: typing.Optional[list[str]] = None, local_gradient: bool = LOCAL_GRADIENT, max_num_adjoint_per_fwd: int = MAX_NUM_ADJOINT_PER_FWD, - reduce_simulation: Literal["auto", True, False] = "auto", + reduce_simulation: typing.Literal["auto", True, False] = "auto", pay_type: typing.Union[PayType, str] = PayType.AUTO, ) -> SimulationDataType: """ @@ -232,14 +232,14 @@ def run_async( simulations: dict[str, SimulationType], folder_name: str = "default", path_dir: str = DEFAULT_DATA_DIR, - callback_url: str = None, - num_workers: int = None, + callback_url: typing.Optional[str] = None, + num_workers: typing.Optional[int] = None, verbose: bool = True, simulation_type: str = "tidy3d", - parent_tasks: dict[str, list[str]] = None, + parent_tasks: typing.Optional[dict[str, list[str]]] = None, local_gradient: bool = LOCAL_GRADIENT, max_num_adjoint_per_fwd: int = MAX_NUM_ADJOINT_PER_FWD, - reduce_simulation: Literal["auto", True, False] = "auto", + reduce_simulation: typing.Literal["auto", True, False] = "auto", pay_type: typing.Union[PayType, str] = PayType.AUTO, ) -> BatchData: """Submits a set of Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] objects to server, @@ -704,7 +704,7 @@ def vjp(data_fields_vjp: AutogradFieldMap) -> AutogradFieldMap: # Build a per-task parent_tasks mapping parent_tasks = {} - for tname_adj in sims_adj_dict.keys(): + for tname_adj in sims_adj_dict: parent_tasks[tname_adj] = [task_id_fwd] run_kwargs["parent_tasks"] = parent_tasks @@ -1070,7 +1070,7 @@ def postprocess_adj( # extract VJPs and put back into sim_fields_vjp AutogradFieldMap for structure_path, vjp_value in vjp_value_map.items(): - sim_path = tuple(["structures", structure_index] + list(structure_path)) + sim_path = ("structures", structure_index, *list(structure_path)) if freq_idx == 0: sim_fields_vjp[sim_path] = vjp_value else: @@ -1099,7 +1099,7 @@ def postprocess_adj( def parse_run_kwargs(**run_kwargs): """Parse the ``run_kwargs`` to extract what should be passed to the ``Job`` initialization.""" - job_fields = list(Job._upload_fields) + ["solver_version", "pay_type"] + job_fields = [*list(Job._upload_fields), "solver_version", "pay_type"] job_init_kwargs = {k: v for k, v in run_kwargs.items() if k in job_fields} return job_init_kwargs diff --git a/tidy3d/web/api/autograd/utils.py b/tidy3d/web/api/autograd/utils.py index 1d5cd136c3..7189096531 100644 --- a/tidy3d/web/api/autograd/utils.py +++ b/tidy3d/web/api/autograd/utils.py @@ -22,7 +22,7 @@ def get_derivative_maps( """Get electric and displacement field derivative maps.""" der_map_E = derivative_map_E(fld_fwd=fld_fwd, fld_adj=fld_adj) der_map_D = derivative_map_D(fld_fwd=fld_fwd, eps_fwd=eps_fwd, fld_adj=fld_adj, eps_adj=eps_adj) - return dict(E=der_map_E, D=der_map_D) + return {"E": der_map_E, "D": der_map_D} def derivative_map_E(fld_fwd: td.FieldData, fld_adj: td.FieldData) -> td.FieldData: diff --git a/tidy3d/web/api/connect_util.py b/tidy3d/web/api/connect_util.py index 60e69320b4..137fe0d684 100644 --- a/tidy3d/web/api/connect_util.py +++ b/tidy3d/web/api/connect_util.py @@ -1,5 +1,7 @@ """connect util for webapi.""" +from __future__ import annotations + import time from functools import wraps @@ -8,8 +10,8 @@ from requests.exceptions import JSONDecodeError from urllib3.exceptions import NewConnectionError -from ...exceptions import WebError -from ...log import log +from tidy3d.exceptions import WebError +from tidy3d.log import log # number of seconds to keep re-trying connection before erroring CONNECTION_RETRY_TIME = 180 diff --git a/tidy3d/web/api/container.py b/tidy3d/web/api/container.py index 851cd35acd..e4ea5afaf4 100644 --- a/tidy3d/web/api/container.py +++ b/tidy3d/web/api/container.py @@ -8,21 +8,22 @@ from abc import ABC from collections.abc import Mapping from concurrent.futures import ThreadPoolExecutor -from typing import Dict, Optional, Tuple +from typing import Literal, Optional import pydantic.v1 as pd from rich.progress import BarColumn, Progress, TaskProgressColumn, TextColumn, TimeElapsedColumn -from ...components.base import Tidy3dBaseModel, cached_property -from ...components.mode.mode_solver import ModeSolver -from ...components.types import Literal, annotate_type -from ...exceptions import DataError -from ...log import get_logging_console, log -from ..api import webapi as web -from ..core.constants import TaskId, TaskName -from ..core.task_core import Folder -from ..core.task_info import RunInfo, TaskInfo -from ..core.types import PayType +from tidy3d.components.base import Tidy3dBaseModel, cached_property +from tidy3d.components.mode.mode_solver import ModeSolver +from tidy3d.components.types import annotate_type +from tidy3d.exceptions import DataError +from tidy3d.log import get_logging_console, log +from tidy3d.web.api import webapi as web +from tidy3d.web.core.constants import TaskId, TaskName +from tidy3d.web.core.task_core import Folder +from tidy3d.web.core.task_info import RunInfo, TaskInfo +from tidy3d.web.core.types import PayType + from .tidy3d_stub import SimulationDataType, SimulationType # Max # of workers for parallel upload / download: above 10, performance is same but with warnings @@ -41,7 +42,6 @@ class WebContainer(Tidy3dBaseModel, ABC): @abstractmethod def _check_path_dir(path: str) -> None: """Make sure local output directory exists and create it if not.""" - pass @staticmethod def _check_folder(folder_name: str) -> None: @@ -164,7 +164,7 @@ class Job(WebContainer): description="Type of simulation, used internally only.", ) - parent_tasks: Tuple[TaskId, ...] = pd.Field( + parent_tasks: tuple[TaskId, ...] = pd.Field( None, title="Parent Tasks", description="Tuple of parent task ids, used internally only." ) @@ -411,13 +411,13 @@ class BatchData(Tidy3dBaseModel, Mapping): * `Performing parallel / batch processing of simulations <../../notebooks/ParameterScan.html>`_ """ - task_paths: Dict[TaskName, str] = pd.Field( + task_paths: dict[TaskName, str] = pd.Field( ..., title="Data Paths", description="Mapping of task_name to path to corresponding data for each task in batch.", ) - task_ids: Dict[TaskName, str] = pd.Field( + task_ids: dict[TaskName, str] = pd.Field( ..., title="Task IDs", description="Mapping of task_name to task_id for each task in batch." ) @@ -499,7 +499,7 @@ class Batch(WebContainer): * `Inverse taper edge coupler <../../notebooks/EdgeCoupler.html>`_ """ - simulations: Dict[TaskName, annotate_type(SimulationType)] = pd.Field( + simulations: dict[TaskName, annotate_type(SimulationType)] = pd.Field( ..., title="Simulations", description="Mapping of task names to Simulations to run as a batch.", @@ -536,7 +536,7 @@ class Batch(WebContainer): description="Type of each simulation in the batch, used internally only.", ) - parent_tasks: Dict[str, Tuple[TaskId, ...]] = pd.Field( + parent_tasks: dict[str, tuple[TaskId, ...]] = pd.Field( None, title="Parent Tasks", description="Collection of parent task ids for each job in batch, used internally only.", @@ -563,7 +563,7 @@ class Batch(WebContainer): description="Specify the payment method.", ) - jobs_cached: Dict[TaskName, Job] = pd.Field( + jobs_cached: dict[TaskName, Job] = pd.Field( None, title="Jobs (Cached)", description="Optional field to specify ``jobs``. Only used as a workaround internally " @@ -610,7 +610,7 @@ def run(self, path_dir: str = DEFAULT_DATA_DIR) -> BatchData: return self.load(path_dir=path_dir) @cached_property - def jobs(self) -> Dict[TaskName, Job]: + def jobs(self) -> dict[TaskName, Job]: """Create a series of tasks in the :class:`.Batch` and upload them to server. Note @@ -694,7 +694,7 @@ def upload(self) -> None: completed += 1 progress.update(pbar, completed=completed) - def get_info(self) -> Dict[TaskName, TaskInfo]: + def get_info(self) -> dict[TaskName, TaskInfo]: """Get information about each task in the :class:`Batch`. Returns @@ -723,7 +723,7 @@ def start(self) -> None: for _, job in self.jobs.items(): executor.submit(job.start) - def get_run_info(self) -> Dict[TaskName, RunInfo]: + def get_run_info(self) -> dict[TaskName, RunInfo]: """get information about a each of the tasks in the :class:`Batch`. Returns @@ -881,7 +881,7 @@ def _job_data_path(task_id: TaskId, path_dir: str = DEFAULT_DATA_DIR): str Full path to the data file. """ - return os.path.join(path_dir, f"{str(task_id)}.hdf5") + return os.path.join(path_dir, f"{task_id!s}.hdf5") @staticmethod def _batch_path(path_dir: str = DEFAULT_DATA_DIR): diff --git a/tidy3d/web/api/material_fitter.py b/tidy3d/web/api/material_fitter.py index 0d91f825e5..82bb158736 100644 --- a/tidy3d/web/api/material_fitter.py +++ b/tidy3d/web/api/material_fitter.py @@ -12,9 +12,9 @@ import requests from pydantic.v1 import BaseModel, Field -from ...plugins.dispersion import DispersionFitter -from ..core.http_util import http -from ..core.types import Submittable +from tidy3d.plugins.dispersion import DispersionFitter +from tidy3d.web.core.http_util import http +from tidy3d.web.core.types import Submittable class ConstraintEnum(str, Enum): diff --git a/tidy3d/web/api/material_libray.py b/tidy3d/web/api/material_libray.py index 3b9e397f2c..98bfeaa0b2 100644 --- a/tidy3d/web/api/material_libray.py +++ b/tidy3d/web/api/material_libray.py @@ -2,14 +2,15 @@ from __future__ import annotations +import builtins import json -from typing import List, Optional +from typing import Optional from pydantic.v1 import Field, parse_obj_as, validator -from ...components.medium import MediumType -from ..core.http_util import http -from ..core.types import Queryable +from tidy3d.components.medium import MediumType +from tidy3d.web.core.http_util import http +from tidy3d.web.core.types import Queryable class MaterialLibray(Queryable, smart_union=True): @@ -31,7 +32,7 @@ def parse_result(cls, values): return json.loads(values) @classmethod - def list(cls) -> List[MaterialLibray]: + def list(cls) -> builtins.list[MaterialLibray]: """List all material libraries. Returns @@ -40,4 +41,4 @@ def list(cls) -> List[MaterialLibray]: List of material libraries/ """ resp = http.get("tidy3d/libraries") - return parse_obj_as(List[MaterialLibray], resp) if resp else None + return parse_obj_as(list[MaterialLibray], resp) if resp else None diff --git a/tidy3d/web/api/mode.py b/tidy3d/web/api/mode.py index 09024aeff8..02364f105b 100644 --- a/tidy3d/web/api/mode.py +++ b/tidy3d/web/api/mode.py @@ -7,28 +7,27 @@ import tempfile import time from datetime import datetime -from typing import Callable, List, Optional, Union +from typing import Callable, Literal, Optional, Union import pydantic.v1 as pydantic from botocore.exceptions import ClientError from joblib import Parallel, delayed from rich.progress import Progress -from ...components.data.monitor_data import ModeSolverData -from ...components.eme.simulation import EMESimulation -from ...components.medium import AbstractCustomMedium -from ...components.simulation import Simulation -from ...components.types import Literal -from ...exceptions import SetupError, WebError -from ...log import get_logging_console, log -from ...plugins.mode.mode_solver import MODE_MONITOR_NAME, ModeSolver -from ...version import __version__ -from ..core.core_config import get_logger_console -from ..core.environment import Env -from ..core.http_util import http -from ..core.s3utils import download_file, download_gz_file, upload_file -from ..core.task_core import Folder -from ..core.types import PayType, ResourceLifecycle, Submittable +from tidy3d.components.data.monitor_data import ModeSolverData +from tidy3d.components.eme.simulation import EMESimulation +from tidy3d.components.medium import AbstractCustomMedium +from tidy3d.components.simulation import Simulation +from tidy3d.exceptions import SetupError, WebError +from tidy3d.log import get_logging_console, log +from tidy3d.plugins.mode.mode_solver import MODE_MONITOR_NAME, ModeSolver +from tidy3d.version import __version__ +from tidy3d.web.core.core_config import get_logger_console +from tidy3d.web.core.environment import Env +from tidy3d.web.core.http_util import http +from tidy3d.web.core.s3utils import download_file, download_gz_file, upload_file +from tidy3d.web.core.task_core import Folder +from tidy3d.web.core.types import PayType, ResourceLifecycle, Submittable SIMULATION_JSON = "simulation.json" SIM_FILE_HDF5_GZ = "simulation.hdf5.gz" @@ -53,8 +52,8 @@ def run( folder_name: str = "Mode Solver", results_file: str = "mode_solver.hdf5", verbose: bool = True, - progress_callback_upload: Callable[[float], None] = None, - progress_callback_download: Callable[[float], None] = None, + progress_callback_upload: Optional[Callable[[float], None]] = None, + progress_callback_download: Optional[Callable[[float], None]] = None, reduce_simulation: Literal["auto", True, False] = "auto", pay_type: Union[PayType, str] = PayType.AUTO, ) -> ModeSolverData: @@ -147,17 +146,17 @@ def run( def run_batch( - mode_solvers: List[ModeSolver], + mode_solvers: list[ModeSolver], task_name: str = "BatchModeSolver", folder_name: str = "BatchModeSolvers", - results_files: List[str] = None, + results_files: Optional[list[str]] = None, verbose: bool = True, max_workers: int = DEFAULT_NUM_WORKERS, max_retries: int = DEFAULT_MAX_RETRIES, retry_delay: float = DEFAULT_RETRY_DELAY, - progress_callback_upload: Callable[[float], None] = None, - progress_callback_download: Callable[[float], None] = None, -) -> List[ModeSolverData]: + progress_callback_upload: Optional[Callable[[float], None]] = None, + progress_callback_download: Optional[Callable[[float], None]] = None, +) -> list[ModeSolverData]: """ Submits a batch of ModeSolver to the server concurrently, manages progress, and retrieves results. @@ -221,7 +220,7 @@ def handle_mode_solver(index, progress, pbar): progress.update(pbar, advance=1) return result except Exception as e: - console.log(f"Error in mode solver {index}: {str(e)}") + console.log(f"Error in mode solver {index}: {e!s}") if retries < max_retries: time.sleep(retry_delay) retries += 1 @@ -376,7 +375,7 @@ def get( to_file: str = "mode_solver.hdf5", sim_file: str = "simulation.hdf5", verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, ) -> ModeSolverTask: """Get mode solver task from the server by id. @@ -418,7 +417,7 @@ def get_info(self) -> ModeSolverTask: return ModeSolverTask(**resp, mode_solver=self.mode_solver) def upload( - self, verbose: bool = True, progress_callback: Callable[[float], None] = None + self, verbose: bool = True, progress_callback: Optional[Callable[[float], None]] = None ) -> None: """Upload this task's 'mode_solver' to the server. @@ -501,7 +500,7 @@ def get_modesolver( to_file: str = "mode_solver.hdf5", sim_file: str = "simulation.hdf5", verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, ) -> ModeSolver: """Get mode solver associated with this task from the server. @@ -585,7 +584,7 @@ def get_result( self, to_file: str = "mode_solver_data.hdf5", verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, ) -> ModeSolverData: """Get mode solver results for this task from the server. @@ -648,7 +647,7 @@ def get_log( self, to_file: str = "mode_solver.log", verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, ) -> pathlib.Path: """Get execution log for this task from the server. diff --git a/tidy3d/web/api/tidy3d_stub.py b/tidy3d/web/api/tidy3d_stub.py index 5a5af337d1..55eddc84ce 100644 --- a/tidy3d/web/api/tidy3d_stub.py +++ b/tidy3d/web/api/tidy3d_stub.py @@ -3,32 +3,31 @@ from __future__ import annotations import json -from typing import Callable, List, Union +from typing import Callable, Optional, Union import pydantic.v1 as pd from pydantic.v1 import BaseModel +from tidy3d import log +from tidy3d.components.base import _get_valid_extension +from tidy3d.components.data.monitor_data import ModeSolverData +from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.eme.data.sim_data import EMESimulationData +from tidy3d.components.eme.simulation import EMESimulation +from tidy3d.components.mode.data.sim_data import ModeSimulationData +from tidy3d.components.mode.simulation import ModeSimulation +from tidy3d.components.simulation import Simulation from tidy3d.components.tcad.data.sim_data import HeatChargeSimulationData, HeatSimulationData from tidy3d.components.tcad.simulation.heat import HeatSimulation from tidy3d.components.tcad.simulation.heat_charge import HeatChargeSimulation - -from ... import log -from ...components.base import _get_valid_extension -from ...components.data.monitor_data import ModeSolverData -from ...components.data.sim_data import SimulationData -from ...components.eme.data.sim_data import EMESimulationData -from ...components.eme.simulation import EMESimulation -from ...components.mode.data.sim_data import ModeSimulationData -from ...components.mode.simulation import ModeSimulation -from ...components.simulation import Simulation -from ...plugins.mode.mode_solver import ModeSolver -from ..core.file_util import ( +from tidy3d.plugins.mode.mode_solver import ModeSolver +from tidy3d.web.core.file_util import ( read_simulation_from_hdf5, read_simulation_from_hdf5_gz, read_simulation_from_json, ) -from ..core.stub import TaskStub, TaskStubData -from ..core.types import TaskType +from tidy3d.web.core.stub import TaskStub, TaskStubData +from tidy3d.web.core.types import TaskType SimulationType = Union[ Simulation, HeatChargeSimulation, HeatSimulation, EMESimulation, ModeSolver, ModeSimulation @@ -76,17 +75,17 @@ def from_file(cls, file_path: str) -> SimulationType: data = json.loads(json_str) type_ = data["type"] - if "Simulation" == type_: + if type_ == "Simulation": sim = Simulation.from_file(file_path) - elif "ModeSolver" == type_: + elif type_ == "ModeSolver": sim = ModeSolver.from_file(file_path) - elif "HeatSimulation" == type_: + elif type_ == "HeatSimulation": sim = HeatSimulation.from_file(file_path) - elif "HeatChargeSimulation" == type_: + elif type_ == "HeatChargeSimulation": sim = HeatChargeSimulation.from_file(file_path) - elif "EMESimulation" == type_: + elif type_ == "EMESimulation": sim = EMESimulation.from_file(file_path) - elif "ModeSimulation" == type_: + elif type_ == "ModeSimulation": sim = ModeSimulation.from_file(file_path) return sim @@ -109,7 +108,7 @@ def to_file( """ self.simulation.to_file(file_path) - def to_hdf5_gz(self, fname: str, custom_encoders: List[Callable] = None) -> None: + def to_hdf5_gz(self, fname: str, custom_encoders: Optional[list[Callable]] = None) -> None: """Exports Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] instance to .hdf5.gz file. Parameters @@ -138,15 +137,15 @@ def get_type(self) -> str: """ if isinstance(self.simulation, Simulation): return TaskType.FDTD.name - elif isinstance(self.simulation, ModeSolver): + if isinstance(self.simulation, ModeSolver): return TaskType.MODE_SOLVER.name - elif isinstance(self.simulation, HeatSimulation): + if isinstance(self.simulation, HeatSimulation): return TaskType.HEAT.name - elif isinstance(self.simulation, HeatChargeSimulation): + if isinstance(self.simulation, HeatChargeSimulation): return TaskType.HEAT_CHARGE.name - elif isinstance(self.simulation, EMESimulation): + if isinstance(self.simulation, EMESimulation): return TaskType.EME.name - elif isinstance(self.simulation, ModeSimulation): + if isinstance(self.simulation, ModeSimulation): return TaskType.MODE.name def validate_pre_upload(self, source_required) -> None: @@ -188,17 +187,17 @@ def from_file(cls, file_path: str) -> SimulationDataType: data = json.loads(json_str) type_ = data["type"] - if "SimulationData" == type_: + if type_ == "SimulationData": sim_data = SimulationData.from_file(file_path) - elif "ModeSolverData" == type_: + elif type_ == "ModeSolverData": sim_data = ModeSolverData.from_file(file_path) - elif "HeatSimulationData" == type_: + elif type_ == "HeatSimulationData": sim_data = HeatSimulationData.from_file(file_path) - elif "HeatChargeSimulationData" == type_: + elif type_ == "HeatChargeSimulationData": sim_data = HeatChargeSimulationData.from_file(file_path) - elif "EMESimulationData" == type_: + elif type_ == "EMESimulationData": sim_data = EMESimulationData.from_file(file_path) - elif "ModeSimulationData" == type_: + elif type_ == "ModeSimulationData": sim_data = ModeSimulationData.from_file(file_path) return sim_data diff --git a/tidy3d/web/api/webapi.py b/tidy3d/web/api/webapi.py index 819d8627de..7b720e21f7 100644 --- a/tidy3d/web/api/webapi.py +++ b/tidy3d/web/api/webapi.py @@ -1,22 +1,23 @@ """Provides lowest level, user-facing interface to server.""" +from __future__ import annotations + import json import os import tempfile import time -from typing import Callable, Dict, List, Union +from typing import Callable, Literal, Optional, Union from requests import HTTPError from rich.progress import Progress -from ...components.medium import AbstractCustomMedium -from ...components.mode.mode_solver import ModeSolver -from ...components.mode.simulation import ModeSimulation -from ...components.types import Literal -from ...exceptions import WebError -from ...log import get_logging_console, log -from ..core.account import Account -from ..core.constants import ( +from tidy3d.components.medium import AbstractCustomMedium +from tidy3d.components.mode.mode_solver import ModeSolver +from tidy3d.components.mode.simulation import ModeSimulation +from tidy3d.exceptions import WebError +from tidy3d.log import get_logging_console, log +from tidy3d.web.core.account import Account +from tidy3d.web.core.constants import ( MODE_DATA_HDF5_GZ, MODE_FILE_HDF5_GZ, SIM_FILE_HDF5, @@ -24,16 +25,12 @@ SIMULATION_DATA_HDF5_GZ, TaskId, ) -from ..core.environment import Env -from ..core.task_core import Folder, SimulationTask -from ..core.task_info import ChargeType, TaskInfo -from ..core.types import PayType -from .connect_util import ( - REFRESH_TIME, - get_grid_points_str, - get_time_steps_str, - wait_for_connection, -) +from tidy3d.web.core.environment import Env +from tidy3d.web.core.task_core import Folder, SimulationTask +from tidy3d.web.core.task_info import ChargeType, TaskInfo +from tidy3d.web.core.types import PayType + +from .connect_util import REFRESH_TIME, get_grid_points_str, get_time_steps_str, wait_for_connection from .tidy3d_stub import SimulationDataType, SimulationType, Tidy3dStub, Tidy3dStubData # time between checking run status @@ -75,14 +72,14 @@ def run( task_name: str, folder_name: str = "default", path: str = "simulation_data.hdf5", - callback_url: str = None, + callback_url: Optional[str] = None, verbose: bool = True, - progress_callback_upload: Callable[[float], None] = None, - progress_callback_download: Callable[[float], None] = None, - solver_version: str = None, - worker_group: str = None, + progress_callback_upload: Optional[Callable[[float], None]] = None, + progress_callback_download: Optional[Callable[[float], None]] = None, + solver_version: Optional[str] = None, + worker_group: Optional[str] = None, simulation_type: str = "tidy3d", - parent_tasks: list[str] = None, + parent_tasks: Optional[list[str]] = None, reduce_simulation: Literal["auto", True, False] = "auto", pay_type: Union[PayType, str] = PayType.AUTO, ) -> SimulationDataType: @@ -196,13 +193,13 @@ def upload( simulation: SimulationType, task_name: str, folder_name: str = "default", - callback_url: str = None, + callback_url: Optional[str] = None, verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, simulation_type: str = "tidy3d", - parent_tasks: List[str] = None, + parent_tasks: Optional[list[str]] = None, source_required: bool = True, - solver_version: str = None, + solver_version: Optional[str] = None, reduce_simulation: Literal["auto", True, False] = "auto", ) -> TaskId: """ @@ -372,8 +369,8 @@ def get_info(task_id: TaskId, verbose: bool = True) -> TaskInfo: @wait_for_connection def start( task_id: TaskId, - solver_version: str = None, - worker_group: str = None, + solver_version: Optional[str] = None, + worker_group: Optional[str] = None, pay_type: Union[PayType, str] = PayType.AUTO, ) -> None: """Start running the simulation associated with task. @@ -628,7 +625,7 @@ def download( task_id: TaskId, path: str = "simulation_data.hdf5", verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, ) -> None: """Download results of task to file. @@ -685,7 +682,7 @@ def download_hdf5( task_id: TaskId, path: str = SIM_FILE_HDF5, verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, ) -> None: """Download the ``.hdf5`` file associated with the :class:`.Simulation` of a given task. @@ -745,7 +742,7 @@ def download_log( task_id: TaskId, path: str = "tidy3d.log", verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, ) -> None: """Download the tidy3d log file associated with a task. @@ -774,7 +771,7 @@ def load( path: str = "simulation_data.hdf5", replace_existing: bool = True, verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, ) -> SimulationDataType: """ Download and Load simulation results into :class:`.SimulationData` object. @@ -886,20 +883,19 @@ def abort(task_id: TaskId): task = SimulationTask.get(task_id) if not task: raise ValueError("Task not found.") - else: - task.abort() - console = get_logging_console() - url = _get_url(task.task_id) - console.log( - f"Task is aborting. View task using web UI at [link={url}]'{url}'[/link] to check the result." - ) - return TaskInfo(**{"taskId": task.task_id, **task.dict()}) + task.abort() + console = get_logging_console() + url = _get_url(task.task_id) + console.log( + f"Task is aborting. View task using web UI at [link={url}]'{url}'[/link] to check the result." + ) + return TaskInfo(**{"taskId": task.task_id, **task.dict()}) @wait_for_connection def get_tasks( - num_tasks: int = None, order: Literal["new", "old"] = "new", folder: str = "default" -) -> List[Dict]: + num_tasks: Optional[int] = None, order: Literal["new", "old"] = "new", folder: str = "default" +) -> list[dict]: """Get a list with the metadata of the last ``num_tasks`` tasks. Parameters @@ -930,7 +926,9 @@ def get_tasks( @wait_for_connection -def estimate_cost(task_id: str, verbose: bool = True, solver_version: str = None) -> float: +def estimate_cost( + task_id: str, verbose: bool = True, solver_version: Optional[str] = None +) -> float: """Compute the maximum FlexCredit charge for a given task. Parameters diff --git a/tidy3d/web/cli/__init__.py b/tidy3d/web/cli/__init__.py index ada31566b2..bd3bf238d0 100644 --- a/tidy3d/web/cli/__init__.py +++ b/tidy3d/web/cli/__init__.py @@ -2,6 +2,8 @@ tidy3d command line tool. """ +from __future__ import annotations + from .app import tidy3d_cli __all__ = ["tidy3d_cli"] diff --git a/tidy3d/web/cli/app.py b/tidy3d/web/cli/app.py index ea16ad4e49..76a9572f09 100644 --- a/tidy3d/web/cli/app.py +++ b/tidy3d/web/cli/app.py @@ -2,6 +2,8 @@ Commandline interface for tidy3d. """ +from __future__ import annotations + import json import os.path import ssl @@ -10,10 +12,11 @@ import requests import toml -from ..cli.constants import CONFIG_FILE, CREDENTIAL_FILE, TIDY3D_DIR -from ..cli.migrate import migrate -from ..core.constants import HEADER_APIKEY, KEY_APIKEY -from ..core.environment import Env +from tidy3d.web.cli.constants import CONFIG_FILE, CREDENTIAL_FILE, TIDY3D_DIR +from tidy3d.web.cli.migrate import migrate +from tidy3d.web.core.constants import HEADER_APIKEY, KEY_APIKEY +from tidy3d.web.core.environment import Env + from .develop.index import develop # Prevent race condition on threads diff --git a/tidy3d/web/cli/constants.py b/tidy3d/web/cli/constants.py index d5ae0b78a3..60961f28fe 100644 --- a/tidy3d/web/cli/constants.py +++ b/tidy3d/web/cli/constants.py @@ -1,5 +1,7 @@ """Constants for the CLI.""" +from __future__ import annotations + import os from os.path import expanduser diff --git a/tidy3d/web/cli/develop/__init__.py b/tidy3d/web/cli/develop/__init__.py index 022b3049fa..98ef3b1300 100644 --- a/tidy3d/web/cli/develop/__init__.py +++ b/tidy3d/web/cli/develop/__init__.py @@ -1,4 +1,6 @@ # Import from documentation.py +from __future__ import annotations + from .documentation import ( build_documentation, # build_documentation_pdf, @@ -35,30 +37,30 @@ from .utils import echo_and_check_subprocess, echo_and_run_subprocess, get_install_directory __all__ = [ + "activate_correct_poetry_python", "benchmark_timing_operations", "benchmark_timing_operations_command", "build_documentation", # "build_documentation_pdf", "build_documentation_from_remote_notebooks", "commit", - # "convert_all_markdown_to_rst_command", - "replace_in_files_command", - "test_options", - "test_in_environment_command", - "activate_correct_poetry_python", "configure_submodules", - "verify_pandoc_is_installed_and_version_less_than_3", - "verify_pipx_is_installed", - "verify_poetry_is_installed", - "verify_sphinx_is_installed", + "develop", + "echo_and_check_subprocess", + "echo_and_run_subprocess", + "get_install_directory", "get_install_directory_command", "install_development_environment", "install_in_poetry", + # "convert_all_markdown_to_rst_command", + "replace_in_files_command", + "test_in_environment_command", + "test_options", "uninstall_development_environment", "update_submodules_remote", "verify_development_environment", - "get_install_directory", - "echo_and_run_subprocess", - "echo_and_check_subprocess", - "develop", + "verify_pandoc_is_installed_and_version_less_than_3", + "verify_pipx_is_installed", + "verify_poetry_is_installed", + "verify_sphinx_is_installed", ] diff --git a/tidy3d/web/cli/develop/documentation.py b/tidy3d/web/cli/develop/documentation.py index fc3fc9d5e8..9da6926577 100644 --- a/tidy3d/web/cli/develop/documentation.py +++ b/tidy3d/web/cli/develop/documentation.py @@ -13,6 +13,8 @@ poetry run tidy3d develop convert-all-markdown-to-rst """ +from __future__ import annotations + import json import os from typing import Optional diff --git a/tidy3d/web/cli/develop/index.py b/tidy3d/web/cli/develop/index.py index ac0af5f329..bbb63fef78 100644 --- a/tidy3d/web/cli/develop/index.py +++ b/tidy3d/web/cli/develop/index.py @@ -1,5 +1,7 @@ """Console script subcommand for tidy3d.""" +from __future__ import annotations + import click __all__ = [ @@ -15,4 +17,3 @@ def develop(): This command group includes several subcommands for various development tasks such as verifying and setting up the development environment, building documentation, testing, and more. """ - pass diff --git a/tidy3d/web/cli/develop/install.py b/tidy3d/web/cli/develop/install.py index 0a7ca6cc2e..302cc4ac55 100644 --- a/tidy3d/web/cli/develop/install.py +++ b/tidy3d/web/cli/develop/install.py @@ -4,6 +4,8 @@ are available as CLI commands when tidy3d is installed. """ +from __future__ import annotations + import platform import re import subprocess @@ -16,16 +18,16 @@ __all__ = [ "activate_correct_poetry_python", "configure_submodules", - "verify_pandoc_is_installed_and_version_less_than_3", - "verify_pipx_is_installed", - "verify_poetry_is_installed", - "verify_sphinx_is_installed", "get_install_directory_command", "install_development_environment", "install_in_poetry", "uninstall_development_environment", "update_submodules_remote", "verify_development_environment", + "verify_pandoc_is_installed_and_version_less_than_3", + "verify_pipx_is_installed", + "verify_poetry_is_installed", + "verify_sphinx_is_installed", ] @@ -33,9 +35,7 @@ def activate_correct_poetry_python(): """ Activate the correct Python environment for Poetry based on the operating system. """ - if platform.system() == "Windows": - echo_and_run_subprocess(["poetry", "env", "use", "python"]) - elif platform.system() == "Darwin": + if platform.system() == "Windows" or platform.system() == "Darwin": echo_and_run_subprocess(["poetry", "env", "use", "python"]) elif platform.system() == "Linux": try: @@ -87,12 +87,10 @@ def verify_pandoc_is_installed_and_version_less_than_3(): if major_version < 3: print(f"Pandoc is installed with version {version}, which is less than 3.") return True - else: - print(f"Pandoc version {version} is installed, but it is not less than 3.") - return False - else: - print("Pandoc version number could not be determined.") + print(f"Pandoc version {version} is installed, but it is not less than 3.") return False + print("Pandoc version number could not be determined.") + return False except subprocess.CalledProcessError: # This exception is raised if the command returned a non-zero exit status @@ -148,9 +146,9 @@ def verify_poetry_is_installed(): if result.returncode == 0: print("Poetry is installed: " + result.stdout) return True - except subprocess.CalledProcessError: + except subprocess.CalledProcessError as exc: # This exception is raised if the command returned a non-zero exit status - raise OSError("Poetry is not installed or not found in the system PATH.") + raise OSError("Poetry is not installed or not found in the system PATH.") from exc def verify_sphinx_is_installed(): @@ -170,9 +168,9 @@ def verify_sphinx_is_installed(): ) # If the command was successful, we'll get the version info print("sphinx is installed: " + result.stdout) - except subprocess.CalledProcessError: + except subprocess.CalledProcessError as exc: # This exception is raised if the command returned a non-zero exit status - raise OSError("sphinx is not installed or not found in the poetry environment.") + raise OSError("sphinx is not installed or not found in the poetry environment.") from exc @develop.command(name="get-install-directory", help="Gets the TIDY3D base directory.") @@ -205,7 +203,7 @@ def install_development_environment(args=None): # Verify and install pipx if required try: verify_pipx_is_installed() - except: # NOQA: E722 + except Exception as exc: if platform.system() == "Windows": echo_and_check_subprocess(["scoop", "install", "pipx"]) echo_and_check_subprocess(["pipx", "ensurepath"]) @@ -219,15 +217,13 @@ def install_development_environment(args=None): raise OSError( "Unsupported operating system installation flow. Verify the subprocess commands in " "tidy3d develop are compatible with your operating system." - ) + ) from exc # Verify and install poetry if required try: verify_poetry_is_installed() - except: # NOQA: E722 - if platform.system() == "Windows": - echo_and_check_subprocess(["pipx", "install", "poetry"]) - elif platform.system() == "Darwin": + except Exception as exc: + if platform.system() == "Windows" or platform.system() == "Darwin": echo_and_check_subprocess(["pipx", "install", "poetry"]) elif platform.system() == "Linux": echo_and_check_subprocess(["python3", "-m", "pipx", "install", "poetry"]) @@ -235,16 +231,16 @@ def install_development_environment(args=None): raise OSError( "Unsupported operating system installation flow. Verify the subprocess commands in " "tidy3d develop are compatible with your operating system." - ) + ) from exc # Verify pandoc is installed try: verify_pandoc_is_installed_and_version_less_than_3() - except: # NOQA: E722 + except Exception as exc: raise OSError( "Please install pandoc < 3 depending on your platform: https://pandoc.org/installing.html . Then run this " "command again. You can also follow our detailed instructions under the development guide." - ) + ) from exc # Makes sure that poetry uses the python environment active on the terminal. @@ -328,7 +324,7 @@ def uninstall_development_environment(args=None): "Unsupported operating system installation flow. Verify the subprocess commands in " "tidy3d develop are compatible with your operating system." ) - else: # NOQA: E722 + else: print("poetry is not found on the PATH. It is already uninstalled from PATH.") # Verify and install pipx if required @@ -357,8 +353,7 @@ def uninstall_development_environment(args=None): "Please uninstall pandoc < 3 depending on your platform: https://pandoc.org/installing.html . Then run this " "command again. You can also follow our detailed instructions under the development guide." ) - else: # NOQA: E722 - print("pandoc is not found on the PATH. It is already uninstalled from PATH.") + print("pandoc is not found on the PATH. It is already uninstalled from PATH.") return 0 diff --git a/tidy3d/web/cli/develop/packaging.py b/tidy3d/web/cli/develop/packaging.py index 48e18f51c7..0b92790dfb 100644 --- a/tidy3d/web/cli/develop/packaging.py +++ b/tidy3d/web/cli/develop/packaging.py @@ -6,6 +6,8 @@ functionality to extract the timing performance of that specific operation and compare it to previous usages. """ +from __future__ import annotations + import pathlib import subprocess from pathlib import Path @@ -47,7 +49,7 @@ def benchmark_timing_operations( operations in the `tests` section and benchmark them properly using this. This function does not require poetry and can be run anywhere where a tidy3d installation is already implemented. The output file has an extension. """ - timing_command_list = list() + timing_command_list = [] if output_file is None: output_file = timing_command.split("_")[1:] + ".log" @@ -60,7 +62,7 @@ def benchmark_timing_operations( "The output file path " + str(output_file_path) + " does not exist and cannot be created." - ) + ) from None if in_poetry_environment: timing_command_list += ["poetry", "run"] @@ -70,9 +72,9 @@ def benchmark_timing_operations( except KeyError: # This has to do with choosing a timing command not available in the dictionary raise KeyError( - f"Make sure the selected timing command {timing_command}" - + "corresponds to an existing command." - ) + f"Make sure the selected timing command {timing_command} " + "corresponds to an existing command." + ) from None echo_and_check_subprocess( command=timing_command_list, stdout=output_file_write, stderr=subprocess.STDOUT @@ -90,7 +92,7 @@ def benchmark_timing_operations( default=True, type=bool, is_flag=True, - help="Runs in poetry environment if " "True.", + help="Runs in poetry environment if True.", ) @click.option( "-o", diff --git a/tidy3d/web/cli/develop/tests.py b/tidy3d/web/cli/develop/tests.py index 4022643882..3a75a07389 100644 --- a/tidy3d/web/cli/develop/tests.py +++ b/tidy3d/web/cli/develop/tests.py @@ -3,6 +3,8 @@ notebooks in order to achieve reproducibility between hardwares. """ +from __future__ import annotations + import click from .index import develop @@ -10,8 +12,8 @@ from .utils import echo_and_run_subprocess __all__ = [ - "test_options", "test_in_environment_command", + "test_options", ] diff --git a/tidy3d/web/cli/develop/utils.py b/tidy3d/web/cli/develop/utils.py index c826c13a49..8faaf5d6e1 100644 --- a/tidy3d/web/cli/develop/utils.py +++ b/tidy3d/web/cli/develop/utils.py @@ -2,15 +2,17 @@ Utility functions for the tidy3d develop CLI. """ +from __future__ import annotations + import pathlib import subprocess import tidy3d __all__ = [ - "get_install_directory", - "echo_and_run_subprocess", "echo_and_check_subprocess", + "echo_and_run_subprocess", + "get_install_directory", ] diff --git a/tidy3d/web/cli/migrate.py b/tidy3d/web/cli/migrate.py index 48a00870da..17fbd3c7ed 100644 --- a/tidy3d/web/cli/migrate.py +++ b/tidy3d/web/cli/migrate.py @@ -1,5 +1,7 @@ """Migrate authentication to API key.""" +from __future__ import annotations + import json import os @@ -7,8 +9,9 @@ import requests import toml -from ..core.constants import HEADER_APPLICATION, HEADER_APPLICATION_VALUE, KEY_APIKEY -from ..core.environment import Env +from tidy3d.web.core.constants import HEADER_APPLICATION, HEADER_APPLICATION_VALUE, KEY_APIKEY +from tidy3d.web.core.environment import Env + from .constants import CONFIG_FILE, CREDENTIAL_FILE, TIDY3D_DIR @@ -41,35 +44,29 @@ def migrate() -> bool: if resp.status_code != 200: click.echo(f"Migrate to api key failed: {resp.text}") return False - else: - # click.echo(json.dumps(resp.json(), indent=4)) - access_token = resp.json()["data"]["auth"]["accessToken"] - headers["Authorization"] = f"Bearer {access_token}" - resp = requests.get(f"{Env.current.web_api_endpoint}/apikey", headers=headers) + # click.echo(json.dumps(resp.json(), indent=4)) + access_token = resp.json()["data"]["auth"]["accessToken"] + headers["Authorization"] = f"Bearer {access_token}" + resp = requests.get(f"{Env.current.web_api_endpoint}/apikey", headers=headers) + if resp.status_code != 200: + click.echo(f"Migrate to api key failed: {resp.text}") + return False + click.echo(json.dumps(resp.json(), indent=4)) + apikey = resp.json()["data"] + if not apikey: + resp = requests.post(f"{Env.current.web_api_endpoint}/apikey", headers=headers) if resp.status_code != 200: click.echo(f"Migrate to api key failed: {resp.text}") return False - else: - click.echo(json.dumps(resp.json(), indent=4)) - apikey = resp.json()["data"] - if not apikey: - resp = requests.post( - f"{Env.current.web_api_endpoint}/apikey", headers=headers - ) - if resp.status_code != 200: - click.echo(f"Migrate to api key failed: {resp.text}") - return False - else: - apikey = resp.json()["data"] - if not os.path.exists(TIDY3D_DIR): - os.mkdir(TIDY3D_DIR) - with open(CONFIG_FILE, "w+", encoding="utf-8") as config_file: - toml_config = toml.loads(config_file.read()) - toml_config.update({KEY_APIKEY: apikey}) - config_file.write(toml.dumps(toml_config)) + apikey = resp.json()["data"] + if not os.path.exists(TIDY3D_DIR): + os.mkdir(TIDY3D_DIR) + with open(CONFIG_FILE, "w+", encoding="utf-8") as config_file: + toml_config = toml.loads(config_file.read()) + toml_config.update({KEY_APIKEY: apikey}) + config_file.write(toml.dumps(toml_config)) - # rename auth.json to auth.json.bak - os.rename(CREDENTIAL_FILE, CREDENTIAL_FILE + ".bak") - return True - else: - click.echo("You can migrate to api key by running 'tidy3d migrate' command.") + # rename auth.json to auth.json.bak + os.rename(CREDENTIAL_FILE, CREDENTIAL_FILE + ".bak") + return True + click.echo("You can migrate to api key by running 'tidy3d migrate' command.") diff --git a/tidy3d/web/core/account.py b/tidy3d/web/core/account.py index c505b66487..07cef4caaa 100644 --- a/tidy3d/web/core/account.py +++ b/tidy3d/web/core/account.py @@ -63,5 +63,4 @@ def get(cls): if resp: account = Account(**resp) return account - else: - return None + return None diff --git a/tidy3d/web/core/cache.py b/tidy3d/web/core/cache.py index 87d55bd752..d83421ca21 100644 --- a/tidy3d/web/core/cache.py +++ b/tidy3d/web/core/cache.py @@ -1,4 +1,6 @@ """Local caches.""" +from __future__ import annotations + FOLDER_CACHE = {} S3_STS_TOKENS = {} diff --git a/tidy3d/web/core/constants.py b/tidy3d/web/core/constants.py index edadf62445..108d148800 100644 --- a/tidy3d/web/core/constants.py +++ b/tidy3d/web/core/constants.py @@ -1,6 +1,8 @@ """Defines constants for core.""" # HTTP Header key and value +from __future__ import annotations + HEADER_APIKEY = "simcloud-api-key" HEADER_VERSION = "tidy3d-python-version" HEADER_SOURCE = "source" diff --git a/tidy3d/web/core/core_config.py b/tidy3d/web/core/core_config.py index 4ab8ed41a8..77f0fb5c33 100644 --- a/tidy3d/web/core/core_config.py +++ b/tidy3d/web/core/core_config.py @@ -1,5 +1,7 @@ """Tidy3d core log, need init config from Tidy3d api""" +from __future__ import annotations + import logging as log # default setting diff --git a/tidy3d/web/core/environment.py b/tidy3d/web/core/environment.py index d7aa016f53..67fc7ba2d8 100644 --- a/tidy3d/web/core/environment.py +++ b/tidy3d/web/core/environment.py @@ -1,5 +1,7 @@ """Environment Setup.""" +from __future__ import annotations + import os import ssl @@ -12,7 +14,7 @@ class EnvironmentConfig(BaseSettings): """Basic Configuration for definition environment.""" def __hash__(self): - return hash((type(self),) + tuple(self.__dict__.values())) + return hash((type(self), *tuple(self.__dict__.values()))) name: str web_api_endpoint: str @@ -82,11 +84,11 @@ class Environment: ... """ - env_map = dict( - dev=dev, - uat=uat, - prod=prod, - ) + env_map = { + "dev": dev, + "uat": uat, + "prod": prod, + } def __init__(self): log = get_logger() diff --git a/tidy3d/web/core/exceptions.py b/tidy3d/web/core/exceptions.py index 398550eca1..4061a8c6f8 100644 --- a/tidy3d/web/core/exceptions.py +++ b/tidy3d/web/core/exceptions.py @@ -1,12 +1,16 @@ """Custom Tidy3D exceptions""" +from __future__ import annotations + +from typing import Optional + from .core_config import get_logger class WebError(Exception): """Any error in tidy3d""" - def __init__(self, message: str = None): + def __init__(self, message: Optional[str] = None): """Log just the error message and then raise the Exception.""" log = get_logger() super().__init__(message) @@ -15,5 +19,3 @@ def __init__(self, message: str = None): class WebNotFoundError(WebError): """A generic error indicating an HTTP 404 (resource not found).""" - - pass diff --git a/tidy3d/web/core/file_util.py b/tidy3d/web/core/file_util.py index 9305eaab1d..833e1e1f71 100644 --- a/tidy3d/web/core/file_util.py +++ b/tidy3d/web/core/file_util.py @@ -1,5 +1,7 @@ """File compression utilities""" +from __future__ import annotations + import gzip import os import shutil @@ -7,7 +9,7 @@ import h5py -from ..core.constants import JSON_TAG +from tidy3d.web.core.constants import JSON_TAG def compress_file_to_gzip(input_file, output_gz_file): diff --git a/tidy3d/web/core/http_util.py b/tidy3d/web/core/http_util.py index 1535959465..3fea4b7a89 100644 --- a/tidy3d/web/core/http_util.py +++ b/tidy3d/web/core/http_util.py @@ -1,10 +1,11 @@ """Http connection pool and authentication management.""" +from __future__ import annotations + import os from enum import Enum from functools import wraps from os.path import expanduser -from typing import Dict import requests import toml @@ -104,7 +105,7 @@ def api_key_auth(request: requests.request) -> requests.request: return request -def get_headers() -> Dict[str, str]: +def get_headers() -> dict[str, str]: """get headers for http request. Returns diff --git a/tidy3d/web/core/s3utils.py b/tidy3d/web/core/s3utils.py index b687cc11d0..8eba4a1fe5 100644 --- a/tidy3d/web/core/s3utils.py +++ b/tidy3d/web/core/s3utils.py @@ -1,12 +1,15 @@ """handles filesystem, storage""" +from __future__ import annotations + import os import pathlib import tempfile import urllib +from collections.abc import Mapping from datetime import datetime from enum import Enum -from typing import Callable, Mapping +from typing import Callable, Optional import boto3 from boto3.s3.transfer import TransferConfig @@ -179,7 +182,7 @@ def _get_progress(action: _S3Action): def get_s3_sts_token( - resource_id: str, file_name: str, extra_arguments: Mapping[str, str] = None + resource_id: str, file_name: str, extra_arguments: Optional[Mapping[str, str]] = None ) -> _S3STSToken: """Get s3 sts token for the given resource id and file name. @@ -213,8 +216,8 @@ def upload_file( path: str, remote_filename: str, verbose: bool = True, - progress_callback: Callable[[float], None] = None, - extra_arguments: Mapping[str, str] = None, + progress_callback: Optional[Callable[[float], None]] = None, + extra_arguments: Optional[Mapping[str, str]] = None, ): """Upload a file to S3. @@ -279,9 +282,9 @@ def _callback(bytes_in_chunk): def download_file( resource_id: str, remote_filename: str, - to_file: str = None, + to_file: Optional[str] = None, verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, ) -> pathlib.Path: """Download file from S3. @@ -358,9 +361,9 @@ def _callback(bytes_in_chunk): def download_gz_file( resource_id: str, remote_filename: str, - to_file: str = None, + to_file: Optional[str] = None, verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, ) -> pathlib.Path: """Download a ``.gz`` file and unzip it into ``to_file``, unless ``to_file`` itself ends in .gz diff --git a/tidy3d/web/core/stub.py b/tidy3d/web/core/stub.py index 9b4c5fd4a2..153fd69f22 100644 --- a/tidy3d/web/core/stub.py +++ b/tidy3d/web/core/stub.py @@ -21,7 +21,6 @@ def from_file(self, file_path) -> TaskStubData: An instance of the component class calling ``load``. """ - pass @abstractmethod def to_file(self, file_path): @@ -37,7 +36,6 @@ def to_file(self, file_path): :class:`Stub` An instance of the component class calling ``load``. """ - pass class TaskStub(ABC): @@ -55,7 +53,6 @@ def from_file(self, file_path) -> TaskStub: :class:`TaskStubData` An instance of the component class calling ``load``. """ - pass @abstractmethod def to_file(self, file_path): @@ -71,7 +68,6 @@ def to_file(self, file_path): :class:`Stub` An instance of the component class calling ``load``. """ - pass @abstractmethod def to_hdf5_gz(self, fname: str) -> None: @@ -82,4 +78,3 @@ def to_hdf5_gz(self, fname: str) -> None: fname : str Full path to the .hdf5.gz file to save the :class:`TaskStub` to. """ - pass diff --git a/tidy3d/web/core/task_core.py b/tidy3d/web/core/task_core.py index 41b75fedd0..41bd46dcce 100644 --- a/tidy3d/web/core/task_core.py +++ b/tidy3d/web/core/task_core.py @@ -6,7 +6,7 @@ import pathlib import tempfile from datetime import datetime -from typing import Callable, List, Optional, Tuple, Union +from typing import Callable, Optional, Union import pydantic.v1 as pd from botocore.exceptions import ClientError @@ -47,7 +47,7 @@ def list(cls) -> []: resp = http.get("tidy3d/projects") return ( parse_obj_as( - List[Folder], + list[Folder], resp, ) if resp @@ -109,7 +109,7 @@ def delete_old(self, days_old: int) -> int: params={"daysOld": days_old}, ) - def list_tasks(self) -> List[Tidy3DResource]: + def list_tasks(self) -> list[Tidy3DResource]: """List all tasks in this folder. Returns @@ -120,7 +120,7 @@ def list_tasks(self) -> List[Tidy3DResource]: resp = http.get(f"tidy3d/projects/{self.folder_id}/tasks") return ( parse_obj_as( - List[SimulationTask], + list[SimulationTask], resp, ) if resp @@ -204,9 +204,9 @@ def create( task_type: str, task_name: str, folder_name: str = "default", - callback_url: str = None, + callback_url: Optional[str] = None, simulation_type: str = "tidy3d", - parent_tasks: List[str] = None, + parent_tasks: Optional[list[str]] = None, file_type: str = "Gz", ) -> SimulationTask: """Create a new task on the server. @@ -281,7 +281,7 @@ def get(cls, task_id: str, verbose: bool = True) -> SimulationTask: return task @classmethod - def get_running_tasks(cls) -> List[SimulationTask]: + def get_running_tasks(cls) -> list[SimulationTask]: """Get a list of running tasks from the server" Returns @@ -293,7 +293,7 @@ def get_running_tasks(cls) -> List[SimulationTask]: resp = http.get("tidy3d/py/tasks") if not resp: return [] - return parse_obj_as(List[SimulationTask], resp) + return parse_obj_as(list[SimulationTask], resp) def delete(self, versions: bool = False): """Delete current task from server. @@ -357,7 +357,7 @@ def upload_simulation( self, stub: TaskStub, verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, remote_sim_file: str = SIM_FILE_HDF5_GZ, ) -> None: """Upload :class:`.Simulation` object to Server. @@ -397,7 +397,7 @@ def upload_file( local_file: str, remote_filename: str, verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, ) -> None: """ Upload file to platform. Using this method when the json file is too large to parse @@ -426,8 +426,8 @@ def upload_file( def submit( self, - solver_version: str = None, - worker_group: str = None, + solver_version: Optional[str] = None, + worker_group: Optional[str] = None, pay_type: Union[PayType, str] = PayType.AUTO, ): """Kick off this task. @@ -498,7 +498,7 @@ def get_sim_data_hdf5( self, to_file: str, verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, remote_data_file: str = SIMULATION_DATA_HDF5_GZ, ) -> pathlib.Path: """Get simulation data file from Server. @@ -555,7 +555,7 @@ def get_simulation_hdf5( self, to_file: str, verbose: bool = True, - progress_callback: Callable[[float], None] = None, + progress_callback: Optional[Callable[[float], None]] = None, remote_sim_file: str = SIM_FILE_HDF5_GZ, ) -> pathlib.Path: """Get simulation.hdf5 file from Server. @@ -585,7 +585,7 @@ def get_simulation_hdf5( progress_callback=progress_callback, ) - def get_running_info(self) -> Tuple[float, float]: + def get_running_info(self) -> tuple[float, float]: """Gets the % done and field_decay for a running task. Returns @@ -606,7 +606,10 @@ def get_running_info(self) -> Tuple[float, float]: return perc_done, field_decay def get_log( - self, to_file: str, verbose: bool = True, progress_callback: Callable[[float], None] = None + self, + to_file: str, + verbose: bool = True, + progress_callback: Optional[Callable[[float], None]] = None, ) -> pathlib.Path: """Get log file from Server. diff --git a/tidy3d/web/core/task_info.py b/tidy3d/web/core/task_info.py index f33093b392..3406c62b3a 100644 --- a/tidy3d/web/core/task_info.py +++ b/tidy3d/web/core/task_info.py @@ -1,5 +1,7 @@ """Defines information about a task""" +from __future__ import annotations + from abc import ABC from datetime import datetime from enum import Enum diff --git a/tidy3d/web/environment.py b/tidy3d/web/environment.py index 287abd3313..af4b089a96 100644 --- a/tidy3d/web/environment.py +++ b/tidy3d/web/environment.py @@ -1,5 +1,7 @@ """preserve from tidy3d.web.environment import Env backward compatibility""" +from __future__ import annotations + from .core.environment import Env __all__ = ["Env"]