From 804d3de01a2dd390a16def769c3cde198c4f5925 Mon Sep 17 00:00:00 2001 From: Yannick Augenstein Date: Wed, 22 Oct 2025 14:10:09 +0200 Subject: [PATCH] chore: update ruff -> 1.14.1 and update rules for python 3.10 --- .../workflows/tidy3d-python-client-tests.yml | 2 +- .pre-commit-config.yaml | 2 +- pyproject.toml | 52 ++--- tests/_test_local/_test_fit_web.py | 3 +- tests/_test_local/_test_plugins_web.py | 1 - tests/_test_local/_test_web.py | 3 +- .../test_autograd_box_polyslab_numerical.py | 4 +- .../test_autograd_conductivity_numerical.py | 8 +- .../test_autograd_mode_polyslab_numerical.py | 4 +- .../numerical/test_autograd_numerical.py | 8 +- .../test_autograd_periodic_numerical.py | 8 +- ...st_autograd_polyslab_sidewall_numerical.py | 2 +- .../test_autograd_symmetry_numerical.py | 8 +- .../test_components/autograd/test_autograd.py | 19 +- .../autograd/test_autograd_polyslab.py | 6 +- .../test_autograd_polyslab_sidewall.py | 6 +- .../autograd/test_autograd_rf_box.py | 16 +- .../autograd/test_autograd_rf_polyslab.py | 16 +- tests/test_components/test_apodization.py | 2 +- tests/test_components/test_frequencies.py | 6 +- tests/test_components/test_geometry.py | 2 +- tests/test_components/test_heat.py | 4 +- tests/test_components/test_heat_charge.py | 20 +- tests/test_components/test_lumped_element.py | 4 +- tests/test_components/test_microwave.py | 4 +- tests/test_components/test_monitor.py | 2 +- tests/test_components/test_scene.py | 9 +- tests/test_components/test_structure.py | 2 +- tests/test_components/test_viz.py | 8 +- tests/test_data/test_data_arrays.py | 12 +- tests/test_data/test_monitor_data.py | 8 +- .../autograd/invdes/test_filters.py | 3 +- .../expressions/test_variables.py | 14 +- .../smatrix/terminal_component_modeler_def.py | 16 +- tests/test_plugins/test_design.py | 3 +- tests/test_plugins/test_dispersion_fitter.py | 62 +++--- tests/test_plugins/test_polyslab.py | 2 +- tests/test_web/test_s3utils.py | 4 +- tests/test_web/test_webapi.py | 8 +- tests/test_web/test_webapi_extra.py | 6 +- tests/utils.py | 14 +- tidy3d/components/autograd/boxes.py | 3 +- .../components/autograd/derivative_utils.py | 26 +-- tidy3d/components/autograd/field_map.py | 3 +- tidy3d/components/autograd/types.py | 18 +- tidy3d/components/base.py | 31 +-- tidy3d/components/base_sim/data/sim_data.py | 3 +- tidy3d/components/base_sim/simulation.py | 93 +++++---- tidy3d/components/bc_placement.py | 15 +- tidy3d/components/beam.py | 6 +- tidy3d/components/boundary.py | 59 +++--- tidy3d/components/data/data_array.py | 38 ++-- tidy3d/components/data/dataset.py | 83 ++++---- tidy3d/components/data/monitor_data.py | 103 +++++----- tidy3d/components/data/sim_data.py | 20 +- tidy3d/components/data/unstructured/base.py | 48 ++--- .../data/unstructured/tetrahedral.py | 10 +- .../data/unstructured/triangular.py | 26 +-- tidy3d/components/data/utils.py | 22 +- tidy3d/components/data/validators.py | 4 +- tidy3d/components/dispersion_fitter.py | 16 +- tidy3d/components/eme/data/monitor_data.py | 18 +- tidy3d/components/eme/data/sim_data.py | 12 +- tidy3d/components/eme/grid.py | 17 +- tidy3d/components/eme/monitor.py | 26 +-- tidy3d/components/eme/simulation.py | 58 +++--- tidy3d/components/eme/sweep.py | 3 +- tidy3d/components/field_projection.py | 9 +- tidy3d/components/frequency_extrapolation.py | 4 +- tidy3d/components/geometry/base.py | 73 +++---- tidy3d/components/geometry/mesh.py | 17 +- tidy3d/components/geometry/polyslab.py | 17 +- tidy3d/components/geometry/primitives.py | 3 +- tidy3d/components/geometry/utils.py | 36 ++-- tidy3d/components/grid/corner_finder.py | 10 +- tidy3d/components/grid/grid.py | 16 +- tidy3d/components/grid/grid_spec.py | 38 ++-- tidy3d/components/grid/mesher.py | 15 +- tidy3d/components/lumped_element.py | 24 +-- tidy3d/components/material/multi_physics.py | 10 +- tidy3d/components/material/solver_types.py | 4 +- tidy3d/components/material/tcad/charge.py | 12 +- tidy3d/components/material/tcad/heat.py | 3 +- tidy3d/components/material/types.py | 22 +- tidy3d/components/medium.py | 169 ++++++++-------- .../components/microwave/data/monitor_data.py | 8 +- .../microwave/impedance_calculator.py | 22 +- tidy3d/components/microwave/mode_spec.py | 15 +- .../microwave/path_integrals/factory.py | 6 +- .../path_integrals/integrals/base.py | 6 +- .../path_integrals/integrals/current.py | 10 +- .../microwave/path_integrals/specs/current.py | 30 ++- .../path_integrals/specs/impedance.py | 8 +- .../microwave/path_integrals/specs/voltage.py | 20 +- .../microwave/path_integrals/types.py | 10 +- tidy3d/components/mode/data/sim_data.py | 8 +- tidy3d/components/mode/mode_solver.py | 44 ++-- tidy3d/components/mode/simulation.py | 34 ++-- tidy3d/components/mode/solver.py | 4 +- tidy3d/components/mode_spec.py | 16 +- tidy3d/components/monitor.py | 8 +- tidy3d/components/parameter_perturbation.py | 96 ++++----- tidy3d/components/scene.py | 182 ++++++++--------- tidy3d/components/simulation.py | 191 +++++++++--------- tidy3d/components/source/base.py | 7 +- tidy3d/components/source/current.py | 5 +- tidy3d/components/source/field.py | 15 +- tidy3d/components/source/time.py | 13 +- tidy3d/components/source/utils.py | 24 +-- tidy3d/components/spice/sources/ac.py | 4 +- tidy3d/components/spice/sources/dc.py | 6 +- tidy3d/components/spice/sources/types.py | 6 +- tidy3d/components/spice/types.py | 11 +- tidy3d/components/structure.py | 33 ++- tidy3d/components/subpixel_spec.py | 12 +- tidy3d/components/tcad/boundary/heat.py | 4 +- .../tcad/data/monitor_data/abstract.py | 7 +- .../tcad/data/monitor_data/charge.py | 8 +- .../components/tcad/data/monitor_data/heat.py | 16 +- .../components/tcad/data/monitor_data/mesh.py | 4 +- tidy3d/components/tcad/data/sim_data.py | 24 +-- tidy3d/components/tcad/data/types.py | 20 +- tidy3d/components/tcad/doping.py | 6 +- .../tcad/generation_recombination.py | 8 +- tidy3d/components/tcad/grid.py | 5 +- tidy3d/components/tcad/simulation/heat.py | 18 +- .../components/tcad/simulation/heat_charge.py | 72 ++++--- tidy3d/components/tcad/source/heat.py | 4 +- tidy3d/components/tcad/types.py | 47 +++-- tidy3d/components/time_modulation.py | 9 +- tidy3d/components/transformation.py | 7 +- tidy3d/components/types/base.py | 20 +- tidy3d/components/types/mode_spec.py | 4 +- tidy3d/components/types/monitor.py | 38 ++-- tidy3d/components/types/monitor_data.py | 25 ++- tidy3d/components/types/simulation.py | 40 ++-- tidy3d/components/types/workflow.py | 14 +- tidy3d/components/validators.py | 4 +- tidy3d/components/viz/axes_utils.py | 3 +- tidy3d/components/viz/visualization_spec.py | 6 +- tidy3d/config/legacy.py | 34 ++-- tidy3d/config/loader.py | 6 +- tidy3d/config/manager.py | 18 +- tidy3d/config/registry.py | 7 +- tidy3d/config/sections.py | 10 +- tidy3d/exceptions.py | 4 +- tidy3d/log.py | 10 +- tidy3d/material_library/material_library.py | 11 +- .../autograd/differential_operators.py | 2 +- tidy3d/plugins/autograd/functions.py | 48 ++--- tidy3d/plugins/autograd/invdes/filters.py | 22 +- .../autograd/invdes/parametrizations.py | 21 +- tidy3d/plugins/autograd/invdes/penalties.py | 16 +- .../autograd/primitives/interpolate.py | 22 +- tidy3d/plugins/autograd/utilities.py | 14 +- tidy3d/plugins/design/design.py | 21 +- tidy3d/plugins/design/method.py | 49 ++--- tidy3d/plugins/design/parameter.py | 6 +- tidy3d/plugins/design/result.py | 4 +- tidy3d/plugins/dispersion/fit.py | 7 +- tidy3d/plugins/dispersion/fit_fast.py | 4 +- tidy3d/plugins/dispersion/web.py | 4 +- tidy3d/plugins/expressions/__init__.py | 42 +++- tidy3d/plugins/expressions/base.py | 4 +- tidy3d/plugins/expressions/metrics.py | 4 +- tidy3d/plugins/expressions/types.py | 72 +------ tidy3d/plugins/expressions/variables.py | 4 +- tidy3d/plugins/invdes/design.py | 16 +- tidy3d/plugins/invdes/initialization.py | 11 +- tidy3d/plugins/invdes/optimizer.py | 30 ++- tidy3d/plugins/invdes/penalty.py | 3 +- tidy3d/plugins/invdes/region.py | 6 +- tidy3d/plugins/invdes/result.py | 6 +- tidy3d/plugins/invdes/transformation.py | 3 +- tidy3d/plugins/invdes/validators.py | 2 +- tidy3d/plugins/klayout/drc/drc.py | 3 +- tidy3d/plugins/klayout/drc/results.py | 9 +- tidy3d/plugins/klayout/util.py | 3 +- tidy3d/plugins/microwave/array_factor.py | 51 +++-- tidy3d/plugins/microwave/lobe_measurer.py | 3 +- tidy3d/plugins/resonance/resonance.py | 5 +- tidy3d/plugins/smatrix/analysis/antenna.py | 6 +- tidy3d/plugins/smatrix/analysis/terminal.py | 2 +- .../smatrix/component_modelers/base.py | 28 +-- .../smatrix/component_modelers/modal.py | 16 +- .../smatrix/component_modelers/terminal.py | 22 +- .../smatrix/component_modelers/types.py | 4 +- tidy3d/plugins/smatrix/data/terminal.py | 22 +- tidy3d/plugins/smatrix/data/types.py | 4 +- tidy3d/plugins/smatrix/ports/base_lumped.py | 11 +- tidy3d/plugins/smatrix/ports/base_terminal.py | 11 +- .../plugins/smatrix/ports/coaxial_lumped.py | 10 +- .../smatrix/ports/rectangular_lumped.py | 10 +- tidy3d/plugins/smatrix/ports/types.py | 10 +- tidy3d/plugins/smatrix/ports/wave.py | 24 +-- tidy3d/plugins/smatrix/utils.py | 4 +- .../waveguide/rectangular_dielectric.py | 68 +++---- tidy3d/updater.py | 4 +- tidy3d/web/api/asynchronous.py | 16 +- tidy3d/web/api/autograd/autograd.py | 50 ++--- tidy3d/web/api/autograd/utils.py | 6 +- tidy3d/web/api/connect_util.py | 3 +- tidy3d/web/api/container.py | 28 ++- tidy3d/web/api/material_fitter.py | 17 +- tidy3d/web/api/material_libray.py | 7 +- tidy3d/web/api/mode.py | 29 +-- tidy3d/web/api/run.py | 49 +++-- tidy3d/web/api/tidy3d_stub.py | 6 +- tidy3d/web/api/webapi.py | 65 +++--- tidy3d/web/cli/develop/documentation.py | 3 +- tidy3d/web/core/account.py | 13 +- tidy3d/web/core/exceptions.py | 4 +- tidy3d/web/core/s3utils.py | 17 +- tidy3d/web/core/task_core.py | 64 +++--- tidy3d/web/core/task_info.py | 27 ++- 215 files changed, 1963 insertions(+), 2207 deletions(-) diff --git a/.github/workflows/tidy3d-python-client-tests.yml b/.github/workflows/tidy3d-python-client-tests.yml index 879f8d6b8e..ce0a430965 100644 --- a/.github/workflows/tidy3d-python-client-tests.yml +++ b/.github/workflows/tidy3d-python-client-tests.yml @@ -166,7 +166,7 @@ jobs: persist-credentials: false - uses: astral-sh/ruff-action@57714a7c8a2e59f32539362ba31877a1957dded1 # v3.5.1 with: - version: 0.11.11 + version: 0.14.1 - 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 51d74be302..c4f6d58236 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ default_install_hook_types: - commit-msg repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.11.11" + rev: "v0.14.1" hooks: - id: ruff-check args: [ --fix ] diff --git a/pyproject.toml b/pyproject.toml index 18b9f01acc..1287ca4ca9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ typing-extensions = "*" ### Optional dependencies ### # development core -ruff = { version = "0.11.11", optional = true } +ruff = { version = "0.14.1", optional = true } coverage = { version = "*", optional = true } dill = { version = "*", optional = true } ipython = { version = "*", optional = true } @@ -236,38 +236,38 @@ requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.ruff] -target-version = "py39" +target-version = "py310" line-length = 100 extend-exclude = ["docs/faq/", "docs/notebooks/"] [tool.ruff.lint] extend-select = [ - "E", # pycodestyle errors - "F", # pyflakes - "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 + "E", # pycodestyle errors + "F", # pyflakes + "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 + "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 + "NPY002", # TODO: Revisit RNG handling + "PLC0415", # allow imports not at top level ] [tool.ruff.lint.isort] diff --git a/tests/_test_local/_test_fit_web.py b/tests/_test_local/_test_fit_web.py index b135514017..b7984f77a8 100644 --- a/tests/_test_local/_test_fit_web.py +++ b/tests/_test_local/_test_fit_web.py @@ -3,7 +3,6 @@ from math import isclose import numpy as np - from tidy3d.plugins.fitter import AdvancedFitterParam, StableDispersionFitter np.random.seed(4) @@ -37,7 +36,7 @@ def test_dispersion_lossless(): num_poles = 3 num_tries = 10 tolerance_rms = 1e-3 - best_medium, best_rms = fitter.fit( + best_medium, _best_rms = fitter.fit( num_tries=num_tries, num_poles=num_poles, tolerance_rms=tolerance_rms ) diff --git a/tests/_test_local/_test_plugins_web.py b/tests/_test_local/_test_plugins_web.py index a475889cff..42c0e4e66d 100644 --- a/tests/_test_local/_test_plugins_web.py +++ b/tests/_test_local/_test_plugins_web.py @@ -1,7 +1,6 @@ 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 5662a27853..80a3c4042e 100644 --- a/tests/_test_local/_test_web.py +++ b/tests/_test_local/_test_web.py @@ -5,9 +5,10 @@ import os from unittest import TestCase, mock -import tidy3d.web as web from tidy3d.web.auth import encode_password, get_credentials +import tidy3d.web as web + from ..utils import SIM_FULL as sim_original CALLBACK_URL = "https://callbackurl" diff --git a/tests/test_components/autograd/numerical/test_autograd_box_polyslab_numerical.py b/tests/test_components/autograd/numerical/test_autograd_box_polyslab_numerical.py index 7db87a3f63..c6fd27064c 100644 --- a/tests/test_components/autograd/numerical/test_autograd_box_polyslab_numerical.py +++ b/tests/test_components/autograd/numerical/test_autograd_box_polyslab_numerical.py @@ -368,8 +368,8 @@ def test_box_and_polyslab_gradients_match(is_3d, infinite_dim_2d, shift_box_cent local_gradient=False, ) - box_value, box_grad = value_and_grad(box_objective)([initial_params]) - polyslab_value, polyslab_grad = value_and_grad(polyslab_objective)([initial_params]) + _box_value, box_grad = value_and_grad(box_objective)([initial_params]) + _polyslab_value, polyslab_grad = value_and_grad(polyslab_objective)([initial_params]) box_grad_np = box_grad polyslab_grad_np = polyslab_grad diff --git a/tests/test_components/autograd/numerical/test_autograd_conductivity_numerical.py b/tests/test_components/autograd/numerical/test_autograd_conductivity_numerical.py index f49547422e..f05a050523 100644 --- a/tests/test_components/autograd/numerical/test_autograd_conductivity_numerical.py +++ b/tests/test_components/autograd/numerical/test_autograd_conductivity_numerical.py @@ -114,7 +114,7 @@ def make_base_sim( ] ) - src_size = sim_size_um[0:2] + (0,) + src_size = (*sim_size_um[0:2], 0) wl_min_src_um = 0.9 * adj_wvl_um wl_max_src_um = 1.1 * adj_wvl_um @@ -409,10 +409,10 @@ def test_finite_difference_conductivity_data( sim_geometry = get_sim_geometry(mesh_wvl_um) box_for_override = td.Box( - center=(0, 0, 0), size=sim_geometry.size[0:2] + (thickness_um + mesh_wvl_um,) + center=(0, 0, 0), size=(*sim_geometry.size[0:2], thickness_um + mesh_wvl_um) ) - eval_fns, eval_fn_names = make_eval_fns(monitor_size_wvl) + eval_fns, _eval_fn_names = make_eval_fns(monitor_size_wvl) sim_path_dir = tmp_path / f"test{test_number}" sim_path_dir.mkdir() @@ -443,7 +443,7 @@ def test_finite_difference_conductivity_data( ) conductivity_init = overall_conductivity_scaling * np.ones((dim, dim, Nz)) - obj, adj_grad = obj_val_and_grad([conductivity_init]) + _obj, adj_grad = obj_val_and_grad([conductivity_init]) # empirical step size for finite difference set as 1/10 the size of the conductivity seed value fd_step = 0.1 * overall_conductivity_scaling diff --git a/tests/test_components/autograd/numerical/test_autograd_mode_polyslab_numerical.py b/tests/test_components/autograd/numerical/test_autograd_mode_polyslab_numerical.py index bcb8127c67..bf6edb47a4 100644 --- a/tests/test_components/autograd/numerical/test_autograd_mode_polyslab_numerical.py +++ b/tests/test_components/autograd/numerical/test_autograd_mode_polyslab_numerical.py @@ -72,7 +72,7 @@ def make_base_sim( ] ) - src_size = sim_size_um[0:2] + (0,) + src_size = (*sim_size_um[0:2], 0) wl_min_src_um = 0.9 * adj_wvl_um wl_max_src_um = 1.1 * adj_wvl_um @@ -425,7 +425,7 @@ def eval_fn(sim_data): vertex_centers_x = 1.1 * mesh_wvl_um * np.cos(angles) vertex_centers_y = 0.8 * mesh_wvl_um * np.sin(angles) - obj, adj_grad = obj_val_and_grad([list(vertex_centers_x) + list(vertex_centers_y)]) + _obj, adj_grad = obj_val_and_grad([list(vertex_centers_x) + list(vertex_centers_y)]) for fd_idx in range(NUM_FINITE_DIFFERENCE): # Create random perturbation of vertices to check against the computed adjoint gradient. diff --git a/tests/test_components/autograd/numerical/test_autograd_numerical.py b/tests/test_components/autograd/numerical/test_autograd_numerical.py index 836bf9edde..77b039ffd0 100644 --- a/tests/test_components/autograd/numerical/test_autograd_numerical.py +++ b/tests/test_components/autograd/numerical/test_autograd_numerical.py @@ -72,7 +72,7 @@ def make_base_sim( ] ) - src_size = sim_size_um[0:2] + (0,) + src_size = (*sim_size_um[0:2], 0) wl_min_src_um = 0.9 * adj_wvl_um wl_max_src_um = 1.1 * adj_wvl_um @@ -269,10 +269,10 @@ def test_finite_difference_field_data(field_data_test_parameters, rng, tmp_path, sim_geometry = get_sim_geometry(mesh_wvl_um) box_for_override = td.Box( - center=(0, 0, 0), size=sim_geometry.size[0:2] + (thickness_um + mesh_wvl_um,) + center=(0, 0, 0), size=(*sim_geometry.size[0:2], thickness_um + mesh_wvl_um) ) - eval_fns, eval_fn_names = make_eval_fns(monitor_size_wvl) + eval_fns, _eval_fn_names = make_eval_fns(monitor_size_wvl) sim_path_dir = tmp_path / f"test{test_number}" sim_path_dir.mkdir() @@ -298,7 +298,7 @@ def test_finite_difference_field_data(field_data_test_parameters, rng, tmp_path, perm_init = FINITE_DIFF_PERM_SEED * np.ones((dim, dim, Nz)) - obj, adj_grad = obj_val_and_grad([perm_init]) + _obj, adj_grad = obj_val_and_grad([perm_init]) # empirical step size from running other finite difference tests for field # cases with permittivity diff --git a/tests/test_components/autograd/numerical/test_autograd_periodic_numerical.py b/tests/test_components/autograd/numerical/test_autograd_periodic_numerical.py index a71a34292a..be7b6819af 100644 --- a/tests/test_components/autograd/numerical/test_autograd_periodic_numerical.py +++ b/tests/test_components/autograd/numerical/test_autograd_periodic_numerical.py @@ -70,7 +70,7 @@ def make_base_sim( ] ) - src_size = sim_size_um[0:2] + (0,) + src_size = (*sim_size_um[0:2], 0) wl_min_src_um = 0.9 * adj_wvl_um wl_max_src_um = 1.1 * adj_wvl_um @@ -324,10 +324,10 @@ def test_finite_difference_diffraction_data( box_for_override = td.Box( center=(sim_geometry.center[0], sim_geometry.center[1], 0), - size=sim_geometry.size[0:2] + (thickness_um + mesh_wvl_um,), + size=(*sim_geometry.size[0:2], thickness_um + mesh_wvl_um), ) - eval_fns, eval_fn_names = make_eval_fns( + _eval_fns, _eval_fn_names = make_eval_fns( orders_x=order_x, orders_y=order_y, polarization=polarization ) @@ -357,7 +357,7 @@ def test_finite_difference_diffraction_data( perm_init = FINITE_DIFF_PERM_SEED * np.ones((dim, dim, Nz)) - obj, adj_grad = obj_val_and_grad([perm_init]) + _obj, adj_grad = obj_val_and_grad([perm_init]) # empirical step size from running other finite difference tests for field # cases with permittivity diff --git a/tests/test_components/autograd/numerical/test_autograd_polyslab_sidewall_numerical.py b/tests/test_components/autograd/numerical/test_autograd_polyslab_sidewall_numerical.py index 9c74da257c..9c1db6bf76 100644 --- a/tests/test_components/autograd/numerical/test_autograd_polyslab_sidewall_numerical.py +++ b/tests/test_components/autograd/numerical/test_autograd_polyslab_sidewall_numerical.py @@ -96,7 +96,7 @@ def test_autograd_polyslab_sidewall_vs_fd(axis, reference_plane, theta0_deg, tmp # objective and adjoint grad obj_fun = lambda tdeg: _objective(tdeg, axis, reference_plane, tmp_path, verbose) - obj, grad_adj = value_and_grad(obj_fun)(anp.array(theta0_deg)) + _obj, grad_adj = value_and_grad(obj_fun)(anp.array(theta0_deg)) # centered finite difference uid = f"axis{axis}_ref{reference_plane}_t{float(theta0_deg):+0.3f}" diff --git a/tests/test_components/autograd/numerical/test_autograd_symmetry_numerical.py b/tests/test_components/autograd/numerical/test_autograd_symmetry_numerical.py index 4153de9c1f..45de4facf0 100644 --- a/tests/test_components/autograd/numerical/test_autograd_symmetry_numerical.py +++ b/tests/test_components/autograd/numerical/test_autograd_symmetry_numerical.py @@ -72,7 +72,7 @@ def make_base_sim( ] ) - src_size = sim_size_um[0:2] + (0,) + src_size = (*sim_size_um[0:2], 0) wl_min_src_um = 0.9 * adj_wvl_um wl_max_src_um = 1.1 * adj_wvl_um @@ -168,7 +168,7 @@ def make_eval_fns(monitor_size_wvl): def intensity(sim_data): field_data = sim_data["monitor_fields"] - shape_x, shape_y, shape_z, *_ = field_data.Ex.values.shape + _shape_x, _shape_y, _shape_z, *_ = field_data.Ex.values.shape return np.sum(np.abs(field_data.Ex.values) ** 2 + np.abs(field_data.Ey.values) ** 2) @@ -273,10 +273,10 @@ def test_adjoint_difference_symmetry( sim_geometry = get_sim_geometry(mesh_wvl_um) box_for_override = td.Box( - center=(0, 0, 0), size=sim_geometry.size[0:2] + (thickness_um + mesh_wvl_um,) + center=(0, 0, 0), size=(*sim_geometry.size[0:2], thickness_um + mesh_wvl_um) ) - eval_fns, eval_fn_names = make_eval_fns(monitor_size_wvl) + eval_fns, _eval_fn_names = make_eval_fns(monitor_size_wvl) sim_path_dir = tmp_path / f"test{test_number}" sim_path_dir.mkdir() diff --git a/tests/test_components/autograd/test_autograd.py b/tests/test_components/autograd/test_autograd.py index 898ff330a8..f78494ef22 100644 --- a/tests/test_components/autograd/test_autograd.py +++ b/tests/test_components/autograd/test_autograd.py @@ -617,7 +617,7 @@ def plot_sim(sim: td.Simulation, plot_eps: bool = True) -> None: plot_fn = sim.plot_eps if plot_eps else sim.plot - f, (ax1, ax2, ax3) = plt.subplots(1, 3, tight_layout=True) + _f, (ax1, ax2, ax3) = plt.subplots(1, 3, tight_layout=True) plot_fn(x=0, ax=ax1) plot_fn(y=0, ax=ax2) plot_fn(z=0, ax=ax3) @@ -949,7 +949,7 @@ def obj(center: tuple, size: tuple) -> float: return objval d_power = ag.value_and_grad(obj, argnum=(0, 1)) - val, (dp_dcenter, dp_dsize) = d_power(self.center0, self.size0) + _val, (dp_dcenter, dp_dsize) = d_power(self.center0, self.size0) assert len(dp_dcenter) == 3 assert len(dp_dsize) == 3 @@ -980,7 +980,7 @@ def objective(*args): values.append(postprocess(sim_data)) return min(values) - val, grad = ag.value_and_grad(objective)(params0) + _val, grad = ag.value_and_grad(objective)(params0) assert anp.all(grad != 0.0), "some gradients are 0" @@ -1034,7 +1034,7 @@ def objective(*args): # if speed test, get the profile with cProfile.Profile() as pr: t = time.time() - val, grad = ag.value_and_grad(objective)(params0) + _val, _grad = ag.value_and_grad(objective)(params0) t2 = time.time() - t pr.print_stats(sort="cumtime") pr.dump_stats("results.prof") @@ -1050,7 +1050,7 @@ def test_autograd_polyslab_cylinder(use_emulated_run, monitor_key): num_pts = 819 - monitor, postprocess = make_monitors()[monitor_key] + monitor, _postprocess = make_monitors()[monitor_key] def make_cylinder(radius, x0, y0, t): return td.Cylinder( @@ -1122,7 +1122,7 @@ def objective(*args): value = postprocess(data) return value - val, grad = ag.value_and_grad(objective)(params0) + _val, grad = ag.value_and_grad(objective)(params0) assert np.all(np.abs(grad) > 0), "some gradients are 0" @@ -1144,7 +1144,7 @@ def objective(*args): value = value + postprocess(sim_data) return value - val, grad = ag.value_and_grad(objective)(params0) + _val, grad = ag.value_and_grad(objective)(params0) assert np.all(np.abs(grad) > 0), "some gradients are 0" @@ -2838,7 +2838,8 @@ def rotated_grad(angle: float, axis: int) -> np.ndarray: if expect_exception: with pytest.raises( - AttributeError, match=".*'Transformed' object has no attribute 'vertices'.*" + AttributeError, + match=r".*'Transformed' object has no attribute 'vertices'.*", ): rotated_grad(theta, axis) else: @@ -2984,7 +2985,7 @@ def objective(params): ) return postprocess(data, data[monitor.name]) - val, grad = ag.value_and_grad(objective)(params0) + _val, grad = ag.value_and_grad(objective)(params0) assert anp.all(grad != 0.0), "some gradients are 0 for conductivity-only test" diff --git a/tests/test_components/autograd/test_autograd_polyslab.py b/tests/test_components/autograd/test_autograd_polyslab.py index 3aa372aabc..79da8cacae 100644 --- a/tests/test_components/autograd/test_autograd_polyslab.py +++ b/tests/test_components/autograd/test_autograd_polyslab.py @@ -13,8 +13,6 @@ from __future__ import annotations -from typing import Optional - import numpy as np import numpy.testing as npt import pytest @@ -95,8 +93,8 @@ def __init__( paths, axis: int, coeffs: dict[str, float], - bounds_intersect: Optional[tuple[tuple[float, ...], tuple[float, ...]]] = None, - simulation_bounds: Optional[tuple[tuple[float, ...], tuple[float, ...]]] = None, + bounds_intersect: tuple[tuple[float, ...], tuple[float, ...]] | None = None, + simulation_bounds: tuple[tuple[float, ...], tuple[float, ...]] | None = None, ) -> None: self.paths = paths self.axis = axis diff --git a/tests/test_components/autograd/test_autograd_polyslab_sidewall.py b/tests/test_components/autograd/test_autograd_polyslab_sidewall.py index a0490afca7..512cbc73fc 100644 --- a/tests/test_components/autograd/test_autograd_polyslab_sidewall.py +++ b/tests/test_components/autograd/test_autograd_polyslab_sidewall.py @@ -52,7 +52,7 @@ def sim_bounds(): def test_constant_g_zero(geom, sim_bounds): - ps, H, theta, P = geom + ps, H, _theta, P = geom sim_min, sim_max = sim_bounds g = lambda xyz: np.ones(xyz.shape[0], dtype=float) di = FakeDerivativeInfo(g, dx=H / 50) @@ -76,7 +76,7 @@ def test_linear_z_matches_closed_form(geom, sim_bounds): def test_2d_returns_zero(geom, sim_bounds): - ps, H, theta, P = geom + ps, H, _theta, _P = geom sim_min, sim_max = sim_bounds z0 = ps.center_axis g = lambda xyz: (xyz[:, 2] - z0) @@ -88,7 +88,7 @@ def test_2d_returns_zero(geom, sim_bounds): def test_vertex_order_invariance(geom, sim_bounds): - ps, H, theta, P = geom + ps, H, _theta, _P = geom sim_min, sim_max = sim_bounds z0 = ps.center_axis g = lambda xyz: (xyz[:, 2] - z0) diff --git a/tests/test_components/autograd/test_autograd_rf_box.py b/tests/test_components/autograd/test_autograd_rf_box.py index 9f88a136ff..e2eb2b667d 100644 --- a/tests/test_components/autograd/test_autograd_rf_box.py +++ b/tests/test_components/autograd/test_autograd_rf_box.py @@ -77,7 +77,7 @@ def make_base_sim( ] ) - src_size = sim_size_um[0:2] + (0,) + src_size = (*sim_size_um[0:2], 0) wl_min_src_um = 0.9 * adj_wvl_um wl_max_src_um = 1.1 * adj_wvl_um @@ -268,7 +268,7 @@ def make_eval_fns(monitor_size_wvl): def intensity(sim_data): field_data = sim_data["monitor_fields"] - shape_x, shape_y, shape_z, *_ = field_data.Ex.values.shape + _shape_x, _shape_y, _shape_z, *_ = field_data.Ex.values.shape proj_pol_rotation = field_data.Ex.values - field_data.Ey.values return np.sum(np.abs(proj_pol_rotation) ** 2) @@ -478,10 +478,10 @@ def test_finite_difference_2d_box_pec(rf_2d_test_parameters, rng, tmp_path, crea box_for_override = td.Box( center=(0, 0, 0), - size=sim_geometry.size[0:2] + (thickness_box_placement_um + 0.3 * mesh_wvl_um,), + size=(*sim_geometry.size[0:2], thickness_box_placement_um + 0.3 * mesh_wvl_um), ) - eval_fns, eval_fn_names = make_eval_fns(monitor_size_wvl) + _eval_fns, _eval_fn_names = make_eval_fns(monitor_size_wvl) sim_path_dir = tmp_path / f"test{test_number}" sim_path_dir.mkdir() @@ -521,7 +521,7 @@ def test_finite_difference_2d_box_pec(rf_2d_test_parameters, rng, tmp_path, crea obj_val_and_grad = ag.value_and_grad(objective) - obj, adj_grad = obj_val_and_grad([all_box_parameters]) + _obj, adj_grad = obj_val_and_grad([all_box_parameters]) fd_grad, valid_mask = run_and_process_fd( all_box_parameters=all_box_parameters, fd_step=fd_step, objective=objective @@ -658,10 +658,10 @@ def test_finite_difference_3d_box_pec(rf_3d_test_parameters, rng, tmp_path, crea box_for_override = td.Box( center=(0, 0, 0), - size=sim_geometry.size[0:2] + (thickness_box_placement_um + 0.3 * mesh_wvl_um,), + size=(*sim_geometry.size[0:2], thickness_box_placement_um + 0.3 * mesh_wvl_um), ) - eval_fns, eval_fn_names = make_eval_fns(monitor_size_wvl) + _eval_fns, _eval_fn_names = make_eval_fns(monitor_size_wvl) sim_path_dir = tmp_path / f"test{test_number}" sim_path_dir.mkdir() @@ -701,7 +701,7 @@ def test_finite_difference_3d_box_pec(rf_3d_test_parameters, rng, tmp_path, crea obj_val_and_grad = ag.value_and_grad(objective) - obj, adj_grad = obj_val_and_grad([all_box_parameters]) + _obj, adj_grad = obj_val_and_grad([all_box_parameters]) fd_grad, valid_mask = run_and_process_fd( all_box_parameters=all_box_parameters, fd_step=fd_step, objective=objective diff --git a/tests/test_components/autograd/test_autograd_rf_polyslab.py b/tests/test_components/autograd/test_autograd_rf_polyslab.py index 30ede80272..09cc7747be 100644 --- a/tests/test_components/autograd/test_autograd_rf_polyslab.py +++ b/tests/test_components/autograd/test_autograd_rf_polyslab.py @@ -72,7 +72,7 @@ def make_base_sim( ] ) - src_size = sim_size_um[0:2] + (0,) + src_size = (*sim_size_um[0:2], 0) wl_min_src_um = 0.9 * adj_wvl_um wl_max_src_um = 1.1 * adj_wvl_um @@ -220,7 +220,7 @@ def objective(polyslab_param_arrays): def make_eval_fns(monitor_size_wvl): def intensity(sim_data): field_data = sim_data["monitor_fields"] - shape_x, shape_y, shape_z, *_ = field_data.Ex.values.shape + _shape_x, _shape_y, _shape_z, *_ = field_data.Ex.values.shape proj_pol_rotation = field_data.Ex.values - field_data.Ey.values return np.sum(np.abs(proj_pol_rotation) ** 2) @@ -428,10 +428,10 @@ def test_finite_difference_2d_polyslab_pec(rf_2d_test_parameters, rng, tmp_path, box_for_override = td.Box( center=(0, 0, 0), - size=sim_geometry.size[0:2] + (thickness_box_placement_um + 0.3 * mesh_wvl_um,), + size=(*sim_geometry.size[0:2], thickness_box_placement_um + 0.3 * mesh_wvl_um), ) - eval_fns, eval_fn_names = make_eval_fns(monitor_size_wvl) + _eval_fns, _eval_fn_names = make_eval_fns(monitor_size_wvl) sim_path_dir = tmp_path / f"test{test_number}" sim_path_dir.mkdir() @@ -468,7 +468,7 @@ def test_finite_difference_2d_polyslab_pec(rf_2d_test_parameters, rng, tmp_path, obj_val_and_grad = ag.value_and_grad(objective) - obj, adj_grad = obj_val_and_grad([polyslab]) + _obj, adj_grad = obj_val_and_grad([polyslab]) fd_grad, valid_mask = run_and_process_fd( polyslab_parameters=polyslab, fd_step=fd_step, objective=objective @@ -585,10 +585,10 @@ def test_finite_difference_3d_polyslab_pec(rf_3d_test_parameters, rng, tmp_path, box_for_override = td.Box( center=(0, 0, 0), - size=sim_geometry.size[0:2] + (thickness_box_placement_um,), + size=(*sim_geometry.size[0:2], thickness_box_placement_um), ) - eval_fns, eval_fn_names = make_eval_fns(monitor_size_wvl) + _eval_fns, _eval_fn_names = make_eval_fns(monitor_size_wvl) sim_path_dir = tmp_path / f"test{test_number}" sim_path_dir.mkdir() @@ -624,7 +624,7 @@ def test_finite_difference_3d_polyslab_pec(rf_3d_test_parameters, rng, tmp_path, obj_val_and_grad = ag.value_and_grad(objective) - obj, adj_grad = obj_val_and_grad([polyslab]) + _obj, adj_grad = obj_val_and_grad([polyslab]) fd_grad, valid_mask = run_and_process_fd( polyslab_parameters=polyslab, fd_step=fd_step, objective=objective diff --git a/tests/test_components/test_apodization.py b/tests/test_components/test_apodization.py index 4fadd253d3..ba4e46efea 100644 --- a/tests/test_components/test_apodization.py +++ b/tests/test_components/test_apodization.py @@ -49,6 +49,6 @@ def test_plot(): a.plot(times) plt.close() - fig, ax = plt.subplots(1, 1) + _fig, ax = plt.subplots(1, 1) a.plot(times, ax=ax) plt.close() diff --git a/tests/test_components/test_frequencies.py b/tests/test_components/test_frequencies.py index ae8288e3e5..899cb882e1 100644 --- a/tests/test_components/test_frequencies.py +++ b/tests/test_components/test_frequencies.py @@ -305,12 +305,14 @@ def test_sweep_decade_edge_cases(): # Negative points per decade with pytest.raises( - ValueError, match="'num_points_per_decade' must be strictly positive, got -1." + ValueError, + match=r"'num_points_per_decade' must be strictly positive, got -1.", ): freq_range.sweep_decade(-1) # Zero points per decade with pytest.raises( - ValueError, match="'num_points_per_decade' must be strictly positive, got 0." + ValueError, + match=r"'num_points_per_decade' must be strictly positive, got 0.", ): freq_range.sweep_decade(0) diff --git a/tests/test_components/test_geometry.py b/tests/test_components/test_geometry.py index 8bf9347753..860c89863a 100644 --- a/tests/test_components/test_geometry.py +++ b/tests/test_components/test_geometry.py @@ -1303,7 +1303,7 @@ def negative_height_func(x, y): with pytest.raises( ValueError, - match="All height values must be non-negative.", + match=r"All height values must be non-negative.", ): td.TriangleMesh.from_height_function( axis=axis, diff --git a/tests/test_components/test_heat.py b/tests/test_components/test_heat.py index 4b4ab27338..ccf8146d85 100644 --- a/tests/test_components/test_heat.py +++ b/tests/test_components/test_heat.py @@ -114,7 +114,7 @@ def make_heat_bcs(): def test_heat_bcs(): - bc_temp, bc_flux, bc_conv = make_heat_bcs() + _bc_temp, _bc_flux, _bc_conv = make_heat_bcs() with pytest.raises(pd.ValidationError): _ = TemperatureBC(temperature=-10) @@ -314,7 +314,7 @@ def test_heat_source(): def make_heat_sim(include_custom_source: bool = True): - fluid_medium, solid_medium = make_heat_mediums() + fluid_medium, _solid_medium = make_heat_mediums() fluid_structure, solid_structure = make_heat_structures() bc_temp, bc_flux, bc_conv = make_heat_bcs() sources = [make_heat_source()] diff --git a/tests/test_components/test_heat_charge.py b/tests/test_components/test_heat_charge.py index f6764390f2..6394d353c2 100644 --- a/tests/test_components/test_heat_charge.py +++ b/tests/test_components/test_heat_charge.py @@ -872,7 +872,7 @@ def test_heat_charge_structures_creation(structures): def test_heat_charge_bcs_validation(boundary_conditions): """Tests the validators for boundary conditions.""" - bc_temp, bc_flux, bc_conv, bc_volt, bc_current = boundary_conditions + _bc_temp, _bc_flux, _bc_conv, _bc_volt, _bc_current = boundary_conditions # Invalid TemperatureBC with pytest.raises(pd.ValidationError): @@ -968,7 +968,7 @@ def test_freqs_validation(): # Test that freqs without SSACVoltageSource raises error with pytest.raises( pd.ValidationError, - match="If 'freqs' is provided and not empty, at least one 'SSACVoltageSource' must be present in the boundary conditions.", + match=r"If 'freqs' is provided and not empty, at least one 'SSACVoltageSource' must be present in the boundary conditions.", ): sim.updated_copy( boundary_spec=[ @@ -984,10 +984,10 @@ def test_freqs_validation(): assert np.isclose(freqs, freqs_input).all() assert np.isclose(1e-3, amplitude) - with pytest.raises(pd.ValidationError, match="'freqs' cannot contain infinite frequencies."): + with pytest.raises(pd.ValidationError, match=r"'freqs' cannot contain infinite frequencies."): sim.updated_copy(analysis_spec=sim.analysis_spec.updated_copy(freqs=[1e2, np.inf])) - with pytest.raises(pd.ValidationError, match="'freqs' cannot contain negative frequencies."): + with pytest.raises(pd.ValidationError, match=r"'freqs' cannot contain negative frequencies."): sim.updated_copy(analysis_spec=sim.analysis_spec.updated_copy(freqs=[1e2, -1e2])) @@ -1315,7 +1315,7 @@ def test_heat_charge_simulation(simulation_data): def test_sim_data_plotting(simulation_data): """Tests whether simulation data can be plotted and appropriate errors are raised.""" - heat_sim_data, cond_sim_data, cap_sim_data, fc_sim_data, mesh_data = simulation_data + heat_sim_data, cond_sim_data, _cap_sim_data, _fc_sim_data, _mesh_data = simulation_data # Plotting temperature data heat_sim_data.plot_field("test", z=0) @@ -1356,7 +1356,7 @@ def test_sim_data_plotting(simulation_data): def test_mesh_plotting(simulation_data): """Tests whether mesh can be plotted and appropriate errors are raised.""" - heat_sim_data, cond_sim_data, cap_sim_data, fc_sim_data, mesh_data = simulation_data + heat_sim_data, cond_sim_data, _cap_sim_data, _fc_sim_data, mesh_data = simulation_data # Plotting mesh from unstructured temperature data heat_sim_data.plot_mesh("tri") @@ -1579,7 +1579,8 @@ def test_charge_simulation( condition_ssac_p = td.VoltageBC(source=td.SSACVoltageSource(voltage=[0, 1], amplitude=1e-3)) # Two AC sources cannot be defined with pytest.raises( - pd.ValidationError, match="Only a single 'SSACVoltageSource' source can be supplied." + pd.ValidationError, + match=r"Only a single 'SSACVoltageSource' source can be supplied.", ): analysis = td.IsothermalSSACAnalysis(freqs=[1e2, 1e3], temperature=300) sim.updated_copy( @@ -1592,7 +1593,8 @@ def test_charge_simulation( # Test SSACAnalysis as well with pytest.raises( - pd.ValidationError, match="Only a single 'SSACVoltageSource' source can be supplied." + pd.ValidationError, + match=r"Only a single 'SSACVoltageSource' source can be supplied.", ): analysis_ssac = td.SSACAnalysis(freqs=[1e2, 1e3], tolerance_settings=charge_tolerance) sim.updated_copy( @@ -2026,7 +2028,7 @@ def test_dynamic_simulation_updates(heat_simulation): def test_plotting_functions(simulation_data): """Test plotting functions with various data.""" - heat_sim_data, cond_sim_data, cap_sim_data, fc_sim_data, mesh_data = simulation_data + heat_sim_data, cond_sim_data, _cap_sim_data, _fc_sim_data, _mesh_data = simulation_data # Valid plotting try: diff --git a/tests/test_components/test_lumped_element.py b/tests/test_components/test_lumped_element.py index 957a4e8aeb..390e45a995 100644 --- a/tests/test_components/test_lumped_element.py +++ b/tests/test_components/test_lumped_element.py @@ -366,7 +366,7 @@ def test_distribution_variants(dist_type, width): else: assert structure is None network = linear_element.to_structure(grid) - L, C = linear_element.estimate_parasitic_elements(grid) + _L, C = linear_element.estimate_parasitic_elements(grid) assert C >= 0 # Grid is fine enough that there are two connections made along x @@ -379,5 +379,5 @@ def test_distribution_variants(dist_type, width): else: assert structure is None network = linear_element.to_structure(grid) - L, C = linear_element.estimate_parasitic_elements(grid) + _L, C = linear_element.estimate_parasitic_elements(grid) assert C >= 0 diff --git a/tests/test_components/test_microwave.py b/tests/test_components/test_microwave.py index b8a882f1b3..19ff6e10e4 100644 --- a/tests/test_components/test_microwave.py +++ b/tests/test_components/test_microwave.py @@ -676,7 +676,7 @@ def test_mode_plane_analyzer_canonical_shapes(colocate, tline_type): size=modal_plane.size, field_data_colocated=mode_monitor.colocate, ) - bounding_boxes, geos = mode_plane_analyzer.get_conductor_bounding_boxes( + bounding_boxes, _geos = mode_plane_analyzer.get_conductor_bounding_boxes( sim.structures, sim.grid, sim.symmetry, @@ -725,7 +725,7 @@ def test_mode_plane_analyzer_advanced(use_2D, symmetry): size=modal_plane.size, field_data_colocated=mode_monitor.colocate, ) - bounding_boxes, geos = mode_plane_analyzer.get_conductor_bounding_boxes( + bounding_boxes, _geos = mode_plane_analyzer.get_conductor_bounding_boxes( sim.structures, sim.grid, sim.symmetry, diff --git a/tests/test_components/test_monitor.py b/tests/test_components/test_monitor.py index b8b9faa60b..050d1ca4e1 100644 --- a/tests/test_components/test_monitor.py +++ b/tests/test_components/test_monitor.py @@ -55,7 +55,7 @@ def test_time_inds(): def test_downsampled(): M = td.FieldMonitor(size=(1, 1, 1), name="f", freqs=[1e12], interval_space=(1, 2, 3)) num_cells = (10, 10, 10) - downsampled_num_cells = a, b, c = M.downsampled_num_cells(num_cells=(10, 10, 10)) + downsampled_num_cells = _a, _b, _c = M.downsampled_num_cells(num_cells=(10, 10, 10)) assert downsampled_num_cells != num_cells diff --git a/tests/test_components/test_scene.py b/tests/test_components/test_scene.py index ff5178d182..e7772b97fc 100644 --- a/tests/test_components/test_scene.py +++ b/tests/test_components/test_scene.py @@ -362,7 +362,7 @@ def test_max_geometry_validation(): medium=td.Medium(permittivity=2.0), ), ] - with pytest.raises(pd.ValidationError, match=f" {MAX_GEOMETRY_COUNT + 2} "): + with pytest.raises(pd.ValidationError, match=rf" {MAX_GEOMETRY_COUNT + 2} "): _ = td.Scene(structures=not_fine) @@ -521,20 +521,21 @@ def test_log_scale_with_custom_limits(): _ = scene.plot_eps(x=0, scale="log", eps_lim=(1e-5, 100)) plt.close() - with pytest.raises(SetupError, match="Log scale cannot be used with non-positive values."): + with pytest.raises(SetupError, match=r"Log scale cannot be used with non-positive values."): _ = scene.plot_eps(x=0, scale="log", eps_lim=(-1e-2, 100)) plt.close() _ = scene.plot_structures_property(x=0, property="eps", scale="log", limits=(1e-2, 100)) plt.close() - with pytest.raises(SetupError, match="Log scale cannot be used with non-positive values."): + with pytest.raises(SetupError, match=r"Log scale cannot be used with non-positive values."): _ = scene.plot_structures_property(x=0, property="eps", scale="log", limits=(-2e-2, 100)) plt.close() # Test that invalid scale raises error with pytest.raises( - SetupError, match="The scale 'invalid' is not supported for plotting structures property." + SetupError, + match=r"The scale 'invalid' is not supported for plotting structures property.", ): _ = scene.plot_structures_property(x=0, property="eps", scale="invalid") plt.close() diff --git a/tests/test_components/test_structure.py b/tests/test_components/test_structure.py index 04553e69d8..65da77ee72 100644 --- a/tests/test_components/test_structure.py +++ b/tests/test_components/test_structure.py @@ -63,7 +63,7 @@ def test_lower_dimension_custom_medium_to_gds(tmp_path): y = np.array([0.0]) z = np.linspace(-1, 1, nz) f = np.array([td.C_0]) - mx, my, mz, _ = np.meshgrid(x, y, z, f, indexing="ij", sparse=True) + 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={"x": x, "y": y, "z": z, "f": f}) eps_components = {f"eps_{d}{d}": eps_diagonal_data for d in "xyz"} diff --git a/tests/test_components/test_viz.py b/tests/test_components/test_viz.py index 4a93435b53..27eaed797c 100644 --- a/tests/test_components/test_viz.py +++ b/tests/test_components/test_viz.py @@ -289,14 +289,14 @@ def test_sim_plot_fill_structures(): run_time=1e-12, ) - fig1, ax1 = plt.subplots() + _fig1, ax1 = plt.subplots() sim.plot(x=0, fill_structures=False, ax=ax1) structure_patches = [p for p in ax1.patches if isinstance(p, mpl.patches.PathPatch)] for patch in structure_patches[:1]: # only one structure, rest is PML etc assert not patch.get_fill(), "Should be unfilled when False" assert patch.get_edgecolor() != "none" - fig2, ax2 = plt.subplots() + _fig2, ax2 = plt.subplots() sim.plot(x=0, fill_structures=True, ax=ax2) structure_patches = [p for p in ax2.patches if isinstance(p, mpl.patches.PathPatch)] for patch in structure_patches[:1]: @@ -314,7 +314,7 @@ def test_sim_plot_structures_fill(): run_time=1e-12, ) - fig1, ax1 = plt.subplots() + _fig1, ax1 = plt.subplots() sim.plot_structures(x=0, fill=False, ax=ax1) structure_patches = [p for p in ax1.patches if isinstance(p, mpl.patches.PathPatch)] assert len(structure_patches) > 0, "No structures plotted" @@ -324,7 +324,7 @@ def test_sim_plot_structures_fill(): assert patch.get_edgecolor() != "none", "Edges should be visible" assert patch.get_linewidth() > 0, "Edge width should be positive" - fig2, ax2 = plt.subplots() + _fig2, ax2 = plt.subplots() sim.plot_structures(x=0, fill=True, ax=ax2) structure_patches = [p for p in ax2.patches if isinstance(p, mpl.patches.PathPatch)] assert len(structure_patches) > 0, "No structures plotted" diff --git a/tests/test_data/test_data_arrays.py b/tests/test_data/test_data_arrays.py index 5e3108187b..6c531aa16d 100644 --- a/tests/test_data/test_data_arrays.py +++ b/tests/test_data/test_data_arrays.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional - import autograd as ag import autograd.numpy as np import numpy @@ -145,7 +143,7 @@ def get_xyz( return x, y, z -def make_scalar_field_data_array(grid_key: str, symmetry=True, colocate: Optional[bool] = None): +def make_scalar_field_data_array(grid_key: str, symmetry=True, colocate: bool | None = None): monitor = FIELD_MONITOR if colocate is not None: monitor = monitor.updated_copy(colocate=colocate) @@ -160,13 +158,11 @@ def make_scalar_field_time_data_array(grid_key: str, symmetry=True): 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: Optional[bool] = None -): +def make_scalar_mode_field_data_array(grid_key: str, symmetry=True, colocate: bool | None = None): monitor = MODE_MONITOR_WITH_FIELDS if colocate is not None: monitor = monitor.updated_copy(colocate=colocate) - XS, YS, ZS = get_xyz(monitor, grid_key, symmetry) + XS, _YS, ZS = get_xyz(monitor, grid_key, symmetry) values = (1 + 0.1j) * np.random.random((len(XS), 1, len(ZS), len(FS), len(MODE_INDICES))) return td.ScalarModeFieldDataArray( @@ -175,7 +171,7 @@ def make_scalar_mode_field_data_array( def make_scalar_mode_field_data_array_smooth(grid_key: str, symmetry=True, rot: float = 0): - XS, YS, ZS = get_xyz(MODE_MONITOR_WITH_FIELDS, grid_key, symmetry) + XS, _YS, ZS = get_xyz(MODE_MONITOR_WITH_FIELDS, grid_key, symmetry) values = np.array([1 + 0.1j])[None, :, None, None, None] * np.sin( 0.5 diff --git a/tests/test_data/test_monitor_data.py b/tests/test_data/test_monitor_data.py index ab0598f8c0..cc628184c6 100644 --- a/tests/test_data/test_monitor_data.py +++ b/tests/test_data/test_monitor_data.py @@ -475,7 +475,7 @@ def test_directivity_data_from_projected_fields(): monitor, proj_angle_data = make_field_dataset_using_power_density( values, theta, phi, freqs, r_proj ) - with pytest.raises(ValueError, match="Chosen limits for `theta` are not appropriate"): + with pytest.raises(ValueError, match=r"Chosen limits for `theta` are not appropriate"): dir_data = td.DirectivityData.from_spherical_field_dataset(monitor, proj_angle_data) # Test invalid phi range @@ -485,7 +485,7 @@ def test_directivity_data_from_projected_fields(): monitor, proj_angle_data = make_field_dataset_using_power_density( values, theta, phi, freqs, r_proj ) - with pytest.raises(ValueError, match="Chosen limits for `phi` are not appropriate"): + with pytest.raises(ValueError, match=r"Chosen limits for `phi` are not appropriate"): dir_data = td.DirectivityData.from_spherical_field_dataset(monitor, proj_angle_data) # Test too coarse sampling @@ -495,7 +495,7 @@ def test_directivity_data_from_projected_fields(): monitor, proj_angle_data = make_field_dataset_using_power_density( values, theta, phi, freqs, r_proj ) - with pytest.raises(ValueError, match="There are not enough sampling points"): + with pytest.raises(ValueError, match=r"There are not enough sampling points"): dir_data = td.DirectivityData.from_spherical_field_dataset(monitor, proj_angle_data) # Test unsorted @@ -505,7 +505,7 @@ def test_directivity_data_from_projected_fields(): monitor, proj_angle_data = make_field_dataset_using_power_density( values, theta, phi, freqs, r_proj ) - with pytest.raises(ValueError, match="theta was not provided as a sorted array."): + with pytest.raises(ValueError, match=r"theta was not provided as a sorted array."): dir_data = td.DirectivityData.from_spherical_field_dataset(monitor, proj_angle_data) # Test success case with proper sampling diff --git a/tests/test_plugins/autograd/invdes/test_filters.py b/tests/test_plugins/autograd/invdes/test_filters.py index ddab528e81..9d4e898692 100644 --- a/tests/test_plugins/autograd/invdes/test_filters.py +++ b/tests/test_plugins/autograd/invdes/test_filters.py @@ -30,7 +30,8 @@ def test_get_kernel_size(radius, dl, size_px, expected): def test_get_kernel_size_invalid_arguments(): with pytest.raises( - ValueError, match="Either 'size_px' or both 'radius' and 'dl' must be provided." + ValueError, + match=r"Either 'size_px' or both 'radius' and 'dl' must be provided.", ): _get_kernel_size(None, None, None) diff --git a/tests/test_plugins/expressions/test_variables.py b/tests/test_plugins/expressions/test_variables.py index da74b03dc0..781768314e 100644 --- a/tests/test_plugins/expressions/test_variables.py +++ b/tests/test_plugins/expressions/test_variables.py @@ -37,19 +37,19 @@ def test_variable_evaluate_named(value): def test_variable_missing_positional(): variable = Variable() - with pytest.raises(ValueError, match="No positional argument provided for unnamed variable."): + with pytest.raises(ValueError, match=r"No positional argument provided for unnamed variable."): variable.evaluate() def test_variable_missing_named(): variable = Variable(name="x") - with pytest.raises(ValueError, match="Variable 'x' not provided."): + with pytest.raises(ValueError, match=r"Variable 'x' not provided."): variable.evaluate() def test_variable_wrong_named(): variable = Variable(name="x") - with pytest.raises(ValueError, match="Variable 'x' not provided."): + with pytest.raises(ValueError, match=r"Variable 'x' not provided."): variable.evaluate(y=5) @@ -89,9 +89,9 @@ def test_variable_missing_args(): variable_unnamed = Variable() variable_named = Variable(name="x") expr = variable_unnamed + variable_named - with pytest.raises(ValueError, match="No positional argument provided for unnamed variable."): + with pytest.raises(ValueError, match=r"No positional argument provided for unnamed variable."): expr(x=3) - with pytest.raises(ValueError, match="Variable 'x' not provided."): + with pytest.raises(ValueError, match=r"Variable 'x' not provided."): expr(5) @@ -99,14 +99,14 @@ def test_variable_multiple_positional_args(): variable1 = Variable() variable2 = Variable() expr = variable1 + variable2 - with pytest.raises(ValueError, match="Multiple positional arguments"): + with pytest.raises(ValueError, match=r"Multiple positional arguments"): expr(5, 3) def test_single_unnamed_variable_multiple_args(): variable = Variable() expr = variable * 2 - with pytest.raises(ValueError, match="Multiple positional arguments"): + with pytest.raises(ValueError, match=r"Multiple positional arguments"): expr(5, 3) diff --git a/tests/test_plugins/smatrix/terminal_component_modeler_def.py b/tests/test_plugins/smatrix/terminal_component_modeler_def.py index be118f036b..a95850a09b 100644 --- a/tests/test_plugins/smatrix/terminal_component_modeler_def.py +++ b/tests/test_plugins/smatrix/terminal_component_modeler_def.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Optional, Union - import numpy as np import tidy3d as td @@ -36,9 +34,7 @@ Router = 1.0 * mm -def make_simulation( - planar_pec: bool, length: Optional[float] = None, grid_spec: td.GridSpec = None -): +def make_simulation(planar_pec: bool, length: float | None = None, grid_spec: td.GridSpec = None): if length: strip_length = length else: @@ -114,7 +110,7 @@ def make_simulation( def make_component_modeler( planar_pec: bool, reference_impedance: complex = 50, - length: Optional[float] = None, + length: float | None = None, port_refinement: bool = True, port_snapping: bool = True, grid_spec: td.GridSpec = None, @@ -172,7 +168,7 @@ def make_component_modeler( return modeler -def make_coaxial_simulation(length: Optional[float] = None, grid_spec: td.GridSpec = None): +def make_coaxial_simulation(length: float | None = None, grid_spec: td.GridSpec = None): if not length: length = default_strip_length @@ -249,10 +245,10 @@ def make_coaxial_simulation(length: Optional[float] = None, grid_spec: td.GridSp def make_coaxial_component_modeler( reference_impedance: complex = 50, - length: Optional[float] = None, + length: float | None = None, port_refinement: bool = True, grid_spec: td.GridSpec = None, - port_types: tuple[Union[CoaxialLumpedPort, WavePort], Union[CoaxialLumpedPort, WavePort]] = ( + port_types: tuple[CoaxialLumpedPort | WavePort, CoaxialLumpedPort | WavePort] = ( CoaxialLumpedPort, CoaxialLumpedPort, ), @@ -265,7 +261,7 @@ def make_coaxial_component_modeler( sim = make_coaxial_simulation(length=length, grid_spec=grid_spec) - def make_port(center, direction, type, name) -> Union[CoaxialLumpedPort, WavePort]: + def make_port(center, direction, type, name) -> CoaxialLumpedPort | WavePort: if type is CoaxialLumpedPort: port_cells = None if port_refinement: diff --git a/tests/test_plugins/test_design.py b/tests/test_plugins/test_design.py index b35ed5bb84..4b3990d1e5 100644 --- a/tests/test_plugins/test_design.py +++ b/tests/test_plugins/test_design.py @@ -3,7 +3,6 @@ from __future__ import annotations import sys -from typing import Optional import matplotlib.pyplot as plt import numpy as np @@ -40,7 +39,7 @@ } -def emulated_batch_run(simulations, path_dir: Optional[str] = None, **kwargs): +def emulated_batch_run(simulations, path_dir: str | None = 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()} diff --git a/tests/test_plugins/test_dispersion_fitter.py b/tests/test_plugins/test_dispersion_fitter.py index fbdf541c4e..f470c13c77 100644 --- a/tests/test_plugins/test_dispersion_fitter.py +++ b/tests/test_plugins/test_dispersion_fitter.py @@ -91,20 +91,20 @@ def test_lossless_dispersion(random_data, mock_remote_api): with pytest.raises(SetupError): fitter = DispersionFitter(wvl_um=[1.0], n_data=(1.0), wvl_range=(2, 3)) - medium, rms = fitter.fit(num_tries=2) + _medium, _rms = fitter.fit(num_tries=2) wvl_um, n_data, _ = random_data fitter = DispersionFitter(wvl_um=wvl_um.tolist(), n_data=tuple(n_data)) - medium, rms = fitter._fit_single() - medium, rms = fitter.fit(num_tries=2) - medium, rms = run_fitter(fitter) + _medium, _rms = fitter._fit_single() + _medium, _rms = fitter.fit(num_tries=2) + _medium, _rms = run_fitter(fitter) fitter = FastDispersionFitter(wvl_um=wvl_um.tolist(), n_data=tuple(n_data)) - medium, rms = fitter.fit(advanced_param=advanced_param) + _medium, _rms = fitter.fit(advanced_param=advanced_param) # from permittivity data fitter = FastDispersionFitter.from_complex_permittivity(wvl_um, n_data**2) - medium2, rms2 = fitter.fit(advanced_param=advanced_param) + _medium2, _rms2 = fitter.fit(advanced_param=advanced_param) @responses.activate @@ -112,25 +112,25 @@ def test_lossy_dispersion(random_data, mock_remote_api): """perform fitting on random lossy data""" wvl_um, n_data, k_data = random_data fitter = DispersionFitter(wvl_um=wvl_um, n_data=n_data, k_data=k_data) - medium, rms = fitter._fit_single() - medium, rms = fitter.fit(num_tries=2) - medium, rms = run_fitter(fitter) + _medium, _rms = fitter._fit_single() + _medium, _rms = fitter.fit(num_tries=2) + _medium, _rms = run_fitter(fitter) fitter = FastDispersionFitter(wvl_um=wvl_um.tolist(), n_data=n_data, k_data=k_data) - medium, rms = fitter.fit(advanced_param=advanced_param) + _medium, _rms = fitter.fit(advanced_param=advanced_param) # from permittivity data eps_complex = (n_data + 1j * k_data) ** 2 fitter = FastDispersionFitter.from_complex_permittivity( wvl_um, eps_complex.real, eps_complex.imag ) - medium2, rms2 = fitter.fit(advanced_param=advanced_param) + _medium2, _rms2 = fitter.fit(advanced_param=advanced_param) # from loss tangent fitter = FastDispersionFitter.from_loss_tangent( wvl_um, eps_complex.real, eps_complex.imag / eps_complex.real ) - medium3, rms3 = fitter.fit(advanced_param=advanced_param) + _medium3, _rms3 = fitter.fit(advanced_param=advanced_param) # test that poles can be close but not exactly equal to provided freqs N = 2 @@ -139,7 +139,7 @@ def test_lossy_dispersion(random_data, mock_remote_api): k_data = np.ones(N) * 0.5 fitter = FastDispersionFitter(wvl_um=wvl_um, n_data=n_data, k_data=k_data) - medium, rms_error = fitter.fit(max_num_poles=2, tolerance_rms=1e-3) + _medium, _rms_error = fitter.fit(max_num_poles=2, tolerance_rms=1e-3) def test_constant_loss_tangent(): @@ -193,25 +193,25 @@ def _test_nk(mock_data): # n only mock_data = [b"wl,n", b"1,2", b"2,2"] fitter = _test_nk(mock_data) - medium, rms = fitter.fit(num_tries=10) + _medium, _rms = fitter.fit(num_tries=10) # n and k mock_data = [b"wl,n", b"1,2", b"3,2.1", b"wl,k", b"1,0", b"3,0.1"] fitter = _test_nk(mock_data) - medium, rms = fitter.fit(num_tries=2) + _medium, _rms = fitter.fit(num_tries=2) def test_dispersion_load_file(): """loads dispersion model from nk data file""" fitter = DispersionFitter.from_file("tests/data/nk_data.csv", skiprows=1, delimiter=",") - medium, rms = fitter.fit(num_tries=2) + _medium, _rms = fitter.fit(num_tries=2) fitter = DispersionFitter.from_file("tests/data/n_data.csv", skiprows=1, delimiter=",") - medium, rms = fitter.fit(num_tries=20) + _medium, _rms = fitter.fit(num_tries=20) fitter = FastDispersionFitter.from_file("tests/data/nk_data.csv", skiprows=1, delimiter=",") - medium, rms = fitter.fit(advanced_param=advanced_param) + _medium, _rms = fitter.fit(advanced_param=advanced_param) def test_dispersion_plot(random_data): @@ -221,14 +221,14 @@ def test_dispersion_plot(random_data): fitter = DispersionFitter(wvl_um=wvl_um, n_data=n_data) fitter.plot(ax=AX) plt.close() - medium, rms = fitter.fit(num_tries=2) + medium, _rms = fitter.fit(num_tries=2) fitter.plot(medium, ax=AX) plt.close() fitter = DispersionFitter(wvl_um=wvl_um, n_data=n_data, k_data=k_data) fitter.plot() plt.close() - medium, rms = fitter.fit(num_tries=2) + medium, _rms = fitter.fit(num_tries=2) fitter.plot(medium, ax=AX) plt.close() @@ -242,44 +242,44 @@ def test_dispersion_set_wvg_range(random_data): wvl_range = [1.2, 1.8] fitter = fitter.copy(update={"wvl_range": wvl_range}) assert len(fitter.freqs) == 7 - medium, rms = fitter.fit(num_tries=2) + _medium, _rms = fitter.fit(num_tries=2) fastfitter = fastfitter.copy(update={"wvl_range": wvl_range}) assert len(fastfitter.freqs) == 7 - medium, rms = fastfitter.fit(advanced_param=advanced_param) + _medium, _rms = fastfitter.fit(advanced_param=advanced_param) wvl_range = [1.2, 2.8] fitter = fitter.copy(update={"wvl_range": wvl_range, "k_data": k_data}) assert len(fitter.freqs) == 9 - medium, rms = fitter.fit(num_tries=2) + _medium, _rms = fitter.fit(num_tries=2) fastfitter = fastfitter.copy(update={"wvl_range": wvl_range}) assert len(fastfitter.freqs) == 9 - medium, rms = fastfitter.fit(advanced_param=advanced_param) + _medium, _rms = fastfitter.fit(advanced_param=advanced_param) wvl_range = [0.2, 1.8] fitter = fitter.copy(update={"wvl_range": wvl_range}) assert len(fitter.freqs) == 9 - medium, rms = fitter.fit(num_tries=2) + _medium, _rms = fitter.fit(num_tries=2) fastfitter = fastfitter.copy(update={"wvl_range": wvl_range}) assert len(fastfitter.freqs) == 9 - medium, rms = fastfitter.fit(advanced_param=advanced_param) + _medium, _rms = fastfitter.fit(advanced_param=advanced_param) wvl_range = [0.2, 2.8] fitter = fitter.copy(update={"wvl_range": wvl_range, "k_data": k_data}) assert len(fitter.freqs) == 11 - medium, rms = fitter.fit(num_tries=2) + _medium, _rms = fitter.fit(num_tries=2) fastfitter = fastfitter.copy(update={"wvl_range": wvl_range}) assert len(fastfitter.freqs) == 11 - medium, rms = fastfitter.fit(advanced_param=advanced_param) + _medium, _rms = fastfitter.fit(advanced_param=advanced_param) def test_dispersion_guess(random_data): """plots a medium fit from file""" - wvl_um, n_data, k_data = random_data + wvl_um, n_data, _k_data = random_data fitter = DispersionFitter(wvl_um=wvl_um, n_data=n_data) - medium, rms = fitter.fit(num_tries=2) + medium, _rms = fitter.fit(num_tries=2) - medium_new, rms_new = fitter.fit(num_tries=1, guess=medium) + _medium_new, _rms_new = fitter.fit(num_tries=1, guess=medium) def test_dispersion_loss_samples(): diff --git a/tests/test_plugins/test_polyslab.py b/tests/test_plugins/test_polyslab.py index 267020d36b..bcb399fcae 100644 --- a/tests/test_plugins/test_polyslab.py +++ b/tests/test_plugins/test_polyslab.py @@ -175,7 +175,7 @@ def make_coupler( wg_height = 0.3 dilation = 0.02 - [substrate_geo] = ComplexPolySlab.from_gds( + [_substrate_geo] = ComplexPolySlab.from_gds( coup_cell_loaded, gds_layer=0, gds_dtype=0, diff --git a/tests/test_web/test_s3utils.py b/tests/test_web/test_s3utils.py index 175e860b6d..3b8c016c49 100644 --- a/tests/test_web/test_s3utils.py +++ b/tests/test_web/test_s3utils.py @@ -75,7 +75,7 @@ def simulate_download_success(Bucket, Key, Filename, Callback, Config, **kwargs) # Check that mock_s3_client.download_file() was invoked with the correct arguments. mock_s3_client.download_file.assert_called_once() - call_args, call_kwargs = mock_s3_client.download_file.call_args + _call_args, call_kwargs = mock_s3_client.download_file.call_args assert call_kwargs["Bucket"] == "test-bucket" assert call_kwargs["Key"] == "test-key" assert call_kwargs["Filename"].endswith(s3utils.IN_TRANSIT_SUFFIX) @@ -112,7 +112,7 @@ def simulate_download_failure(Bucket, Key, Filename, Callback, Config, **kwargs) # Check that mock_s3_client.download_file() was invoked with the correct arguments. mock_s3_client.download_file.assert_called_once() - call_args, call_kwargs = mock_s3_client.download_file.call_args + _call_args, call_kwargs = mock_s3_client.download_file.call_args assert call_kwargs["Bucket"] == "test-bucket" assert call_kwargs["Key"] == "test-key" assert call_kwargs["Filename"].endswith(s3utils.IN_TRANSIT_SUFFIX) diff --git a/tests/test_web/test_webapi.py b/tests/test_web/test_webapi.py index 3d4c7ce949..2d08b0ec5d 100644 --- a/tests/test_web/test_webapi.py +++ b/tests/test_web/test_webapi.py @@ -365,7 +365,7 @@ def test_start_with_valid_priority(mock_start, priority): @pytest.mark.parametrize("priority", [0, -1, 11, 15]) def test_start_with_invalid_priority(mock_start, priority): """Test start with invalid priority values.""" - with pytest.raises(ValueError, match="Priority must be between '1' and '10' if specified."): + with pytest.raises(ValueError, match=r"Priority must be between '1' and '10' if specified."): start(TASK_ID, priority=priority) @@ -383,7 +383,7 @@ def test_run_with_valid_priority(mock_webapi, monkeypatch, priority): def test_run_with_invalid_priority(mock_webapi, priority): """Test run with invalid priority values.""" sim = make_sim() - with pytest.raises(ValueError, match="Priority must be between '1' and '10' if specified."): + with pytest.raises(ValueError, match=r"Priority must be between '1' and '10' if specified."): run(sim, TASK_NAME, folder_name=PROJECT_NAME, priority=priority) @@ -718,7 +718,7 @@ def mock_start_interrupt(self, *args, **kwargs): monkeypatch.setattr(Batch, "start", mock_start_interrupt) # run should save the batch file after upload, even if interrupted - with pytest.raises(RuntimeError, match="Simulated interruption"): + with pytest.raises(RuntimeError, match=r"Simulated interruption"): batch.run(path_dir=str(tmp_path)) @@ -897,7 +897,7 @@ def test_load_invalid_task_raises(mock_webapi): json={"error": "Task not found"}, status=404, ) - with pytest.raises(WebNotFoundError, match="Resource not found"): + with pytest.raises(WebNotFoundError, match=r"Resource not found"): load(INVALID_TASK_ID, replace_existing=True) diff --git a/tests/test_web/test_webapi_extra.py b/tests/test_web/test_webapi_extra.py index 5e16ef42e3..85ef286284 100644 --- a/tests/test_web/test_webapi_extra.py +++ b/tests/test_web/test_webapi_extra.py @@ -14,7 +14,7 @@ def test_get_info_not_found(monkeypatch): monkeypatch.setattr( "tidy3d.web.core.task_core.SimulationTask.get", lambda *args, **kwargs: None ) - with pytest.raises(ValueError, match="Task not found."): + with pytest.raises(ValueError, match=r"Task not found."): get_info("non_existent_task_id") @@ -24,13 +24,13 @@ def test_start_not_found(monkeypatch): monkeypatch.setattr( "tidy3d.web.core.task_core.SimulationTask.get", lambda *args, **kwargs: None ) - with pytest.raises(ValueError, match="Task not found."): + with pytest.raises(ValueError, match=r"Task not found."): start("non_existent_task_id") def test_delete_not_found(): """Tests that delete raises a ValueError when the task id is not found.""" - with pytest.raises(ValueError, match="Task id not found."): + with pytest.raises(ValueError, match=r"Task id not found."): delete("") diff --git a/tests/utils.py b/tests/utils.py index 4f67ddf5c0..bba08c7208 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,7 +2,7 @@ import dataclasses from pathlib import Path -from typing import Any, Optional, Union +from typing import Any import numpy as np import pydantic.v1 as pd @@ -57,9 +57,9 @@ def cartesian_to_unstructured( array: td.SpatialDataArray, pert: float = 0.1, method: str = "linear", - seed: Optional[int] = None, + seed: int | None = None, same_bounds: bool = True, -) -> Union[td.TriangularGridDataset, td.TetrahedralGridDataset]: +) -> td.TriangularGridDataset | td.TetrahedralGridDataset: """Convert a SpatialDataArray into TriangularGridDataset/TetrahedralGridDataset with an optional perturbation of point coordinates. @@ -1532,7 +1532,7 @@ def run_async_emulated(simulations: dict[str, td.Simulation], **kwargs) -> Batch def assert_log_level( - records: list[tuple[int, str]], log_level_expected: str, contains_str: Optional[str] = None + records: list[tuple[int, str]], log_level_expected: str, contains_str: str | None = None ) -> None: """Testing tool: Raises error if a log was not recorded as expected. @@ -1600,8 +1600,8 @@ def assert_log_level( def assert_str_in_log( records: list[tuple[int, str]], log_level_test: str, - excludes_str: Optional[str] = None, - contains_str: Optional[str] = None, + excludes_str: str | None = None, + contains_str: str | None = None, ) -> None: """Testing tool: Raises error if `excludes_str` appears , or `contains_str` doesn't appear at the test log level. Unlike ``assert_log_level``, we don't raise error if the ``log_level_test`` is not present in the records. @@ -1664,7 +1664,7 @@ def handle(self, level, level_name, message): class AbstractAssertLog: """Context manager to check logs.""" - log_level_expected: Union[str, None] + log_level_expected: str | None contains_str: str = None @property diff --git a/tidy3d/components/autograd/boxes.py b/tidy3d/components/autograd/boxes.py index 78aa52289a..a7a5392d44 100644 --- a/tidy3d/components/autograd/boxes.py +++ b/tidy3d/components/autograd/boxes.py @@ -3,7 +3,8 @@ from __future__ import annotations import importlib -from typing import Any, Callable +from collections.abc import Callable +from typing import Any import autograd.numpy as anp from autograd.extend import VJPNode, defjvp, register_notrace diff --git a/tidy3d/components/autograd/derivative_utils.py b/tidy3d/components/autograd/derivative_utils.py index fba1b54ba4..1ff1fe1711 100644 --- a/tidy3d/components/autograd/derivative_utils.py +++ b/tidy3d/components/autograd/derivative_utils.py @@ -2,8 +2,8 @@ from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass, field, replace -from typing import Callable, Optional, Union import numpy as np import xarray as xr @@ -19,7 +19,7 @@ FieldData = dict[str, ScalarFieldDataArray] PermittivityData = dict[str, ScalarFieldDataArray] -EpsType = Union[tidycomplex, FreqDataArray] +EpsType = tidycomplex | FreqDataArray class LazyInterpolator: @@ -115,36 +115,36 @@ class DerivativeInfo: frequencies: ArrayLike """Frequencies at which the adjoint gradient should be computed.""" - H_der_map: Optional[FieldData] = None + H_der_map: FieldData | None = None """Magnetic field gradient map. Dataset where the field components ("Hx", "Hy", "Hz") store the multiplication of the forward and adjoint magnetic fields. The tangential component of this dataset is used when computing adjoint gradients for shifting boundaries of structures composed of PEC mediums.""" - H_fwd: Optional[FieldData] = None + H_fwd: FieldData | None = None """Forward magnetic fields. Dataset where the field components ("Hx", "Hy", "Hz") represent the forward magnetic fields used for computing gradients for a given structure.""" - H_adj: Optional[FieldData] = None + H_adj: FieldData | None = None """Adjoint magnetic fields. Dataset where the field components ("Hx", "Hy", "Hz") represent the adjoint magnetic fields used for computing gradients for a given structure.""" # Optional fields with defaults - eps_background: Optional[EpsType] = None + eps_background: EpsType | None = None """Permittivity in background. Permittivity outside of the Structure as manually specified by Structure.background_medium.""" - eps_no_structure: Optional[ScalarFieldDataArray] = None + eps_no_structure: ScalarFieldDataArray | None = None """Permittivity without structure. The permittivity of the original simulation without the structure that is being differentiated with respect to. Used to approximate permittivity outside of the structure for shape optimization.""" - eps_inf_structure: Optional[ScalarFieldDataArray] = None + eps_inf_structure: ScalarFieldDataArray | None = None """Permittivity with infinite structure. The permittivity of the original simulation where the structure being differentiated with respect to is infinitely large. Used to approximate @@ -162,7 +162,7 @@ class DerivativeInfo: If True, the structure contains a PEC material which changes the gradient formulation at the boundary compared to the dielectric case.""" - interpolators: Optional[dict] = None + interpolators: dict | None = None """Pre-computed interpolators. Optional pre-computed interpolators for field components and permittivity data. When provided, avoids redundant interpolator creation for multiple geometries @@ -216,7 +216,7 @@ def _evaluate_with_interpolators( coords = coords.astype(float_dtype, copy=False) return {name: interp(coords) for name, interp in interpolators.items()} - def create_interpolators(self, dtype: Optional[np.dtype] = None) -> dict: + def create_interpolators(self, dtype: np.dtype | None = None) -> dict: """Create interpolators for field components and permittivity data. Creates and caches ``RegularGridInterpolator`` objects for all field components @@ -342,7 +342,7 @@ def evaluate_gradient_at_points( normals: np.ndarray, perps1: np.ndarray, perps2: np.ndarray, - interpolators: Optional[dict] = None, + interpolators: dict | None = None, ) -> np.ndarray: """Compute adjoint gradients at surface points for shape optimization. @@ -716,8 +716,8 @@ def _project_in_basis( def adaptive_vjp_spacing( self, - wl_fraction: Optional[float] = None, - min_allowed_spacing_fraction: Optional[float] = None, + wl_fraction: float | None = None, + min_allowed_spacing_fraction: float | None = None, ) -> float: """Compute adaptive spacing for finite-difference gradient evaluation. diff --git a/tidy3d/components/autograd/field_map.py b/tidy3d/components/autograd/field_map.py index 101e0b56bd..ab6341375b 100644 --- a/tidy3d/components/autograd/field_map.py +++ b/tidy3d/components/autograd/field_map.py @@ -3,7 +3,8 @@ from __future__ import annotations import json -from typing import Any, Callable +from collections.abc import Callable +from typing import Any import pydantic.v1 as pydantic diff --git a/tidy3d/components/autograd/types.py b/tidy3d/components/autograd/types.py index bb41935695..f01629a99c 100644 --- a/tidy3d/components/autograd/types.py +++ b/tidy3d/components/autograd/types.py @@ -26,20 +26,20 @@ Box.__deepcopy__ = lambda v, memo: _deepcopy(v, memo) # Types for floats, or collections of floats that can also be autograd tracers -TracedFloat = typing.Union[float, Box] -TracedPositiveFloat = typing.Union[pd.PositiveFloat, Box] -TracedSize1D = typing.Union[Size1D, Box] -TracedSize = typing.Union[tuple[TracedSize1D, TracedSize1D, TracedSize1D], Box] -TracedCoordinate = typing.Union[tuple[TracedFloat, TracedFloat, TracedFloat], Box] -TracedVertices = typing.Union[ArrayFloat2D, Box] +TracedFloat = float | Box +TracedPositiveFloat = pd.PositiveFloat | Box +TracedSize1D = Size1D | Box +TracedSize = tuple[TracedSize1D, TracedSize1D, TracedSize1D] | Box +TracedCoordinate = tuple[TracedFloat, TracedFloat, TracedFloat] | Box +TracedVertices = ArrayFloat2D | Box # poles -TracedComplex = typing.Union[Complex, Box] +TracedComplex = Complex | Box TracedPoleAndResidue = tuple[TracedComplex, TracedComplex] # The data type that we pass in and out of the web.run() @autograd.primitive -AutogradTraced = typing.Union[Box, ArrayLike] -PathType = tuple[typing.Union[int, str], ...] +AutogradTraced = Box | ArrayLike +PathType = tuple[int | str, ...] AutogradFieldMap = dict_ag[PathType, AutogradTraced] InterpolationType = typing.Literal["nearest", "linear"] diff --git a/tidy3d/components/base.py b/tidy3d/components/base.py index 5498cf1738..b567a1cc6c 100644 --- a/tidy3d/components/base.py +++ b/tidy3d/components/base.py @@ -9,9 +9,10 @@ import os import pathlib import tempfile +from collections.abc import Callable from functools import wraps from math import ceil -from typing import Any, Callable, Literal, Optional, Union +from typing import Any, Literal import h5py import numpy as np @@ -284,7 +285,7 @@ def copy(self, deep: bool = True, validate: bool = True, **kwargs) -> Self: return new_copy def updated_copy( - self, path: Optional[str] = None, deep: bool = True, validate: bool = True, **kwargs + self, path: str | None = None, deep: bool = True, validate: bool = True, **kwargs ) -> Self: """Make copy of a component instance with ``**kwargs`` indicating updated field values. @@ -365,9 +366,9 @@ def help(self, methods: bool = False) -> None: def from_file( cls, fname: str, - group_path: Optional[str] = None, + group_path: str | None = None, lazy: bool = False, - on_load: Optional[Callable] = None, + on_load: Callable | None = None, **parse_obj_kwargs, ) -> Self: """Loads a :class:`Tidy3dBaseModel` from .yaml, .json, .hdf5, or .hdf5.gz file. @@ -409,7 +410,7 @@ def from_file( return obj @classmethod - def dict_from_file(cls, fname: str, group_path: Optional[str] = None) -> dict: + def dict_from_file(cls, fname: str, group_path: str | None = None) -> dict: """Loads a dictionary containing the model from a .yaml, .json, .hdf5, or .hdf5.gz file. Parameters @@ -668,7 +669,7 @@ def _json_string_from_hdf5(cls, fname: str) -> str: @classmethod def dict_from_hdf5( - cls, fname: str, group_path: str = "", custom_decoders: Optional[list[Callable]] = None + cls, fname: str, group_path: str = "", custom_decoders: list[Callable] | None = None ) -> dict: """Loads a dictionary containing the model contents from a .hdf5 file. @@ -746,7 +747,7 @@ def from_hdf5( cls, fname: str, group_path: str = "", - custom_decoders: Optional[list[Callable]] = None, + custom_decoders: list[Callable] | None = None, **parse_obj_kwargs, ) -> Self: """Loads :class:`Tidy3dBaseModel` instance to .hdf5 file. @@ -779,7 +780,7 @@ def from_hdf5( def to_hdf5( self, fname: str, - custom_encoders: Optional[list[Callable]] = None, + custom_encoders: list[Callable] | None = None, ) -> None: """Exports :class:`Tidy3dBaseModel` instance to .hdf5 file. @@ -840,7 +841,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: Optional[list[Callable]] = None + cls, fname: str, group_path: str = "", custom_decoders: list[Callable] | None = None ) -> dict: """Loads a dictionary containing the model contents from a .hdf5.gz file. @@ -881,7 +882,7 @@ def from_hdf5_gz( cls, fname: str, group_path: str = "", - custom_decoders: Optional[list[Callable]] = None, + custom_decoders: list[Callable] | None = None, **parse_obj_kwargs, ) -> Self: """Loads :class:`Tidy3dBaseModel` instance to .hdf5.gz file. @@ -911,7 +912,7 @@ def from_hdf5_gz( ) return cls.parse_obj(model_dict, **parse_obj_kwargs) - def to_hdf5_gz(self, fname: str, custom_encoders: Optional[list[Callable]] = None) -> None: + def to_hdf5_gz(self, fname: str, custom_encoders: list[Callable] | None = None) -> None: """Exports :class:`Tidy3dBaseModel` instance to .hdf5.gz file. Parameters @@ -1116,7 +1117,7 @@ def insert_value(x, path: tuple[str, ...], sub_dict: dict): def _serialized_traced_field_keys( self, field_mapping: AutogradFieldMap | None = None - ) -> Optional[str]: + ) -> str | None: """Return a serialized, order-independent representation of traced field paths.""" if field_mapping is None: @@ -1229,7 +1230,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[str | tuple[str, int]]]: """Return a dictionary of this object's sub-models indexed by their hash values.""" fields = {} for key in self.__fields__: @@ -1300,7 +1301,7 @@ def to_sci(value: float, exponent: int, precision: int) -> str: def _make_lazy_proxy( target_cls: type, - on_load: Optional[Callable[[Any], None]] = None, + on_load: Callable[[Any], None] | None = None, ) -> type: """ Return a lazy-loading proxy subclass of ``target_cls``. @@ -1322,7 +1323,7 @@ def _make_lazy_proxy( class _LazyProxy(target_cls): def __init__( - self, fname: str, group_path: Optional[str], parse_obj_kwargs: Optional[dict[str, Any]] + self, fname: str, group_path: str | None, parse_obj_kwargs: dict[str, Any] | None ): object.__setattr__(self, "_lazy_fname", fname) object.__setattr__(self, "_lazy_group_path", group_path) diff --git a/tidy3d/components/base_sim/data/sim_data.py b/tidy3d/components/base_sim/data/sim_data.py index 86e752cc55..074bfebaff 100644 --- a/tidy3d/components/base_sim/data/sim_data.py +++ b/tidy3d/components/base_sim/data/sim_data.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC -from typing import Union import numpy as np import pydantic.v1 as pd @@ -86,7 +85,7 @@ def validate_no_ambiguity(cls, val, values): @staticmethod def _field_component_value( - field_component: Union[xr.DataArray, UnstructuredGridDatasetType], val: FieldVal + field_component: xr.DataArray | UnstructuredGridDatasetType, val: FieldVal ) -> xr.DataArray: """return the desired value of a field component. diff --git a/tidy3d/components/base_sim/simulation.py b/tidy3d/components/base_sim/simulation.py index ea4aee26eb..31547e932e 100644 --- a/tidy3d/components/base_sim/simulation.py +++ b/tidy3d/components/base_sim/simulation.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Optional import autograd.numpy as anp import pydantic.v1 as pd @@ -115,7 +114,7 @@ class AbstractSimulation(Box, ABC): description="String specifying the front end version number.", ) - plot_length_units: Optional[LengthUnit] = pd.Field( + plot_length_units: LengthUnit | None = pd.Field( "μm", title="Plot Units", description="When set to a supported ``LengthUnit``, " @@ -242,14 +241,14 @@ def simulation_structure(self) -> Structure: @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - source_alpha: Optional[float] = None, - monitor_alpha: Optional[float] = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + source_alpha: float | None = None, + monitor_alpha: float | None = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, fill_structures: bool = True, **patch_kwargs, ) -> Ax: @@ -307,12 +306,12 @@ def plot( @add_ax_if_none def plot_sources( self, - 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, + x: float | None = None, + y: float | None = None, + z: float | None = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, + alpha: float | None = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's sources on a plane defined by one nonzero x,y,z coordinate. @@ -355,12 +354,12 @@ def plot_sources( @add_ax_if_none def plot_monitors( self, - 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, + x: float | None = None, + y: float | None = None, + z: float | None = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, + alpha: float | None = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's monitors on a plane defined by one nonzero x,y,z coordinate. @@ -403,11 +402,11 @@ def plot_monitors( @add_ax_if_none def plot_symmetries( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's symmetries on a plane defined by one nonzero x,y,z coordinate. @@ -478,9 +477,9 @@ def _make_symmetry_box(self, sym_axis: Axis) -> Box: @add_ax_if_none def plot_boundaries( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **kwargs, ) -> Ax: @@ -512,12 +511,12 @@ def plot_boundaries( @add_ax_if_none def plot_structures( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, fill: bool = True, ) -> Ax: """Plot each of simulation's structures on a plane defined by one nonzero x,y,z coordinate. @@ -556,16 +555,16 @@ def plot_structures( @add_ax_if_none def plot_structures_eps( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - freq: Optional[float] = None, - alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + freq: float | None = None, + alpha: float | None = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | 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. @@ -623,15 +622,15 @@ def plot_structures_eps( @add_ax_if_none def plot_structures_heat_conductivity( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + alpha: float | None = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | 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. diff --git a/tidy3d/components/bc_placement.py b/tidy3d/components/bc_placement.py index 1f48a94fbf..1ae86a560d 100644 --- a/tidy3d/components/bc_placement.py +++ b/tidy3d/components/bc_placement.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC -from typing import Union import pydantic.v1 as pd @@ -110,10 +109,10 @@ class StructureSimulationBoundary(AbstractBCPlacement): ) -BCPlacementType = Union[ - StructureBoundary, - StructureStructureInterface, - MediumMediumInterface, - SimulationBoundary, - StructureSimulationBoundary, -] +BCPlacementType = ( + StructureBoundary + | StructureStructureInterface + | MediumMediumInterface + | SimulationBoundary + | StructureSimulationBoundary +) diff --git a/tidy3d/components/beam.py b/tidy3d/components/beam.py index ff42abf31b..0c97243e88 100644 --- a/tidy3d/components/beam.py +++ b/tidy3d/components/beam.py @@ -4,7 +4,7 @@ from __future__ import annotations from abc import abstractmethod -from typing import Literal, Optional, Union +from typing import Literal import autograd.numpy as np import pydantic.v1 as pd @@ -263,7 +263,7 @@ class PlaneWaveBeamProfile(BeamProfile): See also :class:`.PlaneWave`. """ - angular_spec: Union[FixedInPlaneKSpec, FixedAngleSpec] = pd.Field( + angular_spec: FixedInPlaneKSpec | FixedAngleSpec = pd.Field( FixedAngleSpec(), title="Angular Dependence Specification", description="Specification of plane wave propagation direction dependence on wavelength.", @@ -278,7 +278,7 @@ class PlaneWaveBeamProfile(BeamProfile): "switch between waves with fixed angle and fixed in-plane k.", ) - angle_theta_frequency: Optional[float] = pd.Field( + angle_theta_frequency: float | None = pd.Field( None, title="Frequency at Which Angle Theta is Defined", description="Frequency for which ``angle_theta`` is set. This only has an effect for " diff --git a/tidy3d/components/boundary.py b/tidy3d/components/boundary.py index 974f3ba8ec..f8d9347292 100644 --- a/tidy3d/components/boundary.py +++ b/tidy3d/components/boundary.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC -from typing import Optional, Union import numpy as np import pydantic.v1 as pd @@ -101,7 +100,7 @@ class ABCBoundary(AbstractABCBoundary): See, for example, John B. Schneider, Understanding the Finite-Difference Time-Domain Method, Chapter 6. """ - permittivity: Optional[float] = pd.Field( + permittivity: float | None = pd.Field( None, title="Effective Permittivity", description="Effective permittivity for determining propagation constant. " @@ -110,7 +109,7 @@ class ABCBoundary(AbstractABCBoundary): ge=1.0, ) - conductivity: Optional[pd.NonNegativeFloat] = pd.Field( + conductivity: pd.NonNegativeFloat | None = pd.Field( None, title="Effective Conductivity", description="Effective conductivity for determining propagation constant. " @@ -263,7 +262,7 @@ class ModeABCBoundary(AbstractABCBoundary): "``num_modes`` in the solver will be set to ``mode_index + 1``.", ) - freq_spec: Optional[Union[pd.PositiveFloat, BroadbandModeABCSpec]] = pd.Field( + freq_spec: pd.PositiveFloat | BroadbandModeABCSpec | None = pd.Field( None, title="Absorption Frequency Specification", description="Specifies the frequency at which field is absorbed. If ``None``, then the central frequency of the source is used. If ``BroadbandModeABCSpec``, then the field is absorbed over the specified frequency range.", @@ -288,7 +287,7 @@ def is_plane(cls, val): def from_source( cls, source: ModeSource, - freq_spec: Optional[Union[pd.PositiveFloat, BroadbandModeABCSpec]] = None, + freq_spec: pd.PositiveFloat | BroadbandModeABCSpec | None = None, ) -> ModeABCBoundary: """Instantiate from a ``ModeSource``. @@ -325,9 +324,9 @@ def from_source( @classmethod def from_monitor( cls, - monitor: Union[ModeMonitor, ModeSolverMonitor], + monitor: ModeMonitor | ModeSolverMonitor, mode_index: pd.NonNegativeInt = 0, - freq_spec: Optional[Union[pd.PositiveFloat, BroadbandModeABCSpec]] = None, + freq_spec: pd.PositiveFloat | BroadbandModeABCSpec | None = None, ) -> ModeABCBoundary: """Instantiate from a ``ModeMonitor`` or ``ModeSolverMonitor``. @@ -379,7 +378,7 @@ class InternalAbsorber(Box): "one can use the same `size` and `center` as for the source and simply set `shift` to 1.", ) - boundary_spec: Union[ModeABCBoundary, ABCBoundary] = pd.Field( + boundary_spec: ModeABCBoundary | ABCBoundary = pd.Field( ..., title="Boundary Specification", description="Boundary specification for defining effective propagation index in the one-way wave equation.", @@ -412,9 +411,9 @@ def plot_params(self) -> PlotParams: def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **patch_kwargs, ) -> Ax: @@ -449,7 +448,7 @@ def plot( # """ Bloch boundary """ # sources from which Bloch boundary conditions can be defined -BlochSourceType = Union[GaussianBeam, ModeSource, PlaneWave, TFSF] +BlochSourceType = GaussianBeam | ModeSource | PlaneWave | TFSF class BlochBoundary(BoundaryEdge): @@ -895,24 +894,24 @@ class Absorber(AbsorberSpec): # pml types allowed in simulation init -PMLTypes = Union[PML, StablePML, Absorber, None] +PMLTypes = PML | StablePML | Absorber | None # """ boundary specification classes """ # types of boundaries that can be used in Simulation -BoundaryEdgeType = Union[ - Periodic, - PECBoundary, - PMCBoundary, - PML, - StablePML, - Absorber, - BlochBoundary, - ABCBoundary, - ModeABCBoundary, -] +BoundaryEdgeType = ( + Periodic + | PECBoundary + | PMCBoundary + | PML + | StablePML + | Absorber + | BlochBoundary + | ABCBoundary + | ModeABCBoundary +) class Boundary(Tidy3dBaseModel): @@ -1096,8 +1095,8 @@ def pmc(cls): @classmethod def abc( cls, - permittivity: Optional[pd.PositiveFloat] = None, - conductivity: Optional[pd.NonNegativeFloat] = None, + permittivity: pd.PositiveFloat | None = None, + conductivity: pd.NonNegativeFloat | None = None, ): """ABC boundary specification on both sides along a dimension. @@ -1121,7 +1120,7 @@ def mode_abc( plane: Box, mode_spec: ModeSpecType = DEFAULT_MODE_SPEC_MODE_ABC, mode_index: pd.NonNegativeInt = 0, - freq_spec: Optional[Union[pd.PositiveFloat, BroadbandModeABCSpec]] = None, + freq_spec: pd.PositiveFloat | BroadbandModeABCSpec | None = None, ): """One-way wave equation mode ABC boundary specification on both sides along a dimension. @@ -1161,7 +1160,7 @@ def mode_abc( def mode_abc_from_source( cls, source: ModeSource, - freq_spec: Optional[Union[pd.PositiveFloat, BroadbandModeABCSpec]] = None, + freq_spec: pd.PositiveFloat | BroadbandModeABCSpec | None = None, ): """One-way wave equation mode ABC boundary specification on both sides along a dimension constructed from a mode source. @@ -1186,9 +1185,9 @@ def mode_abc_from_source( @classmethod def mode_abc_from_monitor( cls, - monitor: Union[ModeMonitor, ModeSolverMonitor], + monitor: ModeMonitor | ModeSolverMonitor, mode_index: pd.NonNegativeInt = 0, - freq_spec: Optional[Union[pd.PositiveFloat, BroadbandModeABCSpec]] = None, + freq_spec: pd.PositiveFloat | BroadbandModeABCSpec | None = None, ): """One-way wave equation mode ABC boundary specification on both sides along a dimension constructed from a mode monitor. diff --git a/tidy3d/components/data/data_array.py b/tidy3d/components/data/data_array.py index 74304a2283..1d6fc8d709 100644 --- a/tidy3d/components/data/data_array.py +++ b/tidy3d/components/data/data_array.py @@ -4,7 +4,7 @@ from abc import ABC from collections.abc import Mapping -from typing import Any, Optional, Union +from typing import Any import autograd.numpy as anp import h5py @@ -122,7 +122,7 @@ def assign_data_attrs(cls, val): val.attrs[attr_name] = attr return val - def _interp_validator(self, field_name: Optional[str] = None) -> None: + def _interp_validator(self, field_name: str | None = None) -> None: """Ensure the data can be interpolated or selected by checking for duplicate coordinates. NOTE @@ -220,7 +220,7 @@ def is_uniform(self): raw_data = self.data.ravel() return np.allclose(raw_data, raw_data[0]) - def to_hdf5(self, fname: Union[str, h5py.File], group_path: str) -> None: + def to_hdf5(self, fname: str | h5py.File, group_path: str) -> None: """Save an xr.DataArray to the hdf5 file or file handle with a given path to the group.""" # file name passed @@ -329,10 +329,10 @@ def interp( def _ag_interp( self, - coords: Union[Mapping[Any, Any], None] = None, + coords: Mapping[Any, Any] | None = None, method: InterpOptions = "linear", assume_sorted: bool = False, - kwargs: Union[Mapping[str, Any], None] = None, + kwargs: Mapping[str, Any] | None = None, **coords_kwargs: Any, ) -> Self: """Autograd interp override when tracing over self.data. @@ -1614,21 +1614,15 @@ def _make_impedance_data_array(result: DataArray) -> ImpedanceResultType: ] DATA_ARRAY_MAP = {data_array.__name__: data_array for data_array in DATA_ARRAY_TYPES} -IndexedDataArrayTypes = Union[ - IndexedDataArray, - IndexedVoltageDataArray, - IndexedTimeDataArray, - IndexedFieldVoltageDataArray, - PointDataArray, -] +IndexedDataArrayTypes = ( + IndexedDataArray + | IndexedVoltageDataArray + | IndexedTimeDataArray + | IndexedFieldVoltageDataArray + | PointDataArray +) -IntegralResultType = Union[FreqDataArray, FreqModeDataArray, TimeDataArray] -VoltageIntegralResultType = Union[ - VoltageFreqDataArray, VoltageFreqModeDataArray, VoltageTimeDataArray -] -CurrentIntegralResultType = Union[ - CurrentFreqDataArray, CurrentFreqModeDataArray, CurrentTimeDataArray -] -ImpedanceResultType = Union[ - ImpedanceFreqDataArray, ImpedanceFreqModeDataArray, ImpedanceTimeDataArray -] +IntegralResultType = FreqDataArray | FreqModeDataArray | TimeDataArray +VoltageIntegralResultType = VoltageFreqDataArray | VoltageFreqModeDataArray | VoltageTimeDataArray +CurrentIntegralResultType = CurrentFreqDataArray | CurrentFreqModeDataArray | CurrentTimeDataArray +ImpedanceResultType = ImpedanceFreqDataArray | ImpedanceFreqModeDataArray | ImpedanceTimeDataArray diff --git a/tidy3d/components/data/dataset.py b/tidy3d/components/data/dataset.py index f36c0c9fe3..158a888ad8 100644 --- a/tidy3d/components/data/dataset.py +++ b/tidy3d/components/data/dataset.py @@ -3,7 +3,8 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Callable, Optional, Union, get_args +from collections.abc import Callable +from typing import Any, get_args import numpy as np import pydantic.v1 as pd @@ -148,45 +149,45 @@ def colocate(self, x=None, y=None, z=None) -> xr.Dataset: return self.package_colocate_results(centered_fields) -EMScalarFieldType = Union[ - ScalarFieldDataArray, - ScalarFieldTimeDataArray, - ScalarModeFieldDataArray, - ScalarModeFieldCylindricalDataArray, - EMEScalarModeFieldDataArray, - EMEScalarFieldDataArray, -] +EMScalarFieldType = ( + ScalarFieldDataArray + | ScalarFieldTimeDataArray + | ScalarModeFieldDataArray + | ScalarModeFieldCylindricalDataArray + | EMEScalarModeFieldDataArray + | EMEScalarFieldDataArray +) class ElectromagneticFieldDataset(AbstractFieldDataset, ABC): """Stores a collection of E and H fields with x, y, z components.""" - Ex: Optional[EMScalarFieldType] = pd.Field( + Ex: EMScalarFieldType | None = pd.Field( None, title="Ex", description="Spatial distribution of the x-component of the electric field.", ) - Ey: Optional[EMScalarFieldType] = pd.Field( + Ey: EMScalarFieldType | None = pd.Field( None, title="Ey", description="Spatial distribution of the y-component of the electric field.", ) - Ez: Optional[EMScalarFieldType] = pd.Field( + Ez: EMScalarFieldType | None = pd.Field( None, title="Ez", description="Spatial distribution of the z-component of the electric field.", ) - Hx: Optional[EMScalarFieldType] = pd.Field( + Hx: EMScalarFieldType | None = pd.Field( None, title="Hx", description="Spatial distribution of the x-component of the magnetic field.", ) - Hy: Optional[EMScalarFieldType] = pd.Field( + Hy: EMScalarFieldType | None = pd.Field( None, title="Hy", description="Spatial distribution of the y-component of the magnetic field.", ) - Hz: Optional[EMScalarFieldType] = pd.Field( + Hz: EMScalarFieldType | None = pd.Field( None, title="Hz", description="Spatial distribution of the z-component of the magnetic field.", @@ -238,32 +239,32 @@ class FieldDataset(ElectromagneticFieldDataset): >>> data = FieldDataset(Ex=scalar_field, Hz=scalar_field) """ - Ex: Optional[ScalarFieldDataArray] = pd.Field( + Ex: ScalarFieldDataArray | None = pd.Field( None, title="Ex", description="Spatial distribution of the x-component of the electric field.", ) - Ey: Optional[ScalarFieldDataArray] = pd.Field( + Ey: ScalarFieldDataArray | None = pd.Field( None, title="Ey", description="Spatial distribution of the y-component of the electric field.", ) - Ez: Optional[ScalarFieldDataArray] = pd.Field( + Ez: ScalarFieldDataArray | None = pd.Field( None, title="Ez", description="Spatial distribution of the z-component of the electric field.", ) - Hx: Optional[ScalarFieldDataArray] = pd.Field( + Hx: ScalarFieldDataArray | None = pd.Field( None, title="Hx", description="Spatial distribution of the x-component of the magnetic field.", ) - Hy: Optional[ScalarFieldDataArray] = pd.Field( + Hy: ScalarFieldDataArray | None = pd.Field( None, title="Hy", description="Spatial distribution of the y-component of the magnetic field.", ) - Hz: Optional[ScalarFieldDataArray] = pd.Field( + Hz: ScalarFieldDataArray | None = pd.Field( None, title="Hz", description="Spatial distribution of the z-component of the magnetic field.", @@ -370,32 +371,32 @@ class FieldTimeDataset(ElectromagneticFieldDataset): >>> data = FieldTimeDataset(Ex=scalar_field, Hz=scalar_field) """ - Ex: Optional[ScalarFieldTimeDataArray] = pd.Field( + Ex: ScalarFieldTimeDataArray | None = pd.Field( None, title="Ex", description="Spatial distribution of the x-component of the electric field.", ) - Ey: Optional[ScalarFieldTimeDataArray] = pd.Field( + Ey: ScalarFieldTimeDataArray | None = pd.Field( None, title="Ey", description="Spatial distribution of the y-component of the electric field.", ) - Ez: Optional[ScalarFieldTimeDataArray] = pd.Field( + Ez: ScalarFieldTimeDataArray | None = pd.Field( None, title="Ez", description="Spatial distribution of the z-component of the electric field.", ) - Hx: Optional[ScalarFieldTimeDataArray] = pd.Field( + Hx: ScalarFieldTimeDataArray | None = pd.Field( None, title="Hx", description="Spatial distribution of the x-component of the magnetic field.", ) - Hy: Optional[ScalarFieldTimeDataArray] = pd.Field( + Hy: ScalarFieldTimeDataArray | None = pd.Field( None, title="Hy", description="Spatial distribution of the y-component of the magnetic field.", ) - Hz: Optional[ScalarFieldTimeDataArray] = pd.Field( + Hz: ScalarFieldTimeDataArray | None = pd.Field( None, title="Hz", description="Spatial distribution of the z-component of the magnetic field.", @@ -413,19 +414,19 @@ def apply_phase(self, phase: float) -> AbstractFieldDataset: class AuxFieldDataset(AbstractFieldDataset, ABC): """Stores a collection of aux fields with x, y, z components.""" - Nfx: Optional[EMScalarFieldType] = pd.Field( + Nfx: EMScalarFieldType | None = pd.Field( None, title="Nfx", description="Spatial distribution of the free carrier density for " "polarization in the x-direction.", ) - Nfy: Optional[EMScalarFieldType] = pd.Field( + Nfy: EMScalarFieldType | None = pd.Field( None, title="Nfy", description="Spatial distribution of the free carrier density for " "polarization in the y-direction.", ) - Nfz: Optional[EMScalarFieldType] = pd.Field( + Nfz: EMScalarFieldType | None = pd.Field( None, title="Nfz", description="Spatial distribution of the free carrier density for " @@ -472,19 +473,19 @@ class AuxFieldTimeDataset(AuxFieldDataset): >>> data = AuxFieldTimeDataset(Nfx=scalar_field) """ - Nfx: Optional[ScalarFieldTimeDataArray] = pd.Field( + Nfx: ScalarFieldTimeDataArray | None = pd.Field( None, title="Nfx", description="Spatial distribution of the free carrier density for polarization " "in the x-direction.", ) - Nfy: Optional[ScalarFieldTimeDataArray] = pd.Field( + Nfy: ScalarFieldTimeDataArray | None = pd.Field( None, title="Nfy", description="Spatial distribution of the free carrier density for polarization " "in the y-direction.", ) - Nfz: Optional[ScalarFieldTimeDataArray] = pd.Field( + Nfz: ScalarFieldTimeDataArray | None = pd.Field( None, title="Nfz", description="Spatial distribution of the free carrier density for polarization " @@ -518,32 +519,32 @@ class ModeSolverDataset(ElectromagneticFieldDataset): ... ) """ - Ex: Optional[ScalarModeFieldDataArray] = pd.Field( + Ex: ScalarModeFieldDataArray | None = pd.Field( None, title="Ex", description="Spatial distribution of the x-component of the electric field of the mode.", ) - Ey: Optional[ScalarModeFieldDataArray] = pd.Field( + Ey: ScalarModeFieldDataArray | None = pd.Field( None, title="Ey", description="Spatial distribution of the y-component of the electric field of the mode.", ) - Ez: Optional[ScalarModeFieldDataArray] = pd.Field( + Ez: ScalarModeFieldDataArray | None = pd.Field( None, title="Ez", description="Spatial distribution of the z-component of the electric field of the mode.", ) - Hx: Optional[ScalarModeFieldDataArray] = pd.Field( + Hx: ScalarModeFieldDataArray | None = pd.Field( None, title="Hx", description="Spatial distribution of the x-component of the magnetic field of the mode.", ) - Hy: Optional[ScalarModeFieldDataArray] = pd.Field( + Hy: ScalarModeFieldDataArray | None = pd.Field( None, title="Hy", description="Spatial distribution of the y-component of the magnetic field of the mode.", ) - Hz: Optional[ScalarModeFieldDataArray] = pd.Field( + Hz: ScalarModeFieldDataArray | None = pd.Field( None, title="Hz", description="Spatial distribution of the z-component of the magnetic field of the mode.", @@ -555,14 +556,14 @@ class ModeSolverDataset(ElectromagneticFieldDataset): description="Complex-valued effective propagation constants associated with the mode.", ) - n_group_raw: Optional[GroupIndexDataArray] = pd.Field( + n_group_raw: GroupIndexDataArray | None = pd.Field( None, alias="n_group", # This is for backwards compatibility only when loading old data title="Group Index", description="Index associated with group velocity of the mode.", ) - dispersion_raw: Optional[ModeDispersionDataArray] = pd.Field( + dispersion_raw: ModeDispersionDataArray | None = pd.Field( None, title="Dispersion", description="Dispersion parameter for the mode.", diff --git a/tidy3d/components/data/monitor_data.py b/tidy3d/components/data/monitor_data.py index b8f310561a..464dea3a95 100644 --- a/tidy3d/components/data/monitor_data.py +++ b/tidy3d/components/data/monitor_data.py @@ -5,8 +5,9 @@ import struct import warnings from abc import ABC +from collections.abc import Callable from math import isclose -from typing import Any, Callable, Literal, Optional, Union, get_args +from typing import Any, Literal, get_args import autograd.numpy as np import pydantic.v1 as pd @@ -125,7 +126,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: Optional[str] = None + self, freq_array: FreqDataArray, method: str | None = None ) -> MonitorData: """Scale fields in :class:`.MonitorData` by an array of values stored in a :class:`.FreqDataArray`. @@ -173,7 +174,7 @@ def _make_adjoint_sources(self, dataset_names: list[str], fwidth: float) -> list return [] @staticmethod - def flip_direction(direction: Union[str, DataArray]) -> str: + def flip_direction(direction: str | DataArray) -> str: """Flip the direction of a string ``('+', '-') -> ('-', '+')``.""" if isinstance(direction, DataArray): @@ -197,14 +198,14 @@ def get_amplitude(x) -> complex: class AbstractFieldData(MonitorData, AbstractFieldDataset, ABC): """Collection of scalar fields with some symmetry properties.""" - monitor: Union[ - FieldMonitor, - FieldTimeMonitor, - AuxFieldTimeMonitor, - PermittivityMonitor, - ModeMonitor, - MediumMonitor, - ] + monitor: ( + FieldMonitor + | FieldTimeMonitor + | AuxFieldTimeMonitor + | PermittivityMonitor + | ModeMonitor + | MediumMonitor + ) symmetry: tuple[Symmetry, Symmetry, Symmetry] = pd.Field( (0, 0, 0), @@ -403,13 +404,9 @@ def at_coords(self, coords: Coords) -> xr.Dataset: class ElectromagneticFieldData(AbstractFieldData, ElectromagneticFieldDataset, ABC): """Collection of electromagnetic fields.""" - grid_primal_correction: Union[ - float, - FreqDataArray, - TimeDataArray, - FreqModeDataArray, - EMEFreqModeDataArray, - ] = pd.Field( + grid_primal_correction: ( + float | FreqDataArray | TimeDataArray | FreqModeDataArray | EMEFreqModeDataArray + ) = pd.Field( 1.0, title="Field correction factor", description="Correction factor that needs to be applied for data corresponding to a 2D " @@ -417,13 +414,9 @@ class ElectromagneticFieldData(AbstractFieldData, ElectromagneticFieldDataset, A "which the data was computed. The factor is applied to fields defined on the primal grid " "locations along the normal direction.", ) - grid_dual_correction: Union[ - float, - FreqDataArray, - TimeDataArray, - FreqModeDataArray, - EMEFreqModeDataArray, - ] = pd.Field( + grid_dual_correction: ( + float | FreqDataArray | TimeDataArray | FreqModeDataArray | EMEFreqModeDataArray + ) = pd.Field( 1.0, title="Field correction factor", description="Correction factor that needs to be applied for data corresponding to a 2D " @@ -737,7 +730,7 @@ def mode_area(self) -> FreqModeDataArray: return FreqModeDataArray(area) def dot( - self, field_data: Union[FieldData, ModeData, ModeSolverData], conjugate: bool = True + self, field_data: FieldData | ModeData | ModeSolverData, conjugate: bool = True ) -> ModeAmpsDataArray: r"""Dot product (modal overlap) with another :class:`.FieldData` object. Both datasets have to be frequency-domain data associated with a 2D monitor. Along the tangential directions, @@ -829,7 +822,7 @@ def _interpolated_tangential_fields(self, coords: ArrayFloat2D) -> dict[str, Dat return fields def outer_dot( - self, field_data: Union[FieldData, ModeData], conjugate: bool = True + self, field_data: FieldData | ModeData, conjugate: bool = True ) -> MixedModeDataArray: r"""Dot product (modal overlap) with another :class:`.FieldData` object. @@ -1084,10 +1077,10 @@ def to_zbf( fname: str, units: UnitsZBF = "mm", background_refractive_index: float = 1, - n_x: Optional[int] = None, - n_y: Optional[int] = None, - freq: Optional[float] = None, - mode_index: Optional[int] = None, + n_x: int | None = None, + n_y: int | None = None, + freq: float | None = None, + mode_index: int | None = None, r_x: float = 0, r_y: float = 0, z_x: float = 0, @@ -1813,7 +1806,7 @@ def _assign_coords(self, **assign_coords_kwargs): def _find_ordering_one_freq( self, data_to_sort: ModeData, - overlap_thresh: Union[float, np.array], + overlap_thresh: float | np.array, ) -> 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"] @@ -2231,7 +2224,7 @@ def _apply_mode_reorder(self, sort_inds_2d): return self.updated_copy(**modify_data) def sort_modes( - self, sort_spec: Optional[ModeSortSpec] = None, track_freq: Optional[TrackFreq] = None + self, sort_spec: ModeSortSpec | None = None, track_freq: TrackFreq | None = None ) -> ModeSolverData: """Sort modes per frequency according to ``sort_spec``. @@ -2459,7 +2452,7 @@ class FluxData(MonitorData): def _make_adjoint_sources( self, dataset_names: list[str], fwidth: float - ) -> list[Union[CustomCurrentSource, PointDipole]]: + ) -> list[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 @@ -2512,20 +2505,20 @@ class FluxTimeData(MonitorData): ) -ProjFieldType = Union[ - FieldProjectionAngleDataArray, - FieldProjectionCartesianDataArray, - FieldProjectionKSpaceDataArray, - DiffractionDataArray, -] +ProjFieldType = ( + FieldProjectionAngleDataArray + | FieldProjectionCartesianDataArray + | FieldProjectionKSpaceDataArray + | DiffractionDataArray +) -ProjMonitorType = Union[ - FieldProjectionAngleMonitor, - FieldProjectionCartesianMonitor, - FieldProjectionKSpaceMonitor, - DiffractionMonitor, - DirectivityMonitor, -] +ProjMonitorType = ( + FieldProjectionAngleMonitor + | FieldProjectionCartesianMonitor + | FieldProjectionKSpaceMonitor + | DiffractionMonitor + | DirectivityMonitor +) class AbstractFieldProjectionData(MonitorData): @@ -2691,7 +2684,7 @@ def eta(self) -> complex: return ETA_0 / np.sqrt(eps_complex) @staticmethod - def propagation_factor(dist: Union[float, None], k: complex, is_2d_simulation: bool) -> complex: + def propagation_factor(dist: float | None, k: complex, is_2d_simulation: bool) -> complex: """A normalization factor that includes both phase and amplitude decay associated with propagation over a distance with a given wavenumber.""" if dist is None: return 1.0 @@ -2799,7 +2792,7 @@ def radar_cross_section(self) -> DataArray: def _make_adjoint_sources( self, dataset_names: list[str], fwidth: float - ) -> list[Union[CustomCurrentSource, PointDipole]]: + ) -> list[CustomCurrentSource | PointDipole]: """Error if server-side field projection is used for autograd""" raise NotImplementedError( @@ -3369,7 +3362,7 @@ class DiffractionData(AbstractFieldProjectionData): units=MICROMETER, ) - bloch_vecs: Union[tuple[float, float], tuple[ArrayFloat1D, ArrayFloat1D]] = pd.Field( + bloch_vecs: tuple[float, float] | tuple[ArrayFloat1D, ArrayFloat1D] = pd.Field( ..., title="Bloch vectors", description="Bloch vectors along the local x and y directions in units of " @@ -3377,7 +3370,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: float | np.ndarray) -> np.ndarray: """Diffraction orders shifted by the Bloch vector.""" return bloch_vec + np.atleast_2d(orders).T @@ -3385,7 +3378,7 @@ def shifted_orders(orders: tuple[int, ...], bloch_vec: Union[float, np.ndarray]) def reciprocal_coords( orders: np.ndarray, size: float, - bloch_vec: Union[float, np.ndarray], + bloch_vec: float | np.ndarray, f: float, medium: MediumType, ) -> np.ndarray: @@ -3760,7 +3753,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: Optional[float] = None + self, pol_basis: PolarizationBasis = "linear", tilt_angle: float | None = 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 @@ -3838,7 +3831,7 @@ def radiated_power(self) -> FreqDataArray: return FreqDataArray(sign * self.flux.values, {"f": self.f}) def partial_directivity( - self, pol_basis: PolarizationBasis = "linear", tilt_angle: Optional[float] = None + self, pol_basis: PolarizationBasis = "linear", tilt_angle: float | None = 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 @@ -3907,7 +3900,7 @@ def calc_partial_gain( self, power_in: FreqDataArray, pol_basis: PolarizationBasis = "linear", - tilt_angle: Optional[float] = None, + tilt_angle: float | None = 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 9c81bdbfae..9f47d6f395 100644 --- a/tidy3d/components/data/sim_data.py +++ b/tidy3d/components/data/sim_data.py @@ -7,7 +7,7 @@ import re from abc import ABC from collections import defaultdict -from typing import Callable, Optional, Union +from collections.abc import Callable import h5py import numpy as np @@ -57,7 +57,7 @@ class AdjointSourceInfo(Tidy3dBaseModel): description="Set of processed sources to include in the adjoint simulation.", ) - post_norm: Union[float, FreqDataArray] = pd.Field( + post_norm: float | FreqDataArray = pd.Field( ..., title="Post Normalization Values", description="Factor to multiply the adjoint fields by after running " @@ -428,7 +428,7 @@ def mnt_data_from_file(cls, fname: str, mnt_name: str, **parse_obj_kwargs) -> Mo raise ValueError(f"No monitor with name '{mnt_name}' found in data file.") @staticmethod - def apply_phase(data: Union[xr.DataArray, xr.Dataset], phase: float = 0.0) -> xr.DataArray: + def apply_phase(data: xr.DataArray | xr.Dataset, phase: float = 0.0) -> xr.DataArray: """Apply a phase to xarray data.""" if phase != 0.0: if np.any(np.iscomplex(data.values)): @@ -449,8 +449,8 @@ def plot_field_monitor_data( eps_alpha: float = 0.2, phase: float = 0.0, robust: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, + vmin: float | None = None, + vmax: float | None = None, ax: Ax = None, shading: str = "flat", **sel_kwargs, @@ -665,8 +665,8 @@ def plot_field( eps_alpha: float = 0.2, phase: float = 0.0, robust: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, + vmin: float | None = None, + vmax: float | None = None, ax: Ax = None, shading: str = "flat", **sel_kwargs, @@ -743,11 +743,11 @@ def plot_scalar_array( field_data: xr.DataArray, axis: Axis, position: float, - freq: Optional[float] = None, + freq: float | None = None, eps_alpha: float = 0.2, robust: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, + vmin: float | None = None, + vmax: float | None = None, cmap_type: ColormapType = "divergent", ax: Ax = None, **kwargs, diff --git a/tidy3d/components/data/unstructured/base.py b/tidy3d/components/data/unstructured/base.py index b5866fd2d6..18d094bd87 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, Optional, Union +from typing import Literal import numpy as np import pydantic.v1 as pd @@ -500,7 +500,7 @@ def _read_vtkLegacyFile(fname: str): def _from_vtk_obj( cls, vtk_obj, - field: Optional[str] = None, + field: str | None = None, remove_degenerate_cells: bool = False, remove_unused_points: bool = False, values_type=IndexedDataArray, @@ -533,7 +533,7 @@ def _from_vtk_obj_internal( def from_vtu( cls, file: str, - field: Optional[str] = None, + field: str | None = None, remove_degenerate_cells: bool = False, remove_unused_points: bool = False, ignore_invalid_cells: bool = False, @@ -572,7 +572,7 @@ def from_vtu( def from_vtk( cls, file: str, - field: Optional[str] = None, + field: str | None = None, remove_degenerate_cells: bool = False, remove_unused_points: bool = False, ignore_invalid_cells: bool = False, @@ -641,7 +641,7 @@ def _get_values_from_vtk( cls, vtk_obj, num_points: pd.PositiveInt, - field: Optional[str] = None, + field: str | None = None, values_type=IndexedDataArray, expect_complex=None, ) -> IndexedDataArray: @@ -774,7 +774,7 @@ def _plane_slice_raw(self, axis: Axis, pos: float): @abstractmethod @requires_vtk - def plane_slice(self, axis: Axis, pos: float) -> Union[XrDataArray, UnstructuredGridDataset]: + def plane_slice(self, axis: Axis, pos: float) -> XrDataArray | UnstructuredGridDataset: """Slice data with a plane and return the Tidy3D representation of the result (``UnstructuredGridDataset``). @@ -870,12 +870,12 @@ def reflect( def interp( self, - x: Union[float, ArrayLike] = None, - y: Union[float, ArrayLike] = None, - z: Union[float, ArrayLike] = None, - fill_value: Optional[ - Union[float, Literal["extrapolate"]] - ] = None, # TODO: an array if multiple fields? + x: float | ArrayLike = None, + y: float | ArrayLike = None, + z: float | ArrayLike = None, + fill_value: float + | Literal["extrapolate"] + | None = None, # TODO: an array if multiple fields? use_vtk: bool = False, method: Literal["linear", "nearest"] = "linear", max_samples_per_step: int = DEFAULT_MAX_SAMPLES_PER_STEP, @@ -992,12 +992,12 @@ def _non_spatial_interp(self, method="linear", fill_value=np.nan, **coords_kwarg def _spatial_interp( self, - x: Union[float, ArrayLike], - y: Union[float, ArrayLike], - z: Union[float, ArrayLike], - fill_value: Optional[ - Union[float, Literal["extrapolate"]] - ] = None, # TODO: an array if multiple fields? + x: float | ArrayLike, + y: float | ArrayLike, + z: float | ArrayLike, + fill_value: float + | Literal["extrapolate"] + | None = None, # TODO: an array if multiple fields? use_vtk: bool = False, method: Literal["linear", "nearest"] = "linear", max_samples_per_step: int = DEFAULT_MAX_SAMPLES_PER_STEP, @@ -1269,7 +1269,7 @@ def _interp_py_general( max_samples_per_step: int, max_cells_per_step: int, rel_tol: float, - axis_ignore: Union[Axis, None], + axis_ignore: Axis | None, ) -> ArrayLike: """A general function (2D and 3D) to interpolate data at provided x, y, and z using vectorized python implementation. @@ -1697,12 +1697,12 @@ def _interp_py_chunk( @requires_vtk def sel( self, - x: Union[float, ArrayLike] = None, - y: Union[float, ArrayLike] = None, - z: Union[float, ArrayLike] = None, - method: Optional[Literal["None", "nearest", "pad", "ffill", "backfill", "bfill"]] = None, + x: float | ArrayLike = None, + y: float | ArrayLike = None, + z: float | ArrayLike = None, + method: Literal["None", "nearest", "pad", "ffill", "backfill", "bfill"] | None = None, **sel_kwargs, - ) -> Union[UnstructuredGridDataset, XrDataArray]: + ) -> UnstructuredGridDataset | XrDataArray: """Extract/interpolate data along one or more spatial or non-spatial directions. Must provide at least one argument among 'x', 'y', 'z' or non-spatial dimensions through additional arguments. Along spatial dimensions a suitable slicing of grid is applied (plane slice, line slice, or interpolation). Selection along non-spatial dimensions is forwarded to diff --git a/tidy3d/components/data/unstructured/tetrahedral.py b/tidy3d/components/data/unstructured/tetrahedral.py index 42ac4b58b1..17186db11a 100644 --- a/tidy3d/components/data/unstructured/tetrahedral.py +++ b/tidy3d/components/data/unstructured/tetrahedral.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - import numpy as np import pydantic.v1 as pd from xarray import DataArray as XrDataArray @@ -298,12 +296,12 @@ def _interp_py( @requires_vtk def sel( self, - x: Union[float, ArrayLike] = None, - y: Union[float, ArrayLike] = None, - z: Union[float, ArrayLike] = None, + x: float | ArrayLike = None, + y: float | ArrayLike = None, + z: float | ArrayLike = None, method=None, **sel_kwargs, - ) -> Union[TriangularGridDataset, XrDataArray]: + ) -> TriangularGridDataset | XrDataArray: """Extract/interpolate data along one or more spatial or non-spatial directions. Must provide at least one argument among 'x', 'y', 'z' or non-spatial dimensions through additional arguments. Along spatial dimensions a suitable slicing of grid is applied (plane slice, line slice, or interpolation). Selection along non-spatial dimensions is forwarded to diff --git a/tidy3d/components/data/unstructured/triangular.py b/tidy3d/components/data/unstructured/triangular.py index 32d54f259b..6d68790796 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 Literal, Optional, Union +from typing import Literal import numpy as np import pydantic.v1 as pd @@ -311,10 +311,10 @@ def reflect( def _spatial_interp( self, - x: Union[float, ArrayLike], - y: Union[float, ArrayLike], - z: Union[float, ArrayLike], - fill_value: Optional[Union[float, Literal["extrapolate"]]] = None, + x: float | ArrayLike, + y: float | ArrayLike, + z: float | ArrayLike, + fill_value: float | Literal["extrapolate"] | None = None, use_vtk: bool = False, method: Literal["linear", "nearest"] = "linear", ignore_normal_pos: bool = True, @@ -450,10 +450,10 @@ def _interp_py( @requires_vtk def sel( self, - x: Union[float, ArrayLike] = None, - y: Union[float, ArrayLike] = None, - z: Union[float, ArrayLike] = None, - method: Optional[Literal["None", "nearest", "pad", "ffill", "backfill", "bfill"]] = None, + x: float | ArrayLike = None, + y: float | ArrayLike = None, + z: float | ArrayLike = None, + method: Literal["None", "nearest", "pad", "ffill", "backfill", "bfill"] | None = None, **sel_kwargs, ) -> XrDataArray: """Extract/interpolate data along one or more spatial or non-spatial directions. Must provide at least one argument @@ -584,11 +584,11 @@ def plot( grid: bool = True, cbar: bool = True, cmap: str = "viridis", - vmin: Optional[float] = None, - vmax: Optional[float] = None, + vmin: float | None = None, + vmax: float | None = None, shading: Literal["gourand", "flat"] = "gouraud", - cbar_kwargs: Optional[dict] = None, - pcolor_kwargs: Optional[dict] = None, + cbar_kwargs: dict | None = None, + pcolor_kwargs: dict | None = 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 e2c96c0031..d3c32ebcee 100644 --- a/tidy3d/components/data/utils.py +++ b/tidy3d/components/data/utils.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - import numpy as np import xarray as xr @@ -14,13 +12,13 @@ from .unstructured.tetrahedral import TetrahedralGridDataset from .unstructured.triangular import TriangularGridDataset -UnstructuredGridDatasetType = Union[TriangularGridDataset, TetrahedralGridDataset] +UnstructuredGridDatasetType = TriangularGridDataset | TetrahedralGridDataset -CustomSpatialDataType = Union[SpatialDataArray, UnstructuredGridDatasetType] -CustomSpatialDataTypeAnnotated = Union[SpatialDataArray, annotate_type(UnstructuredGridDatasetType)] +CustomSpatialDataType = SpatialDataArray | UnstructuredGridDatasetType +CustomSpatialDataTypeAnnotated = SpatialDataArray | annotate_type(UnstructuredGridDatasetType) -def _get_numpy_array(data_array: Union[ArrayLike, DataArray, UnstructuredGridDataset]) -> ArrayLike: +def _get_numpy_array(data_array: ArrayLike | DataArray | UnstructuredGridDataset) -> ArrayLike: """Get numpy representation of dataarray/dataset values.""" if isinstance(data_array, UnstructuredGridDataset): return data_array.values.values @@ -30,8 +28,8 @@ def _get_numpy_array(data_array: Union[ArrayLike, DataArray, UnstructuredGridDat def _zeros_like( - data_array: Union[ArrayLike, xr.DataArray, UnstructuredGridDataset], -) -> Union[ArrayLike, xr.DataArray, UnstructuredGridDataset]: + data_array: ArrayLike | xr.DataArray | UnstructuredGridDataset, +) -> ArrayLike | xr.DataArray | UnstructuredGridDataset: """Get a zeroed replica of dataarray/dataset.""" if isinstance(data_array, UnstructuredGridDataset): return data_array.updated_copy(values=xr.zeros_like(data_array.values)) @@ -41,8 +39,8 @@ def _zeros_like( def _ones_like( - data_array: Union[ArrayLike, xr.DataArray, UnstructuredGridDataset], -) -> Union[ArrayLike, xr.DataArray, UnstructuredGridDataset]: + data_array: ArrayLike | xr.DataArray | UnstructuredGridDataset, +) -> ArrayLike | xr.DataArray | UnstructuredGridDataset: """Get a unity replica of dataarray/dataset.""" if isinstance(data_array, UnstructuredGridDataset): return data_array.updated_copy(values=xr.ones_like(data_array.values)) @@ -52,8 +50,8 @@ def _ones_like( def _check_same_coordinates( - a: Union[ArrayLike, xr.DataArray, UnstructuredGridDataset], - b: Union[ArrayLike, xr.DataArray, UnstructuredGridDataset], + a: ArrayLike | xr.DataArray | UnstructuredGridDataset, + b: ArrayLike | xr.DataArray | UnstructuredGridDataset, ) -> bool: """Check whether two array are defined at the same coordinates.""" diff --git a/tidy3d/components/data/validators.py b/tidy3d/components/data/validators.py index 4988c36044..875043aa48 100644 --- a/tidy3d/components/data/validators.py +++ b/tidy3d/components/data/validators.py @@ -1,8 +1,6 @@ # special validators for Datasets from __future__ import annotations -from typing import Optional - import numpy as np import pydantic.v1 as pd @@ -23,7 +21,7 @@ def no_nans(cls, val): if val is None: return val - def error_if_has_nans(value, identifier: Optional[str] = None) -> None: + def error_if_has_nans(value, identifier: str | None = None) -> None: """Recursively check if value (or iterable) has nans and error if so.""" def has_nans(values) -> bool: diff --git a/tidy3d/components/dispersion_fitter.py b/tidy3d/components/dispersion_fitter.py index 8c1be3a827..527ee53ed3 100644 --- a/tidy3d/components/dispersion_fitter.py +++ b/tidy3d/components/dispersion_fitter.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional - import numpy as np from pydantic.v1 import Field, NonNegativeFloat, PositiveFloat, PositiveInt, validator @@ -144,20 +142,20 @@ class AdvancedFastFitterParam(Tidy3dBaseModel): description="Whether to show unweighted RMS error in addition to the default weighted " 'RMS error. Requires ``td.config.logging_level = "INFO"``.', ) - relaxed: Optional[bool] = Field( + relaxed: bool | None = Field( None, title="Relaxed", description="Whether to use relaxed fitting algorithm, which " "has better pole relocation properties. If ``None``, will try both original and relaxed " "algorithms.", ) - smooth: Optional[bool] = Field( + smooth: bool | None = Field( None, title="Smooth", description="Whether to use real starting poles, which can help when fitting smooth data. " "If ``None``, will try both real and complex starting poles.", ) - logspacing: Optional[bool] = Field( + logspacing: bool | None = Field( None, title="Log spacing", description="Whether to space the poles logarithmically. " @@ -223,14 +221,14 @@ class FastFitterData(AdvancedFastFitterParam): title="eps_inf", description="Value of ``eps_inf``.", ) - poles: Optional[ArrayComplex1D] = Field( + poles: ArrayComplex1D | None = Field( None, title="Pole frequencies in eV", description="Pole frequencies in eV" ) - residues: Optional[ArrayComplex1D] = Field( + residues: ArrayComplex1D | None = Field( None, title="Residues in eV", description="Residues in eV" ) - passivity_optimized: Optional[bool] = Field( + passivity_optimized: bool | None = Field( False, title="Passivity optimized", description="Whether the fit was optimized to enforce passivity. If None, " @@ -758,7 +756,7 @@ def fit( resp_data: ArrayComplex1D, min_num_poles: PositiveInt = 1, max_num_poles: PositiveInt = DEFAULT_MAX_POLES, - resp_inf: Optional[float] = None, + resp_inf: float | None = None, tolerance_rms: NonNegativeFloat = DEFAULT_TOLERANCE_RMS, advanced_param: AdvancedFastFitterParam = None, scale_factor: PositiveFloat = 1, diff --git a/tidy3d/components/eme/data/monitor_data.py b/tidy3d/components/eme/data/monitor_data.py index 558023c0e8..234a06963e 100644 --- a/tidy3d/components/eme/data/monitor_data.py +++ b/tidy3d/components/eme/data/monitor_data.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - import pydantic.v1 as pd from tidy3d.components.base_sim.data.monitor_data import AbstractMonitorData @@ -50,11 +48,11 @@ class EMECoefficientData(AbstractMonitorData, EMECoefficientDataset): ) -EMEMonitorDataType = Union[ - EMEModeSolverData, - EMEFieldData, - EMECoefficientData, - ModeSolverData, - PermittivityData, - MediumData, -] +EMEMonitorDataType = ( + EMEModeSolverData + | EMEFieldData + | EMECoefficientData + | ModeSolverData + | PermittivityData + | MediumData +) diff --git a/tidy3d/components/eme/data/sim_data.py b/tidy3d/components/eme/data/sim_data.py index 03d16e65d9..1d6992ecbd 100644 --- a/tidy3d/components/eme/data/sim_data.py +++ b/tidy3d/components/eme/data/sim_data.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Literal, Optional, Union +from typing import Literal import numpy as np import pydantic.v1 as pd @@ -35,11 +35,11 @@ class EMESimulationData(AbstractYeeGridSimulationData): "associated with the monitors of the original :class:`.EMESimulation`.", ) - smatrix: Optional[EMESMatrixDataset] = pd.Field( + smatrix: EMESMatrixDataset | None = pd.Field( None, title="S Matrix", description="Scattering matrix of the EME simulation." ) - port_modes_raw: Optional[EMEModeSolverData] = pd.Field( + port_modes_raw: EMEModeSolverData | None = pd.Field( None, title="Port Modes", description="Modes associated with the two ports of the EME device. " @@ -57,7 +57,7 @@ def port_modes(self): return self.port_modes_raw.symmetry_expanded_copy def _extract_mode_solver_data( - self, data: EMEModeSolverData, eme_cell_index: int, sweep_index: Optional[int] = None + self, data: EMEModeSolverData, eme_cell_index: int, sweep_index: int | None = None ) -> ModeSolverData: """Extract :class:`.ModeSolverData` at a given ``eme_cell_index``. Assumes the :class:`.EMEModeSolverMonitor` spans the entire simulation and has @@ -162,7 +162,7 @@ def port_modes_list_sweep(self) -> list[tuple[ModeSolverData, ModeSolverData]]: return port_modes_list def smatrix_in_basis( - self, modes1: Union[FieldData, ModeData] = None, modes2: Union[FieldData, ModeData] = None + self, modes1: FieldData | ModeData = None, modes2: FieldData | ModeData = None ) -> EMESMatrixDataset: """Express the scattering matrix in the provided basis. Change of basis is done by computing overlaps between provided modes and port modes. @@ -366,7 +366,7 @@ def smatrix_in_basis( def field_in_basis( self, field: EMEFieldData, - modes: Union[FieldData, ModeData] = None, + modes: FieldData | ModeData = None, port_index: Literal[0, 1] = 0, ) -> EMEFieldData: """Express the electromagnetic field in the provided basis. diff --git a/tidy3d/components/eme/grid.py b/tidy3d/components/eme/grid.py index d197c60677..be10d5e6a1 100644 --- a/tidy3d/components/eme/grid.py +++ b/tidy3d/components/eme/grid.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Literal, Optional, Union +from typing import Literal import numpy as np import pydantic.v1 as pd @@ -26,7 +26,7 @@ class EMEModeSpec(ModeSpec): """Mode spec for EME cells. Overrides some of the defaults and allowed values.""" - track_freq: Union[TrackFreq, None] = pd.Field( + track_freq: TrackFreq | None = pd.Field( None, title="Mode Tracking Frequency", description="Parameter that turns on/off mode tracking based on their similarity. " @@ -106,7 +106,7 @@ class EMEGridSpec(Tidy3dBaseModel, ABC): "the EME solver to reuse the modes and cell interface scattering matrices.", ) - name: Optional[str] = pd.Field( + name: str | None = pd.Field( None, title="Name", description="Name of this 'EMEGridSpec'. Used in 'EMEPeriodicitySweep'." ) @@ -378,9 +378,6 @@ def num_real_cells(self) -> int: return len(self.mode_specs) -EMESubgridType = Union[EMEUniformGrid, EMEExplicitGrid, "EMECompositeGrid"] - - class EMECompositeGrid(EMEGridSpec): """EME grid made out of multiple subgrids. @@ -397,7 +394,7 @@ class EMECompositeGrid(EMEGridSpec): ... ) """ - subgrids: list[EMESubgridType] = pd.Field( + subgrids: list[EMEUniformGrid | EMEExplicitGrid | EMECompositeGrid] = pd.Field( ..., title="Subgrids", description="Subgrids in the composite grid." ) @@ -536,8 +533,8 @@ def from_structure_groups( structure_groups: list[list[Structure]], axis: Axis, mode_specs: list[EMEModeSpec], - names: Optional[list[str]] = None, - num_reps: Optional[list[pd.PositiveInt]] = None, + names: list[str] | None = None, + num_reps: list[pd.PositiveInt] | None = None, ) -> EMECompositeGrid: """Create a composite EME grid with boundaries aligned with structure bounding boxes. @@ -817,4 +814,4 @@ def cell_indices_in_box(self, box: Box) -> list[pd.NonNegativeInteger]: return indices -EMEGridSpecType = Union[EMEUniformGrid, EMECompositeGrid, EMEExplicitGrid] +EMEGridSpecType = EMEUniformGrid | EMECompositeGrid | EMEExplicitGrid diff --git a/tidy3d/components/eme/monitor.py b/tidy3d/components/eme/monitor.py index bee336a3c7..6ce31e0d73 100644 --- a/tidy3d/components/eme/monitor.py +++ b/tidy3d/components/eme/monitor.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Literal, Optional, Union +from typing import Literal import pydantic.v1 as pd @@ -22,7 +22,7 @@ class EMEMonitor(AbstractMonitor, ABC): """Abstract EME monitor.""" - freqs: Optional[FreqArray] = pd.Field( + freqs: FreqArray | None = pd.Field( None, title="Monitor Frequencies", description="Frequencies at which the monitor will record. " @@ -30,7 +30,7 @@ class EMEMonitor(AbstractMonitor, ABC): "A value of 'None' will record at all simulation 'freqs'.", ) - num_modes: Optional[pd.NonNegativeInt] = pd.Field( + num_modes: pd.NonNegativeInt | None = pd.Field( None, title="Number of Modes", description="Maximum number of modes for the monitor to record. " @@ -38,7 +38,7 @@ class EMEMonitor(AbstractMonitor, ABC): "A value of 'None' will record all modes.", ) - num_sweep: Optional[pd.NonNegativeInt] = pd.Field( + num_sweep: pd.NonNegativeInt | None = pd.Field( 1, title="Number of Sweep Indices", description="Number of sweep indices for the monitor to record. " @@ -228,7 +228,7 @@ class EMEFieldMonitor(EMEMonitor, AbstractFieldMonitor): "primal grid nodes). Default (False) is used internally in EME propagation.", ) - num_modes: Optional[pd.NonNegativeInt] = pd.Field( + num_modes: pd.NonNegativeInt | None = pd.Field( None, title="Number of Modes", description="Maximum number of modes for the monitor to record. " @@ -302,11 +302,11 @@ def storage_size( return bytes_single -EMEMonitorType = Union[ - EMEModeSolverMonitor, - EMEFieldMonitor, - EMECoefficientMonitor, - ModeSolverMonitor, - PermittivityMonitor, - MediumMonitor, -] +EMEMonitorType = ( + EMEModeSolverMonitor + | EMEFieldMonitor + | EMECoefficientMonitor + | ModeSolverMonitor + | PermittivityMonitor + | MediumMonitor +) diff --git a/tidy3d/components/eme/simulation.py b/tidy3d/components/eme/simulation.py index b01b928e4f..a8c603c75f 100644 --- a/tidy3d/components/eme/simulation.py +++ b/tidy3d/components/eme/simulation.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Literal, Optional, Union +from typing import Literal try: import matplotlib as mpl @@ -247,7 +247,7 @@ class EMESimulation(AbstractYeeGridSimulation): "along the propagation axis.", ) - sweep_spec: Optional[EMESweepSpecType] = pd.Field( + sweep_spec: EMESweepSpecType | None = pd.Field( None, title="EME Sweep Specification", description="Specification for a parameter sweep to be performed during the EME " @@ -255,7 +255,7 @@ class EMESimulation(AbstractYeeGridSimulation): "in 'sim_data.smatrix'. Other simulation monitor data is not included in the sweep.", ) - constraint: Optional[Literal["passive", "unitary"]] = pd.Field( + constraint: Literal["passive", "unitary"] | None = pd.Field( "passive", title="EME Constraint", description="Constraint for EME propagation, imposed at cell interfaces. " @@ -308,12 +308,12 @@ def _validate_structures(cls, val): @add_ax_if_none def plot_eme_ports( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, **kwargs, ) -> Ax: """Plot the EME ports.""" @@ -352,12 +352,12 @@ def plot_eme_ports( def plot_eme_subgrid_boundaries( self, eme_grid_spec: EMEGridSpec, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, **kwargs, ) -> Ax: """Plot the EME subgrid boundaries. @@ -404,12 +404,12 @@ def plot_eme_subgrid_boundaries( @add_ax_if_none def plot_eme_grid( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, **kwargs, ) -> Ax: """Plot the EME grid.""" @@ -445,14 +445,14 @@ def plot_eme_grid( @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - source_alpha: Optional[float] = None, - monitor_alpha: Optional[float] = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + source_alpha: float | None = None, + monitor_alpha: float | None = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, **patch_kwargs, ) -> Ax: """Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate. @@ -1109,11 +1109,11 @@ def _to_fdtd_sim(self) -> Simulation: def subsection( self, region: Box, - grid_spec: Union[GridSpec, Literal["identical"]] = None, - eme_grid_spec: Union[EMEGridSpec, Literal["identical"]] = None, - symmetry: Optional[tuple[Symmetry, Symmetry, Symmetry]] = None, + grid_spec: GridSpec | Literal["identical"] = None, + eme_grid_spec: EMEGridSpec | Literal["identical"] = None, + symmetry: tuple[Symmetry, Symmetry, Symmetry] | None = None, warn_symmetry_expansion: bool = True, - monitors: Optional[tuple[MonitorType, ...]] = None, + monitors: tuple[MonitorType, ...] | None = None, remove_outside_structures: bool = True, remove_outside_custom_mediums: bool = False, **kwargs, diff --git a/tidy3d/components/eme/sweep.py b/tidy3d/components/eme/sweep.py index 6ded35c645..ea661cf872 100644 --- a/tidy3d/components/eme/sweep.py +++ b/tidy3d/components/eme/sweep.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Union import pydantic.v1 as pd @@ -126,4 +125,4 @@ def num_sweep(self) -> pd.PositiveInt: return len(self.num_reps) -EMESweepSpecType = Union[EMELengthSweep, EMEModeSweep, EMEFreqSweep, EMEPeriodicitySweep] +EMESweepSpecType = EMELengthSweep | EMEModeSweep | EMEFreqSweep | EMEPeriodicitySweep diff --git a/tidy3d/components/field_projection.py b/tidy3d/components/field_projection.py index 4346a85d50..460421af99 100644 --- a/tidy3d/components/field_projection.py +++ b/tidy3d/components/field_projection.py @@ -3,7 +3,6 @@ from __future__ import annotations from collections.abc import Iterable -from typing import Union import autograd.numpy as anp import numpy as np @@ -46,7 +45,7 @@ # Numpy float array and related array types -ArrayLikeN2F = Union[float, tuple[float, ...], ArrayComplex4D] +ArrayLikeN2F = float | tuple[float, ...] | ArrayComplex4D class FieldProjector(Tidy3dBaseModel): @@ -79,7 +78,7 @@ class FieldProjector(Tidy3dBaseModel): "near field.", ) - pts_per_wavelength: Union[int, type(None)] = pydantic.Field( + pts_per_wavelength: int | type(None) = pydantic.Field( PTS_PER_WVL, title="Points per wavelength", description="Number of points per wavelength in the background medium with which " @@ -355,8 +354,8 @@ def _resample_surface_currents( @staticmethod def trapezoid( ary: np.ndarray, - pts: Union[Iterable[np.ndarray], np.ndarray], - axes: Union[Iterable[int], int] = 0, + pts: Iterable[np.ndarray] | np.ndarray, + axes: Iterable[int] | int = 0, ): """Trapezoidal integration in n dimensions. diff --git a/tidy3d/components/frequency_extrapolation.py b/tidy3d/components/frequency_extrapolation.py index e2768bba36..51c532aaad 100644 --- a/tidy3d/components/frequency_extrapolation.py +++ b/tidy3d/components/frequency_extrapolation.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional - import pydantic.v1 as pydantic from tidy3d.components.base import Tidy3dBaseModel @@ -34,7 +32,7 @@ class AbstractLowFrequencySmoothingSpec(Tidy3dBaseModel): le=3, ) - max_deviation: Optional[float] = pydantic.Field( + max_deviation: float | None = pydantic.Field( 0.5, title="Maximum Deviation", description="The maximum deviation (in fraction of the trusted values) to allow for the low frequency smoothing.", diff --git a/tidy3d/components/geometry/base.py b/tidy3d/components/geometry/base.py index bc9912541b..059dc3c085 100644 --- a/tidy3d/components/geometry/base.py +++ b/tidy3d/components/geometry/base.py @@ -5,7 +5,8 @@ import functools import pathlib from abc import ABC, abstractmethod -from typing import Any, Callable, Optional, Union +from collections.abc import Callable +from typing import Any import autograd.numpy as np import pydantic.v1 as pydantic @@ -249,7 +250,7 @@ def intersections_tilted_plane( """ def intersections_plane( - self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + self, x: float | None = None, y: float | None = None, z: float | None = None ) -> list[Shapely]: """Returns list of shapely geometries at plane specified by one non-None value of x,y,z. @@ -372,7 +373,7 @@ def contains( return True def intersects_plane( - self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + self, x: float | None = None, y: float | None = None, z: float | None = None ) -> bool: """Whether self intersects plane specified by one non-None value of x,y,z. @@ -503,9 +504,9 @@ def _update_from_bounds(self, bounds: tuple[float, float], axis: Axis) -> Geomet @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, plot_length_units: LengthUnit = None, viz_spec: VisualizationSpec = None, @@ -539,7 +540,7 @@ def plot( """ # find shapes that intersect self at plane - axis, position = self.parse_xyz_kwargs(x=x, y=y, z=z) + axis, _position = self.parse_xyz_kwargs(x=x, y=y, z=z) shapes_intersect = self.intersections_plane(x=x, y=y, z=z) plot_params = self.plot_params @@ -668,9 +669,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: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, plot_length_units: LengthUnit = None, ) -> Ax: """Sets the axis labels, tick labels, and title based on ``axis`` @@ -967,7 +968,7 @@ def scaled(self, x: float = 1.0, y: float = 1.0, z: float = 1.0) -> Geometry: """ return Transformed(geometry=self, transform=Transformed.scaling(x, y, z)) - def rotated(self, angle: float, axis: Union[Axis, Coordinate]) -> Geometry: + def rotated(self, angle: float, axis: Axis | Coordinate) -> Geometry: """Return a rotated copy of this geometry. Parameters @@ -1158,7 +1159,7 @@ def kspace_2_sph(ux: float, uy: float, axis: Axis) -> tuple[float, float]: def load_gds_vertices_gdstk( gds_cell, gds_layer: int, - gds_dtype: Optional[int] = None, + gds_dtype: int | None = None, gds_scale: pydantic.PositiveFloat = 1.0, ) -> list[ArrayFloat2D]: """Load polygon vertices from a ``gdstk.Cell``. @@ -1212,7 +1213,7 @@ def from_gds( axis: Axis, slab_bounds: tuple[float, float], gds_layer: int, - gds_dtype: Optional[int] = None, + gds_dtype: int | None = None, gds_scale: pydantic.PositiveFloat = 1.0, dilation: float = 0.0, sidewall_angle: float = 0, @@ -1323,9 +1324,9 @@ def from_shapely( @verify_packages_import(["gdstk"]) def to_gdstk( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, gds_layer: pydantic.NonNegativeInt = 0, gds_dtype: pydantic.NonNegativeInt = 0, ) -> list: @@ -1373,9 +1374,9 @@ def to_gdstk( def to_gds( self, cell, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, gds_layer: pydantic.NonNegativeInt = 0, gds_dtype: pydantic.NonNegativeInt = 0, ) -> None: @@ -1413,9 +1414,9 @@ def to_gds( def to_gds_file( self, fname: str, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, gds_layer: pydantic.NonNegativeInt = 0, gds_dtype: pydantic.NonNegativeInt = 0, gds_cell_name: str = "MAIN", @@ -1696,7 +1697,7 @@ def reference_axis_pos(self) -> float: return self.center_axis def intersections_plane( - self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + self, x: float | None = None, y: float | None = None, z: float | None = None ): """Returns shapely geometry at plane specified by one non None value of x,y,z. @@ -2063,7 +2064,7 @@ def _do_intersections_tilted_plane( return path.polygons_full def intersections_plane( - self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + self, x: float | None = None, y: float | None = None, z: float | None = None ): """Returns shapely geometry at plane specified by one non None value of x,y,z. @@ -2220,12 +2221,12 @@ def _update_from_bounds(self, bounds: tuple[float, float], axis: Axis) -> Box: def _plot_arrow( self, 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, + x: float | None = None, + y: float | None = None, + z: float | None = None, + color: str | None = None, + alpha: float | None = None, + bend_radius: float | None = None, bend_axis: Axis = None, both_dirs: bool = False, ax: Ax = None, @@ -2561,8 +2562,8 @@ def _arr_at_face(arr: xr.DataArray, interp_point: float, dim_normal: str) -> xr. @staticmethod def _integrate_face( arr_at_face: xr.DataArray, - integration_dims: Union[tuple[str], tuple[str, str]], - integration_bounds: Union[tuple[Coordinate2D], tuple[Coordinate2D, Coordinate2D]], + integration_dims: tuple[str] | tuple[str, str], + integration_bounds: tuple[Coordinate2D] | tuple[Coordinate2D, Coordinate2D], ) -> complex: """Perform the integration of the surface and sum over the frequency component of the gradient.""" @@ -2778,7 +2779,7 @@ def _derivative_face_pec( Surface gradient vjp value """ - fld_H_normal, flds_H_perp = self.pop_axis(("Hx", "Hy", "Hz"), axis=axis_normal) + _fld_H_normal, flds_H_perp = self.pop_axis(("Hx", "Hy", "Hz"), axis=axis_normal) Hs_perp = tuple(H_der_map[key] for key in flds_H_perp) is_2d, zero_dimension, do_singularity_correction = self._check_singularity_correction_pec( @@ -3047,7 +3048,7 @@ def scaling(x: float = 1.0, y: float = 1.0, z: float = 1.0) -> MatrixReal4x4: ) @staticmethod - def rotation(angle: float, axis: Union[Axis, Coordinate]) -> MatrixReal4x4: + def rotation(angle: float, axis: Axis | Coordinate) -> MatrixReal4x4: """Return a rotation matrix. Parameters @@ -3259,7 +3260,7 @@ def intersections_tilted_plane( ) def intersections_plane( - self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + self, x: float | None = None, y: float | None = None, z: float | None = None ) -> list[Shapely]: """Returns list of shapely geometries at plane specified by one non-None value of x,y,z. @@ -3470,7 +3471,7 @@ def intersections_tilted_plane( ] def intersections_plane( - self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + self, x: float | None = None, y: float | None = None, z: float | None = None ) -> list[Shapely]: """Returns list of shapely geometries at plane specified by one non-None value of x,y,z. diff --git a/tidy3d/components/geometry/mesh.py b/tidy3d/components/geometry/mesh.py index ccf6305c8f..7e019ab145 100644 --- a/tidy3d/components/geometry/mesh.py +++ b/tidy3d/components/geometry/mesh.py @@ -3,7 +3,8 @@ from __future__ import annotations from abc import ABC -from typing import Callable, Literal, Optional, Union +from collections.abc import Callable +from typing import Literal import numpy as np import pydantic.v1 as pydantic @@ -34,7 +35,7 @@ class TriangleMesh(base.Geometry, ABC): >>> stl_geom = TriangleMesh.from_vertices_faces(vertices, faces) """ - mesh_dataset: Optional[TriangleMeshDataset] = pydantic.Field( + mesh_dataset: TriangleMeshDataset | None = pydantic.Field( ..., title="Surface mesh data", description="Surface mesh data.", @@ -152,9 +153,9 @@ def from_stl( filename: str, scale: float = 1.0, origin: tuple[float, float, float] = (0, 0, 0), - solid_index: Optional[int] = None, + solid_index: int | None = None, **kwargs, - ) -> Union[TriangleMesh, base.GeometryGroup]: + ) -> TriangleMesh | base.GeometryGroup: """Load a :class:`.TriangleMesh` directly from an STL file. The ``solid_index`` parameter can be used to select a single solid from the file. Otherwise, if the file contains a single solid, it will be loaded as a @@ -554,7 +555,7 @@ def intersections_tilted_plane( return path.polygons_full def intersections_plane( - self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + self, x: float | None = None, y: float | None = None, z: float | None = None ) -> list[Shapely]: """Returns list of shapely geometries at plane specified by one non-None value of x,y,z. @@ -656,9 +657,9 @@ def inside( @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **patch_kwargs, ) -> Ax: diff --git a/tidy3d/components/geometry/polyslab.py b/tidy3d/components/geometry/polyslab.py index 2c74efba9c..b787965db3 100644 --- a/tidy3d/components/geometry/polyslab.py +++ b/tidy3d/components/geometry/polyslab.py @@ -5,7 +5,6 @@ import math from copy import copy from functools import lru_cache -from typing import Optional, Union import autograd.numpy as np import pydantic.v1 as pydantic @@ -265,7 +264,7 @@ def from_gds( axis: Axis, slab_bounds: tuple[float, float], gds_layer: int, - gds_dtype: Optional[int] = None, + gds_dtype: int | None = None, gds_scale: pydantic.PositiveFloat = 1.0, dilation: float = 0.0, sidewall_angle: float = 0, @@ -327,7 +326,7 @@ def from_gds( def _load_gds_vertices( gds_cell, gds_layer: int, - gds_dtype: Optional[int] = None, + gds_dtype: int | None = None, gds_scale: pydantic.PositiveFloat = 1.0, ) -> list[ArrayFloat2D]: """Import :class:`PolySlab` from a ``gdstk.Cell``. @@ -1531,7 +1530,7 @@ def _z_slices(self, sim_min: np.ndarray, sim_max: np.ndarray, is_2d: bool, dx: f @staticmethod def _clip_edge_to_bounds_t( v0_3d: np.ndarray, v1_3d: np.ndarray, sim_min: np.ndarray, sim_max: np.ndarray - ) -> Optional[tuple[float, float]]: + ) -> tuple[float, float] | None: """Parametric bounds [t0,t1] of segment within [sim_min, sim_max].""" t_start, t_end = 0.0, 1.0 edge_clip_tolerance = config.adjoint.edge_clip_tolerance @@ -1633,7 +1632,7 @@ def _collect_sidewall_patches( axis_vec[self.axis] = 1.0 # densify along axis as |theta| grows: dz scales with cos(theta) - z_centers, dz, z0, z1 = self._z_slices(sim_min, sim_max, is_2d=is_2d, dx=dx * cos_th) + z_centers, dz, _z0, _z1 = self._z_slices(sim_min, sim_max, is_2d=is_2d, dx=dx * cos_th) # early exit: no slices if (not is_2d) and len(z_centers) == 0: @@ -1817,7 +1816,7 @@ def _compute_derivative_sidewall_angle( sim_min: np.ndarray, sim_max: np.ndarray, is_2d: bool = False, - interpolators: Optional[dict] = None, + interpolators: dict | None = None, ) -> float: """VJP for dJ/dtheta where theta = sidewall_angle. @@ -2092,7 +2091,7 @@ def _compute_derivative_vertices( sim_min: np.ndarray, sim_max: np.ndarray, is_2d: bool = False, - interpolators: Optional[dict] = None, + interpolators: dict | None = None, ) -> np.ndarray: """VJP for the vertices of a ``PolySlab``. @@ -2288,7 +2287,7 @@ def scaled(self, x: float = 1.0, y: float = 1.0, z: float = 1.0) -> PolySlab: scaled_slab_bounds = tuple(scale_normal * bound for bound in self.slab_bounds) return self.updated_copy(vertices=scaled_vertices, slab_bounds=scaled_slab_bounds) - def rotated(self, angle: float, axis: Union[Axis, Coordinate]) -> PolySlab: + def rotated(self, angle: float, axis: Axis | Coordinate) -> PolySlab: """Return a rotated copy of this geometry. Parameters @@ -2362,7 +2361,7 @@ def from_gds( axis: Axis, slab_bounds: tuple[float, float], gds_layer: int, - gds_dtype: Optional[int] = None, + gds_dtype: int | None = None, gds_scale: pydantic.PositiveFloat = 1.0, dilation: float = 0.0, sidewall_angle: float = 0, diff --git a/tidy3d/components/geometry/primitives.py b/tidy3d/components/geometry/primitives.py index b75304c2ab..6f2b343ba1 100644 --- a/tidy3d/components/geometry/primitives.py +++ b/tidy3d/components/geometry/primitives.py @@ -3,7 +3,6 @@ from __future__ import annotations from math import isclose -from typing import Optional import autograd.numpy as anp import numpy as np @@ -111,7 +110,7 @@ def intersections_tilted_plane( return [shapely.Polygon(vertices[:, :2])] def intersections_plane( - self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None + self, x: float | None = None, y: float | None = None, z: float | None = None ): """Returns shapely geometry at plane specified by one non None value of x,y,z. diff --git a/tidy3d/components/geometry/utils.py b/tidy3d/components/geometry/utils.py index 8f4c556cf0..f91cfa0954 100644 --- a/tidy3d/components/geometry/utils.py +++ b/tidy3d/components/geometry/utils.py @@ -6,7 +6,7 @@ from collections.abc import Iterable from enum import Enum from math import isclose -from typing import Any, Optional, Union +from typing import Any import numpy as np import pydantic.v1 as pydantic @@ -37,21 +37,21 @@ from . import base, mesh, polyslab, primitives -GeometryType = Union[ - base.Box, - base.Transformed, - base.ClipOperation, - base.GeometryGroup, - primitives.Sphere, - primitives.Cylinder, - polyslab.PolySlab, - polyslab.ComplexPolySlabBase, - mesh.TriangleMesh, -] +GeometryType = ( + base.Box + | base.Transformed + | base.ClipOperation + | base.GeometryGroup + | primitives.Sphere + | primitives.Cylinder + | polyslab.PolySlab + | polyslab.ComplexPolySlabBase + | mesh.TriangleMesh +) def flatten_shapely_geometries( - geoms: Union[Shapely, Iterable[Shapely]], keep_types: tuple[type, ...] = (Polygon,) + geoms: Shapely | Iterable[Shapely], keep_types: tuple[type, ...] = (Polygon,) ) -> list[Shapely]: """ Flatten nested geometries into a flat list, while only keeping the specified types. @@ -184,7 +184,7 @@ def flatten_groups( *geometries: GeometryType, flatten_nonunion_type: bool = False, flatten_transformed: bool = False, - transform: Optional[MatrixReal4x4] = None, + transform: MatrixReal4x4 | None = None, ) -> GeometryType: """Iterates over all geometries, flattening groups and unions. @@ -449,9 +449,9 @@ class SnappingSpec(Tidy3dBaseModel): description="Describes how snapping positions will be chosen.", ) - margin: Optional[ - tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt, pydantic.NonNegativeInt] - ] = pydantic.Field( + margin: ( + tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt, pydantic.NonNegativeInt] | None + ) = pydantic.Field( (0, 0, 0), title="Margin", description="Number of additional grid points to consider when expanding or contracting " @@ -711,7 +711,7 @@ def _shift_value_signed( bounds: Bound, direction: Direction, shift: int, - name: Optional[str] = None, + name: str | None = None, ) -> float: """Calculate the signed distance corresponding to moving the object by ``shift`` number of cells in the positive or negative ``direction`` along the dimension given by diff --git a/tidy3d/components/grid/corner_finder.py b/tidy3d/components/grid/corner_finder.py index 6c71cd0c46..56fd4703bc 100644 --- a/tidy3d/components/grid/corner_finder.py +++ b/tidy3d/components/grid/corner_finder.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any, Literal, Optional +from typing import Any, Literal import numpy as np import pydantic.v1 as pd @@ -39,28 +39,28 @@ class CornerFinderSpec(Tidy3dBaseModel): lt=np.pi, ) - distance_threshold: Optional[pd.PositiveFloat] = pd.Field( + distance_threshold: pd.PositiveFloat | None = pd.Field( None, title="Distance Threshold In Corner Identification", description="If not ``None`` and the distance of the vertex to its neighboring vertices " "is below the threshold value based on Douglas-Peucker algorithm, the vertex is disqualified as a corner.", ) - concave_resolution: Optional[pd.PositiveInt] = pd.Field( + concave_resolution: pd.PositiveInt | None = pd.Field( None, title="Concave Region Resolution.", description="Specifies number of steps to use for determining `dl_min` based on concave featues." "If set to ``None``, then the corresponding `dl_min` reduction is not applied.", ) - convex_resolution: Optional[pd.PositiveInt] = pd.Field( + convex_resolution: pd.PositiveInt | None = pd.Field( None, title="Convex Region Resolution.", description="Specifies number of steps to use for determining `dl_min` based on convex featues." "If set to ``None``, then the corresponding `dl_min` reduction is not applied.", ) - mixed_resolution: Optional[pd.PositiveInt] = pd.Field( + mixed_resolution: pd.PositiveInt | None = pd.Field( None, title="Mixed Region Resolution.", description="Specifies number of steps to use for determining `dl_min` based on mixed featues." diff --git a/tidy3d/components/grid/grid.py b/tidy3d/components/grid/grid.py index 73f8485a9b..718eb2dbab 100644 --- a/tidy3d/components/grid/grid.py +++ b/tidy3d/components/grid/grid.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Literal, Union +from typing import Literal import numpy as np import pydantic.v1 as pd @@ -93,10 +93,10 @@ def cell_size_meshgrid(self): def _interp_from_xarray( self, - array: Union[SpatialDataArray, ScalarFieldDataArray], + array: SpatialDataArray | ScalarFieldDataArray, interp_method: InterpMethod, - fill_value: Union[Literal["extrapolate"], float] = "extrapolate", - ) -> Union[SpatialDataArray, ScalarFieldDataArray]: + fill_value: Literal["extrapolate"] | float = "extrapolate", + ) -> SpatialDataArray | ScalarFieldDataArray: """ Similar to ``xarrray.DataArray.interp`` with 2 enhancements: @@ -174,7 +174,7 @@ def _interp_from_unstructured( self, array: UnstructuredGridDatasetType, interp_method: InterpMethod, - fill_value: Union[Literal["extrapolate"], float] = "extrapolate", + fill_value: Literal["extrapolate"] | float = "extrapolate", ) -> SpatialDataArray: """ Interpolate from untructured grid onto a Cartesian one. @@ -207,10 +207,10 @@ def _interp_from_unstructured( def spatial_interp( self, - array: Union[SpatialDataArray, ScalarFieldDataArray, UnstructuredGridDatasetType], + array: SpatialDataArray | ScalarFieldDataArray | UnstructuredGridDatasetType, interp_method: InterpMethod, - fill_value: Union[Literal["extrapolate"], float] = "extrapolate", - ) -> Union[SpatialDataArray, ScalarFieldDataArray]: + fill_value: Literal["extrapolate"] | float = "extrapolate", + ) -> SpatialDataArray | ScalarFieldDataArray: """ Similar to ``xarrray.DataArray.interp`` with 2 enhancements: diff --git a/tidy3d/components/grid/grid_spec.py b/tidy3d/components/grid/grid_spec.py index 93a945eac8..2e7b6a6cf6 100644 --- a/tidy3d/components/grid/grid_spec.py +++ b/tidy3d/components/grid/grid_spec.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Literal, Optional, Union +from typing import Any, Literal import numpy as np import pydantic.v1 as pd @@ -880,7 +880,7 @@ def estimated_min_dl( return self._filtered_dl(min_dl, sim_size) -GridType = Union[UniformGrid, CustomGrid, AutoGrid, CustomGridBoundaries, QuasiUniformGrid] +GridType = UniformGrid | CustomGrid | AutoGrid | CustomGridBoundaries | QuasiUniformGrid class GridRefinement(Tidy3dBaseModel): @@ -900,13 +900,13 @@ class GridRefinement(Tidy3dBaseModel): """ - refinement_factor: Optional[pd.PositiveFloat] = pd.Field( + refinement_factor: pd.PositiveFloat | None = pd.Field( None, title="Mesh Refinement Factor", description="Refine grid step size in vacuum by this factor.", ) - dl: Optional[pd.PositiveFloat] = pd.Field( + dl: pd.PositiveFloat | None = pd.Field( None, title="Grid Size", description="Grid step size in the refined region.", @@ -1013,14 +1013,14 @@ class LayerRefinementSpec(Box): description="Specifies dimension of the layer normal axis (0,1,2) -> (x,y,z).", ) - min_steps_along_axis: Optional[pd.PositiveFloat] = pd.Field( + min_steps_along_axis: pd.PositiveFloat | None = pd.Field( None, title="Minimal Number Of Steps Along Axis", description="If not ``None`` and the thickness of the layer is nonzero, set minimal " "number of steps discretizing the layer thickness.", ) - bounds_refinement: Optional[GridRefinement] = pd.Field( + bounds_refinement: GridRefinement | None = pd.Field( None, title="Mesh Refinement Factor Around Layer Bounds", description="If not ``None``, refine mesh around minimum and maximum positions " @@ -1028,14 +1028,14 @@ class LayerRefinementSpec(Box): "refinement here is only applied if it sets a smaller grid size.", ) - bounds_snapping: Optional[Literal["bounds", "lower", "upper", "center"]] = pd.Field( + bounds_snapping: Literal["bounds", "lower", "upper", "center"] | None = pd.Field( "lower", title="Placing Grid Snapping Point Along Axis", description="If not ``None``, enforcing grid boundaries to pass through ``lower``, " "``center``, or ``upper`` position of the layer; or both ``lower`` and ``upper`` with ``bounds``.", ) - corner_finder: Optional[CornerFinderSpec] = pd.Field( + corner_finder: CornerFinderSpec | None = pd.Field( CornerFinderSpec(), title="Inplane Corner Detection Specification", description="Specification for inplane corner detection. Inplane mesh refinement " @@ -1049,7 +1049,7 @@ class LayerRefinementSpec(Box): "grid boundaries to pass through corners of geometries specified by ``corner_finder``.", ) - corner_refinement: Optional[GridRefinement] = pd.Field( + corner_refinement: GridRefinement | None = pd.Field( GridRefinement(), title="Inplane Mesh Refinement Factor Around Corners", description="If not ``None`` and ``corner_finder`` is not ``None``, refine mesh around " @@ -1103,9 +1103,9 @@ def from_layer_bounds( min_steps_along_axis: np.PositiveFloat = None, bounds_refinement: GridRefinement = None, bounds_snapping: Literal["bounds", "lower", "upper", "center"] = "lower", - corner_finder: Union[CornerFinderSpec, None, object] = Undefined, + corner_finder: CornerFinderSpec | None | object = Undefined, corner_snapping: bool = True, - corner_refinement: Union[GridRefinement, None, object] = Undefined, + corner_refinement: GridRefinement | None | object = Undefined, refinement_inside_sim_only: bool = True, gap_meshing_iters: pd.NonNegativeInt = 1, dl_min_from_gap_width: bool = True, @@ -2346,7 +2346,7 @@ def all_snapping_points( self, structures: list[Structure], lumped_elements: list[LumpedElementType], - internal_snapping_points: Optional[list[CoordinateOptional]] = None, + internal_snapping_points: list[CoordinateOptional] | None = 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`. @@ -2436,7 +2436,7 @@ def all_override_structures( sim_size: tuple[float, 3], lumped_elements: list[LumpedElementType], structure_priority_mode: PriorityMode = "equal", - internal_override_structures: Optional[list[MeshOverrideStructure]] = None, + internal_override_structures: list[MeshOverrideStructure] | None = 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. @@ -2535,8 +2535,8 @@ def make_grid( 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, + internal_override_structures: list[MeshOverrideStructure] | None = None, + internal_snapping_points: list[CoordinateOptional] | None = None, boundary_types: tuple[tuple[str, str], tuple[str, str], tuple[str, str]] = [ [None, None], [None, None], @@ -2601,8 +2601,8 @@ def _make_grid_and_snapping_lines( 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, + internal_override_structures: list[MeshOverrideStructure] | None = None, + internal_snapping_points: list[CoordinateOptional] | None = None, boundary_types: tuple[tuple[str, str], tuple[str, str], tuple[str, str]] = [ [None, None], [None, None], @@ -2721,8 +2721,8 @@ def _make_grid_one_iteration( 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, + internal_override_structures: list[MeshOverrideStructure] | None = None, + internal_snapping_points: list[CoordinateOptional] | None = None, dl_min_from_gaps: pd.PositiveFloat = inf, structure_priority_mode: PriorityMode = "equal", ) -> Grid: diff --git a/tidy3d/components/grid/mesher.py b/tidy3d/components/grid/mesher.py index f66be82606..3c36e68a2b 100644 --- a/tidy3d/components/grid/mesher.py +++ b/tidy3d/components/grid/mesher.py @@ -7,7 +7,6 @@ from abc import ABC, abstractmethod from itertools import compress from math import isclose -from typing import Union import numpy as np import pydantic.v1 as pd @@ -558,12 +557,12 @@ def reorder_structures( ) ) - ordered_structures = ( - [structures_others[0]] - + structures_unshadowed - + structures_others[1:] - + structures_enforced - ) + ordered_structures = [ + structures_others[0], + *structures_unshadowed, + *structures_others[1:], + *structures_enforced, + ] return len(structures_others) + len(structures_unshadowed), ordered_structures @staticmethod @@ -1416,4 +1415,4 @@ def grid_type_in_interval( return 2 -MesherType = Union[GradedMesher] +MesherType = GradedMesher diff --git a/tidy3d/components/lumped_element.py b/tidy3d/components/lumped_element.py index bf8a71b039..8d79e5d033 100644 --- a/tidy3d/components/lumped_element.py +++ b/tidy3d/components/lumped_element.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from math import isclose -from typing import Annotated, Literal, Optional, Union +from typing import Annotated, Literal import numpy as np import pydantic.v1 as pd @@ -60,7 +60,7 @@ class LumpedElement(MicrowaveBaseModel, ABC): min_length=1, ) - num_grid_cells: Optional[pd.PositiveInt] = pd.Field( + num_grid_cells: pd.PositiveInt | None = pd.Field( DEFAULT_LUMPED_ELEMENT_NUM_CELLS, title="Lumped element grid cells", description="Number of mesh grid cells associated with the lumped element along each direction. " @@ -573,21 +573,21 @@ class RLCNetwork(MicrowaveBaseModel): """ - resistance: Optional[pd.PositiveFloat] = pd.Field( + resistance: pd.PositiveFloat | None = pd.Field( None, title="Resistance", description="Resistance value in ohms.", unit=OHM, ) - capacitance: Optional[pd.PositiveFloat] = pd.Field( + capacitance: pd.PositiveFloat | None = pd.Field( None, title="Capacitance", description="Capacitance value in farads.", unit=FARAD, ) - inductance: Optional[pd.PositiveFloat] = pd.Field( + inductance: pd.PositiveFloat | None = pd.Field( None, title="Inductance", description="Inductance value in henrys.", @@ -914,7 +914,7 @@ class LinearLumpedElement(RectangularLumpedElement): * `Using lumped elements in Tidy3D simulations <../../notebooks/LinearLumpedElements.html>`_ """ - network: Union[RLCNetwork, AdmittanceNetwork] = pd.Field( + network: RLCNetwork | AdmittanceNetwork = pd.Field( ..., title="Network", description="The linear element produces an equivalent medium that emulates the " @@ -979,9 +979,7 @@ def _create_box_for_network(self, grid: Grid) -> Box: return snap_box_to_grid(grid, cell_box, snap_spec=snap_spec) - def _create_connection_boxes( - self, cell_box: Box, grid: Grid - ) -> tuple[Optional[Box], Optional[Box]]: + def _create_connection_boxes(self, cell_box: Box, grid: Grid) -> tuple[Box | None, Box | None]: """Creates PEC structures that connect the network portion of the lumped element to the boundaries of the lumped element. """ @@ -1031,7 +1029,7 @@ def to_structure(self, grid) -> Structure: medium=Medium2D(**medium_dict), ) - def to_PEC_connection(self, grid) -> Optional[Structure]: + def to_PEC_connection(self, grid) -> Structure | None: """Converts the :class:`LinearLumpedElement` object to a :class:`.Structure`, representing any PEC connections. """ @@ -1169,10 +1167,6 @@ def impedance(self, freqs: np.ndarray) -> np.ndarray: # lumped elements allowed in Simulation.lumped_elements LumpedElementType = Annotated[ - Union[ - LumpedResistor, - CoaxialLumpedResistor, - LinearLumpedElement, - ], + LumpedResistor | CoaxialLumpedResistor | LinearLumpedElement, pd.Field(discriminator=TYPE_TAG_STR), ] diff --git a/tidy3d/components/material/multi_physics.py b/tidy3d/components/material/multi_physics.py index 7e26a476b9..5e946e83c8 100644 --- a/tidy3d/components/material/multi_physics.py +++ b/tidy3d/components/material/multi_physics.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Optional - import pydantic.v1 as pd from tidy3d.components.base import Tidy3dBaseModel @@ -81,9 +79,9 @@ class MultiPhysicsMedium(Tidy3dBaseModel): ... ) """ - name: Optional[str] = pd.Field(None, title="Name", description="Medium name") + name: str | None = pd.Field(None, title="Name", description="Medium name") - optical: Optional[OpticalMediumType] = pd.Field( + optical: OpticalMediumType | None = pd.Field( None, title="Optical properties", description="Specifies optical properties.", @@ -96,14 +94,14 @@ class MultiPhysicsMedium(Tidy3dBaseModel): # description="Specifies electrical properties for RF simulations. This is currently not in use.", # ) - heat: Optional[HeatMediumType] = pd.Field( + heat: HeatMediumType | None = pd.Field( None, title="Heat properties", description="Specifies properties for Heat simulations.", discriminator=TYPE_TAG_STR, ) - charge: Optional[ChargeMediumType] = pd.Field( + charge: ChargeMediumType | None = pd.Field( None, title="Charge properties", description="Specifies properties for Charge simulations.", diff --git a/tidy3d/components/material/solver_types.py b/tidy3d/components/material/solver_types.py index f597276aa1..edddf50a49 100644 --- a/tidy3d/components/material/solver_types.py +++ b/tidy3d/components/material/solver_types.py @@ -4,8 +4,6 @@ from __future__ import annotations -from typing import Union - from tidy3d.components.material.tcad.charge import ( ChargeConductorMedium, ChargeInsulatorMedium, @@ -17,6 +15,6 @@ OpticalMediumType = MediumType ElectricalMediumType = MediumType HeatMediumType = ThermalSpecType -ChargeMediumType = Union[ChargeConductorMedium, ChargeInsulatorMedium, SemiconductorMedium] +ChargeMediumType = ChargeConductorMedium | ChargeInsulatorMedium | SemiconductorMedium OpticalMediumType3D = ElectricalMediumType3D = ChargeMediumType3D = MediumType3D diff --git a/tidy3d/components/material/tcad/charge.py b/tidy3d/components/material/tcad/charge.py index aee8d1a5e6..8af782361a 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 Union - import pydantic.v1 as pd from tidy3d.components.data.data_array import SpatialDataArray @@ -256,21 +254,21 @@ class SemiconductorMedium(AbstractChargeMedium): """ - N_c: Union[EffectiveDOSModelType, pd.PositiveFloat] = pd.Field( + N_c: EffectiveDOSModelType | pd.PositiveFloat = pd.Field( ..., title="Effective density of electron states", description=":math:`N_c` Effective density of states in the conduction band.", units=PERCMCUBE, ) - N_v: Union[EffectiveDOSModelType, pd.PositiveFloat] = pd.Field( + N_v: EffectiveDOSModelType | pd.PositiveFloat = pd.Field( ..., title="Effective density of hole states", description=":math:`N_v` Effective density of states in the valence band.", units=PERCMCUBE, ) - E_g: Union[EnergyBandGapModelType, pd.PositiveFloat] = pd.Field( + E_g: EnergyBandGapModelType | pd.PositiveFloat = pd.Field( ..., title="Band-gap energy", description=":math:`E_g` Band-gap energy", @@ -302,7 +300,7 @@ class SemiconductorMedium(AbstractChargeMedium): units=ELECTRON_VOLT, ) - N_a: Union[pd.NonNegativeFloat, SpatialDataArray, tuple[DopingBoxType, ...]] = pd.Field( + N_a: pd.NonNegativeFloat | SpatialDataArray | tuple[DopingBoxType, ...] = pd.Field( (), title="Doping: Acceptor concentration", description="Concentration of acceptor impurities, which create mobile holes, resulting in p-type material. " @@ -311,7 +309,7 @@ class SemiconductorMedium(AbstractChargeMedium): units=PERCMCUBE, ) - N_d: Union[pd.NonNegativeFloat, SpatialDataArray, tuple[DopingBoxType, ...]] = pd.Field( + N_d: pd.NonNegativeFloat | SpatialDataArray | tuple[DopingBoxType, ...] = pd.Field( (), title="Doping: Donor concentration", description="Concentration of donor impurities, which create mobile electrons, resulting in n-type material. " diff --git a/tidy3d/components/material/tcad/heat.py b/tidy3d/components/material/tcad/heat.py index 47014cbdfd..6de9a855fd 100644 --- a/tidy3d/components/material/tcad/heat.py +++ b/tidy3d/components/material/tcad/heat.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC -from typing import Union import pydantic.v1 as pd @@ -198,5 +197,5 @@ class SolidSpec(SolidMedium): """Solid medium class for backwards compatibility""" -ThermalSpecType = Union[FluidSpec, SolidSpec, SolidMedium, FluidMedium] +ThermalSpecType = FluidSpec | SolidSpec | SolidMedium | FluidMedium # Note this needs to remain here to avoid circular imports in the new medium structure. diff --git a/tidy3d/components/material/types.py b/tidy3d/components/material/types.py index 3ea694c581..43cb611839 100644 --- a/tidy3d/components/material/types.py +++ b/tidy3d/components/material/types.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Union - from .multi_physics import MultiPhysicsMedium from .solver_types import ( ChargeMediumType, @@ -13,14 +11,14 @@ OpticalMediumType3D, ) -StructureMediumType = Union[ - MultiPhysicsMedium, - OpticalMediumType, - ElectricalMediumType, - HeatMediumType, - ChargeMediumType, -] +StructureMediumType = ( + MultiPhysicsMedium + | OpticalMediumType + | ElectricalMediumType + | HeatMediumType + | ChargeMediumType +) -MultiPhysicsMediumType3D = Union[ - MultiPhysicsMedium, OpticalMediumType3D, ElectricalMediumType3D, ChargeMediumType3D -] +MultiPhysicsMediumType3D = ( + MultiPhysicsMedium | OpticalMediumType3D | ElectricalMediumType3D | ChargeMediumType3D +) diff --git a/tidy3d/components/medium.py b/tidy3d/components/medium.py index e733df186c..b882e30f9a 100644 --- a/tidy3d/components/medium.py +++ b/tidy3d/components/medium.py @@ -4,8 +4,9 @@ import functools from abc import ABC, abstractmethod +from collections.abc import Callable from math import isclose -from typing import Callable, Literal, Optional, Union +from typing import Literal import autograd.numpy as np @@ -402,7 +403,7 @@ class TwoPhotonAbsorption(NonlinearModel): "with Tidy3D version < 2.8 and may be removed in a future release.", ) - beta: Union[float, Complex] = pd.Field( + beta: float | Complex = pd.Field( 0, title="TPA coefficient", description="Coefficient for two-photon absorption (TPA).", @@ -446,7 +447,7 @@ class TwoPhotonAbsorption(NonlinearModel): units=f"{MICROMETER}^(3 e_h)", ) - n0: Optional[Complex] = pd.Field( + n0: Complex | None = pd.Field( None, title="Complex linear refractive index", description="Complex linear refractive index of the medium, computed for instance using " @@ -454,7 +455,7 @@ class TwoPhotonAbsorption(NonlinearModel): "frequencies of the simulation sources (as long as these are all equal).", ) - freq0: Optional[pd.PositiveFloat] = pd.Field( + freq0: pd.PositiveFloat | None = pd.Field( None, title="Central frequency", description="Central frequency, used to calculate the energy of the free-carriers " @@ -584,7 +585,7 @@ class KerrNonlinearity(NonlinearModel): units=f"{MICROMETER}^2 / {WATT}", ) - n0: Optional[Complex] = pd.Field( + n0: Complex | None = pd.Field( None, title="Complex linear refractive index", description="Complex linear refractive index of the medium, computed for instance using " @@ -647,7 +648,7 @@ def complex_fields(self) -> bool: return self.use_complex_fields -NonlinearModelType = Union[NonlinearSusceptibility, TwoPhotonAbsorption, KerrNonlinearity] +NonlinearModelType = NonlinearSusceptibility | TwoPhotonAbsorption | KerrNonlinearity class NonlinearSpec(ABC, Tidy3dBaseModel): @@ -768,7 +769,7 @@ class AbstractMedium(ABC, Tidy3dBaseModel): "useful in some cases.", ) - nonlinear_spec: Union[NonlinearSpec, NonlinearSusceptibility] = pd.Field( + nonlinear_spec: NonlinearSpec | NonlinearSusceptibility = pd.Field( None, title="Nonlinear Spec", description="Nonlinear spec applied on top of the base medium properties.", @@ -780,7 +781,7 @@ class AbstractMedium(ABC, Tidy3dBaseModel): description="Modulation spec applied on top of the base medium properties.", ) - viz_spec: Optional[VisualizationSpec] = pd.Field( + viz_spec: VisualizationSpec | None = pd.Field( None, title="Visualization Specification", description="Plotting specification for visualizing medium.", @@ -860,7 +861,7 @@ def _validate_modulation_spec_post_init(self): "Time modulation is not currently supported for the components of a 2D medium." ) - heat_spec: Optional[ThermalSpecType] = pd.Field( + heat_spec: ThermalSpecType | None = pd.Field( None, title="Heat Specification", description="DEPRECATED: Use :class:`MultiPhysicsMedium`. Specification of the medium heat properties. They are " @@ -1079,7 +1080,7 @@ def eps_comp(self, row: Axis, col: Axis, frequency: float) -> complex: return 0j def _eps_plot( - self, frequency: float, eps_component: Optional[PermittivityComponent] = None + self, frequency: float, eps_component: PermittivityComponent | None = None ) -> float: """Returns real part of epsilon for plotting. A specific component of the epsilon tensor can be selected for anisotropic medium. @@ -1601,8 +1602,8 @@ def _get_real_vals(self, x: np.ndarray) -> np.ndarray: def _eps_bounds( self, - frequency: Optional[float] = None, - eps_component: Optional[PermittivityComponent] = None, + frequency: float | None = None, + eps_component: PermittivityComponent | None = None, ) -> tuple[float, float]: """Returns permittivity bounds for setting the color bounds when plotting. @@ -2063,7 +2064,7 @@ class CustomIsotropicMedium(AbstractCustomMedium, Medium): units=PERMITTIVITY, ) - conductivity: Optional[CustomSpatialDataTypeAnnotated] = pd.Field( + conductivity: CustomSpatialDataTypeAnnotated | None = pd.Field( None, title="Conductivity", description="Electric conductivity. Defined such that the imaginary part of the complex " @@ -2228,7 +2229,7 @@ class CustomMedium(AbstractCustomMedium): >>> eps = dielectric.eps_model(200e12) """ - eps_dataset: Optional[PermittivityDataset] = pd.Field( + eps_dataset: PermittivityDataset | None = pd.Field( None, title="Permittivity Dataset", description="[To be deprecated] User-supplied dataset containing complex-valued " @@ -2236,14 +2237,14 @@ class CustomMedium(AbstractCustomMedium): "will be interpolated based on ``interp_method``.", ) - permittivity: Optional[CustomSpatialDataTypeAnnotated] = pd.Field( + permittivity: CustomSpatialDataTypeAnnotated | None = pd.Field( None, title="Permittivity", description="Spatial profile of relative permittivity.", units=PERMITTIVITY, ) - conductivity: Optional[CustomSpatialDataTypeAnnotated] = pd.Field( + conductivity: CustomSpatialDataTypeAnnotated | None = pd.Field( None, title="Conductivity", description="Spatial profile Electric conductivity. Defined such " @@ -2643,8 +2644,8 @@ def eps_model(self, frequency: float) -> complex: @classmethod def from_eps_raw( cls, - eps: Union[ScalarFieldDataArray, CustomSpatialDataType], - freq: Optional[float] = None, + eps: ScalarFieldDataArray | CustomSpatialDataType, + freq: float | None = None, interp_method: InterpMethod = "nearest", **kwargs, ) -> CustomMedium: @@ -2712,9 +2713,9 @@ def from_eps_raw( @classmethod def from_nk( cls, - n: Union[ScalarFieldDataArray, CustomSpatialDataType], - k: Optional[Union[ScalarFieldDataArray, CustomSpatialDataType]] = None, - freq: Optional[float] = None, + n: ScalarFieldDataArray | CustomSpatialDataType, + k: ScalarFieldDataArray | CustomSpatialDataType | None = None, + freq: float | None = None, interp_method: InterpMethod = "nearest", **kwargs, ) -> CustomMedium: @@ -2805,7 +2806,7 @@ def grids(self, bounds: Bound) -> dict[str, Grid]: pt_mins = dict(zip("xyz", rmin)) pt_maxs = dict(zip("xyz", rmax)) - def make_grid(scalar_field: Union[ScalarFieldDataArray, SpatialDataArray]) -> Grid: + def make_grid(scalar_field: 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]: @@ -3621,8 +3622,8 @@ def loss_upper_bound(self) -> float: @staticmethod def _get_vjps_from_params( - dJ_deps_complex: Union[complex, np.ndarray], - poles_vals: list[tuple[Union[complex, np.ndarray], Union[complex, np.ndarray]]], + dJ_deps_complex: complex | np.ndarray, + poles_vals: list[tuple[complex | np.ndarray, complex | np.ndarray]], omega: float, requested_paths: list[tuple], project_real: bool = False, @@ -6101,7 +6102,7 @@ def roughness_correction_factor( return correction -SurfaceRoughnessType = Union[HammerstadSurfaceRoughness, HuraySurfaceRoughness] +SurfaceRoughnessType = HammerstadSurfaceRoughness | HuraySurfaceRoughness class LossyMetalMedium(Medium): @@ -6313,19 +6314,15 @@ def plot( return ax -IsotropicUniformMediumFor2DType = Union[ - Medium, LossyMetalMedium, PoleResidue, Sellmeier, Lorentz, Debye, Drude, PECMedium -] -IsotropicUniformMediumType = Union[IsotropicUniformMediumFor2DType, PMCMedium] -IsotropicCustomMediumType = Union[ - CustomPoleResidue, - CustomSellmeier, - CustomLorentz, - CustomDebye, - CustomDrude, -] -IsotropicCustomMediumInternalType = Union[IsotropicCustomMediumType, CustomIsotropicMedium] -IsotropicMediumType = Union[IsotropicCustomMediumType, IsotropicUniformMediumType] +IsotropicUniformMediumFor2DType = ( + Medium | LossyMetalMedium | PoleResidue | Sellmeier | Lorentz | Debye | Drude | PECMedium +) +IsotropicUniformMediumType = IsotropicUniformMediumFor2DType | PMCMedium +IsotropicCustomMediumType = ( + CustomPoleResidue | CustomSellmeier | CustomLorentz | CustomDebye | CustomDrude +) +IsotropicCustomMediumInternalType = IsotropicCustomMediumType | CustomIsotropicMedium +IsotropicMediumType = IsotropicCustomMediumType | IsotropicUniformMediumType class AnisotropicMedium(AbstractMedium): @@ -6464,7 +6461,7 @@ def eps_comp(self, row: Axis, col: Axis, frequency: float) -> complex: return self.components[field_name].eps_model(frequency) def _eps_plot( - self, frequency: float, eps_component: Optional[PermittivityComponent] = None + self, frequency: float, eps_component: PermittivityComponent | None = None ) -> float: """Returns real part of epsilon for plotting. A specific component of the epsilon tensor can be selected for anisotropic medium. @@ -6793,7 +6790,7 @@ def eps_comp(self, row: Axis, col: Axis, frequency: float) -> complex: return AbstractMedium.eps_sigma_to_eps_complex(eps, sig, frequency) def _eps_plot( - self, frequency: float, eps_component: Optional[PermittivityComponent] = None + self, frequency: float, eps_component: PermittivityComponent | None = None ) -> float: """Returns real part of epsilon for plotting. A specific component of the epsilon tensor can be selected for anisotropic medium. @@ -6887,28 +6884,28 @@ class CustomAnisotropicMedium(AbstractCustomMedium, AnisotropicMedium): * `Defining fully anisotropic materials <../../notebooks/FullyAnisotropic.html>`_ """ - xx: Union[IsotropicCustomMediumType, CustomMedium] = pd.Field( + xx: IsotropicCustomMediumType | CustomMedium = pd.Field( ..., title="XX Component", description="Medium describing the xx-component of the diagonal permittivity tensor.", discriminator=TYPE_TAG_STR, ) - yy: Union[IsotropicCustomMediumType, CustomMedium] = pd.Field( + yy: IsotropicCustomMediumType | CustomMedium = pd.Field( ..., title="YY Component", description="Medium describing the yy-component of the diagonal permittivity tensor.", discriminator=TYPE_TAG_STR, ) - zz: Union[IsotropicCustomMediumType, CustomMedium] = pd.Field( + zz: IsotropicCustomMediumType | CustomMedium = pd.Field( ..., title="ZZ Component", description="Medium describing the zz-component of the diagonal permittivity tensor.", discriminator=TYPE_TAG_STR, ) - interp_method: Optional[InterpMethod] = pd.Field( + interp_method: InterpMethod | None = pd.Field( None, title="Interpolation method", description="When the value is ``None`` each component will follow its own " @@ -7030,8 +7027,8 @@ def eps_dataarray_freq( def _eps_bounds( self, - frequency: Optional[float] = None, - eps_component: Optional[PermittivityComponent] = None, + frequency: float | None = None, + eps_component: PermittivityComponent | None = None, ) -> tuple[float, float]: """Returns permittivity bounds for setting the color bounds when plotting. @@ -7093,21 +7090,21 @@ class CustomAnisotropicMediumInternal(CustomAnisotropicMedium): >>> anisotropic_dielectric = CustomAnisotropicMedium(xx=medium_xx, yy=medium_yy, zz=medium_zz) """ - xx: Union[IsotropicCustomMediumInternalType, CustomMedium] = pd.Field( + xx: IsotropicCustomMediumInternalType | CustomMedium = pd.Field( ..., title="XX Component", description="Medium describing the xx-component of the diagonal permittivity tensor.", discriminator=TYPE_TAG_STR, ) - yy: Union[IsotropicCustomMediumInternalType, CustomMedium] = pd.Field( + yy: IsotropicCustomMediumInternalType | CustomMedium = pd.Field( ..., title="YY Component", description="Medium describing the yy-component of the diagonal permittivity tensor.", discriminator=TYPE_TAG_STR, ) - zz: Union[IsotropicCustomMediumInternalType, CustomMedium] = pd.Field( + zz: IsotropicCustomMediumInternalType | CustomMedium = pd.Field( ..., title="ZZ Component", description="Medium describing the zz-component of the diagonal permittivity tensor.", @@ -7131,7 +7128,7 @@ class AbstractPerturbationMedium(ABC, Tidy3dBaseModel): "have an effect.", ) - perturbation_spec: Optional[Union[PermittivityPerturbation, IndexPerturbation]] = pd.Field( + perturbation_spec: PermittivityPerturbation | IndexPerturbation | None = pd.Field( None, title="Perturbation Spec", description="Specification of medium perturbation as one of predefined types.", @@ -7145,7 +7142,7 @@ def perturbed_copy( electron_density: CustomSpatialDataType = None, hole_density: CustomSpatialDataType = None, interp_method: InterpMethod = "linear", - ) -> Union[AbstractMedium, AbstractCustomMedium]: + ) -> AbstractMedium | AbstractCustomMedium: """Sample perturbations on provided heat and/or charge data and create a custom medium. Any of ``temperature``, ``electron_density``, and ``hole_density`` can be ``None``. If all passed arguments are ``None`` then a non-custom medium is returned. @@ -7184,9 +7181,9 @@ def perturbed_copy( @classmethod def from_unperturbed( cls, - medium: Union[Medium, DispersiveMedium], + medium: Medium | DispersiveMedium, subpixel: bool = True, - perturbation_spec: Union[PermittivityPerturbation, IndexPerturbation] = None, + perturbation_spec: PermittivityPerturbation | IndexPerturbation = None, **kwargs, ) -> AbstractPerturbationMedium: """Construct a medium with pertubation models from an unpertubed one. @@ -7244,14 +7241,14 @@ class PerturbationMedium(Medium, AbstractPerturbationMedium): ... ) """ - permittivity_perturbation: Optional[ParameterPerturbation] = pd.Field( + permittivity_perturbation: ParameterPerturbation | None = pd.Field( None, title="Permittivity Perturbation", description="List of heat and/or charge perturbations to permittivity.", units=PERMITTIVITY, ) - conductivity_perturbation: Optional[ParameterPerturbation] = pd.Field( + conductivity_perturbation: ParameterPerturbation | None = pd.Field( None, title="Permittivity Perturbation", description="List of heat and/or charge perturbations to permittivity.", @@ -7337,7 +7334,7 @@ def perturbed_copy( electron_density: CustomSpatialDataType = None, hole_density: CustomSpatialDataType = None, interp_method: InterpMethod = "linear", - ) -> Union[Medium, CustomMedium]: + ) -> Medium | CustomMedium: """Sample perturbations on provided heat and/or charge data and return 'CustomMedium'. Any of temperature, electron_density, and hole_density can be 'None'. If all passed arguments are 'None' then a 'Medium' object is returned. All provided fields must have @@ -7460,7 +7457,7 @@ class PerturbationPoleResidue(PoleResidue, AbstractPerturbationMedium): ... ) """ - eps_inf_perturbation: Optional[ParameterPerturbation] = pd.Field( + eps_inf_perturbation: ParameterPerturbation | None = pd.Field( None, title="Perturbation of Epsilon at Infinity", description="Perturbations to relative permittivity at infinite frequency " @@ -7468,9 +7465,9 @@ class PerturbationPoleResidue(PoleResidue, AbstractPerturbationMedium): units=PERMITTIVITY, ) - poles_perturbation: Optional[ - tuple[tuple[Optional[ParameterPerturbation], Optional[ParameterPerturbation]], ...] - ] = pd.Field( + poles_perturbation: ( + tuple[tuple[ParameterPerturbation | None, ParameterPerturbation | None], ...] | None + ) = pd.Field( None, title="Perturbations of Poles", description="Perturbations to poles of the model.", @@ -7548,7 +7545,7 @@ def perturbed_copy( electron_density: CustomSpatialDataType = None, hole_density: CustomSpatialDataType = None, interp_method: InterpMethod = "linear", - ) -> Union[PoleResidue, CustomPoleResidue]: + ) -> PoleResidue | CustomPoleResidue: """Sample perturbations on provided heat and/or charge data and return 'CustomPoleResidue'. Any of temperature, electron_density, and hole_density can be 'None'. If all passed arguments are 'None' then a 'PoleResidue' object is returned. All provided fields must have @@ -7646,28 +7643,28 @@ def perturbed_copy( # types of mediums that can be used in Simulation and Structures -MediumType3D = Union[ - Medium, - AnisotropicMedium, - PECMedium, - PMCMedium, - PoleResidue, - Sellmeier, - Lorentz, - Debye, - Drude, - FullyAnisotropicMedium, - CustomMedium, - CustomPoleResidue, - CustomSellmeier, - CustomLorentz, - CustomDebye, - CustomDrude, - CustomAnisotropicMedium, - PerturbationMedium, - PerturbationPoleResidue, - LossyMetalMedium, -] +MediumType3D = ( + Medium + | AnisotropicMedium + | PECMedium + | PMCMedium + | PoleResidue + | Sellmeier + | Lorentz + | Debye + | Drude + | FullyAnisotropicMedium + | CustomMedium + | CustomPoleResidue + | CustomSellmeier + | CustomLorentz + | CustomDebye + | CustomDrude + | CustomAnisotropicMedium + | PerturbationMedium + | PerturbationPoleResidue + | LossyMetalMedium +) class Medium2D(AbstractMedium): @@ -7731,7 +7728,7 @@ def _validate_inplane_pec(cls, val, values): @classmethod def _weighted_avg( cls, meds: list[IsotropicUniformMediumFor2DType], weights: list[float] - ) -> Union[PoleResidue, PECMedium]: + ) -> PoleResidue | PECMedium: """Average ``meds`` with weights ``weights``.""" eps_inf = 1 poles = [] @@ -8075,11 +8072,11 @@ def is_comp_pec_2d(self, comp: Axis, axis: Axis): # types of mediums that can be used in Simulation and Structures -MediumType = Union[MediumType3D, Medium2D, AnisotropicMediumFromMedium2D] +MediumType = MediumType3D | Medium2D | AnisotropicMediumFromMedium2D # Utility function -def medium_from_nk(n: float, k: float, freq: float, **kwargs) -> Union[Medium, Lorentz]: +def medium_from_nk(n: float, k: float, freq: float, **kwargs) -> Medium | Lorentz: """Convert ``n`` and ``k`` values at frequency ``freq`` to :class:`.Medium` if ``Re[epsilon]>=1``, or :class:`Lorentz` if if ``Re[epsilon]<1``. diff --git a/tidy3d/components/microwave/data/monitor_data.py b/tidy3d/components/microwave/data/monitor_data.py index 0a31435689..6fce884c06 100644 --- a/tidy3d/components/microwave/data/monitor_data.py +++ b/tidy3d/components/microwave/data/monitor_data.py @@ -4,8 +4,6 @@ from __future__ import annotations -from typing import Optional - import pydantic.v1 as pd import xarray as xr @@ -123,7 +121,7 @@ def reflection_efficiency(self) -> FreqDataArray: return reflection_efficiency def partial_gain( - self, pol_basis: PolarizationBasis = "linear", tilt_angle: Optional[float] = None + self, pol_basis: PolarizationBasis = "linear", tilt_angle: float | None = 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``, @@ -165,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: Optional[float] = None + self, pol_basis: PolarizationBasis = "linear", tilt_angle: float | None = 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``, @@ -264,7 +262,7 @@ class MicrowaveModeData(ModeData, MicrowaveBaseModel): ..., title="Monitor", description="Mode monitor associated with the data." ) - transmission_line_data: Optional[TransmissionLineDataset] = pd.Field( + transmission_line_data: TransmissionLineDataset | None = pd.Field( None, title="Transmission Line Data", description="Additional data relevant to transmission lines in RF and microwave applications, " diff --git a/tidy3d/components/microwave/impedance_calculator.py b/tidy3d/components/microwave/impedance_calculator.py index 8f80c57320..f912abdbbe 100644 --- a/tidy3d/components/microwave/impedance_calculator.py +++ b/tidy3d/components/microwave/impedance_calculator.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional, Union - import numpy as np import pydantic.v1 as pd @@ -33,10 +31,10 @@ from tidy3d.components.monitor import ModeMonitor, ModeSolverMonitor from tidy3d.exceptions import ValidationError -VoltageIntegralType = Union[AxisAlignedVoltageIntegral, Custom2DVoltageIntegral] -CurrentIntegralType = Union[ - AxisAlignedCurrentIntegral, Custom2DCurrentIntegral, CompositeCurrentIntegral -] +VoltageIntegralType = AxisAlignedVoltageIntegral | Custom2DVoltageIntegral +CurrentIntegralType = ( + AxisAlignedCurrentIntegral | Custom2DCurrentIntegral | CompositeCurrentIntegral +) class ImpedanceCalculator(MicrowaveBaseModel): @@ -67,13 +65,13 @@ class ImpedanceCalculator(MicrowaveBaseModel): >>> _ = ImpedanceCalculator(voltage_integral=v_int) """ - voltage_integral: Optional[VoltageIntegralType] = pd.Field( + voltage_integral: VoltageIntegralType | None = pd.Field( None, title="Voltage Integral", description="Definition of path integral for computing voltage.", ) - current_integral: Optional[CurrentIntegralType] = pd.Field( + current_integral: CurrentIntegralType | None = pd.Field( None, title="Current Integral", description="Definition of contour integral for computing current.", @@ -81,10 +79,10 @@ class ImpedanceCalculator(MicrowaveBaseModel): def compute_impedance( self, em_field: IntegrableMonitorDataType, return_voltage_and_current=False - ) -> Union[ - ImpedanceResultType, - tuple[ImpedanceResultType, VoltageIntegralResultType, CurrentIntegralResultType], - ]: + ) -> ( + ImpedanceResultType + | tuple[ImpedanceResultType, VoltageIntegralResultType, CurrentIntegralResultType] + ): """Compute impedance for the supplied ``em_field`` using ``voltage_integral`` and ``current_integral``. If only a single integral has been defined, impedance is computed using the total flux in ``em_field``. diff --git a/tidy3d/components/microwave/mode_spec.py b/tidy3d/components/microwave/mode_spec.py index ee7dcddeb6..0e6c5dad83 100644 --- a/tidy3d/components/microwave/mode_spec.py +++ b/tidy3d/components/microwave/mode_spec.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional, Union - import pydantic.v1 as pd from tidy3d.components.base import cached_property @@ -46,10 +44,9 @@ class MicrowaveModeSpec(AbstractModeSpec, MicrowaveBaseModel): ... ) """ - impedance_specs: Union[ - annotate_type(ImpedanceSpecType), - tuple[Optional[annotate_type(ImpedanceSpecType)], ...], - ] = pd.Field( + impedance_specs: ( + annotate_type(ImpedanceSpecType) | tuple[annotate_type(ImpedanceSpecType) | None, ...] + ) = pd.Field( default_factory=AutoImpedanceSpec._default_without_license_warning, title="Impedance Specifications", description="Field controls how the impedance is calculated for each mode calculated by the mode solver. " @@ -60,9 +57,9 @@ class MicrowaveModeSpec(AbstractModeSpec, MicrowaveBaseModel): ) @cached_property - def _impedance_specs_as_tuple(self) -> tuple[Optional[ImpedanceSpecType]]: + def _impedance_specs_as_tuple(self) -> tuple[ImpedanceSpecType | None]: """Gets the impedance_specs field converted to a tuple.""" - if isinstance(self.impedance_specs, Union[tuple, list]): + if isinstance(self.impedance_specs, tuple | list): return tuple(self.impedance_specs) return (self.impedance_specs,) @@ -79,7 +76,7 @@ def check_impedance_specs_consistent_with_num_modes(cls, val, values): """Check that the number of impedance specifications is equal to the number of modes. A single impedance spec is also permitted.""" num_modes = values.get("num_modes") - if isinstance(val, Union[tuple, list]): + if isinstance(val, tuple | list): num_impedance_specs = len(val) else: return val diff --git a/tidy3d/components/microwave/path_integrals/factory.py b/tidy3d/components/microwave/path_integrals/factory.py index 9100dcc206..a1df88a62e 100644 --- a/tidy3d/components/microwave/path_integrals/factory.py +++ b/tidy3d/components/microwave/path_integrals/factory.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional - from tidy3d.components.microwave.impedance_calculator import ( CurrentIntegralType, VoltageIntegralType, @@ -89,8 +87,8 @@ def make_current_integral(path_spec: CurrentPathSpecType) -> CurrentIntegralType def make_path_integrals( - microwave_mode_spec: MicrowaveModeSpec, auto_spec: Optional[CustomImpedanceSpec] = None -) -> tuple[tuple[Optional[VoltageIntegralType]], tuple[Optional[CurrentIntegralType]]]: + microwave_mode_spec: MicrowaveModeSpec, auto_spec: CustomImpedanceSpec | None = None +) -> tuple[tuple[VoltageIntegralType | None], tuple[CurrentIntegralType | None]]: """ Given a microwave mode specification and monitor, create the voltage and current path integrals used for the impedance computation. diff --git a/tidy3d/components/microwave/path_integrals/integrals/base.py b/tidy3d/components/microwave/path_integrals/integrals/base.py index 04fa762596..4db2d6e87e 100644 --- a/tidy3d/components/microwave/path_integrals/integrals/base.py +++ b/tidy3d/components/microwave/path_integrals/integrals/base.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Literal, Union +from typing import Literal import numpy as np import xarray as xr @@ -22,8 +22,8 @@ from tidy3d.constants import fp_eps from tidy3d.exceptions import DataError -IntegrableMonitorDataType = Union[FieldData, FieldTimeData, ModeData, ModeSolverData] -EMScalarFieldType = Union[ScalarFieldDataArray, ScalarFieldTimeDataArray, ScalarModeFieldDataArray] +IntegrableMonitorDataType = FieldData | FieldTimeData | ModeData | ModeSolverData +EMScalarFieldType = ScalarFieldDataArray | ScalarFieldTimeDataArray | ScalarModeFieldDataArray FieldParameter = Literal["E", "H"] diff --git a/tidy3d/components/microwave/path_integrals/integrals/current.py b/tidy3d/components/microwave/path_integrals/integrals/current.py index d6ffddd332..d045e77a7b 100644 --- a/tidy3d/components/microwave/path_integrals/integrals/current.py +++ b/tidy3d/components/microwave/path_integrals/integrals/current.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - import numpy as np import xarray as xr @@ -133,7 +131,7 @@ class CompositeCurrentIntegral(CompositeCurrentIntegralSpec): @cached_property def current_integrals( self, - ) -> tuple[Union[AxisAlignedCurrentIntegral, Custom2DCurrentIntegral], ...]: + ) -> tuple[AxisAlignedCurrentIntegral | Custom2DCurrentIntegral, ...]: """ "Collection of closed current path integrals.""" from tidy3d.components.microwave.path_integrals.factory import ( make_current_integral, @@ -218,7 +216,7 @@ def compute_current(self, em_field: IntegrableMonitorDataType) -> IntegralResult def _check_phase_sign_consistency( self, - phase_difference: Union[FreqDataArray, FreqModeDataArray], + phase_difference: FreqDataArray | FreqModeDataArray, ) -> bool: """ Check that the provided current data has a consistent phase with respect to the reference @@ -260,8 +258,8 @@ def _check_phase_sign_consistency( def _check_phase_amplitude_consistency( self, - current_in_phase: Union[FreqDataArray, FreqModeDataArray], - current_out_phase: Union[FreqDataArray, FreqModeDataArray], + current_in_phase: FreqDataArray | FreqModeDataArray, + current_out_phase: FreqDataArray | FreqModeDataArray, ) -> bool: """ Check that the summed in phase and out of phase components of current have a consistent relative amplitude. diff --git a/tidy3d/components/microwave/path_integrals/specs/current.py b/tidy3d/components/microwave/path_integrals/specs/current.py index 8447902e47..8030ef522b 100644 --- a/tidy3d/components/microwave/path_integrals/specs/current.py +++ b/tidy3d/components/microwave/path_integrals/specs/current.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Literal, Optional, Union +from typing import Literal import numpy as np import pydantic.v1 as pd @@ -147,9 +147,9 @@ def _to_path_integral_specs( @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **path_kwargs, ) -> Ax: @@ -242,9 +242,9 @@ class Custom2DCurrentIntegralSpec(Custom2DPathIntegralSpec): @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **path_kwargs, ) -> Ax: @@ -310,12 +310,10 @@ class CompositeCurrentIntegralSpec(MicrowaveBaseModel): ... ) """ - path_specs: tuple[Union[AxisAlignedCurrentIntegralSpec, Custom2DCurrentIntegralSpec], ...] = ( - pd.Field( - ..., - title="Path Specifications", - description="Definition of the disjoint path specifications for each isolated contour integral.", - ) + path_specs: tuple[AxisAlignedCurrentIntegralSpec | Custom2DCurrentIntegralSpec, ...] = pd.Field( + ..., + title="Path Specifications", + description="Definition of the disjoint path specifications for each isolated contour integral.", ) sum_spec: Literal["sum", "split"] = pd.Field( @@ -331,9 +329,9 @@ class CompositeCurrentIntegralSpec(MicrowaveBaseModel): @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **path_kwargs, ) -> Ax: diff --git a/tidy3d/components/microwave/path_integrals/specs/impedance.py b/tidy3d/components/microwave/path_integrals/specs/impedance.py index cdffbdffba..28fb43d727 100644 --- a/tidy3d/components/microwave/path_integrals/specs/impedance.py +++ b/tidy3d/components/microwave/path_integrals/specs/impedance.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional, Union - import pydantic.v1 as pd from tidy3d.components.base import skip_if_fields_missing @@ -50,13 +48,13 @@ class CustomImpedanceSpec(MicrowaveBaseModel): ... ) """ - voltage_spec: Optional[VoltagePathSpecType] = pd.Field( + voltage_spec: VoltagePathSpecType | None = pd.Field( None, title="Voltage Integration Path", description="Path specification for computing the voltage associated with a mode profile.", ) - current_spec: Optional[CurrentPathSpecType] = pd.Field( + current_spec: CurrentPathSpecType | None = pd.Field( None, title="Current Integration Path", description="Path specification for computing the current associated with a mode profile.", @@ -79,4 +77,4 @@ def check_path_spec_combinations(cls, val, values): return val -ImpedanceSpecType = Union[AutoImpedanceSpec, CustomImpedanceSpec] +ImpedanceSpecType = AutoImpedanceSpec | CustomImpedanceSpec diff --git a/tidy3d/components/microwave/path_integrals/specs/voltage.py b/tidy3d/components/microwave/path_integrals/specs/voltage.py index 340940f20b..6bb0a9cd85 100644 --- a/tidy3d/components/microwave/path_integrals/specs/voltage.py +++ b/tidy3d/components/microwave/path_integrals/specs/voltage.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional - import numpy as np import pydantic.v1 as pd from typing_extensions import Self @@ -38,9 +36,9 @@ def from_terminal_positions( cls, plus_terminal: float, minus_terminal: float, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, extrapolate_to_endpoints: bool = True, snap_path_to_grid: bool = True, ) -> Self: @@ -94,9 +92,9 @@ def from_terminal_positions( @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **path_kwargs, ) -> Ax: @@ -159,9 +157,9 @@ class Custom2DVoltageIntegralSpec(Custom2DPathIntegralSpec): @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **path_kwargs, ) -> Ax: diff --git a/tidy3d/components/microwave/path_integrals/types.py b/tidy3d/components/microwave/path_integrals/types.py index 0bfda5285b..9f7095a282 100644 --- a/tidy3d/components/microwave/path_integrals/types.py +++ b/tidy3d/components/microwave/path_integrals/types.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - from tidy3d.components.microwave.path_integrals.specs.current import ( AxisAlignedCurrentIntegralSpec, CompositeCurrentIntegralSpec, @@ -14,7 +12,7 @@ Custom2DVoltageIntegralSpec, ) -VoltagePathSpecType = Union[AxisAlignedVoltageIntegralSpec, Custom2DVoltageIntegralSpec] -CurrentPathSpecType = Union[ - AxisAlignedCurrentIntegralSpec, Custom2DCurrentIntegralSpec, CompositeCurrentIntegralSpec -] +VoltagePathSpecType = AxisAlignedVoltageIntegralSpec | Custom2DVoltageIntegralSpec +CurrentPathSpecType = ( + AxisAlignedCurrentIntegralSpec | Custom2DCurrentIntegralSpec | CompositeCurrentIntegralSpec +) diff --git a/tidy3d/components/mode/data/sim_data.py b/tidy3d/components/mode/data/sim_data.py index 7647d220e7..f906dd3e4a 100644 --- a/tidy3d/components/mode/data/sim_data.py +++ b/tidy3d/components/mode/data/sim_data.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Literal, Optional, Union +from typing import Literal import pydantic.v1 as pd @@ -14,7 +14,7 @@ from tidy3d.components.types import TYPE_TAG_STR, Ax, PlotScale from tidy3d.components.types.monitor_data import ModeSolverDataType -ModeSimulationMonitorDataType = Union[PermittivityData, MediumData] +ModeSimulationMonitorDataType = PermittivityData | MediumData class ModeSimulationData(AbstractYeeGridSimulationData): @@ -50,8 +50,8 @@ def plot_field( scale: PlotScale = "lin", eps_alpha: float = 0.2, robust: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, + vmin: float | None = None, + vmax: float | None = None, ax: Ax = None, **sel_kwargs, ) -> Ax: diff --git a/tidy3d/components/mode/mode_solver.py b/tidy3d/components/mode/mode_solver.py index f703ca812e..e9d95cbe95 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 Literal, Optional, Union, get_args +from typing import Literal, get_args import numpy as np import pydantic.v1 as pydantic @@ -101,9 +101,9 @@ # Maximum allowed size of the field data produced by the mode solver MAX_MODES_DATA_SIZE_GB = 20 -MODE_SIMULATION_TYPE = Union[Simulation, EMESimulation] -MODE_SIMULATION_DATA_TYPE = Union[SimulationData, EMESimulationData] -MODE_PLANE_TYPE = Union[Box, ModeSource, ModeMonitor, ModeSolverMonitor] +MODE_SIMULATION_TYPE = Simulation | EMESimulation +MODE_SIMULATION_DATA_TYPE = SimulationData | EMESimulationData +MODE_PLANE_TYPE = Box | ModeSource | ModeMonitor | ModeSolverMonitor # When using ``angle_rotation`` without a bend, use a very large effective radius EFFECTIVE_RADIUS_FACTOR = 10_000 @@ -756,7 +756,7 @@ def rotated_bend_center(self) -> list: def _car_2_cyn( self, mode_solver_data: ModeSolverData - ) -> dict[Union[ScalarModeFieldCylindricalDataArray, ModeIndexDataArray]]: + ) -> dict[ScalarModeFieldCylindricalDataArray | ModeIndexDataArray]: """Convert cartesian fields to cylindrical fields centered at the rotated bend center.""" @@ -956,9 +956,7 @@ def _car_2_cyn( def _mode_rotation( self, - solver_ref_data_cylindrical: dict[ - Union[ScalarModeFieldCylindricalDataArray, ModeIndexDataArray] - ], + solver_ref_data_cylindrical: dict[ScalarModeFieldCylindricalDataArray | ModeIndexDataArray], solver: ModeSolver, ) -> ModeSolverData: """Rotate the mode solver solution from the reference plane in cylindrical coordinates @@ -1392,7 +1390,7 @@ def _filter_polarization(self, mode_solver_data: ModeSolverData): def _make_path_integrals( self, - ) -> tuple[tuple[Optional[VoltageIntegralType]], tuple[Optional[CurrentIntegralType]]]: + ) -> tuple[tuple[VoltageIntegralType | None], tuple[CurrentIntegralType | None]]: """Wrapper for making path integrals from the MicrowaveModeSpec. Note: overriden in the backend to support auto creation of path integrals.""" if not self._has_microwave_mode_spec: @@ -2004,9 +2002,7 @@ def to_source( **kwargs, ) - def to_monitor( - self, freqs: Optional[list[float]] = None, name: Optional[str] = None - ) -> ModeMonitor: + def to_monitor(self, freqs: list[float] | None = None, name: str | None = None) -> ModeMonitor: """Creates :class:`ModeMonitor` from a :class:`.ModeSolver` instance plus additional specifications. @@ -2048,9 +2044,7 @@ def to_monitor( name=name, ) - def to_mode_solver_monitor( - self, name: str, colocate: Optional[bool] = None - ) -> ModeSolverMonitor: + def to_mode_solver_monitor(self, name: str, colocate: bool | None = None) -> ModeSolverMonitor: """Creates :class:`ModeSolverMonitor` from a :class:`.ModeSolver` instance. Parameters @@ -2123,8 +2117,8 @@ def sim_with_source( @require_fdtd_simulation def sim_with_monitor( self, - freqs: Optional[list[float]] = None, - name: Optional[str] = None, + freqs: list[float] | None = None, + name: str | None = 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 @@ -2181,8 +2175,8 @@ def plot_field( scale: PlotScale = "lin", eps_alpha: float = 0.2, robust: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, + vmin: float | None = None, + vmax: float | None = None, ax: Ax = None, **sel_kwargs, ) -> Ax: @@ -2241,8 +2235,8 @@ def plot_field( def plot( self, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, fill_structures: bool = True, **patch_kwargs, ) -> Ax: @@ -2299,8 +2293,8 @@ def plot( def plot_eps( self, - freq: Optional[float] = None, - alpha: Optional[float] = None, + freq: float | None = None, + alpha: float | None = None, ax: Ax = None, ) -> Ax: """Plot the mode plane simulation's components. @@ -2353,8 +2347,8 @@ def plot_eps( def plot_structures_eps( self, - freq: Optional[float] = None, - alpha: Optional[float] = None, + freq: float | None = None, + alpha: float | None = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, diff --git a/tidy3d/components/mode/simulation.py b/tidy3d/components/mode/simulation.py index d0190747af..c91aa9c9db 100644 --- a/tidy3d/components/mode/simulation.py +++ b/tidy3d/components/mode/simulation.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional, Union - import numpy as np import pydantic.v1 as pd @@ -33,13 +31,13 @@ from .mode_solver import ModeSolver -ModeSimulationMonitorType = Union[PermittivityMonitor, MediumMonitor] +ModeSimulationMonitorType = PermittivityMonitor | MediumMonitor # dummy run time for conversion to FDTD sim # should be very small -- otherwise, generating tmesh will fail or take a long time RUN_TIME = 1e-30 -MODE_PLANE_TYPE = Union[Box, ModeSource, ModeMonitor, ModeSolverMonitor] +MODE_PLANE_TYPE = Box | ModeSource | ModeMonitor | ModeSolverMonitor # attributes shared between ModeSimulation class and ModeSolver class @@ -319,7 +317,7 @@ def _as_fdtd_sim(self) -> Simulation: def from_simulation( cls, simulation: AbstractYeeGridSimulation, - wavelength: Optional[pd.PositiveFloat] = None, + wavelength: pd.PositiveFloat | None = None, **kwargs, ) -> ModeSimulation: """Creates :class:`.ModeSimulation` from a :class:`.AbstractYeeGridSimulation`. @@ -376,7 +374,7 @@ def reduced_simulation_copy(self) -> ModeSimulation: @classmethod def from_mode_solver( - cls, mode_solver: ModeSolver, wavelength: Optional[pd.PositiveFloat] = None + cls, mode_solver: ModeSolver, wavelength: pd.PositiveFloat | None = None ) -> ModeSimulation: """Creates :class:`.ModeSimulation` from a :class:`.ModeSolver`. @@ -403,15 +401,15 @@ def from_mode_solver( def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - source_alpha: Optional[float] = 0, - monitor_alpha: Optional[float] = 0, - lumped_element_alpha: Optional[float] = 0, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + source_alpha: float | None = 0, + monitor_alpha: float | None = 0, + lumped_element_alpha: float | None = 0, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, fill_structures: bool = True, **patch_kwargs, ) -> Ax: @@ -498,8 +496,8 @@ def plot_mode_plane( def plot_eps_mode_plane( self, - freq: Optional[float] = None, - alpha: Optional[float] = None, + freq: float | None = None, + alpha: float | None = None, ax: Ax = None, ) -> Ax: """Plot the mode plane simulation's components. @@ -531,8 +529,8 @@ def plot_eps_mode_plane( def plot_structures_eps_mode_plane( self, - freq: Optional[float] = None, - alpha: Optional[float] = None, + freq: float | None = None, + alpha: float | None = 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 afb4fc9671..4823100e8a 100644 --- a/tidy3d/components/mode/solver.py +++ b/tidy3d/components/mode/solver.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import numpy as np @@ -53,7 +53,7 @@ def compute_modes( symmetry=(0, 0), direction="+", solver_basis_fields=None, - plane_center: Optional[tuple[float, float]] = None, + plane_center: tuple[float, float] | None = None, ) -> tuple[Numpy, Numpy, EpsSpecType]: """ Solve for the modes of a waveguide cross-section. diff --git a/tidy3d/components/mode_spec.py b/tidy3d/components/mode_spec.py index c0d37a0bb1..a70d2694f8 100644 --- a/tidy3d/components/mode_spec.py +++ b/tidy3d/components/mode_spec.py @@ -4,7 +4,7 @@ from abc import ABC from math import isclose -from typing import Literal, Optional, Union +from typing import Literal import numpy as np import pydantic.v1 as pd @@ -39,7 +39,7 @@ class ModeSortSpec(Tidy3dBaseModel): """ # Filtering stage - filter_key: Optional[MODE_DATA_KEYS] = pd.Field( + filter_key: MODE_DATA_KEYS | None = pd.Field( None, title="Filtering key", description="Quantity used to filter modes into two groups before sorting.", @@ -56,13 +56,13 @@ class ModeSortSpec(Tidy3dBaseModel): ) # Sorting stage - sort_key: Optional[MODE_DATA_KEYS] = pd.Field( + sort_key: MODE_DATA_KEYS | None = pd.Field( None, title="Sorting key", description="Quantity used to sort modes within each filtered group. If ``None``, " "sorting is by descending effective index.", ) - sort_reference: Optional[float] = pd.Field( + sort_reference: float | None = pd.Field( None, title="Sorting reference", description=( @@ -76,7 +76,7 @@ class ModeSortSpec(Tidy3dBaseModel): ) # Frequency tracking - applied after sorting and filtering - track_freq: Optional[TrackFreq] = pd.Field( + track_freq: TrackFreq | None = pd.Field( "central", title="Tracking base frequency", description="If provided, enables cross-frequency mode tracking. Can be 'lowest', " @@ -176,13 +176,13 @@ class AbstractModeSpec(Tidy3dBaseModel, ABC): "Note: currently only supported when 'angle_phi' is a multiple of 'np.pi'.", ) - track_freq: Optional[TrackFreq] = pd.Field( + track_freq: TrackFreq | None = pd.Field( None, title="Mode Tracking Frequency (deprecated)", description="Deprecated. Use 'sort_spec.track_freq' instead.", ) - group_index_step: Union[pd.PositiveFloat, bool] = pd.Field( + group_index_step: pd.PositiveFloat | bool = pd.Field( False, title="Frequency step for group index computation", description="Control the computation of the group index alongside the effective index. If " @@ -311,7 +311,7 @@ def _track_freq_deprecated(cls, val): return val @property - def _track_freq(self) -> Optional[TrackFreq]: + def _track_freq(self) -> TrackFreq | None: """Private resolver for tracking frequency: prefers ModeSpec.track_freq if set, otherwise falls back to ModeSortSpec.track_freq.""" if self.track_freq is not None: diff --git a/tidy3d/components/monitor.py b/tidy3d/components/monitor.py index 64386a5d09..08e1b2ee62 100644 --- a/tidy3d/components/monitor.py +++ b/tidy3d/components/monitor.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Literal, Optional +from typing import Literal import numpy as np import pydantic.v1 as pydantic @@ -360,9 +360,9 @@ class AbstractModeMonitor(PlanarMonitor, FreqMonitor): def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **patch_kwargs, ) -> Ax: diff --git a/tidy3d/components/parameter_perturbation.py b/tidy3d/components/parameter_perturbation.py index 8b4cf51392..c427676e35 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, Optional, Union +from collections.abc import Callable import numpy as np import pydantic.v1 as pd @@ -41,7 +41,7 @@ class AbstractPerturbation(ABC, Tidy3dBaseModel): @cached_property @abstractmethod - def perturbation_range(self) -> Union[tuple[float, float], tuple[Complex, Complex]]: + def perturbation_range(self) -> tuple[float, float] | tuple[Complex, Complex]: """Perturbation range.""" @cached_property @@ -50,7 +50,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: float | Complex): """Find value range for a linear perturbation.""" if coeff in (0, 0j): # to avoid 0*inf return np.array([0, 0]) @@ -58,8 +58,8 @@ def _linear_range(interval: tuple[float, float], ref: float, coeff: Union[float, @staticmethod def _get_val( - field: Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType], val: FieldVal - ) -> Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType]: + field: ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType, val: FieldVal + ) -> ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType: """Get specified value from a field.""" if val == "real": @@ -88,19 +88,19 @@ def _get_val( def ensure_temp_in_range( sample: Callable[ - Union[ArrayLike[float], CustomSpatialDataType], - Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType], + ArrayLike[float] | CustomSpatialDataType, + ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType, ], ) -> Callable[ - Union[ArrayLike[float], CustomSpatialDataType], - Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType], + ArrayLike[float] | CustomSpatialDataType, + ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType, ]: """Decorate ``sample`` to log warning if temperature supplied is out of bounds.""" @functools.wraps(sample) def _sample( - self, temperature: Union[ArrayLike[float], CustomSpatialDataType] - ) -> Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType]: + self, temperature: ArrayLike[float] | CustomSpatialDataType + ) -> ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType: """New sample function.""" if np.iscomplexobj(temperature): @@ -130,8 +130,8 @@ class HeatPerturbation(AbstractPerturbation): @abstractmethod def sample( - self, temperature: Union[ArrayLike[float], CustomSpatialDataType] - ) -> Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType]: + self, temperature: ArrayLike[float] | CustomSpatialDataType + ) -> ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType: """Sample perturbation. Parameters @@ -231,7 +231,7 @@ class LinearHeatPerturbation(HeatPerturbation): units=KELVIN, ) - coeff: Union[float, Complex] = pd.Field( + coeff: float | Complex = pd.Field( ..., title="Thermo-optic Coefficient", description="Sensitivity (derivative) of perturbation with respect to temperature.", @@ -239,14 +239,14 @@ class LinearHeatPerturbation(HeatPerturbation): ) @cached_property - def perturbation_range(self) -> Union[tuple[float, float], tuple[Complex, Complex]]: + def perturbation_range(self) -> 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) @ensure_temp_in_range def sample( - self, temperature: Union[ArrayLike[float], CustomSpatialDataType] - ) -> Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType]: + self, temperature: ArrayLike[float] | CustomSpatialDataType + ) -> ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType: """Sample perturbation at temperature points. Parameters @@ -336,7 +336,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) -> 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() @@ -368,8 +368,8 @@ def compute_temperature_range(cls, values): @ensure_temp_in_range def sample( - self, temperature: Union[ArrayLike[float], CustomSpatialDataType] - ) -> Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType]: + self, temperature: ArrayLike[float] | CustomSpatialDataType + ) -> ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType: """Sample perturbation at provided temperature points. Parameters @@ -418,7 +418,7 @@ def is_complex(self) -> bool: return np.iscomplexobj(self.perturbation_values) -HeatPerturbationType = Union[LinearHeatPerturbation, CustomHeatPerturbation] +HeatPerturbationType = LinearHeatPerturbation | CustomHeatPerturbation """ Elementary charge perturbation classes """ @@ -427,26 +427,26 @@ def is_complex(self) -> bool: def ensure_charge_in_range( sample: Callable[ [ - Union[ArrayLike[float], CustomSpatialDataType], - Union[ArrayLike[float], CustomSpatialDataType], + ArrayLike[float] | CustomSpatialDataType, + ArrayLike[float] | CustomSpatialDataType, ], - Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType], + ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType, ], ) -> Callable[ [ - Union[ArrayLike[float], CustomSpatialDataType], - Union[ArrayLike[float], CustomSpatialDataType], + ArrayLike[float] | CustomSpatialDataType, + ArrayLike[float] | CustomSpatialDataType, ], - Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType], + ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType, ]: """Decorate ``sample`` to log warning if charge supplied is out of bounds.""" @functools.wraps(sample) def _sample( self, - electron_density: Union[ArrayLike[float], CustomSpatialDataType], - hole_density: Union[ArrayLike[float], CustomSpatialDataType], - ) -> Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType]: + electron_density: ArrayLike[float] | CustomSpatialDataType, + hole_density: ArrayLike[float] | CustomSpatialDataType, + ) -> ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType: """New sample function.""" # disable complex input @@ -498,9 +498,9 @@ class ChargePerturbation(AbstractPerturbation): @abstractmethod def sample( self, - electron_density: Union[ArrayLike[float], CustomSpatialDataType], - hole_density: Union[ArrayLike[float], CustomSpatialDataType], - ) -> Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType]: + electron_density: ArrayLike[float] | CustomSpatialDataType, + hole_density: ArrayLike[float] | CustomSpatialDataType, + ) -> ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType: """Sample perturbation. Parameters @@ -661,7 +661,7 @@ class LinearChargePerturbation(ChargePerturbation): ) @cached_property - def perturbation_range(self) -> Union[tuple[float, float], tuple[Complex, Complex]]: + def perturbation_range(self) -> tuple[float, float] | tuple[Complex, Complex]: """Range of possible perturbation values within provided ``electron_range`` and ``hole_range``. """ @@ -676,9 +676,9 @@ def perturbation_range(self) -> Union[tuple[float, float], tuple[Complex, Comple @ensure_charge_in_range def sample( self, - electron_density: Union[ArrayLike[float], CustomSpatialDataType], - hole_density: Union[ArrayLike[float], CustomSpatialDataType], - ) -> Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType]: + electron_density: ArrayLike[float] | CustomSpatialDataType, + hole_density: ArrayLike[float] | CustomSpatialDataType, + ) -> ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType: """Sample perturbation at electron and hole density points. Parameters @@ -822,7 +822,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) -> 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() @@ -865,9 +865,9 @@ def compute_eh_ranges(cls, values): @ensure_charge_in_range def sample( self, - electron_density: Union[ArrayLike[float], CustomSpatialDataType], - hole_density: Union[ArrayLike[float], CustomSpatialDataType], - ) -> Union[ArrayLike[float], ArrayLike[Complex], CustomSpatialDataType]: + electron_density: ArrayLike[float] | CustomSpatialDataType, + hole_density: ArrayLike[float] | CustomSpatialDataType, + ) -> ArrayLike[float] | ArrayLike[Complex] | CustomSpatialDataType: """Sample perturbation at electron and hole density points. Parameters @@ -970,9 +970,9 @@ def is_complex(self) -> bool: return np.iscomplexobj(self.perturbation_values) -ChargePerturbationType = Union[LinearChargePerturbation, CustomChargePerturbation] +ChargePerturbationType = LinearChargePerturbation | CustomChargePerturbation -PerturbationType = Union[HeatPerturbationType, ChargePerturbationType] +PerturbationType = HeatPerturbationType | ChargePerturbationType class ParameterPerturbation(Tidy3dBaseModel): @@ -1037,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) -> tuple[float, float] | tuple[Complex, Complex]: """Range of possible parameter perturbation values due to both heat and charge effects.""" prange = np.zeros(2) @@ -1158,13 +1158,13 @@ class PermittivityPerturbation(Tidy3dBaseModel): >>> permittivity_pb = PermittivityPerturbation(delta_eps=delta_eps, delta_sigma=delta_sigma) """ - delta_eps: Optional[ParameterPerturbation] = pd.Field( + delta_eps: ParameterPerturbation | None = pd.Field( None, title="Permittivity Perturbation", description="Perturbation model for permittivity.", ) - delta_sigma: Optional[ParameterPerturbation] = pd.Field( + delta_sigma: ParameterPerturbation | None = pd.Field( None, title="Conductivity Perturbation", description="Perturbation model for conductivity.", @@ -1647,13 +1647,13 @@ class IndexPerturbation(Tidy3dBaseModel): >>> index_pb = IndexPerturbation(delta_n=dn_pb, delta_k=dk_pb, freq=C_0) """ - delta_n: Optional[ParameterPerturbation] = pd.Field( + delta_n: ParameterPerturbation | None = pd.Field( None, title="Refractive Index Perturbation", description="Perturbation of the real part of refractive index.", ) - delta_k: Optional[ParameterPerturbation] = pd.Field( + delta_k: ParameterPerturbation | None = pd.Field( None, title="Exctinction Coefficient Perturbation", description="Perturbation of the imaginary part of refractive index.", diff --git a/tidy3d/components/scene.py b/tidy3d/components/scene.py index 85b643188c..3cc78056e2 100644 --- a/tidy3d/components/scene.py +++ b/tidy3d/components/scene.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Literal, Optional, Union +from typing import Literal import autograd.numpy as np @@ -138,7 +138,7 @@ class Scene(Tidy3dBaseModel): "`PECMedium` to 100, and others to 0.", ) - plot_length_units: Optional[LengthUnit] = pd.Field( + plot_length_units: LengthUnit | None = pd.Field( "μm", title="Plot Units", description="When set to a supported ``LengthUnit``, " @@ -421,11 +421,11 @@ def intersecting_structures( @staticmethod def _get_plot_lims( bounds: Bound, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = 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) @@ -456,12 +456,12 @@ def _get_plot_lims( @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, fill_structures: bool = True, **patch_kwargs, ) -> Ax: @@ -500,12 +500,12 @@ def plot( @add_ax_if_none def plot_structures( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, fill: bool = True, ) -> Ax: """Plot each of scene's structures on a plane defined by one nonzero x,y,z coordinate. @@ -657,11 +657,11 @@ def _add_cbar( def _set_plot_bounds( bounds: Bound, ax: Ax, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, ) -> Ax: """Sets the xy limits of the scene at a plane, useful after plotting. @@ -693,11 +693,11 @@ def _set_plot_bounds( def _get_structures_2dbox( self, 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, + x: float | None = None, + y: float | None = None, + z: float | None = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, ) -> list[tuple[Medium, Shapely]]: """Compute list of shapes to plot on 2d box specified by (x_min, x_max), (y_min, y_max). @@ -809,15 +809,15 @@ def _filter_structures_plane( @add_ax_if_none def plot_eps( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - freq: Optional[float] = None, - alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + freq: float | None = None, + alpha: float | None = None, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, - eps_lim: tuple[Union[float, None], Union[float, None]] = (None, None), + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, + eps_lim: tuple[float | None, float | None] = (None, None), scale: PlotScale = "lin", ) -> Ax: """Plot each of scene's components on a plane defined by one nonzero x,y,z coordinate. @@ -876,20 +876,20 @@ def plot_eps( @add_ax_if_none def plot_structures_eps( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - freq: Optional[float] = None, - alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + freq: float | None = None, + alpha: float | None = None, cbar: bool = True, reverse: bool = False, - eps_lim: tuple[Union[float, None], Union[float, None]] = (None, None), + eps_lim: tuple[float | None, float | None] = (None, None), scale: PlotScale = "lin", ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, grid: Grid = None, - eps_component: Optional[PermittivityComponent] = None, + eps_component: PermittivityComponent | None = None, ) -> Ax: """Plot each of scene'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. @@ -956,21 +956,21 @@ def plot_structures_eps( @add_ax_if_none def plot_structures_property( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - freq: Optional[float] = None, - alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + freq: float | None = None, + alpha: float | None = None, cbar: bool = True, reverse: bool = False, - limits: tuple[Union[float, None], Union[float, None]] = (None, None), + limits: tuple[float | None, float | None] = (None, None), scale: PlotScale = "lin", ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, grid: Grid = None, property: Literal["eps", "doping", "N_a", "N_d"] = "eps", - eps_component: Optional[PermittivityComponent] = None, + eps_component: PermittivityComponent | None = None, ) -> Ax: """Plot each of scene'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. @@ -1197,7 +1197,7 @@ def _add_cbar_eps( eps_max: float, ax: Ax = None, reverse: bool = False, - norm: Optional[mpl.colors.Normalize] = None, + norm: mpl.colors.Normalize | None = None, ) -> None: """Add a permittivity colorbar to plot.""" Scene._add_cbar( @@ -1212,8 +1212,8 @@ def _add_cbar_eps( @staticmethod def _eps_bounds( medium_list: list[Medium], - freq: Optional[float] = None, - eps_component: Optional[PermittivityComponent] = None, + freq: float | None = None, + eps_component: PermittivityComponent | None = None, ) -> 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] @@ -1230,7 +1230,7 @@ def _eps_bounds( return eps_min, eps_max def eps_bounds( - self, freq: Optional[float] = None, eps_component: Optional[str] = None + self, freq: float | None = None, eps_component: str | None = None ) -> tuple[float, float]: """Compute range of (real) permittivity present in the scene at frequency "freq". @@ -1267,7 +1267,7 @@ def _pcolormesh_shape_custom_medium_structure_eps( shape: Shapely, ax: Ax, grid: Grid, - eps_component: Optional[PermittivityComponent] = None, + eps_component: PermittivityComponent | None = None, norm: mpl.colors.Normalize = None, ): """ @@ -1418,9 +1418,9 @@ def _get_structure_eps_plot_params( eps_min: float, eps_max: float, reverse: bool = False, - alpha: Optional[float] = None, - eps_component: Optional[PermittivityComponent] = None, - norm: Optional[mpl.colors.Normalize] = None, + alpha: float | None = None, + eps_component: PermittivityComponent | None = None, + norm: mpl.colors.Normalize | None = None, ) -> PlotParams: """Constructs the plot parameters for a given medium in scene.plot_eps().""" @@ -1467,9 +1467,9 @@ def _plot_shape_structure_eps( eps_max: float, ax: Ax, reverse: bool = False, - alpha: Optional[float] = None, - eps_component: Optional[PermittivityComponent] = None, - norm: Optional[mpl.colors.Normalize] = None, + alpha: float | None = None, + eps_component: PermittivityComponent | None = None, + norm: mpl.colors.Normalize | None = None, ) -> Ax: """Plot a structure's cross section shape for a given medium, grayscale for permittivity.""" plot_params = self._get_structure_eps_plot_params( @@ -1491,15 +1491,15 @@ def _plot_shape_structure_eps( @add_ax_if_none def plot_heat_charge_property( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + alpha: float | None = None, cbar: bool = True, property: str = "heat_conductivity", ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = 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. @@ -1545,15 +1545,15 @@ def plot_heat_charge_property( @add_ax_if_none def plot_structures_heat_conductivity( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + alpha: float | None = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = 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. @@ -1610,16 +1610,16 @@ def plot_structures_heat_conductivity( @add_ax_if_none def plot_structures_heat_charge_property( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + alpha: float | None = None, cbar: bool = True, property: str = "heat_conductivity", reverse: bool = False, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = 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. @@ -1762,7 +1762,7 @@ def _get_structure_heat_charge_property_plot_params( property_val_min: float, property_val_max: float, reverse: bool = False, - alpha: Optional[float] = None, + alpha: float | None = None, property: str = "heat_conductivity", ) -> PlotParams: """Constructs the plot parameters for a given medium in @@ -1809,7 +1809,7 @@ def _plot_shape_structure_heat_charge_property( property: str, ax: Ax, reverse: bool = False, - alpha: Optional[float] = None, + alpha: float | None = None, ) -> Ax: """Plot a structure's cross section shape for a given medium, grayscale for thermal conductivity. @@ -1829,14 +1829,14 @@ def _plot_shape_structure_heat_charge_property( @add_ax_if_none def plot_heat_conductivity( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + alpha: float | None = None, cbar: bool = True, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = 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. @@ -2103,7 +2103,7 @@ def _pcolormesh_shape_doping_box( """ coords = "xyz" normal_axis_ind, normal_position = Box.parse_xyz_kwargs(x=x, y=y, z=z) - normal_axis, plane_axes = Box.pop_axis(coords, normal_axis_ind) + _normal_axis, _plane_axes = Box.pop_axis(coords, normal_axis_ind) # make grid for eps interpolation # we will do this by combining shape bounds and points where custom eps is provided diff --git a/tidy3d/components/simulation.py b/tidy3d/components/simulation.py index b654b935aa..7f2e04045a 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 Literal, Optional, Union, get_args +from typing import Literal, get_args import autograd.numpy as np @@ -302,7 +302,7 @@ class AbstractYeeGridSimulation(AbstractSimulation, ABC): * `Using automatic nonuniform meshing <../../notebooks/AutoGrid.html>`_ """ - subpixel: Union[bool, SubpixelSpec] = pydantic.Field( + subpixel: bool | SubpixelSpec = pydantic.Field( SubpixelSpec(), title="Subpixel Averaging", description="Apply subpixel averaging methods of the permittivity on structure interfaces " @@ -356,7 +356,7 @@ class AbstractYeeGridSimulation(AbstractSimulation, ABC): * `Dielectric constant assignment on Yee grids `_ """ - simulation_type: Optional[Literal["autograd_fwd", "autograd_bwd", "tidy3d", None]] = ( + simulation_type: Literal["autograd_fwd", "autograd_bwd", "tidy3d", None] | None = ( pydantic.Field( "tidy3d", title="Simulation Type", @@ -365,7 +365,7 @@ class AbstractYeeGridSimulation(AbstractSimulation, ABC): ) ) - post_norm: Union[float, FreqDataArray] = pydantic.Field( + post_norm: float | FreqDataArray = pydantic.Field( 1.0, title="Post Normalization Values", description="Factor to multiply the fields by after running, " @@ -491,12 +491,12 @@ def _shifted_internal_absorbers(self) -> list[InternalAbsorber]: @add_ax_if_none def plot_absorbers( self, - 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, + x: float | None = None, + y: float | None = None, + z: float | None = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, + alpha: float | None = None, ax: Ax = None, shifted: bool = False, ) -> Ax: @@ -541,17 +541,17 @@ def plot_absorbers( @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - source_alpha: Optional[float] = None, - monitor_alpha: Optional[float] = None, - lumped_element_alpha: Optional[float] = None, - absorber_alpha: Optional[float] = None, + source_alpha: float | None = None, + monitor_alpha: float | None = None, + lumped_element_alpha: float | None = None, + absorber_alpha: float | None = None, absorber_actual_placement: bool = False, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, fill_structures: bool = True, **patch_kwargs, ) -> Ax: @@ -638,21 +638,21 @@ def plot( @add_ax_if_none def plot_eps( self, - 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, - absorber_alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + freq: float | None = None, + alpha: float | None = None, + source_alpha: float | None = None, + monitor_alpha: float | None = None, + lumped_element_alpha: float | None = None, + absorber_alpha: float | None = None, absorber_actual_placement: bool = False, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, ax: Ax = None, - eps_component: Optional[PermittivityComponent] = None, - eps_lim: tuple[Union[float, None], Union[float, None]] = (None, None), + eps_component: PermittivityComponent | None = None, + eps_lim: tuple[float | None, 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. @@ -761,18 +761,18 @@ def plot_eps( @add_ax_if_none def plot_structures_eps( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - freq: Optional[float] = None, - alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + freq: float | None = None, + alpha: float | None = None, cbar: bool = True, reverse: bool = False, ax: Ax = 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), + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, + eps_component: PermittivityComponent | None = None, + eps_lim: tuple[float | None, 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. @@ -852,11 +852,11 @@ def plot_structures_eps( @add_ax_if_none def plot_pml( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's absorbing boundaries @@ -948,7 +948,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: Optional[float] = None) -> tuple[float, float]: + def eps_bounds(self, freq: float | None = None) -> tuple[float, float]: """Compute range of (real) permittivity present in the simulation at frequency "freq".""" log.warning( @@ -1026,12 +1026,12 @@ def internal_snapping_points(self) -> list[CoordinateOptional]: @add_ax_if_none def plot_lumped_elements( self, - 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, + x: float | None = None, + y: float | None = None, + z: float | None = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, + alpha: float | None = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's lumped elements on a plane defined by one @@ -1071,12 +1071,12 @@ def plot_lumped_elements( @add_ax_if_none def plot_grid( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, override_structures_alpha: float = 1, snapping_points_alpha: float = 1, **kwargs, @@ -1219,9 +1219,9 @@ def plot_grid( @add_ax_if_none def plot_boundaries( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **kwargs, ) -> Ax: @@ -1514,9 +1514,7 @@ 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: Optional[bool] = None - ): + def _discretize_inds_monitor(self, monitor: Monitor | Box, colocate: bool | None = 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 @@ -1575,7 +1573,7 @@ def epsilon( self, box: Box, coord_key: str = "centers", - freq: Optional[float] = None, + freq: float | None = None, ) -> xr.DataArray: """Get array of permittivity at volume specified by box and freq. @@ -1617,7 +1615,7 @@ def epsilon_on_grid( self, grid: Grid, coord_key: str = "centers", - freq: Optional[float] = None, + freq: float | None = None, ) -> xr.DataArray: """Get array of permittivity at a given freq on a given grid. @@ -1856,17 +1854,17 @@ def subsection( self, region: Box, boundary_spec: BoundarySpec = None, - grid_spec: Union[GridSpec, Literal["identical"]] = None, - symmetry: Optional[tuple[Symmetry, Symmetry, Symmetry]] = None, + grid_spec: GridSpec | Literal["identical"] = None, + symmetry: tuple[Symmetry, Symmetry, Symmetry] | None = None, warn_symmetry_expansion: bool = True, - sources: Optional[tuple[SourceType, ...]] = None, - monitors: Optional[tuple[MonitorType, ...]] = None, + sources: tuple[SourceType, ...] | None = None, + monitors: tuple[MonitorType, ...] | None = None, remove_outside_structures: bool = True, remove_outside_custom_mediums: bool = False, include_pml_cells: bool = False, validate_geometries: bool = True, deep_copy: bool = True, - internal_absorbers: Optional[tuple[InternalAbsorber, ...]] = None, + internal_absorbers: tuple[InternalAbsorber, ...] | None = None, **kwargs, ) -> AbstractYeeGridSimulation: """Generate a simulation instance containing only the ``region``. @@ -2146,7 +2144,7 @@ def validate_pre_upload(self) -> None: self._validate_finalized() log.end_capture(self) - def _make_pec_frame(self, obj: Union[ModeSource, InternalAbsorber]) -> Structure: + def _make_pec_frame(self, obj: ModeSource | InternalAbsorber) -> Structure: """Make a pec frame around a mode source or an internal absorber. For mode sources, the frame is added around the injection plane. For internal absorbers, a backing pec plate is also added on the non-absorbing side. @@ -2174,7 +2172,7 @@ def _make_pec_frame(self, obj: Union[ModeSource, InternalAbsorber]) -> Structure return structure def _pec_frame_box( - self, obj: Union[ModeSource, InternalAbsorber], expand: bool = False + self, obj: ModeSource | InternalAbsorber, expand: bool = False ) -> tuple[Box, int, str]: """Return pec bounding box, frame axis and object's direction""" @@ -2714,7 +2712,7 @@ class Simulation(AbstractYeeGridSimulation): """ - normalize_index: Union[pydantic.NonNegativeInt, None] = pydantic.Field( + normalize_index: pydantic.NonNegativeInt | None = pydantic.Field( 0, title="Normalization index", description="Index of the source in the tuple of sources whose spectrum will be used to " @@ -2889,7 +2887,7 @@ class Simulation(AbstractYeeGridSimulation): """ # TODO: at a later time (once well tested) we could consider making default of RunTimeSpec() - run_time: Union[pydantic.PositiveFloat, RunTimeSpec] = pydantic.Field( + run_time: pydantic.PositiveFloat | RunTimeSpec = pydantic.Field( ..., title="Run Time", description="Total electromagnetic evolution time in seconds. " @@ -2950,7 +2948,7 @@ class Simulation(AbstractYeeGridSimulation): """ - low_freq_smoothing: Optional[LowFrequencySmoothingSpec] = pydantic.Field( + low_freq_smoothing: LowFrequencySmoothingSpec | None = pydantic.Field( None, title="Low Frequency Smoothing", description="The low frequency smoothing parameters for the simulation.", @@ -4240,7 +4238,7 @@ def _validate_mode_objects(self) -> None: """Create a ModeSolver for each mode object in order to validate.""" from .mode.mode_solver import ModeSolver - def validate_mode_object(mode_obj: Union[ModeSource, AbstractModeMonitor], msg_prefix: str): + def validate_mode_object(mode_obj: ModeSource | AbstractModeMonitor, msg_prefix: str): # Warn if pml is too thick ModeSolver._warn_thick_pml( simulation=self, @@ -5091,14 +5089,15 @@ def _check_bloch_vec( def to_gdstk( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, - gds_layer_dtype_map: Optional[ - dict[AbstractMedium, tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt]] - ] = None, + gds_layer_dtype_map: dict[ + AbstractMedium, tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt] + ] + | None = None, ) -> list: """Convert a simulation's planar slice to a .gds type polygon list. @@ -5162,14 +5161,15 @@ def to_gdstk( def to_gds( self, cell, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, - gds_layer_dtype_map: Optional[ - dict[AbstractMedium, tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt]] - ] = None, + gds_layer_dtype_map: dict[ + AbstractMedium, tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt] + ] + | None = None, ) -> None: """Append the simulation structures to a .gds cell. @@ -5216,14 +5216,15 @@ def to_gds( def to_gds_file( self, fname: str, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, - gds_layer_dtype_map: Optional[ - dict[AbstractMedium, tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt]] - ] = None, + gds_layer_dtype_map: dict[ + AbstractMedium, tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt] + ] + | None = None, gds_cell_name: str = "MAIN", ) -> None: """Append the simulation structures to a .gds cell. diff --git a/tidy3d/components/source/base.py b/tidy3d/components/source/base.py index c94366ed3e..100f4ea9df 100644 --- a/tidy3d/components/source/base.py +++ b/tidy3d/components/source/base.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC -from typing import Optional import pydantic.v1 as pydantic @@ -70,9 +69,9 @@ def _freqs_lower_bound(cls, val): def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **patch_kwargs, ) -> Ax: diff --git a/tidy3d/components/source/current.py b/tidy3d/components/source/current.py index a768a94e59..9f777a7a41 100644 --- a/tidy3d/components/source/current.py +++ b/tidy3d/components/source/current.py @@ -4,10 +4,9 @@ from abc import ABC from math import cos, isclose, sin -from typing import Optional +from typing import Literal import pydantic.v1 as pydantic -from typing_extensions import Literal from tidy3d.components.base import cached_property from tidy3d.components.data.dataset import FieldDataset @@ -208,7 +207,7 @@ class CustomCurrentSource(ReverseInterpolatedSource): * `Defining spatially-varying sources <../../notebooks/CustomFieldSource.html>`_ """ - current_dataset: Optional[FieldDataset] = pydantic.Field( + current_dataset: FieldDataset | None = pydantic.Field( ..., title="Current Dataset", description=":class:`.FieldDataset` containing the desired frequency-domain " diff --git a/tidy3d/components/source/field.py b/tidy3d/components/source/field.py index 1632edcaa8..25e491a249 100644 --- a/tidy3d/components/source/field.py +++ b/tidy3d/components/source/field.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC -from typing import Optional, Union import numpy as np import pydantic.v1 as pydantic @@ -215,7 +214,7 @@ class CustomFieldSource(FieldSource, PlanarSource): * `Defining spatially-varying sources <../../notebooks/CustomFieldSource.html>`_ """ - field_dataset: Optional[FieldDataset] = pydantic.Field( + field_dataset: FieldDataset | None = pydantic.Field( ..., title="Field Dataset", description=":class:`.FieldDataset` containing the desired frequency-domain " @@ -408,7 +407,7 @@ class ModeSource(DirectionalSource, PlanarSource, BroadbandSource): "``num_modes`` in the solver will be set to ``mode_index + 1``.", ) - frame: Optional[PECFrame] = pydantic.Field( + frame: PECFrame | None = pydantic.Field( None, title="Source Frame", description="Add a thin frame around the source during the FDTD run to improve " @@ -492,7 +491,7 @@ class PlaneWave(AngledFieldSource, PlanarSource, BroadbandSource): * `Using FDTD to Compute a Transmission Spectrum `__ """ - angular_spec: Union[FixedInPlaneKSpec, FixedAngleSpec] = pydantic.Field( + angular_spec: FixedInPlaneKSpec | FixedAngleSpec = pydantic.Field( FixedInPlaneKSpec(), title="Angular Dependence Specification", description="Specification of plane wave propagation direction dependence on wavelength.", @@ -533,7 +532,7 @@ def _post_init_validators(self) -> None: the source frequency range is entirely below ``f_crit * CRITICAL_FREQUENCY_FACTOR.""" if self._is_fixed_angle or self.num_freqs == 1: return - freq_min, freq_max = self.source_time.frequency_range_sigma(sigma=CHEB_GRID_WIDTH) + _freq_min, freq_max = self.source_time.frequency_range_sigma(sigma=CHEB_GRID_WIDTH) f_crit = self.source_time._freq0 * np.sin(self.angle_theta) if f_crit * CRITICAL_FREQUENCY_FACTOR > freq_max: raise SetupError( @@ -721,9 +720,9 @@ def injection_plane_center(self) -> Coordinate: def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **patch_kwargs, ) -> Ax: diff --git a/tidy3d/components/source/time.py b/tidy3d/components/source/time.py index f2532ea3d7..dca7da79c1 100644 --- a/tidy3d/components/source/time.py +++ b/tidy3d/components/source/time.py @@ -4,7 +4,6 @@ import logging from abc import ABC, abstractmethod -from typing import Optional, Union import numpy as np import pydantic.v1 as pydantic @@ -86,7 +85,7 @@ def _frequency_range_sigma_cached(self) -> FreqBound: return self.frequency_range_sigma(sigma=DEFAULT_SIGMA) @abstractmethod - def end_time(self) -> Optional[float]: + def end_time(self) -> float | None: """Time after which the source is effectively turned off / close to zero amplitude.""" @cached_property @@ -218,7 +217,7 @@ def amp_time(self, time: float) -> complex: return pulse_amp - def end_time(self) -> Optional[float]: + def end_time(self) -> float | None: """Time after which the source is effectively turned off / close to zero amplitude.""" # TODO: decide if we should continue to return an end_time if the DC component remains @@ -422,7 +421,7 @@ def amp_time(self, time: float) -> complex: return const * offset * oscillation * amp - def end_time(self) -> Optional[float]: + def end_time(self) -> float | None: """Time after which the source is effectively turned off / close to zero amplitude.""" return None @@ -467,7 +466,7 @@ class CustomSourceTime(Pulse): description="Time delay of the envelope in units of 1 / (``2pi * fwidth``).", ) - source_time_dataset: Optional[TimeDataset] = pydantic.Field( + source_time_dataset: TimeDataset | None = pydantic.Field( ..., title="Source time dataset", description="Dataset for storing the envelope of the custom source time. " @@ -590,7 +589,7 @@ def amp_time(self, time: float) -> complex: return offset * oscillation * amp * envelope - def end_time(self) -> Optional[float]: + def end_time(self) -> float | None: """Time after which the source is effectively turned off / close to zero amplitude.""" if self.source_time_dataset is None: @@ -605,4 +604,4 @@ def end_time(self) -> Optional[float]: return np.max(t_non_zero) -SourceTimeType = Union[GaussianPulse, ContinuousWave, CustomSourceTime] +SourceTimeType = GaussianPulse | ContinuousWave | CustomSourceTime diff --git a/tidy3d/components/source/utils.py b/tidy3d/components/source/utils.py index 4996138c13..a2c61be880 100644 --- a/tidy3d/components/source/utils.py +++ b/tidy3d/components/source/utils.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - from .current import CustomCurrentSource, PointDipole, UniformCurrentSource from .field import ( TFSF, @@ -15,14 +13,14 @@ ) # sources allowed in Simulation.sources -SourceType = Union[ - UniformCurrentSource, - PointDipole, - GaussianBeam, - AstigmaticGaussianBeam, - ModeSource, - PlaneWave, - CustomFieldSource, - CustomCurrentSource, - TFSF, -] +SourceType = ( + UniformCurrentSource + | PointDipole + | GaussianBeam + | AstigmaticGaussianBeam + | ModeSource + | PlaneWave + | CustomFieldSource + | CustomCurrentSource + | TFSF +) diff --git a/tidy3d/components/spice/sources/ac.py b/tidy3d/components/spice/sources/ac.py index 17af5fd9ff..2584a34e7d 100644 --- a/tidy3d/components/spice/sources/ac.py +++ b/tidy3d/components/spice/sources/ac.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Optional - import pydantic.v1 as pd from tidy3d.components.base import Tidy3dBaseModel @@ -33,7 +31,7 @@ class SSACVoltageSource(Tidy3dBaseModel): ... ) """ - name: Optional[str] = pd.Field( + name: str | None = pd.Field( None, title="Name", description="Unique name for the SSAC voltage source.", diff --git a/tidy3d/components/spice/sources/dc.py b/tidy3d/components/spice/sources/dc.py index dcc1eb0e44..70ca86a450 100644 --- a/tidy3d/components/spice/sources/dc.py +++ b/tidy3d/components/spice/sources/dc.py @@ -21,7 +21,7 @@ from __future__ import annotations -from typing import Literal, Optional +from typing import Literal import pydantic.v1 as pd @@ -48,7 +48,7 @@ class DCVoltageSource(Tidy3dBaseModel): >>> voltage_source = td.DCVoltageSource(voltage=voltages) """ - name: Optional[str] = pd.Field( + name: str | None = pd.Field( None, title="Name", description="Unique name for the DC voltage source", @@ -108,7 +108,7 @@ class DCCurrentSource(Tidy3dBaseModel): >>> current_source = td.DCCurrentSource(current=0.4) """ - name: Optional[str] = pd.Field( + name: str | None = pd.Field( None, title="Name", description="Unique name for the DC current source", diff --git a/tidy3d/components/spice/sources/types.py b/tidy3d/components/spice/sources/types.py index 071e4c2ced..84b48cf7da 100644 --- a/tidy3d/components/spice/sources/types.py +++ b/tidy3d/components/spice/sources/types.py @@ -1,9 +1,7 @@ from __future__ import annotations -from typing import Union - from .ac import SSACVoltageSource from .dc import DCCurrentSource, DCVoltageSource, GroundVoltage -VoltageSourceType = Union[DCVoltageSource, SSACVoltageSource, GroundVoltage] -CurrentSourceType = Union[DCCurrentSource] +VoltageSourceType = DCVoltageSource | SSACVoltageSource | GroundVoltage +CurrentSourceType = DCCurrentSource diff --git a/tidy3d/components/spice/types.py b/tidy3d/components/spice/types.py index a46d7ddebd..73e2e825ca 100644 --- a/tidy3d/components/spice/types.py +++ b/tidy3d/components/spice/types.py @@ -1,13 +1,14 @@ from __future__ import annotations -from typing import Union - from tidy3d.components.spice.analysis.ac import IsothermalSSACAnalysis, SSACAnalysis from tidy3d.components.spice.analysis.dc import ( IsothermalSteadyChargeDCAnalysis, SteadyChargeDCAnalysis, ) -ElectricalAnalysisType = Union[ - SteadyChargeDCAnalysis, IsothermalSteadyChargeDCAnalysis, SSACAnalysis, IsothermalSSACAnalysis -] +ElectricalAnalysisType = ( + SteadyChargeDCAnalysis + | IsothermalSteadyChargeDCAnalysis + | SSACAnalysis + | IsothermalSSACAnalysis +) diff --git a/tidy3d/components/structure.py b/tidy3d/components/structure.py index f091622eaf..ddf9d2a159 100644 --- a/tidy3d/components/structure.py +++ b/tidy3d/components/structure.py @@ -5,7 +5,6 @@ import pathlib from collections import defaultdict from functools import cmp_to_key -from typing import Optional, Union import autograd.numpy as anp import numpy as np @@ -145,9 +144,9 @@ def viz_spec(self): @add_ax_if_none def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **patch_kwargs, ) -> Ax: @@ -405,9 +404,9 @@ def eps_comp(self, row: Axis, col: Axis, frequency: float, coords: Coords) -> co def to_gdstk( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, gds_layer: pydantic.NonNegativeInt = 0, @@ -474,9 +473,9 @@ def to_gdstk( def to_gds( self, cell, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, gds_layer: pydantic.NonNegativeInt = 0, @@ -526,9 +525,9 @@ def to_gds( def to_gds_file( self, fname: str, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, permittivity_threshold: pydantic.NonNegativeFloat = 1, frequency: pydantic.PositiveFloat = 0, gds_layer: pydantic.NonNegativeInt = 0, @@ -661,9 +660,9 @@ class MeshOverrideStructure(AbstractStructure): """ dl: tuple[ - Optional[pydantic.PositiveFloat], - Optional[pydantic.PositiveFloat], - Optional[pydantic.PositiveFloat], + pydantic.PositiveFloat | None, + pydantic.PositiveFloat | None, + pydantic.PositiveFloat | None, ] = pydantic.Field( ..., title="Grid Size", @@ -724,4 +723,4 @@ def _unshadowed_cannot_be_enforced(cls, val, values): return val -StructureType = Union[Structure, MeshOverrideStructure] +StructureType = Structure | MeshOverrideStructure diff --git a/tidy3d/components/subpixel_spec.py b/tidy3d/components/subpixel_spec.py index 89d0ceba8c..11a065d70a 100644 --- a/tidy3d/components/subpixel_spec.py +++ b/tidy3d/components/subpixel_spec.py @@ -1,8 +1,6 @@ # Defines specifications for subpixel averaging from __future__ import annotations -from typing import Union - import pydantic.v1 as pd from .base import Tidy3dBaseModel, cached_property @@ -65,7 +63,7 @@ class ContourPathAveraging(AbstractSubpixelAveragingMethod): """ -DielectricSubpixelType = Union[Staircasing, PolarizedAveraging, ContourPathAveraging] +DielectricSubpixelType = Staircasing | PolarizedAveraging | ContourPathAveraging class VolumetricAveraging(AbstractSubpixelAveragingMethod): @@ -83,7 +81,7 @@ class VolumetricAveraging(AbstractSubpixelAveragingMethod): ) -MetalSubpixelType = Union[Staircasing, VolumetricAveraging] +MetalSubpixelType = Staircasing | VolumetricAveraging class HeuristicPECStaircasing(AbstractSubpixelAveragingMethod): @@ -136,8 +134,8 @@ def courant_ratio(self) -> float: return 1 - self.timestep_reduction -PECSubpixelType = Union[Staircasing, HeuristicPECStaircasing, PECConformal] -PMCSubpixelType = Union[Staircasing, HeuristicPECStaircasing] +PECSubpixelType = Staircasing | HeuristicPECStaircasing | PECConformal +PMCSubpixelType = Staircasing | HeuristicPECStaircasing class SurfaceImpedance(PECConformal): @@ -156,7 +154,7 @@ class SurfaceImpedance(PECConformal): ) -LossyMetalSubpixelType = Union[Staircasing, VolumetricAveraging, SurfaceImpedance] +LossyMetalSubpixelType = Staircasing | VolumetricAveraging | SurfaceImpedance class SubpixelSpec(Tidy3dBaseModel): diff --git a/tidy3d/components/tcad/boundary/heat.py b/tidy3d/components/tcad/boundary/heat.py index 7430cc22f2..da6d2f6974 100644 --- a/tidy3d/components/tcad/boundary/heat.py +++ b/tidy3d/components/tcad/boundary/heat.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - import pydantic.v1 as pd from tidy3d.components.base import Tidy3dBaseModel @@ -162,7 +160,7 @@ class ConvectionBC(HeatChargeBC): units=KELVIN, ) - transfer_coeff: Union[pd.NonNegativeFloat, VerticalNaturalConvectionCoeffModel] = pd.Field( + transfer_coeff: pd.NonNegativeFloat | VerticalNaturalConvectionCoeffModel = pd.Field( title="Heat Transfer Coefficient", description="Heat transfer coefficient value.", units=HEAT_TRANSFER_COEFF, diff --git a/tidy3d/components/tcad/data/monitor_data/abstract.py b/tidy3d/components/tcad/data/monitor_data/abstract.py index 147141974d..4ea9effb4f 100644 --- a/tidy3d/components/tcad/data/monitor_data/abstract.py +++ b/tidy3d/components/tcad/data/monitor_data/abstract.py @@ -4,7 +4,6 @@ import copy from abc import ABC, abstractmethod -from typing import Union import numpy as np import pydantic.v1 as pd @@ -21,10 +20,8 @@ from tidy3d.constants import MICROMETER from tidy3d.log import log -FieldDataset = Union[ - SpatialDataArray, annotate_type(Union[TriangularGridDataset, TetrahedralGridDataset]) -] -UnstructuredFieldType = Union[TriangularGridDataset, TetrahedralGridDataset] +FieldDataset = SpatialDataArray | annotate_type(TriangularGridDataset | TetrahedralGridDataset) +UnstructuredFieldType = TriangularGridDataset | TetrahedralGridDataset class HeatChargeMonitorData(AbstractMonitorData, ABC): diff --git a/tidy3d/components/tcad/data/monitor_data/charge.py b/tidy3d/components/tcad/data/monitor_data/charge.py index 9e056696f1..1d6d8b216e 100644 --- a/tidy3d/components/tcad/data/monitor_data/charge.py +++ b/tidy3d/components/tcad/data/monitor_data/charge.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - import numpy as np import pydantic.v1 as pd @@ -29,11 +27,9 @@ from tidy3d.components.viz import add_ax_if_none from tidy3d.exceptions import DataError -FieldDataset = Union[ - SpatialDataArray, annotate_type(Union[TriangularGridDataset, TetrahedralGridDataset]) -] +FieldDataset = SpatialDataArray | annotate_type(TriangularGridDataset | TetrahedralGridDataset) -UnstructuredFieldType = Union[TriangularGridDataset, TetrahedralGridDataset] +UnstructuredFieldType = TriangularGridDataset | TetrahedralGridDataset class SteadyPotentialData(HeatChargeMonitorData): diff --git a/tidy3d/components/tcad/data/monitor_data/heat.py b/tidy3d/components/tcad/data/monitor_data/heat.py index 9734735ec9..d3cc7380b4 100644 --- a/tidy3d/components/tcad/data/monitor_data/heat.py +++ b/tidy3d/components/tcad/data/monitor_data/heat.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional, Union - import pydantic.v1 as pd from tidy3d.components.data.data_array import ( @@ -19,12 +17,12 @@ from tidy3d.components.types import annotate_type from tidy3d.constants import KELVIN -FieldDataset = Union[ - SpatialDataArray, - ScalarFieldTimeDataArray, - annotate_type(Union[TriangularGridDataset, TetrahedralGridDataset]), -] -UnstructuredFieldType = Union[TriangularGridDataset, TetrahedralGridDataset] +FieldDataset = ( + SpatialDataArray + | ScalarFieldTimeDataArray + | annotate_type(TriangularGridDataset | TetrahedralGridDataset) +) +UnstructuredFieldType = TriangularGridDataset | TetrahedralGridDataset class TemperatureData(HeatChargeMonitorData): @@ -48,7 +46,7 @@ class TemperatureData(HeatChargeMonitorData): ..., title="Monitor", description="Temperature monitor associated with the data." ) - temperature: Optional[FieldDataset] = pd.Field( + temperature: FieldDataset | None = pd.Field( ..., title="Temperature", description="Spatial temperature field.", diff --git a/tidy3d/components/tcad/data/monitor_data/mesh.py b/tidy3d/components/tcad/data/monitor_data/mesh.py index af3a4ec3f3..26d6eb2027 100644 --- a/tidy3d/components/tcad/data/monitor_data/mesh.py +++ b/tidy3d/components/tcad/data/monitor_data/mesh.py @@ -2,15 +2,13 @@ from __future__ import annotations -from typing import Union - import pydantic.v1 as pd from tidy3d.components.data.utils import TetrahedralGridDataset, TriangularGridDataset from tidy3d.components.tcad.data.monitor_data.abstract import HeatChargeMonitorData from tidy3d.components.tcad.monitors.mesh import VolumeMeshMonitor -UnstructuredFieldType = Union[TriangularGridDataset, TetrahedralGridDataset] +UnstructuredFieldType = TriangularGridDataset | TetrahedralGridDataset class VolumeMeshData(HeatChargeMonitorData): diff --git a/tidy3d/components/tcad/data/sim_data.py b/tidy3d/components/tcad/data/sim_data.py index 1c6c86fb9d..f6976a62d1 100644 --- a/tidy3d/components/tcad/data/sim_data.py +++ b/tidy3d/components/tcad/data/sim_data.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC -from typing import Literal, Optional +from typing import Literal import numpy as np import pydantic.v1 as pd @@ -57,27 +57,27 @@ class DeviceCharacteristics(Tidy3dBaseModel): """ - steady_dc_hole_capacitance: Optional[SteadyVoltageDataArray] = pd.Field( + steady_dc_hole_capacitance: SteadyVoltageDataArray | None = pd.Field( None, title="Steady DC hole capacitance", description="Device steady DC capacitance data based on holes. If the simulation " "has converged, these result should be close to that of electrons.", ) - steady_dc_electron_capacitance: Optional[SteadyVoltageDataArray] = pd.Field( + steady_dc_electron_capacitance: SteadyVoltageDataArray | None = pd.Field( None, title="Steady DC electron capacitance", description="Device steady DC capacitance data based on electrons. If the simulation " "has converged, these result should be close to that of holes.", ) - steady_dc_current_voltage: Optional[SteadyVoltageDataArray] = pd.Field( + steady_dc_current_voltage: SteadyVoltageDataArray | None = pd.Field( None, title="Steady DC current-voltage", description="Device steady DC current-voltage relation for the device.", ) - steady_dc_resistance_voltage: Optional[SteadyVoltageDataArray] = pd.Field( + steady_dc_resistance_voltage: SteadyVoltageDataArray | None = pd.Field( None, title="Small signal resistance", description="Steady DC computation of the small signal resistance. This is computed " @@ -85,7 +85,7 @@ class DeviceCharacteristics(Tidy3dBaseModel): "is given in Ohms. Note that in 2D the resistance is given in :math:`\\Omega \\mu`.", ) - ac_current_voltage: Optional[FreqVoltageDataArray] = pd.Field( + ac_current_voltage: FreqVoltageDataArray | None = pd.Field( None, title="Small-signal AC current-voltage", description="Small-signal AC current as a function of DC bias voltage and frequency. " @@ -104,7 +104,7 @@ class AbstractHeatChargeSimulationData(AbstractSimulationData, ABC): ) @staticmethod - def _get_field_by_name(monitor_data: TCADMonitorDataType, field_name: Optional[str] = None): + def _get_field_by_name(monitor_data: TCADMonitorDataType, field_name: str | None = None): """Return a field data based on a monitor dataset and a specified field name.""" if field_name is None: if len(monitor_data.field_components) > 1: @@ -127,7 +127,7 @@ def _get_field_by_name(monitor_data: TCADMonitorDataType, field_name: Optional[s def plot_mesh( self, monitor_name: str, - field_name: Optional[str] = None, + field_name: str | None = None, structures_fill: bool = True, ax: Ax = None, **sel_kwargs, @@ -262,7 +262,7 @@ class HeatChargeSimulationData(AbstractHeatChargeSimulationData): "associated with the monitors of the original :class:`.Simulation`.", ) - device_characteristics: Optional[DeviceCharacteristics] = pd.Field( + device_characteristics: DeviceCharacteristics | None = pd.Field( None, title="Device characteristics", description="Data characterizing the device :class:`DeviceCharacteristics`.", @@ -273,13 +273,13 @@ class HeatChargeSimulationData(AbstractHeatChargeSimulationData): def plot_field( self, monitor_name: str, - field_name: Optional[Literal["temperature", "potential"]] = None, + field_name: Literal["temperature", "potential"] | None = None, val: RealFieldVal = "real", scale: Literal["lin", "log"] = "lin", structures_alpha: float = 0.2, robust: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, + vmin: float | None = None, + vmax: float | None = None, ax: Ax = None, **sel_kwargs, ) -> Ax: diff --git a/tidy3d/components/tcad/data/types.py b/tidy3d/components/tcad/data/types.py index f072f11cd7..8646d799f7 100644 --- a/tidy3d/components/tcad/data/types.py +++ b/tidy3d/components/tcad/data/types.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - from tidy3d.components.tcad.data.monitor_data.charge import ( SteadyCapacitanceData, SteadyCurrentDensityData, @@ -14,12 +12,12 @@ ) from tidy3d.components.tcad.data.monitor_data.heat import TemperatureData -TCADMonitorDataType = Union[ - TemperatureData, - SteadyPotentialData, - SteadyFreeCarrierData, - SteadyElectricFieldData, - SteadyEnergyBandData, - SteadyCapacitanceData, - SteadyCurrentDensityData, -] +TCADMonitorDataType = ( + TemperatureData + | SteadyPotentialData + | SteadyFreeCarrierData + | SteadyElectricFieldData + | SteadyEnergyBandData + | SteadyCapacitanceData + | SteadyCurrentDensityData +) diff --git a/tidy3d/components/tcad/doping.py b/tidy3d/components/tcad/doping.py index 3925b0440f..2293994520 100644 --- a/tidy3d/components/tcad/doping.py +++ b/tidy3d/components/tcad/doping.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - import numpy as np import pydantic.v1 as pd import xarray as xr @@ -314,7 +312,7 @@ class CustomDoping(AbstractDopingBox): def _get_contrib(self, coords: dict, meshgrid: bool = True): """Returns the contribution to the doping a the locations specified in coords""" - indices_in_box, X, Y, Z = self._get_indices_in_box(coords=coords, meshgrid=meshgrid) + indices_in_box, X, _Y, _Z = self._get_indices_in_box(coords=coords, meshgrid=meshgrid) contrib = np.zeros(X.shape) # interpolate @@ -339,4 +337,4 @@ def _get_contrib(self, coords: dict, meshgrid: bool = True): return contrib.squeeze() -DopingBoxType = Union[ConstantDoping, GaussianDoping, CustomDoping] +DopingBoxType = ConstantDoping | GaussianDoping | CustomDoping diff --git a/tidy3d/components/tcad/generation_recombination.py b/tidy3d/components/tcad/generation_recombination.py index 51bb5013c9..680ffcf948 100644 --- a/tidy3d/components/tcad/generation_recombination.py +++ b/tidy3d/components/tcad/generation_recombination.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Union - import numpy as np import pydantic.v1 as pd @@ -64,7 +62,7 @@ class FossumCarrierLifetime(Tidy3dBaseModel): alpha: float = pd.Field(..., title="Exponent constant", description="Exponent constant") -CarrierLifetimeType = Union[FossumCarrierLifetime] +CarrierLifetimeType = FossumCarrierLifetime class AugerRecombination(Tidy3dBaseModel): @@ -168,11 +166,11 @@ class ShockleyReedHallRecombination(Tidy3dBaseModel): - This model represents mid-gap traps Shockley-Reed-Hall recombination. """ - tau_n: Union[pd.PositiveFloat, CarrierLifetimeType] = pd.Field( + tau_n: pd.PositiveFloat | CarrierLifetimeType = pd.Field( ..., title="Electron lifetime", description="Electron lifetime", units=SECOND ) - tau_p: Union[pd.PositiveFloat, CarrierLifetimeType] = pd.Field( + tau_p: pd.PositiveFloat | CarrierLifetimeType = pd.Field( ..., title="Hole lifetime", description="Hole lifetime", units=SECOND ) diff --git a/tidy3d/components/tcad/grid.py b/tidy3d/components/tcad/grid.py index fcf0cf6f27..c7b88d85f2 100644 --- a/tidy3d/components/tcad/grid.py +++ b/tidy3d/components/tcad/grid.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC -from typing import Union import numpy as np import pydantic.v1 as pd @@ -215,7 +214,7 @@ class DistanceUnstructuredGrid(UnstructuredGrid): "``dl_bulk`` is used instead.", ) - mesh_refinements: tuple[annotate_type(Union[GridRefinementRegion, GridRefinementLine]), ...] = ( + mesh_refinements: tuple[annotate_type(GridRefinementRegion | GridRefinementLine), ...] = ( pd.Field( (), title="Mesh refinement structures", @@ -234,4 +233,4 @@ def names_exist_bcs(cls, val, values): return val -UnstructuredGridType = Union[UniformUnstructuredGrid, DistanceUnstructuredGrid] +UnstructuredGridType = UniformUnstructuredGrid | DistanceUnstructuredGrid diff --git a/tidy3d/components/tcad/simulation/heat.py b/tidy3d/components/tcad/simulation/heat.py index ace1d8a855..27a8c7276f 100644 --- a/tidy3d/components/tcad/simulation/heat.py +++ b/tidy3d/components/tcad/simulation/heat.py @@ -3,8 +3,6 @@ from __future__ import annotations -from typing import Optional - import pydantic.v1 as pd from tidy3d.components.tcad.simulation.heat_charge import HeatChargeSimulation @@ -60,16 +58,16 @@ def issue_warning_deprecated(cls, values): @add_ax_if_none def plot_heat_conductivity( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - alpha: Optional[float] = None, - source_alpha: Optional[float] = None, - monitor_alpha: Optional[float] = None, + alpha: float | None = None, + source_alpha: float | None = None, + monitor_alpha: float | None = None, colorbar: str = "conductivity", - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = 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 5598cfa48a..18834e6f94 100644 --- a/tidy3d/components/tcad/simulation/heat_charge.py +++ b/tidy3d/components/tcad/simulation/heat_charge.py @@ -1,10 +1,8 @@ -# ruff: noqa: W293, W291 """Defines heat simulation class""" from __future__ import annotations from enum import Enum -from typing import Optional, Union import numpy as np import pydantic.v1 as pd @@ -110,7 +108,7 @@ ChargeSourceTypes = () ElectricBCTypes = (VoltageBC, CurrentBC, InsulatingBC) -AnalysisSpecType = Union[ElectricalAnalysisType, UnsteadyHeatAnalysis] +AnalysisSpecType = ElectricalAnalysisType | UnsteadyHeatAnalysis # define some limits for transient heat simulations TRANSIENT_HEAT_MAX_STEPS = 1000 @@ -303,12 +301,10 @@ class HeatChargeSimulation(AbstractSimulation): description="Monitors in the simulation.", ) - boundary_spec: tuple[annotate_type(Union[HeatChargeBoundarySpec, HeatBoundarySpec]), ...] = ( - pd.Field( - (), - title="Boundary Condition Specifications", - description="List of boundary condition specifications.", - ) + boundary_spec: tuple[annotate_type(HeatChargeBoundarySpec | HeatBoundarySpec), ...] = pd.Field( + (), + title="Boundary Condition Specifications", + description="List of boundary condition specifications.", ) # NOTE: creating a union with HeatBoundarySpec for backwards compatibility @@ -1155,16 +1151,16 @@ def check_non_isothermal_is_possible(cls, values): @add_ax_if_none def plot_property( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - alpha: Optional[float] = None, - source_alpha: Optional[float] = None, - monitor_alpha: Optional[float] = None, + alpha: float | None = None, + source_alpha: float | None = None, + monitor_alpha: float | None = None, property: str = "heat_conductivity", - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, ) -> Ax: """Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate. @@ -1254,16 +1250,16 @@ def plot_property( @add_ax_if_none def plot_heat_conductivity( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - alpha: Optional[float] = None, - source_alpha: Optional[float] = None, - monitor_alpha: Optional[float] = None, + alpha: float | None = None, + source_alpha: float | None = None, + monitor_alpha: float | None = None, colorbar: str = "conductivity", - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, **kwargs, ) -> Ax: """ @@ -1301,7 +1297,7 @@ def plot_heat_conductivity( The supplied or created matplotlib axes. """ log.warning( - """This function `plot_heat_conductivity` is + """This function `plot_heat_conductivity` is deprecated and will be discontinued. In its place you can use `plot_property(property="heat_conductivity")`""" ) @@ -1329,9 +1325,9 @@ def plot_heat_conductivity( @add_ax_if_none def plot_boundaries( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, property: str = "heat_conductivity", ax: Ax = None, ) -> Ax: @@ -1595,7 +1591,7 @@ def _construct_reverse_boundaries( boundaries_reverse = [] for name, _, shape, bounds in shapes[:0:-1]: - minx, miny, maxx, maxy = bounds + _minx, _miny, _maxx, _maxy = bounds # intersect existing boundaries for index, (_bc_spec, _name, _bdry, _bounds, _completed) in enumerate( @@ -1712,13 +1708,13 @@ def _construct_heat_charge_boundaries( @add_ax_if_none def plot_sources( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, property: str = "heat_conductivity", - hlim: Optional[tuple[float, float]] = None, - vlim: Optional[tuple[float, float]] = None, - alpha: Optional[float] = None, + hlim: tuple[float, float] | None = None, + vlim: tuple[float, float] | None = None, + alpha: float | None = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's sources on a plane defined by one nonzero x,y,z coordinate. @@ -1837,7 +1833,7 @@ def _get_structure_source_plot_params( source: HeatChargeSourceType, source_min: float, source_max: float, - alpha: Optional[float] = None, + alpha: float | None = None, ) -> PlotParams: """Constructs the plot parameters for a given medium in simulation.plot_eps().""" @@ -1864,7 +1860,7 @@ def _plot_shape_structure_source( source_min: float, source_max: float, ax: Ax, - alpha: Optional[float] = None, + alpha: float | None = None, ) -> Ax: """Plot a structure's cross section shape for a given medium, grayscale for permittivity.""" plot_params = self._get_structure_source_plot_params( diff --git a/tidy3d/components/tcad/source/heat.py b/tidy3d/components/tcad/source/heat.py index e47de2963d..c4f0ec90d1 100644 --- a/tidy3d/components/tcad/source/heat.py +++ b/tidy3d/components/tcad/source/heat.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - import pydantic.v1 as pd from tidy3d.components.data.data_array import SpatialDataArray @@ -21,7 +19,7 @@ class HeatSource(StructureBasedHeatChargeSource): >>> heat_source = HeatSource(rate=1, structures=["box"]) """ - rate: Union[float, SpatialDataArray] = pd.Field( + rate: float | SpatialDataArray = pd.Field( title="Volumetric Heat Rate", description="Volumetric rate of heating or cooling (if negative).", units=VOLUMETRIC_HEAT_RATE, diff --git a/tidy3d/components/tcad/types.py b/tidy3d/components/tcad/types.py index cf9b5c658f..c6411a532c 100644 --- a/tidy3d/components/tcad/types.py +++ b/tidy3d/components/tcad/types.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - from tidy3d.components.tcad.bandgap import SlotboomBandGapNarrowing from tidy3d.components.tcad.bandgap_energy import ( ConstantEnergyBandGap, @@ -40,27 +38,28 @@ from tidy3d.components.tcad.source.coupled import HeatFromElectricSource from tidy3d.components.tcad.source.heat import HeatSource, UniformHeatSource -EffectiveDOSModelType = Union[ - ConstantEffectiveDOS, IsotropicEffectiveDOS, MultiValleyEffectiveDOS, DualValleyEffectiveDOS -] -EnergyBandGapModelType = Union[ConstantEnergyBandGap, VarshniEnergyBandGap] -MobilityModelType = Union[CaugheyThomasMobility, ConstantMobilityModel] -RecombinationModelType = Union[ - AugerRecombination, DistributedGeneration, RadiativeRecombination, ShockleyReedHallRecombination -] -BandGapNarrowingModelType = Union[SlotboomBandGapNarrowing] +EffectiveDOSModelType = ( + ConstantEffectiveDOS | IsotropicEffectiveDOS | MultiValleyEffectiveDOS | DualValleyEffectiveDOS +) +EnergyBandGapModelType = ConstantEnergyBandGap | VarshniEnergyBandGap +MobilityModelType = CaugheyThomasMobility | ConstantMobilityModel +RecombinationModelType = ( + AugerRecombination + | DistributedGeneration + | RadiativeRecombination + | ShockleyReedHallRecombination +) +BandGapNarrowingModelType = SlotboomBandGapNarrowing # types of monitors that are accepted by heat simulation -HeatChargeMonitorType = Union[ - TemperatureMonitor, - SteadyPotentialMonitor, - SteadyFreeCarrierMonitor, - SteadyEnergyBandMonitor, - SteadyElectricFieldMonitor, - SteadyCapacitanceMonitor, - SteadyCurrentDensityMonitor, -] -HeatChargeSourceType = Union[HeatSource, HeatFromElectricSource, UniformHeatSource] -HeatChargeBCType = Union[ - TemperatureBC, HeatFluxBC, ConvectionBC, VoltageBC, CurrentBC, InsulatingBC -] +HeatChargeMonitorType = ( + TemperatureMonitor + | SteadyPotentialMonitor + | SteadyFreeCarrierMonitor + | SteadyEnergyBandMonitor + | SteadyElectricFieldMonitor + | SteadyCapacitanceMonitor + | SteadyCurrentDensityMonitor +) +HeatChargeSourceType = HeatSource | HeatFromElectricSource | UniformHeatSource +HeatChargeBCType = TemperatureBC | HeatFluxBC | ConvectionBC | VoltageBC | CurrentBC | InsulatingBC diff --git a/tidy3d/components/time_modulation.py b/tidy3d/components/time_modulation.py index a4d82d7384..f0592b4183 100644 --- a/tidy3d/components/time_modulation.py +++ b/tidy3d/components/time_modulation.py @@ -4,7 +4,6 @@ from abc import ABC, abstractmethod from math import isclose -from typing import Union import numpy as np import pydantic.v1 as pd @@ -79,7 +78,7 @@ def max_modulation(self) -> float: return abs(self.amplitude) -TimeModulationType = Union[ContinuousWaveTimeModulation] +TimeModulationType = ContinuousWaveTimeModulation class AbstractSpaceModulation(ABC, Tidy3dBaseModel): @@ -128,14 +127,14 @@ class SpaceModulation(AbstractSpaceModulation): >>> space = SpaceModulation(amplitude=amp, phase=phase) """ - amplitude: Union[float, SpatialDataArray] = pd.Field( + amplitude: float | SpatialDataArray = pd.Field( 1, title="Amplitude of modulation in space", description="Amplitude of modulation that can vary spatially. " "It takes the unit of whatever is being modulated.", ) - phase: Union[float, SpatialDataArray] = pd.Field( + phase: float | SpatialDataArray = pd.Field( 0, title="Phase of modulation in space", description="Phase of modulation that can vary spatially.", @@ -199,7 +198,7 @@ def sel_inside(self, bounds: Bound) -> SpaceModulation: return self.updated_copy(amplitude=amp_reduced, phase=phase_reduced) -SpaceModulationType = Union[SpaceModulation] +SpaceModulationType = SpaceModulation class SpaceTimeModulation(Tidy3dBaseModel): diff --git a/tidy3d/components/transformation.py b/tidy3d/components/transformation.py index 4e2643a9ae..633b02ff3d 100644 --- a/tidy3d/components/transformation.py +++ b/tidy3d/components/transformation.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Union import numpy as np import pydantic.v1 as pd @@ -74,7 +73,7 @@ def rotate_tensor(self, tensor: TensorReal) -> TensorReal: class RotationAroundAxis(AbstractRotation): """Rotation of vectors and tensors around a given vector.""" - axis: Union[Axis, Coordinate] = pd.Field( + axis: Axis | Coordinate = pd.Field( 0, title="Axis of Rotation", description="A vector that specifies the axis of rotation, or a single int: 0, 1, or 2, " @@ -201,5 +200,5 @@ def matrix(self) -> TensorReal: return R -RotationType = Union[RotationAroundAxis] -ReflectionType = Union[ReflectionFromPlane] +RotationType = RotationAroundAxis +ReflectionType = ReflectionFromPlane diff --git a/tidy3d/components/types/base.py b/tidy3d/components/types/base.py index 60c8c8ea03..9f125c9408 100644 --- a/tidy3d/components/types/base.py +++ b/tidy3d/components/types/base.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Literal, Optional, Union +from typing import Literal import autograd.numpy as np import pydantic.v1 as pydantic @@ -104,9 +104,9 @@ def __modify_schema__(cls, field_schema): def constrained_array( - dtype: Optional[type] = None, - ndim: Optional[int] = None, - shape: Optional[tuple[pydantic.NonNegativeInt, ...]] = None, + dtype: type | None = None, + ndim: int | None = None, + shape: tuple[pydantic.NonNegativeInt, ...] | None = None, ) -> type: """Generate an ArrayLike sub-type with constraints built in.""" @@ -189,10 +189,10 @@ def __modify_schema__(cls, field_schema): Size1D = pydantic.NonNegativeFloat Size = tuple[Size1D, Size1D, Size1D] Coordinate = tuple[float, float, float] -CoordinateOptional = tuple[Optional[float], Optional[float], Optional[float]] +CoordinateOptional = tuple[float | None, float | None, float | None] Coordinate2D = tuple[float, float] Bound = tuple[Coordinate, Coordinate] -GridSize = Union[pydantic.PositiveFloat, tuple[pydantic.PositiveFloat, ...]] +GridSize = pydantic.PositiveFloat | tuple[pydantic.PositiveFloat, ...] Axis = Literal[0, 1, 2] Axis2D = Literal[0, 1] Shapely = BaseGeometry @@ -207,8 +207,8 @@ def __modify_schema__(cls, field_schema): # custom medium InterpMethod = Literal["nearest", "linear"] -# Complex = Union[complex, ComplexNumber] -Complex = Union[tidycomplex, ComplexNumber] +# Complex = complex | ComplexNumber +Complex = tidycomplex | ComplexNumber PoleAndResidue = tuple[Complex, Complex] # PoleAndResidue = Tuple[Tuple[float, float], Tuple[float, float]] @@ -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 = tuple[float, ...] | ArrayFloat1D +ObsGridArray = tuple[float, ...] | ArrayFloat1D PolarizationBasis = Literal["linear", "circular"] AuxField = Literal["Nfx", "Nfy", "Nfz"] diff --git a/tidy3d/components/types/mode_spec.py b/tidy3d/components/types/mode_spec.py index 1e11a69122..566e887f91 100644 --- a/tidy3d/components/types/mode_spec.py +++ b/tidy3d/components/types/mode_spec.py @@ -2,10 +2,8 @@ from __future__ import annotations -from typing import Union - from tidy3d.components.microwave.mode_spec import MicrowaveModeSpec from tidy3d.components.mode_spec import ModeSpec # Type aliases -ModeSpecType = Union[ModeSpec, MicrowaveModeSpec] +ModeSpecType = ModeSpec | MicrowaveModeSpec diff --git a/tidy3d/components/types/monitor.py b/tidy3d/components/types/monitor.py index d8585cab51..8ce5d50433 100644 --- a/tidy3d/components/types/monitor.py +++ b/tidy3d/components/types/monitor.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - from tidy3d.components.microwave.monitor import MicrowaveModeMonitor, MicrowaveModeSolverMonitor from tidy3d.components.monitor import ( AuxFieldTimeMonitor, @@ -23,21 +21,21 @@ ) # types of monitors that are accepted by simulation -MonitorType = Union[ - FieldMonitor, - FieldTimeMonitor, - AuxFieldTimeMonitor, - MediumMonitor, - PermittivityMonitor, - FluxMonitor, - FluxTimeMonitor, - ModeMonitor, - ModeSolverMonitor, - FieldProjectionAngleMonitor, - FieldProjectionCartesianMonitor, - FieldProjectionKSpaceMonitor, - DiffractionMonitor, - DirectivityMonitor, - MicrowaveModeMonitor, - MicrowaveModeSolverMonitor, -] +MonitorType = ( + FieldMonitor + | FieldTimeMonitor + | AuxFieldTimeMonitor + | MediumMonitor + | PermittivityMonitor + | FluxMonitor + | FluxTimeMonitor + | ModeMonitor + | ModeSolverMonitor + | FieldProjectionAngleMonitor + | FieldProjectionCartesianMonitor + | FieldProjectionKSpaceMonitor + | DiffractionMonitor + | DirectivityMonitor + | MicrowaveModeMonitor + | MicrowaveModeSolverMonitor +) diff --git a/tidy3d/components/types/monitor_data.py b/tidy3d/components/types/monitor_data.py index 50f5188865..9d7d253133 100644 --- a/tidy3d/components/types/monitor_data.py +++ b/tidy3d/components/types/monitor_data.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Union - from tidy3d.components.data.monitor_data import ( AuxFieldTimeData, DiffractionData, @@ -23,8 +21,8 @@ from tidy3d.components.microwave.data.monitor_data import MicrowaveModeData, MicrowaveModeSolverData # Type aliases -ModeDataType = Union[ModeData, MicrowaveModeData] -ModeSolverDataType = Union[ModeSolverData, MicrowaveModeSolverData] +ModeDataType = ModeData | MicrowaveModeData +ModeSolverDataType = ModeSolverData | MicrowaveModeSolverData MonitorDataTypes = ( FieldData, FieldTimeData, @@ -43,4 +41,21 @@ MicrowaveModeData, MicrowaveModeSolverData, ) -MonitorDataType = Union[MonitorDataTypes] +MonitorDataType = ( + FieldData + | FieldTimeData + | PermittivityData + | MediumData + | ModeSolverData + | ModeData + | FluxData + | FluxTimeData + | AuxFieldTimeData + | FieldProjectionKSpaceData + | FieldProjectionCartesianData + | FieldProjectionAngleData + | DiffractionData + | DirectivityData + | MicrowaveModeData + | MicrowaveModeSolverData +) diff --git a/tidy3d/components/types/simulation.py b/tidy3d/components/types/simulation.py index 1194b5cfbd..75df93b39f 100644 --- a/tidy3d/components/types/simulation.py +++ b/tidy3d/components/types/simulation.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Union - 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 @@ -20,22 +18,22 @@ from tidy3d.components.tcad.simulation.heat_charge import HeatChargeSimulation from tidy3d.plugins.mode.mode_solver import ModeSolver -SimulationType = Union[ - Simulation, - HeatChargeSimulation, - HeatSimulation, - EMESimulation, - ModeSolver, - ModeSimulation, - VolumeMesher, -] -SimulationDataType = Union[ - SimulationData, - HeatChargeSimulationData, - HeatSimulationData, - EMESimulationData, - MicrowaveModeSolverData, - ModeSolverData, - ModeSimulationData, - VolumeMesherData, -] +SimulationType = ( + Simulation + | HeatChargeSimulation + | HeatSimulation + | EMESimulation + | ModeSolver + | ModeSimulation + | VolumeMesher +) +SimulationDataType = ( + SimulationData + | HeatChargeSimulationData + | HeatSimulationData + | EMESimulationData + | MicrowaveModeSolverData + | ModeSolverData + | ModeSimulationData + | VolumeMesherData +) diff --git a/tidy3d/components/types/workflow.py b/tidy3d/components/types/workflow.py index 5527270c0d..d15c1113ad 100644 --- a/tidy3d/components/types/workflow.py +++ b/tidy3d/components/types/workflow.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Union - from tidy3d.components.types.simulation import SimulationDataType, SimulationType from tidy3d.plugins.smatrix.component_modelers.modal import ( ModalComponentModeler, @@ -16,13 +14,5 @@ TerminalComponentModelerData, ) -WorkflowType = Union[ - SimulationType, - ModalComponentModeler, - TerminalComponentModeler, -] -WorkflowDataType = Union[ - SimulationDataType, - ModalComponentModelerData, - TerminalComponentModelerData, -] +WorkflowType = SimulationType | ModalComponentModeler | TerminalComponentModeler +WorkflowDataType = SimulationDataType | ModalComponentModelerData | TerminalComponentModelerData diff --git a/tidy3d/components/validators.py b/tidy3d/components/validators.py index 4116a7e2ca..61f8c94c61 100644 --- a/tidy3d/components/validators.py +++ b/tidy3d/components/validators.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any import numpy as np import pydantic.v1 as pydantic @@ -374,7 +374,7 @@ def validate_parameter_perturbation( field_name: str, base_field_name: str, allowed_real_range: tuple[tuple[float, float], ...], - allowed_imag_range: Optional[tuple[tuple[float, float], ...]] = None, + allowed_imag_range: tuple[tuple[float, float], ...] | None = None, allowed_complex: bool = True, ): """Assert perturbations do not drive a parameter out of physical bounds.""" diff --git a/tidy3d/components/viz/axes_utils.py b/tidy3d/components/viz/axes_utils.py index 85007e773c..93f79d85c3 100644 --- a/tidy3d/components/viz/axes_utils.py +++ b/tidy3d/components/viz/axes_utils.py @@ -1,7 +1,6 @@ from __future__ import annotations from functools import wraps -from typing import Optional from tidy3d.components.types import Ax, Axis, LengthUnit from tidy3d.constants import UnitScaling @@ -142,7 +141,7 @@ def set_default_labels_and_title( axis: Axis, position: float, ax: Ax, - plot_length_units: Optional[LengthUnit] = None, + plot_length_units: LengthUnit | None = None, ) -> Ax: """Adds axis labels and title to plots involving spatial dimensions. When the ``plot_length_units`` are specified, the plot axes are scaled, and diff --git a/tidy3d/components/viz/visualization_spec.py b/tidy3d/components/viz/visualization_spec.py index abe22ed7cf..8f20ccc1db 100644 --- a/tidy3d/components/viz/visualization_spec.py +++ b/tidy3d/components/viz/visualization_spec.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any import pydantic.v1 as pd @@ -38,13 +38,13 @@ class VisualizationSpec(Tidy3dBaseModel): description="Color applied to the faces in visualization.", ) - edgecolor: Optional[str] = pd.Field( + edgecolor: str | None = pd.Field( "", title="Edge color", description="Color applied to the edges in visualization.", ) - alpha: Optional[pd.confloat(ge=0.0, le=1.0)] = pd.Field( + alpha: pd.confloat(ge=0.0, le=1.0) | None = pd.Field( 1.0, title="Opacity", description="Opacity/alpha value in plotting between 0 and 1.", diff --git a/tidy3d/config/legacy.py b/tidy3d/config/legacy.py index adc5a10436..36eeba6f59 100644 --- a/tidy3d/config/legacy.py +++ b/tidy3d/config/legacy.py @@ -9,7 +9,7 @@ import os import ssl from pathlib import Path -from typing import Any, Optional +from typing import Any import toml @@ -130,17 +130,17 @@ class LegacyEnvironmentConfig: def __init__( self, - manager: Optional[ConfigManager] = None, - name: Optional[str] = None, + manager: ConfigManager | None = None, + name: str | None = None, *, - web_api_endpoint: Optional[str] = None, - website_endpoint: Optional[str] = None, - s3_region: Optional[str] = None, - ssl_verify: Optional[bool] = None, - enable_caching: Optional[bool] = None, - ssl_version: Optional[ssl.TLSVersion] = None, - env_vars: Optional[dict[str, str]] = None, - environment: Optional[LegacyEnvironment] = None, + web_api_endpoint: str | None = None, + website_endpoint: str | None = None, + s3_region: str | None = None, + ssl_verify: bool | None = None, + enable_caching: bool | None = None, + ssl_version: ssl.TLSVersion | None = None, + env_vars: dict[str, str] | None = None, + environment: LegacyEnvironment | None = None, ) -> None: if name is None: raise ValueError("Environment name is required") @@ -165,7 +165,7 @@ def __init__( self._overrides["env_vars"] = dict(env_vars) @property - def manager(self) -> Optional[ConfigManager]: + def manager(self) -> ConfigManager | None: return self._manager def active(self) -> None: @@ -181,17 +181,17 @@ def active(self) -> None: environment.set_current(self) @property - def web_api_endpoint(self) -> Optional[str]: + def web_api_endpoint(self) -> str | None: value = self._value("api_endpoint") return _maybe_str(value) @property - def website_endpoint(self) -> Optional[str]: + def website_endpoint(self) -> str | None: value = self._value("website_endpoint") return _maybe_str(value) @property - def s3_region(self) -> Optional[str]: + def s3_region(self) -> str | None: return self._value("s3_region") @property @@ -266,7 +266,7 @@ class LegacyEnvironment: """Legacy Env wrapper that maps to profiles.""" def __init__(self, manager: ConfigManager): - self._previous_env_vars: dict[str, Optional[str]] = {} + self._previous_env_vars: dict[str, str | None] = {} self.reset_manager(manager) def reset_manager(self, manager: ConfigManager) -> None: @@ -341,7 +341,7 @@ def _restore_env_vars(self) -> None: self._previous_env_vars = {} -def _maybe_str(value: Any) -> Optional[str]: +def _maybe_str(value: Any) -> str | None: if value is None: return None return str(value) diff --git a/tidy3d/config/loader.py b/tidy3d/config/loader.py index b536701caa..b29a565704 100644 --- a/tidy3d/config/loader.py +++ b/tidy3d/config/loader.py @@ -7,7 +7,7 @@ import tempfile from copy import deepcopy from pathlib import Path -from typing import Any, Optional +from typing import Any import toml import tomlkit @@ -21,7 +21,7 @@ class ConfigLoader: """Handle reading and writing configuration files.""" - def __init__(self, config_dir: Optional[Path] = None): + def __init__(self, config_dir: Path | None = None): self.config_dir = config_dir or resolve_config_directory() self.config_dir.mkdir(mode=0o700, parents=True, exist_ok=True) self._docs: dict[Path, tomlkit.TOMLDocument] = {} @@ -160,7 +160,7 @@ def load_environment_overrides() -> dict[str, Any]: if not segments: continue if segments[0] == "auth": - segments = ("web",) + segments[1:] + segments = ("web", *segments[1:]) _assign_path(overrides, segments, value) return overrides diff --git a/tidy3d/config/manager.py b/tidy3d/config/manager.py index 7919aafefc..a371737b71 100644 --- a/tidy3d/config/manager.py +++ b/tidy3d/config/manager.py @@ -10,7 +10,7 @@ from enum import Enum from io import StringIO from pathlib import Path -from typing import Any, Optional, get_args, get_origin +from typing import Any, get_args, get_origin from pydantic import BaseModel from rich.console import Console @@ -109,8 +109,8 @@ class ConfigManager: def __init__( self, - profile: Optional[str] = None, - config_dir: Optional[os.PathLike[str]] = None, + profile: str | None = None, + config_dir: os.PathLike[str] | None = None, ): loader_path = None if config_dir is None else Path(config_dir) self._loader = ConfigLoader(loader_path) @@ -124,7 +124,7 @@ def __init__( self._raw_tree: dict[str, Any] = {} self._effective_tree: dict[str, Any] = {} self._env_overrides: dict[str, Any] = load_environment_overrides() - self._web_env_previous: dict[str, Optional[str]] = {} + self._web_env_previous: dict[str, str | None] = {} attach_manager(self) self._reload() @@ -298,7 +298,7 @@ def on_section_registered(self, section: str) -> None: def on_handler_registered(self, section: str) -> None: self._apply_handlers(section=section) - def _resolve_initial_profile(self, profile: Optional[str]) -> str: + def _resolve_initial_profile(self, profile: str | None) -> str: if profile: return normalize_profile_name(str(profile)) @@ -354,13 +354,13 @@ def _build_models(self) -> None: self._section_models = new_sections self._plugin_models = new_plugins - def _get_model(self, name: str) -> Optional[BaseModel]: + def _get_model(self, name: str) -> BaseModel | None: if name.startswith("plugins."): plugin = name.split(".", 1)[1] return self._plugin_models.get(plugin) return self._section_models.get(name) - def _apply_handlers(self, section: Optional[str] = None) -> None: + def _apply_handlers(self, section: str | None = None) -> None: handlers = get_handlers() targets = [section] if section else handlers.keys() for target in targets: @@ -445,7 +445,7 @@ def __str__(self) -> str: return self.format() -def _deep_get(tree: dict[str, Any], path: Iterable[str]) -> Optional[dict[str, Any]]: +def _deep_get(tree: dict[str, Any], path: Iterable[str]) -> dict[str, Any] | None: node: Any = tree for segment in path: if not isinstance(node, dict): @@ -456,7 +456,7 @@ def _deep_get(tree: dict[str, Any], path: Iterable[str]) -> Optional[dict[str, A return node if isinstance(node, dict) else None -def _resolve_model_type(annotation: Any) -> Optional[type[BaseModel]]: +def _resolve_model_type(annotation: Any) -> type[BaseModel] | None: """Return the first BaseModel subclass found in an annotation (if any).""" if isinstance(annotation, type) and issubclass(annotation, BaseModel): diff --git a/tidy3d/config/registry.py b/tidy3d/config/registry.py index 8ee8e216a5..42c6d1d9a5 100644 --- a/tidy3d/config/registry.py +++ b/tidy3d/config/registry.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Callable, Optional, TypeVar +from collections.abc import Callable +from typing import TypeVar from pydantic import BaseModel @@ -10,7 +11,7 @@ _SECTIONS: dict[str, type[BaseModel]] = {} _HANDLERS: dict[str, Callable[[BaseModel], None]] = {} -_MANAGER: Optional[ConfigManagerProtocol] = None +_MANAGER: ConfigManagerProtocol | None = None class ConfigManagerProtocol: @@ -30,7 +31,7 @@ def attach_manager(manager: ConfigManagerProtocol) -> None: _MANAGER = manager -def get_manager() -> Optional[ConfigManagerProtocol]: +def get_manager() -> ConfigManagerProtocol | None: """Return the currently attached configuration manager, if any.""" return _MANAGER diff --git a/tidy3d/config/sections.py b/tidy3d/config/sections.py index 62e5b8a473..7234155cfe 100644 --- a/tidy3d/config/sections.py +++ b/tidy3d/config/sections.py @@ -4,7 +4,7 @@ import ssl from pathlib import Path -from typing import Any, Literal, Optional +from typing import Any, Literal from urllib.parse import urlparse import numpy as np @@ -75,7 +75,7 @@ def apply_logging(config: LoggingConfig) -> None: class SimulationConfig(ConfigSection): """Simulation-related configuration.""" - use_local_subpixel: Optional[bool] = Field( + use_local_subpixel: bool | None = Field( None, title="Use local subpixel", description=( @@ -209,7 +209,7 @@ class AdjointConfig(ConfigSection): ge=0.0, ) - solver_freq_chunk_size: Optional[PositiveInt] = Field( + solver_freq_chunk_size: PositiveInt | None = Field( None, title="Adjoint frequency chunk size", description=( @@ -277,7 +277,7 @@ def apply_adjoint(config: AdjointConfig) -> None: class WebConfig(ConfigSection): """Web/HTTP configuration.""" - apikey: Optional[SecretStr] = Field( + apikey: SecretStr | None = Field( None, title="API key", description="Tidy3D API key.", @@ -323,7 +323,7 @@ class WebConfig(ConfigSection): le=300, ) - ssl_version: Optional[ssl.TLSVersion] = Field( + ssl_version: ssl.TLSVersion | None = Field( None, title="SSL/TLS version", description="Optional SSL/TLS version to enforce for requests.", diff --git a/tidy3d/exceptions.py b/tidy3d/exceptions.py index 96b137d749..5629fc2a05 100644 --- a/tidy3d/exceptions.py +++ b/tidy3d/exceptions.py @@ -2,15 +2,13 @@ from __future__ import annotations -from typing import Optional - from .log import log class Tidy3dError(ValueError): """Any error in tidy3d""" - def __init__(self, message: Optional[str] = None): + def __init__(self, message: str | None = 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 3cfa825773..07ecdb2be6 100644 --- a/tidy3d/log.py +++ b/tidy3d/log.py @@ -3,18 +3,18 @@ from __future__ import annotations import inspect +from collections.abc import Callable from contextlib import contextmanager from datetime import datetime -from typing import Callable, Optional, Union +from typing import Literal from rich.console import Console from rich.text import Text -from typing_extensions import Literal # Note: "SUPPORT" and "USER" levels are meant for backend runs only. # Logging in frontend code should just use the standard debug/info/warning/error/critical. LogLevel = Literal["DEBUG", "SUPPORT", "USER", "INFO", "WARNING", "ERROR", "CRITICAL"] -LogValue = Union[int, LogLevel] +LogValue = int | LogLevel # Logging levels compatible with logging module _level_value = { @@ -245,7 +245,7 @@ def _log( message: str, *args, log_once: bool = False, - custom_loc: Optional[list] = None, + custom_loc: list | None = None, capture: bool = True, ) -> None: """Distribute log messages to all handlers""" @@ -314,7 +314,7 @@ def warning( message: str, *args, log_once: bool = False, - custom_loc: Optional[list] = None, + custom_loc: list | None = 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 37bb932b27..6d09e27079 100644 --- a/tidy3d/material_library/material_library.py +++ b/tidy3d/material_library/material_library.py @@ -3,7 +3,6 @@ from __future__ import annotations import json -from typing import Union import pydantic.v1 as pd @@ -84,7 +83,7 @@ class AbstractVariantItem(Tidy3dBaseModel): ) @property - def summarize_mediums(self) -> dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: + def summarize_mediums(self) -> dict[str, PoleResidue | Medium2D | MultiPhysicsMedium]: return {} def __str__(self): @@ -100,14 +99,14 @@ def _repr_pretty_(self, p, cycle): class VariantItem(AbstractVariantItem): """Reference, data_source, and material model for a variant of a material.""" - medium: Union[PoleResidue, MultiPhysicsMedium] = pd.Field( + medium: PoleResidue | MultiPhysicsMedium = pd.Field( ..., title="Material dispersion model", description="A dispersive medium described by the pole-residue pair model.", ) @property - def summarize_mediums(self) -> dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: + def summarize_mediums(self) -> dict[str, PoleResidue | Medium2D | MultiPhysicsMedium]: return {"medium": self.medium} @@ -171,7 +170,7 @@ class VariantItem2D(AbstractVariantItem): ) @property - def summarize_mediums(self) -> dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: + def summarize_mediums(self) -> dict[str, PoleResidue | Medium2D | MultiPhysicsMedium]: return {"medium": self.medium} @@ -222,7 +221,7 @@ def medium(self, optical_axis: Axis) -> AnisotropicMedium: return AnisotropicMedium.parse_obj(mat_dict) @property - def summarize_mediums(self) -> dict[str, Union[PoleResidue, Medium2D, MultiPhysicsMedium]]: + def summarize_mediums(self) -> dict[str, PoleResidue | Medium2D | MultiPhysicsMedium]: return {"ordinary": self.ordinary, "extraordinary": self.extraordinary} diff --git a/tidy3d/plugins/autograd/differential_operators.py b/tidy3d/plugins/autograd/differential_operators.py index 3bd92356eb..f6ab754229 100644 --- a/tidy3d/plugins/autograd/differential_operators.py +++ b/tidy3d/plugins/autograd/differential_operators.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Callable +from collections.abc import Callable from autograd.builtins import tuple as atuple from autograd.core import make_vjp diff --git a/tidy3d/plugins/autograd/functions.py b/tidy3d/plugins/autograd/functions.py index 34516e89a8..db11958c23 100644 --- a/tidy3d/plugins/autograd/functions.py +++ b/tidy3d/plugins/autograd/functions.py @@ -1,7 +1,7 @@ from __future__ import annotations -from collections.abc import Iterable -from typing import Callable, Literal, Union +from collections.abc import Callable, Iterable +from typing import Literal import autograd.numpy as np import numpy as onp @@ -88,10 +88,10 @@ def _get_pad_indices( def pad( array: NDArray, - pad_width: Union[int, tuple[int, int]], + pad_width: int | tuple[int, int], *, mode: PaddingType = "constant", - axis: Union[int, Iterable[int], None] = None, + axis: int | Iterable[int] | None = None, constant_value: float = 0.0, ) -> NDArray: """Pad an array along specified axes with a given mode and padding width. @@ -157,7 +157,7 @@ def convolve( kernel: NDArray, *, padding: PaddingType = "constant", - axes: Union[tuple[list[int], list[int]], None] = None, + axes: tuple[list[int], list[int]] | None = None, mode: Literal["full", "valid", "same"] = "same", ) -> NDArray: """Convolve an array with a given kernel. @@ -226,8 +226,8 @@ def _get_footprint(size, structure, maxval): @primitive def grey_dilation( array: NDArray, - size: Union[int, tuple[int, int], None] = None, - structure: Union[NDArray, None] = None, + size: int | tuple[int, int] | None = None, + structure: NDArray | None = None, *, mode: PaddingType = "reflect", maxval: float = 1e4, @@ -341,8 +341,8 @@ def vjp(g): def grey_erosion( array: NDArray, - size: Union[Union[int, tuple[int, int]], None] = None, - structure: Union[NDArray, None] = None, + size: int | tuple[int, int] | None = None, + structure: NDArray | None = None, *, mode: PaddingType = "reflect", maxval: float = 1e4, @@ -383,8 +383,8 @@ def grey_erosion( def grey_opening( array: NDArray, - size: Union[Union[int, tuple[int, int]], None] = None, - structure: Union[NDArray, None] = None, + size: int | tuple[int, int] | None = None, + structure: NDArray | None = None, *, mode: PaddingType = "reflect", maxval: float = 1e4, @@ -416,8 +416,8 @@ def grey_opening( def grey_closing( array: NDArray, - size: Union[Union[int, tuple[int, int]], None] = None, - structure: Union[NDArray, None] = None, + size: int | tuple[int, int] | None = None, + structure: NDArray | None = None, *, mode: PaddingType = "reflect", maxval: float = 1e4, @@ -449,8 +449,8 @@ def grey_closing( def morphological_gradient( array: NDArray, - size: Union[Union[int, tuple[int, int]], None] = None, - structure: Union[NDArray, None] = None, + size: int | tuple[int, int] | None = None, + structure: NDArray | None = None, *, mode: PaddingType = "reflect", maxval: float = 1e4, @@ -482,8 +482,8 @@ def morphological_gradient( def morphological_gradient_internal( array: NDArray, - size: Union[Union[int, tuple[int, int]], None] = None, - structure: Union[NDArray, None] = None, + size: int | tuple[int, int] | None = None, + structure: NDArray | None = None, *, mode: PaddingType = "reflect", maxval: float = 1e4, @@ -513,8 +513,8 @@ def morphological_gradient_internal( def morphological_gradient_external( array: NDArray, - size: Union[Union[int, tuple[int, int]], None] = None, - structure: Union[NDArray, None] = None, + size: int | tuple[int, int] | None = None, + structure: NDArray | None = None, *, mode: PaddingType = "reflect", maxval: float = 1e4, @@ -582,7 +582,7 @@ def rescale( def threshold( - array: NDArray, vmin: float = 0.0, vmax: float = 1.0, level: Union[float, None] = None + array: NDArray, vmin: float = 0.0, vmax: float = 1.0, level: float | None = None ) -> NDArray: """Apply a threshold to an array, setting values below the threshold to `vmin` and values above to `vmax`. @@ -618,9 +618,7 @@ def threshold( return np.where(array < level, vmin, vmax) -def smooth_max( - x: NDArray, tau: float = 1.0, axis: Union[int, tuple[int, ...], None] = None -) -> float: +def smooth_max(x: NDArray, tau: float = 1.0, axis: int | tuple[int, ...] | None = None) -> float: """Compute the smooth maximum of an array using temperature parameter tau. Parameters @@ -640,9 +638,7 @@ def smooth_max( return tau * logsumexp(x / tau, axis=axis) -def smooth_min( - x: NDArray, tau: float = 1.0, axis: Union[int, tuple[int, ...], None] = None -) -> float: +def smooth_min(x: NDArray, tau: float = 1.0, axis: int | tuple[int, ...] | None = None) -> float: """Compute the smooth minimum of an array using temperature parameter tau. Parameters diff --git a/tidy3d/plugins/autograd/invdes/filters.py b/tidy3d/plugins/autograd/invdes/filters.py index b8bb019563..52f205b03b 100644 --- a/tidy3d/plugins/autograd/invdes/filters.py +++ b/tidy3d/plugins/autograd/invdes/filters.py @@ -1,9 +1,9 @@ from __future__ import annotations import abc -from collections.abc import Iterable +from collections.abc import Callable, Iterable from functools import lru_cache, partial -from typing import Annotated, Callable, Optional, Union +from typing import Annotated import numpy as np import pydantic.v1 as pd @@ -20,7 +20,7 @@ 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: 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: float | tuple[float, ...], dl: float | tuple[float, ...], **kwargs ) -> AbstractFilter: """Create a filter from radius and grid spacing. @@ -125,9 +125,9 @@ 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, ...]], + radius: float | tuple[float, ...], + dl: float | tuple[float, ...], + size_px: int | tuple[int, ...], ) -> tuple[int, ...]: """Determine the kernel size based on the provided radius, grid spacing, or size in pixels. @@ -163,10 +163,10 @@ def _get_kernel_size( def make_filter( - radius: Optional[Union[float, tuple[float, ...]]] = None, - dl: Optional[Union[float, tuple[float, ...]]] = None, + radius: float | tuple[float, ...] | None = None, + dl: float | tuple[float, ...] | None = None, *, - size_px: Optional[Union[int, tuple[int, ...]]] = None, + size_px: int | tuple[int, ...] | None = None, normalize: bool = True, padding: PaddingType = "reflect", filter_type: KernelType, @@ -225,4 +225,4 @@ def make_filter( :func:`~filters.make_filter` : Function to create a filter based on the specified kernel type and size. """ -FilterType = Annotated[Union[ConicFilter, CircularFilter], pd.Field(discriminator=TYPE_TAG_STR)] +FilterType = Annotated[ConicFilter | CircularFilter, pd.Field(discriminator=TYPE_TAG_STR)] diff --git a/tidy3d/plugins/autograd/invdes/parametrizations.py b/tidy3d/plugins/autograd/invdes/parametrizations.py index a21e22c1d0..973e3ca9bc 100644 --- a/tidy3d/plugins/autograd/invdes/parametrizations.py +++ b/tidy3d/plugins/autograd/invdes/parametrizations.py @@ -1,7 +1,8 @@ from __future__ import annotations from collections import deque -from typing import Callable, Literal, Optional, Union +from collections.abc import Callable +from typing import Literal import autograd.numpy as np import pydantic.v1 as pd @@ -22,13 +23,13 @@ class FilterAndProject(Tidy3dBaseModel): """A class that combines filtering and projection operations.""" - radius: Union[float, tuple[float, ...]] = pd.Field( + radius: float | tuple[float, ...] = pd.Field( ..., title="Radius", description="The radius of the kernel." ) - dl: Union[float, tuple[float, ...]] = pd.Field( + dl: float | tuple[float, ...] = pd.Field( ..., title="Grid Spacing", description="The grid spacing." ) - size_px: Union[int, tuple[int, ...]] = pd.Field( + size_px: int | tuple[int, ...] = pd.Field( None, title="Size in Pixels", description="The size of the kernel in pixels." ) beta: pd.NonNegativeFloat = pd.Field( @@ -45,7 +46,7 @@ class FilterAndProject(Tidy3dBaseModel): ) def __call__( - self, array: NDArray, beta: Optional[float] = None, eta: Optional[float] = None + self, array: NDArray, beta: float | None = None, eta: float | None = None ) -> NDArray: """Apply the filter and projection to an input array. @@ -78,10 +79,10 @@ def __call__( def make_filter_and_project( - radius: Optional[Union[float, tuple[float, ...]]] = None, - dl: Optional[Union[float, tuple[float, ...]]] = None, + radius: float | tuple[float, ...] | None = None, + dl: float | tuple[float, ...] | None = None, *, - size_px: Optional[Union[int, tuple[int, ...]]] = None, + size_px: int | tuple[int, ...] | None = None, beta: float = BETA_DEFAULT, eta: float = ETA_DEFAULT, filter_type: KernelType = "conic", @@ -109,10 +110,10 @@ def initialize_params_from_simulation( param_to_structure: Callable[..., td.Structure], params0: np.ndarray, *, - freq: Optional[float] = None, + freq: float | None = None, outside_handling: Literal["extrapolate", "mask", "nan"] = "mask", maxiter: int = 100, - bounds: tuple[Optional[float], Optional[float]] = (0.0, 1.0), + bounds: tuple[float | None, float | None] = (0.0, 1.0), rel_improve_tol: float = 1e-3, verbose: bool = False, **param_kwargs, diff --git a/tidy3d/plugins/autograd/invdes/penalties.py b/tidy3d/plugins/autograd/invdes/penalties.py index 92eaac6e79..4616055a19 100644 --- a/tidy3d/plugins/autograd/invdes/penalties.py +++ b/tidy3d/plugins/autograd/invdes/penalties.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Callable, Optional, Union +from collections.abc import Callable import autograd.numpy as np import pydantic.v1 as pd @@ -16,13 +16,13 @@ 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: float | tuple[float, ...] = pd.Field( ..., title="Radius", description="The radius of the kernel." ) - dl: Union[float, tuple[float, ...]] = pd.Field( + dl: float | tuple[float, ...] = pd.Field( ..., title="Grid Spacing", description="The grid spacing." ) - size_px: Union[int, tuple[int, ...]] = pd.Field( + size_px: int | tuple[int, ...] = pd.Field( None, title="Size in Pixels", description="The size of the kernel in pixels." ) beta: pd.NonNegativeFloat = pd.Field( @@ -90,10 +90,10 @@ def _close(arr: NDArray): def make_erosion_dilation_penalty( - radius: Union[float, tuple[float, ...]], - dl: Union[float, tuple[float, ...]], + radius: float | tuple[float, ...], + dl: float | tuple[float, ...], *, - size_px: Optional[Union[int, tuple[int, ...]]] = None, + size_px: int | tuple[int, ...] | None = None, beta: float = 20.0, eta: float = 0.5, delta_eta: float = 0.01, @@ -169,7 +169,7 @@ def bezier_with_grads( return b, dbdt, dbd2t -def bezier_curvature(x: NDArray, y: NDArray, t: Union[NDArray, float] = 0.5) -> NDArray: +def bezier_curvature(x: NDArray, y: NDArray, t: NDArray | float = 0.5) -> NDArray: """ Calculate the curvature of a Bezier curve at a given parameter t. diff --git a/tidy3d/plugins/autograd/primitives/interpolate.py b/tidy3d/plugins/autograd/primitives/interpolate.py index ac5b6c2d4c..da9318bf0f 100644 --- a/tidy3d/plugins/autograd/primitives/interpolate.py +++ b/tidy3d/plugins/autograd/primitives/interpolate.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Optional - import numpy as np from autograd.extend import defvjp, primitive from numpy.typing import NDArray @@ -164,7 +162,7 @@ def get_linear_derivative_wrt_y( def compute_quadratic_coefficients( x: NDArray, y: NDArray, - left_deriv: Optional[float] = None, + left_deriv: float | None = None, ) -> tuple[NDArray, NDArray, NDArray]: """Compute quadratic spline coefficients. @@ -239,7 +237,7 @@ def evaluate_quadratic_spline( def get_quadratic_derivative_wrt_y( x: NDArray, y: NDArray, - left_deriv: Optional[float] = None, + left_deriv: float | None = None, ) -> tuple[NDArray, NDArray, NDArray]: """Compute derivative of quadratic spline coefficients wrt ``y``. @@ -292,7 +290,7 @@ def get_quadratic_derivative_wrt_y( def setup_cubic_tridiagonal( x: NDArray, y: NDArray, - endpoint_derivs: tuple[Optional[float], Optional[float]] = (None, None), + endpoint_derivs: tuple[float | None, float | None] = (None, None), ) -> tuple[NDArray, NDArray, NDArray, NDArray, NDArray]: """Return (lower, diag, upper, rhs, h) for the cubic spline system. @@ -397,7 +395,7 @@ def _solve_tridiagonal_multi(lower: NDArray, diag: NDArray, upper: NDArray, B: N np.ndarray Solution matrix """ - n, k = B.shape + n, _k = B.shape c = upper.copy() d = diag.copy() a = lower.copy() @@ -484,7 +482,7 @@ def evaluate_cubic_spline( def compute_spline_coefficients( x: NDArray, y: NDArray, - endpoint_derivs: tuple[Optional[float], Optional[float]] = (None, None), + endpoint_derivs: tuple[float | None, float | None] = (None, None), ) -> tuple[NDArray, NDArray, NDArray, NDArray]: """Compute the cubic spline coefficients ``(a, b, c, d)``. @@ -510,7 +508,7 @@ def compute_spline_coefficients( def get_cubic_derivative_wrt_y( x: NDArray, y: NDArray, - endpoint_derivs: tuple[Optional[float], Optional[float]] = (None, None), + endpoint_derivs: tuple[float | None, float | None] = (None, None), ) -> tuple[NDArray, NDArray, NDArray, NDArray, NDArray]: """Compute derivatives of cubic spline coefficients ``(a, b, c, d)`` wrt ``y`` values. @@ -584,7 +582,7 @@ def get_cubic_derivative_wrt_y( def compute_spline_coeffs( x_points: NDArray, y_points: NDArray, - endpoint_derivatives: tuple[Optional[float], Optional[float]] = (None, None), + endpoint_derivatives: tuple[float | None, float | None] = (None, None), order: int = 3, ) -> tuple: """Compute spline coefficients for the given order. @@ -649,7 +647,7 @@ def get_spline_derivatives_wrt_y( order: int, x_points: NDArray, y_points: NDArray, - endpoint_derivatives: tuple[Optional[float], Optional[float]] = (None, None), + endpoint_derivatives: tuple[float | None, float | None] = (None, None), ): """Returns a tuple of derivative arrays for the given spline order. @@ -687,7 +685,7 @@ def _interpolate_spline( y_points: NDArray, num_points: int, order: int, - endpoint_derivatives: tuple[Optional[float], Optional[float]] = (None, None), + endpoint_derivatives: tuple[float | None, float | None] = (None, None), ) -> tuple[NDArray, NDArray]: """Primitive function to perform spline interpolation of a given order with optional endpoint derivatives. @@ -781,7 +779,7 @@ def interpolate_spline( y_points: NDArray, num_points: int, order: int, - endpoint_derivatives: tuple[Optional[float], Optional[float]] = (None, None), + endpoint_derivatives: tuple[float | None, float | None] = (None, None), ) -> tuple[NDArray, NDArray]: """Differentiable spline interpolation of a given order with optional endpoint derivatives. diff --git a/tidy3d/plugins/autograd/utilities.py b/tidy3d/plugins/autograd/utilities.py index 7a7b5f83a8..0d7cab605b 100644 --- a/tidy3d/plugins/autograd/utilities.py +++ b/tidy3d/plugins/autograd/utilities.py @@ -1,8 +1,8 @@ from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Callable, Iterable from functools import reduce, wraps -from typing import Any, Callable, Optional, Union +from typing import Any import autograd.numpy as anp import numpy as np @@ -86,9 +86,9 @@ def make_kernel(kernel_type: KernelType, size: Iterable[int], normalize: bool = def get_kernel_size_px( - radius: Optional[Union[float, Iterable[float]]] = None, - dl: Optional[Union[float, Iterable[float]]] = None, -) -> Union[int, list[int]]: + radius: float | Iterable[float] | None = None, + dl: float | Iterable[float] | None = None, +) -> int | list[int]: """Calculate the kernel size in pixels based on the provided radius and grid spacing. Parameters @@ -124,7 +124,7 @@ def get_kernel_size_px( ) -def chain(*funcs: Union[Callable, Iterable[Callable]]): +def chain(*funcs: Callable | Iterable[Callable]): """Chain multiple functions together to apply them sequentially to an array. Parameters @@ -168,7 +168,7 @@ def chained(array: NDArray): return chained -def scalar_objective(func: Optional[Callable] = None, *, has_aux: bool = False) -> Callable: +def scalar_objective(func: Callable | None = 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. diff --git a/tidy3d/plugins/design/design.py b/tidy3d/plugins/design/design.py index cee82953e9..8010d511af 100644 --- a/tidy3d/plugins/design/design.py +++ b/tidy3d/plugins/design/design.py @@ -3,7 +3,8 @@ from __future__ import annotations import inspect -from typing import Any, Callable, Optional, Union +from collections.abc import Callable +from typing import Any import pydantic.v1 as pd @@ -112,9 +113,9 @@ def _package_run_results( fn_args: list[dict[str, Any]], fn_values: list[Any], fn_source: str, - task_names: Optional[tuple[str]] = None, - task_paths: Optional[list] = None, - aux_values: Optional[list[Any]] = None, + task_names: tuple[str] | None = None, + task_paths: list | None = None, + aux_values: list[Any] | None = None, opt_output: Any = None, ) -> Result: """How to package results from ``method.run`` and ``method.run_batch``""" @@ -144,7 +145,7 @@ def get_fn_source(function: Callable) -> str: except (TypeError, OSError): return None - def run(self, fn: Callable, fn_post: Optional[Callable] = None, verbose: bool = True) -> Result: + def run(self, fn: Callable, fn_post: Callable | None = 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 @@ -314,7 +315,7 @@ def fn_combined(self, args_list: list[dict[str, Any]]) -> list[Any]: def _fn_mid( self, pre_out: dict[int, Any], sim_counter: int, console: Console - ) -> Union[dict[int, Any], BatchData]: + ) -> dict[int, Any] | BatchData: """A function of the output of ``fn_pre`` that gives the input to ``fn_post``.""" # Keep copy of original to use if no tidy3d simulation required @@ -456,10 +457,8 @@ 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_post: Callable[ - Union[SimulationData, list[SimulationData], dict[str, SimulationData]], Any - ], + fn_pre: Callable[Any, Simulation | list[Simulation] | dict[str, Simulation]], + fn_post: Callable[SimulationData | list[SimulationData] | dict[str, SimulationData], Any], path_dir: str = ".", **batch_kwargs, ) -> Result: @@ -564,7 +563,7 @@ def _estimate_sim_cost(sim): return None return round(per_run_estimate * run_count, 3) - def summarize(self, fn_pre: Optional[Callable] = None, verbose: bool = True) -> dict[str, Any]: + def summarize(self, fn_pre: Callable | None = 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 22b0745bce..3a212a92bb 100644 --- a/tidy3d/plugins/design/method.py +++ b/tidy3d/plugins/design/method.py @@ -3,7 +3,8 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Literal import numpy as np import pydantic.v1 as pd @@ -27,7 +28,7 @@ def _run(self, parameters: tuple[ParameterType, ...], run_fn: Callable) -> tuple """Defines the search algorithm.""" @abstractmethod - def _get_run_count(self, parameters: Optional[list] = None) -> int: + def _get_run_count(self, parameters: list | None = 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: @@ -231,7 +232,7 @@ 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: Optional[list] = None) -> int: + def _get_run_count(self, parameters: list | None = None) -> int: """Return the maximum number of runs for the method based on current method arguments.""" return self.initial_iter + self.n_iter @@ -382,24 +383,22 @@ class MethodGenAlg(MethodOptimize, ABC): description="The style of parent selector. See the `PyGAD docs `_ for more details.", ) - keep_parents: Union[pd.PositiveInt, Literal[-1, 0]] = pd.Field( + keep_parents: pd.PositiveInt | Literal[-1, 0] = pd.Field( default=-1, title="Keep Parents", description="The number of parents to keep unaltered in the population of the next generation. Default value of -1 keeps all current parents for the next generation. This value is overwritten if ``keep_parents`` is > 0. See the `PyGAD docs `_ for more details.", ) - keep_elitism: Union[pd.PositiveInt, Literal[0]] = pd.Field( + keep_elitism: pd.PositiveInt | Literal[0] = pd.Field( default=1, title="Keep Elitism", description="The number of top solutions to be included in the population of the next generation. Overwrites ``keep_parents`` if value is > 0. See the `PyGAD docs `_ for more details.", ) - crossover_type: Union[None, Literal["single_point", "two_points", "uniform", "scattered"]] = ( - pd.Field( - default="single_point", - title="Crossover Type", - description="The style of crossover operation. See the `PyGAD docs `_ for more details.", - ) + crossover_type: None | Literal["single_point", "two_points", "uniform", "scattered"] = pd.Field( + default="single_point", + title="Crossover Type", + description="The style of crossover operation. See the `PyGAD docs `_ for more details.", ) crossover_prob: pd.confloat(ge=0, le=1) = pd.Field( @@ -408,15 +407,13 @@ class MethodGenAlg(MethodOptimize, ABC): description="The probability of performing a crossover between two parents.", ) - mutation_type: Union[None, Literal["random", "swap", "inversion", "scramble", "adaptive"]] = ( - pd.Field( - default="random", - title="Mutation Type", - description="The style of gene mutation. See the `PyGAD docs `_ for more details.", - ) + mutation_type: None | Literal["random", "swap", "inversion", "scramble", "adaptive"] = pd.Field( + default="random", + title="Mutation Type", + description="The style of gene mutation. See the `PyGAD docs `_ for more details.", ) - mutation_prob: Union[pd.confloat(ge=0, le=1), Literal[None]] = pd.Field( + mutation_prob: pd.confloat(ge=0, le=1) | Literal[None] = pd.Field( default=0.2, title="Mutation Probability", description="The probability of mutating a gene.", @@ -430,7 +427,7 @@ 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: Optional[list] = None) -> int: + def _get_run_count(self, parameters: list | None = 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) @@ -663,7 +660,7 @@ class MethodParticleSwarm(MethodOptimize, ABC): description="The weight or inertia of particles in the optimization.", ) - ftol: Union[pd.confloat(ge=0, le=1), Literal[-inf]] = pd.Field( + ftol: pd.confloat(ge=0, le=1) | Literal[-inf] = pd.Field( default=-inf, title="Relative Error for Convergence", description="Relative error in ``objective_func(best_solution)`` acceptable for convergence. See the `PySwarms docs `_ for details. Off by default.", @@ -681,7 +678,7 @@ 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: Optional[list] = None) -> int: + def _get_run_count(self, parameters: list | None = None) -> int: """Return the maximum number of runs for the method based on current method arguments.""" return self.n_particles * self.n_iter @@ -789,7 +786,7 @@ class AbstractMethodRandom(MethodSample, ABC): def _get_sampler(self, parameters: tuple[ParameterType, ...]) -> qmc_type.QMCEngine: """Sampler for this ``Method`` class. If ``None``, sets a default.""" - def _get_run_count(self, parameters: Optional[list] = None) -> int: + def _get_run_count(self, parameters: list | None = None) -> int: """Return the maximum number of runs for the method based on current method arguments.""" return self.num_points @@ -831,10 +828,4 @@ def _get_sampler(self, parameters: tuple[ParameterType, ...]) -> qmc_type.QMCEng return qmc.LatinHypercube(d=d, seed=self.seed) -MethodType = Union[ - MethodMonteCarlo, - MethodGrid, - MethodBayOpt, - MethodGenAlg, - MethodParticleSwarm, -] +MethodType = MethodMonteCarlo | MethodGrid | MethodBayOpt | MethodGenAlg | MethodParticleSwarm diff --git a/tidy3d/plugins/design/parameter.py b/tidy3d/plugins/design/parameter.py index 4d96646c2e..b3d33362a7 100644 --- a/tidy3d/plugins/design/parameter.py +++ b/tidy3d/plugins/design/parameter.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Union +from typing import Any import numpy as np import pydantic.v1 as pd @@ -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[float | int, float | int] = pd.Field( ..., title="Span", description="(min, max) range within which are allowed values for the variable. Is inclusive of max value.", @@ -214,4 +214,4 @@ def sample_first(self) -> Any: return self.allowed_values[0] -ParameterType = Union[ParameterInt, ParameterFloat, ParameterAny] +ParameterType = ParameterInt | ParameterFloat | ParameterAny diff --git a/tidy3d/plugins/design/result.py b/tidy3d/plugins/design/result.py index abf843183d..62c963ab57 100644 --- a/tidy3d/plugins/design/result.py +++ b/tidy3d/plugins/design/result.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any import numpy as np import pandas @@ -246,7 +246,7 @@ def to_dataframe(self, include_aux: bool = False) -> pandas.DataFrame: return df @classmethod - def from_dataframe(cls, df: pandas.DataFrame, dims: Optional[list[str]] = None) -> Result: + def from_dataframe(cls, df: pandas.DataFrame, dims: list[str] | None = None) -> Result: """Load a result directly from a `pandas.DataFrame` object. Parameters diff --git a/tidy3d/plugins/dispersion/fit.py b/tidy3d/plugins/dispersion/fit.py index b9977e08b2..c64408ac28 100644 --- a/tidy3d/plugins/dispersion/fit.py +++ b/tidy3d/plugins/dispersion/fit.py @@ -4,7 +4,6 @@ import codecs import csv -from typing import Optional import numpy as np import requests @@ -44,7 +43,7 @@ class DispersionFitter(Tidy3dBaseModel): description="Imaginary part of the complex index of refraction.", ) - wvl_range: tuple[Optional[float], Optional[float]] = Field( + wvl_range: tuple[float | None, float | None] = 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, " @@ -752,7 +751,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[float | None, float | None] = (None, None), ) -> DispersionFitter: """Loads :class:`DispersionFitter` from wavelength and complex relative permittivity data @@ -784,7 +783,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[float | None, float | None] = (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 356dbde0a9..d999004dae 100644 --- a/tidy3d/plugins/dispersion/fit_fast.py +++ b/tidy3d/plugins/dispersion/fit_fast.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional - import numpy as np from pydantic.v1 import NonNegativeFloat, PositiveInt @@ -46,7 +44,7 @@ def fit( self, min_num_poles: PositiveInt = 1, max_num_poles: PositiveInt = DEFAULT_MAX_POLES, - eps_inf: Optional[float] = None, + eps_inf: float | None = None, tolerance_rms: NonNegativeFloat = DEFAULT_TOLERANCE_RMS, advanced_param: AdvancedFastFitterParam = None, ) -> tuple[PoleResidue, float]: diff --git a/tidy3d/plugins/dispersion/web.py b/tidy3d/plugins/dispersion/web.py index 186eb0f782..c0e5516e39 100644 --- a/tidy3d/plugins/dispersion/web.py +++ b/tidy3d/plugins/dispersion/web.py @@ -4,7 +4,7 @@ import ssl from enum import Enum -from typing import Literal, Optional +from typing import Literal import pydantic.v1 as pydantic import requests @@ -83,7 +83,7 @@ class AdvancedFitterParam(Tidy3dBaseModel): title="Number of inner iterations", description="Number of iterations in each inner optimization.", ) - random_seed: Optional[int] = Field( + random_seed: int | None = Field( 0, title="Random seed for starting coefficients", description="The fitting tool performs global optimizations with random " diff --git a/tidy3d/plugins/expressions/__init__.py b/tidy3d/plugins/expressions/__init__.py index 616aac08e8..2da541efee 100644 --- a/tidy3d/plugins/expressions/__init__.py +++ b/tidy3d/plugins/expressions/__init__.py @@ -1,5 +1,14 @@ from __future__ import annotations +import importlib +import inspect +from typing import Annotated + +from pydantic.v1 import Field + +from tidy3d.components.types import TYPE_TAG_STR + +from . import types as _types from .base import Expression from .functions import Cos, Exp, Log, Log10, Sin, Sqrt, Tan from .metrics import ModeAmp, ModePower, generate_validation_data @@ -22,18 +31,14 @@ ] # The following code dynamically collects all classes that are subclasses of Expression -# from the specified modules and updates their forward references. This is necessary to handle -# cases where classes reference each other before they are fully defined. The local_vars dictionary -# is used to store these classes and any other necessary types for the forward reference updates. - -import importlib -import inspect - -from .types import ExpressionType +# from the specified modules, builds a discriminated union for serialization, and updates their +# forward references. This is necessary to handle cases where classes reference each other before +# they are fully defined. The local_vars dictionary is used to store these classes and any other +# necessary types for the forward reference updates. _module_names = ["base", "variables", "functions", "metrics", "operators"] _model_classes = set() -_local_vars = {"ExpressionType": ExpressionType} +_local_vars: dict[str, type[Expression]] = {} for module_name in _module_names: module = importlib.import_module(f".{module_name}", package=__name__) @@ -42,5 +47,24 @@ _model_classes.add(obj) _local_vars[name] = obj +_concrete_classes = [ + cls + for cls in sorted(_model_classes, key=lambda candidate: candidate.__name__) + if not inspect.isabstract(cls) +] + +_expression_union = Expression +if _concrete_classes: + _expression_union = _concrete_classes[0] + for cls in _concrete_classes[1:]: + _expression_union = _expression_union | cls + +ExpressionType = Annotated[_expression_union, Field(discriminator=TYPE_TAG_STR)] +_types.ExpressionType = ExpressionType +_types.NumberOrExpression = _types.NumberType | ExpressionType + +__all__.append("ExpressionType") +_local_vars["ExpressionType"] = ExpressionType + for cls in _model_classes: cls.update_forward_refs(**_local_vars) diff --git a/tidy3d/plugins/expressions/base.py b/tidy3d/plugins/expressions/base.py index ff52b648ef..6d827f716b 100644 --- a/tidy3d/plugins/expressions/base.py +++ b/tidy3d/plugins/expressions/base.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from collections.abc import Generator -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any from tidy3d.components.base import Tidy3dBaseModel from tidy3d.components.types import TYPE_TAG_STR @@ -63,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: str | None = None ) -> Generator[Expression, None, None]: """ Find all instances of a given type or field in the expression. diff --git a/tidy3d/plugins/expressions/metrics.py b/tidy3d/plugins/expressions/metrics.py index 277085bfe7..c0b7f39751 100644 --- a/tidy3d/plugins/expressions/metrics.py +++ b/tidy3d/plugins/expressions/metrics.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Optional, Union +from typing import Any import autograd.numpy as np import pydantic.v1 as pd @@ -69,7 +69,7 @@ class ModeAmp(Metric): title="Monitor Name", description="The name of the mode monitor. This needs to match the name of the monitor in the simulation.", ) - f: Optional[Union[float, FreqArray]] = pd.Field( # type: ignore + f: float | FreqArray | None = pd.Field( # type: ignore None, title="Frequency Array", description="The frequency array. If None, all frequencies in the monitor will be used.", diff --git a/tidy3d/plugins/expressions/types.py b/tidy3d/plugins/expressions/types.py index 1f6a12e9fe..bc355f8b55 100644 --- a/tidy3d/plugins/expressions/types.py +++ b/tidy3d/plugins/expressions/types.py @@ -1,73 +1,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Annotated, Union +from typing import ForwardRef, Union -from pydantic.v1 import Field +from tidy3d.components.types import ArrayLike, Complex -from tidy3d.components.types import TYPE_TAG_STR, ArrayLike, Complex +NumberType = int | float | Complex | ArrayLike -if TYPE_CHECKING: - from .functions import Cos, Exp, Log, Log10, Sin, Sqrt, Tan - from .metrics import ModeAmp, ModePower - from .operators import ( - Abs, - Add, - Divide, - FloorDivide, - MatMul, - Modulus, - Multiply, - Negate, - Power, - Subtract, - ) - from .variables import Constant, Variable +ExpressionType = ForwardRef("ExpressionType") -NumberType = Union[int, float, Complex, ArrayLike] - -OperatorType = Annotated[ - Union[ - "Add", - "Subtract", - "Multiply", - "Divide", - "Power", - "Modulus", - "FloorDivide", - "MatMul", - "Negate", - "Abs", - ], - Field(discriminator=TYPE_TAG_STR), -] - -FunctionType = Annotated[ - Union[ - "Sin", - "Cos", - "Tan", - "Exp", - "Log", - "Log10", - "Sqrt", - ], - Field(discriminator=TYPE_TAG_STR), -] - -MetricType = Annotated[ - Union[ - "Constant", - "Variable", - "ModeAmp", - "ModePower", - ], - Field(discriminator=TYPE_TAG_STR), -] - -ExpressionType = Union[ - OperatorType, - FunctionType, - MetricType, -] - -NumberOrExpression = Union[NumberType, ExpressionType] +NumberOrExpression = Union[NumberType, ExpressionType] # noqa: UP007 diff --git a/tidy3d/plugins/expressions/variables.py b/tidy3d/plugins/expressions/variables.py index 13cd5534b8..0801e05e3b 100644 --- a/tidy3d/plugins/expressions/variables.py +++ b/tidy3d/plugins/expressions/variables.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any import pydantic.v1 as pd @@ -39,7 +39,7 @@ class Variable(Expression): 10 """ - name: Optional[str] = pd.Field( + name: str | None = pd.Field( None, title="Name", description="The name of the variable used for lookup during evaluation.", diff --git a/tidy3d/plugins/invdes/design.py b/tidy3d/plugins/invdes/design.py index 737698b9b6..c2a2ba3e6f 100644 --- a/tidy3d/plugins/invdes/design.py +++ b/tidy3d/plugins/invdes/design.py @@ -3,7 +3,7 @@ from __future__ import annotations import abc -import typing +from collections.abc import Callable import autograd.numpy as anp import numpy as np @@ -19,7 +19,7 @@ from .region import DesignRegionType from .validators import check_pixel_size -PostProcessFnType = typing.Callable[[td.SimulationData], float] +PostProcessFnType = Callable[[td.SimulationData], float] class AbstractInverseDesign(InvdesBaseModel, abc.ABC): @@ -43,15 +43,15 @@ class AbstractInverseDesign(InvdesBaseModel, abc.ABC): description="If ``True``, will print the regular output from ``web`` functions.", ) - metric: typing.Optional[ExpressionType] = pd.Field( + metric: ExpressionType | None = pd.Field( None, title="Objective Metric", description="Serializable expression defining the objective function.", ) def make_objective_fn( - self, post_process_fn: typing.Optional[typing.Callable] = None, maximize: bool = True - ) -> typing.Callable[[anp.ndarray], tuple[float, dict]]: + self, post_process_fn: Callable | None = None, maximize: bool = True + ) -> Callable[[anp.ndarray], tuple[float, dict]]: """Construct the objective function for this InverseDesign object.""" if (post_process_fn is None) and (self.metric is None): @@ -62,7 +62,7 @@ def make_objective_fn( direction_multiplier = 1 if maximize else -1 - def objective_fn(params: anp.ndarray, aux_data: typing.Optional[dict] = None) -> float: + def objective_fn(params: anp.ndarray, aux_data: dict | None = None) -> float: """Full objective function.""" data = self.to_simulation_data(params=params) @@ -265,7 +265,7 @@ class InverseDesignMulti(AbstractInverseDesign): description="Set of simulation without the design regions or monitors used in the objective fn.", ) - output_monitor_names: tuple[typing.Union[tuple[str, ...], None], ...] = pd.Field( + output_monitor_names: tuple[tuple[str, ...] | None, ...] = pd.Field( None, title="Output Monitor Names", description="Optional names of monitors whose data the differentiable output depends on." @@ -328,4 +328,4 @@ def to_simulation_data(self, params: anp.ndarray, **kwargs) -> web.BatchData: # return self.run_async(simulations, **kwargs) -InverseDesignType = typing.Union[InverseDesign, InverseDesignMulti] +InverseDesignType = InverseDesign | InverseDesignMulti diff --git a/tidy3d/plugins/invdes/initialization.py b/tidy3d/plugins/invdes/initialization.py index 3acd297d25..e61768e30e 100644 --- a/tidy3d/plugins/invdes/initialization.py +++ b/tidy3d/plugins/invdes/initialization.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Optional, Union import numpy as np import pydantic.v1 as pd @@ -43,7 +42,7 @@ class RandomInitializationSpec(AbstractInitializationSpec): title="Maximum Value", description="Maximum value for the random parameters (exclusive).", ) - seed: Optional[pd.NonNegativeInt] = pd.Field( + seed: pd.NonNegativeInt | None = pd.Field( None, description="Seed for the random number generator." ) @@ -126,8 +125,6 @@ def create_parameters(self, shape: tuple[int, ...]) -> NDArray: return params -InitializationSpecType = Union[ - RandomInitializationSpec, - UniformInitializationSpec, - CustomInitializationSpec, -] +InitializationSpecType = ( + RandomInitializationSpec | UniformInitializationSpec | CustomInitializationSpec +) diff --git a/tidy3d/plugins/invdes/optimizer.py b/tidy3d/plugins/invdes/optimizer.py index a43ba47e23..403404a307 100644 --- a/tidy3d/plugins/invdes/optimizer.py +++ b/tidy3d/plugins/invdes/optimizer.py @@ -84,9 +84,7 @@ def display_fn(self, result: InverseDesignResult, step_index: int) -> None: print(f"\tpost_process_val = {result.post_process_val[-1]:.3e}") print(f"\tpenalty = {result.penalty[-1]:.3e}") - def initialize_result( - self, params0: typing.Optional[anp.ndarray] = None - ) -> InverseDesignResult: + def initialize_result(self, params0: anp.ndarray | None = None) -> InverseDesignResult: """ Create an initially empty `InverseDesignResult` from the starting parameters. @@ -111,8 +109,8 @@ def initialize_result( def run( self, - post_process_fn: typing.Optional[typing.Callable] = None, - callback: typing.Optional[typing.Callable] = None, + post_process_fn: typing.Callable | None = None, + callback: typing.Callable | None = None, params0: anp.ndarray = None, ) -> InverseDesignResult: """Run this inverse design problem from an optional initial set of parameters. @@ -140,9 +138,9 @@ def run( def continue_run( self, result: InverseDesignResult, - num_steps: typing.Optional[int] = None, - post_process_fn: typing.Optional[typing.Callable] = None, - callback: typing.Optional[typing.Callable] = None, + num_steps: int | None = None, + post_process_fn: typing.Callable | None = None, + callback: typing.Callable | None = None, ) -> InverseDesignResult: """Run optimizer for a series of steps with an initialized state. @@ -178,7 +176,7 @@ def continue_run( # main optimization loop for step_index in range(done_steps, done_steps + num_steps): aux_data = {} - val, grad = val_and_grad_fn(params, aux_data=aux_data) + _val, grad = val_and_grad_fn(params, aux_data=aux_data) if anp.allclose(grad, 0.0): td.log.warning( @@ -230,9 +228,9 @@ def continue_run( def continue_run_from_file( self, fname: str, - num_steps: typing.Optional[int] = None, - post_process_fn: typing.Optional[typing.Callable] = None, - callback: typing.Optional[typing.Callable] = None, + num_steps: int | None = None, + post_process_fn: typing.Callable | None = None, + callback: typing.Callable | None = None, ) -> InverseDesignResult: """Continue the optimization run from a ``.pkl`` file with an ``InverseDesignResult``.""" result = InverseDesignResult.from_file(fname) @@ -245,9 +243,9 @@ def continue_run_from_file( def continue_run_from_history( self, - num_steps: typing.Optional[int] = None, - post_process_fn: typing.Optional[typing.Callable] = None, - callback: typing.Optional[typing.Callable] = None, + num_steps: int | None = None, + post_process_fn: typing.Callable | None = None, + callback: typing.Callable | None = None, ) -> InverseDesignResult: """Continue the optimization run from a ``.pkl`` file with an ``InverseDesignResult``.""" return self.continue_run_from_file( @@ -289,7 +287,7 @@ def initial_state(self, parameters: np.ndarray) -> dict: return {"m": zeros, "v": zeros, "t": 0} def update( - self, parameters: np.ndarray, gradient: np.ndarray, state: typing.Optional[dict] = None + self, parameters: np.ndarray, gradient: np.ndarray, state: dict | None = None ) -> tuple[np.ndarray, dict]: if state is None: state = self.initial_state(parameters) diff --git a/tidy3d/plugins/invdes/penalty.py b/tidy3d/plugins/invdes/penalty.py index 1683f577fd..f48eb3daef 100644 --- a/tidy3d/plugins/invdes/penalty.py +++ b/tidy3d/plugins/invdes/penalty.py @@ -2,7 +2,6 @@ from __future__ import annotations import abc -import typing import autograd.numpy as anp import pydantic.v1 as pd @@ -98,4 +97,4 @@ def evaluate(self, x: anp.ndarray, pixel_size: float) -> float: return self.weight * penalty_unweighted -PenaltyType = typing.Union[ErosionDilationPenalty] +PenaltyType = ErosionDilationPenalty diff --git a/tidy3d/plugins/invdes/region.py b/tidy3d/plugins/invdes/region.py index 17fa5009c4..9f97f07cf7 100644 --- a/tidy3d/plugins/invdes/region.py +++ b/tidy3d/plugins/invdes/region.py @@ -2,8 +2,8 @@ from __future__ import annotations import abc -import typing import warnings +from typing import Literal import autograd.numpy as anp import numpy as np @@ -167,7 +167,7 @@ class TopologyDesignRegion(DesignRegion): "inside of the penalties directly through the ``.weight`` field.", ) - override_structure_dl: typing.Union[pd.PositiveFloat, typing.Literal[False]] = pd.Field( + override_structure_dl: pd.PositiveFloat | Literal[False] = pd.Field( None, title="Design Region Override Structure", description="Defines grid size when adding an ``override_structure`` to the " @@ -366,4 +366,4 @@ def evaluate_penalty(self, penalty: PenaltyType, material_density: anp.ndarray) return penalty.evaluate(x=material_density, pixel_size=self.pixel_size) -DesignRegionType = typing.Union[TopologyDesignRegion] +DesignRegionType = TopologyDesignRegion diff --git a/tidy3d/plugins/invdes/result.py b/tidy3d/plugins/invdes/result.py index 3a000ab80c..3f135a5b45 100644 --- a/tidy3d/plugins/invdes/result.py +++ b/tidy3d/plugins/invdes/result.py @@ -137,20 +137,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, list[td.Simulation]]: + def get_sim(self, index: int = -1) -> 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, list[td.SimulationData]]: + ) -> 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, list[td.Simulation]]: + def sim_last(self) -> 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 c5060209cf..472778bff9 100644 --- a/tidy3d/plugins/invdes/transformation.py +++ b/tidy3d/plugins/invdes/transformation.py @@ -2,7 +2,6 @@ from __future__ import annotations import abc -import typing import autograd.numpy as anp import pydantic.v1 as pd @@ -82,4 +81,4 @@ def evaluate(self, spatial_data: anp.ndarray, design_region_dl: float) -> anp.nd return data_projected -TransformationType = typing.Union[FilterProject] +TransformationType = FilterProject diff --git a/tidy3d/plugins/invdes/validators.py b/tidy3d/plugins/invdes/validators.py index 07a522b42e..947b15889e 100644 --- a/tidy3d/plugins/invdes/validators.py +++ b/tidy3d/plugins/invdes/validators.py @@ -33,7 +33,7 @@ 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: typing.Optional[int] = None + sim: td.Simulation, pixel_size: float, index: int | None = None ) -> None: """Check a pixel size compared to the simulation min wvl in material.""" if not sim.sources: diff --git a/tidy3d/plugins/klayout/drc/drc.py b/tidy3d/plugins/klayout/drc/drc.py index c219cff4ca..7c680b18f0 100644 --- a/tidy3d/plugins/klayout/drc/drc.py +++ b/tidy3d/plugins/klayout/drc/drc.py @@ -5,7 +5,6 @@ import re from pathlib import Path from subprocess import run -from typing import Union import pydantic.v1 as pd from pydantic.v1 import validator @@ -118,7 +117,7 @@ class DRCRunner(Tidy3dBaseModel): def run( self, - source: Union[Geometry, Structure, Simulation, Path], + source: Geometry | Structure | Simulation | Path, td_object_gds_savefile: Path = DEFAULT_GDSFILE, resultsfile: Path = DEFAULT_RESULTSFILE, **to_gds_file_kwargs, diff --git a/tidy3d/plugins/klayout/drc/results.py b/tidy3d/plugins/klayout/drc/results.py index f13713f986..cef3247619 100644 --- a/tidy3d/plugins/klayout/drc/results.py +++ b/tidy3d/plugins/klayout/drc/results.py @@ -5,7 +5,6 @@ import re import xml.etree.ElementTree as ET from pathlib import Path -from typing import Union import pydantic.v1 as pd @@ -154,7 +153,7 @@ def parse_polygons(value: str) -> MultiPolygonMarker: return MultiPolygonMarker(polygons=tuple(polygons)) -def parse_violation_value(value: str) -> Union[EdgeMarker, EdgePairMarker, MultiPolygonMarker]: +def parse_violation_value(value: str) -> EdgeMarker | EdgePairMarker | MultiPolygonMarker: """ Parse a violation value based on its type (edge, edge-pair, or polygon). @@ -211,7 +210,7 @@ class MultiPolygonMarker(Tidy3dBaseModel): ) -DRCMarker = Union[EdgeMarker, EdgePairMarker, MultiPolygonMarker] +DRCMarker = EdgeMarker | EdgePairMarker | MultiPolygonMarker class DRCViolation(Tidy3dBaseModel): @@ -290,7 +289,7 @@ def __str__(self) -> str: return summary @classmethod - def load(cls, resultsfile: Union[str, Path]) -> DRCResults: + def load(cls, resultsfile: str | Path) -> DRCResults: """Create a :class:`.DRCResults` instance from a results file. Parameters @@ -319,7 +318,7 @@ def load(cls, resultsfile: Union[str, Path]) -> DRCResults: return cls(violations_by_category=violations_from_file(resultsfile=resultsfile)) -def violations_from_file(resultsfile: Union[str, Path]) -> dict[str, DRCViolation]: +def violations_from_file(resultsfile: str | Path) -> dict[str, DRCViolation]: """Loads a KLayout DRC results file and returns the results as a dictionary of :class:`.DRCViolation` objects. Parameters diff --git a/tidy3d/plugins/klayout/util.py b/tidy3d/plugins/klayout/util.py index 5139d64fc0..83b532118b 100644 --- a/tidy3d/plugins/klayout/util.py +++ b/tidy3d/plugins/klayout/util.py @@ -1,12 +1,11 @@ from __future__ import annotations from shutil import which -from typing import Union import tidy3d as td -def check_installation(raise_error: bool = False) -> Union[str, None]: +def check_installation(raise_error: bool = False) -> str | None: """Checks if KLayout is installed and added to the system PATH. If it is, this returns the path to the executable. Otherwise, returns ``None``. Equivalent to $which("klayout") in the terminal. diff --git a/tidy3d/plugins/microwave/array_factor.py b/tidy3d/plugins/microwave/array_factor.py index b312649bb8..213b2c679f 100644 --- a/tidy3d/plugins/microwave/array_factor.py +++ b/tidy3d/plugins/microwave/array_factor.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Optional, Union import numpy as np import pydantic.v1 as pd @@ -33,7 +32,7 @@ class AbstractAntennaArrayCalculator(MicrowaveBaseModel, ABC): """Abstract base for phased array calculators.""" - taper: Union[RectangularTaper, RadialTaper] = pd.Field( + taper: RectangularTaper | RadialTaper = pd.Field( None, discriminator=TYPE_TAG_STR, title="Antenna Array Taper", @@ -173,7 +172,7 @@ def _try_to_expand_geometry( def _duplicate_or_expand_list_of_objects( self, objects: tuple[ - Union[Structure, MeshOverrideStructure, LayerRefinementSpec, LumpedElement], ... + Structure | MeshOverrideStructure | LayerRefinementSpec | LumpedElement, ... ], old_sim_bounds: Bound, new_sim_bounds: Bound, @@ -467,9 +466,9 @@ def make_antenna_array(self, simulation: Simulation) -> Simulation: @abstractmethod def array_factor( self, - theta: Union[float, ArrayLike], - phi: Union[float, ArrayLike], - frequency: Union[NonNegativeFloat, ArrayLike], + theta: float | ArrayLike, + phi: float | ArrayLike, + frequency: NonNegativeFloat | ArrayLike, ) -> ArrayLike: """ Compute the array factor for an antenna array. @@ -747,12 +746,10 @@ class RectangularAntennaArrayCalculator(AbstractAntennaArrayCalculator): description="Phase-shifts between antennas along x, y, and z directions.", ) - amp_multipliers: tuple[Optional[ArrayLike], Optional[ArrayLike], Optional[ArrayLike]] = ( - pd.Field( - (None, None, None), - title="Amplitude Multipliers", - description="Amplitude multipliers spatially distributed along x, y, and z directions.", - ) + amp_multipliers: tuple[ArrayLike | None, ArrayLike | None, ArrayLike | None] = pd.Field( + (None, None, None), + title="Amplitude Multipliers", + description="Amplitude multipliers spatially distributed along x, y, and z directions.", ) @pd.validator("amp_multipliers", pre=True, always=True) @@ -836,9 +833,9 @@ def _extend_dims(self) -> tuple[Axis, ...]: def array_factor( self, - theta: Union[float, ArrayLike], - phi: Union[float, ArrayLike], - frequency: Union[NonNegativeFloat, ArrayLike], + theta: float | ArrayLike, + phi: float | ArrayLike, + frequency: NonNegativeFloat | ArrayLike, medium: MediumType3D = Undefined, ) -> ArrayLike: """ @@ -1151,15 +1148,15 @@ def _get_weights_continuous(self, p_vec: ArrayLike) -> ArrayLike: # define a list of acceptable rectangular windows -RectangularWindowType = Union[ - HammingWindow, - HannWindow, - KaiserWindow, - TaylorWindow, - ChebWindow, - BlackmanWindow, - BlackmanHarrisWindow, -] +RectangularWindowType = ( + HammingWindow + | HannWindow + | KaiserWindow + | TaylorWindow + | ChebWindow + | BlackmanWindow + | BlackmanHarrisWindow +) class AbstractTaper(MicrowaveBaseModel, ABC): @@ -1182,21 +1179,21 @@ def amp_multipliers( class RectangularTaper(AbstractTaper): """Class for rectangular taper.""" - window_x: Optional[RectangularWindowType] = pd.Field( + window_x: RectangularWindowType | None = pd.Field( None, title="X Axis Window", description="Window type used to taper array antenna along x axis.", discriminator=TYPE_TAG_STR, ) - window_y: Optional[RectangularWindowType] = pd.Field( + window_y: RectangularWindowType | None = pd.Field( None, title="Y Axis Window", description="Window type used to taper array antenna along y axis.", discriminator=TYPE_TAG_STR, ) - window_z: Optional[RectangularWindowType] = pd.Field( + window_z: RectangularWindowType | None = pd.Field( None, title="Z Axis Window", description="Window type used to taper array antenna along z axis.", diff --git a/tidy3d/plugins/microwave/lobe_measurer.py b/tidy3d/plugins/microwave/lobe_measurer.py index 18985ac7c9..24ba221e49 100644 --- a/tidy3d/plugins/microwave/lobe_measurer.py +++ b/tidy3d/plugins/microwave/lobe_measurer.py @@ -3,7 +3,6 @@ from __future__ import annotations from math import isclose, isnan -from typing import Optional import numpy as np import pydantic.v1 as pd @@ -297,7 +296,7 @@ def side_lobes(self) -> DataFrame: return side_lobes @property - def sidelobe_level(self) -> Optional[float]: + def sidelobe_level(self) -> float | None: """The sidelobe level returned on a linear scale.""" if self.side_lobes.empty: return None diff --git a/tidy3d/plugins/resonance/resonance.py b/tidy3d/plugins/resonance/resonance.py index 0f49c0f0c9..b0141d650d 100644 --- a/tidy3d/plugins/resonance/resonance.py +++ b/tidy3d/plugins/resonance/resonance.py @@ -3,7 +3,6 @@ from __future__ import annotations from functools import partial -from typing import Union import numpy as np import xarray as xr @@ -111,7 +110,7 @@ def _check_freq_window(cls, val): ) return val - def run(self, signals: Union[FieldTimeData, tuple[FieldTimeData, ...]]) -> xr.Dataset: + def run(self, signals: 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 @@ -262,7 +261,7 @@ def _aggregate_field_time_comps( ) def _aggregate_field_time( - self, signals: Union[FieldTimeData, tuple[FieldTimeData, ...]] + self, signals: FieldTimeData | tuple[FieldTimeData, ...] ) -> ScalarFieldTimeDataArray: """Aggregates several :class:`.FieldTimeData` into a single :class:`.ScalarFieldTimeDataArray`.""" diff --git a/tidy3d/plugins/smatrix/analysis/antenna.py b/tidy3d/plugins/smatrix/analysis/antenna.py index bc1701bd4d..605be534a2 100644 --- a/tidy3d/plugins/smatrix/analysis/antenna.py +++ b/tidy3d/plugins/smatrix/analysis/antenna.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Optional - import numpy as np from tidy3d.components.microwave.data.monitor_data import AntennaMetricsData @@ -13,8 +11,8 @@ def get_antenna_metrics_data( terminal_component_modeler_data: TerminalComponentModelerData, - port_amplitudes: Optional[dict[NetworkIndex, complex]] = None, - monitor_name: Optional[str] = None, + port_amplitudes: dict[NetworkIndex, complex] | None = None, + monitor_name: str | None = None, ) -> AntennaMetricsData: """Calculate antenna parameters using superposition of fields from multiple port excitations. diff --git a/tidy3d/plugins/smatrix/analysis/terminal.py b/tidy3d/plugins/smatrix/analysis/terminal.py index 54dbab3086..76c4a3d0e6 100644 --- a/tidy3d/plugins/smatrix/analysis/terminal.py +++ b/tidy3d/plugins/smatrix/analysis/terminal.py @@ -196,7 +196,7 @@ def _compute_port_voltages_currents( I_matrix = V_matrix.copy(deep=True) for network_index in network_indices: - port, mode_index = modeler.network_dict[network_index] + port, _mode_index = modeler.network_dict[network_index] V_out, I_out = compute_port_VI(port, sim_data) indexer = {"port": network_index} V_matrix = V_matrix._with_updated_data(data=V_out.data, coords=indexer) diff --git a/tidy3d/plugins/smatrix/component_modelers/base.py b/tidy3d/plugins/smatrix/component_modelers/base.py index 3bc63233b3..666ef81a38 100644 --- a/tidy3d/plugins/smatrix/component_modelers/base.py +++ b/tidy3d/plugins/smatrix/component_modelers/base.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Literal, Optional, Union +from typing import TYPE_CHECKING, Literal import pydantic.v1 as pd @@ -34,8 +34,8 @@ FWIDTH_FRAC = 1.0 / 10 DEFAULT_DATA_DIR = "." -IndexType = Union[MatrixIndex, NetworkIndex] -ElementType = Union[Element, NetworkElement] +IndexType = MatrixIndex | NetworkIndex +ElementType = Element | NetworkElement TaskNameFormat = Literal["RF", "PF"] @@ -53,7 +53,7 @@ class AbstractComponentModeler(ABC, Tidy3dBaseModel): description="Simulation describing the device without any sources present.", ) - ports: tuple[Union[Port, TerminalPortType], ...] = pd.Field( + ports: tuple[Port | TerminalPortType, ...] = pd.Field( (), title="Ports", description="Collection of ports describing the scattering matrix elements. " @@ -78,7 +78,7 @@ class AbstractComponentModeler(ABC, Tidy3dBaseModel): "pulse spectrum which can have a nonzero DC component.", ) - run_only: Optional[tuple[IndexType, ...]] = pd.Field( + run_only: tuple[IndexType, ...] | None = pd.Field( None, title="Run Only", description="Set of matrix indices that define the simulations to run. " @@ -95,7 +95,7 @@ class AbstractComponentModeler(ABC, Tidy3dBaseModel): "matrix element. If all elements of a given column of the scattering matrix are defined " "by ``element_mappings``, the simulation corresponding to this column is skipped automatically.", ) - custom_source_time: Optional[SourceTimeType] = pd.Field( + custom_source_time: SourceTimeType | None = pd.Field( None, title="Custom Source Time", description="If provided, this will be used as specification of the source time-dependence in simulations. " @@ -183,7 +183,7 @@ def _freqs_in_custom_source_time(cls, val, values): @staticmethod def get_task_name( - port: Port, mode_index: Optional[int] = None, format: Optional[TaskNameFormat] = "RF" + port: Port, mode_index: int | None = None, format: TaskNameFormat | None = "RF" ) -> str: """Generates a standardized task name from a port object. @@ -195,7 +195,7 @@ def get_task_name( ---------- port : Port The port object from which to derive the base name. - mode_index : Optional[int], optional + mode_index : int, optional If provided, this index is appended to the port name (e.g., 'port_1@1'), overriding the `format` argument. Defaults to None. format : TaskNameFormat, optional @@ -293,7 +293,7 @@ def matrix_indices_run_sim(self) -> tuple[IndexType, ...]: return source_indices_needed - def _shift_value_signed(self, port: Union[Port, WavePort]) -> float: + def _shift_value_signed(self, port: Port | WavePort) -> float: """How far (signed) to shift the source from the monitor.""" return _shift_value_signed( @@ -312,13 +312,13 @@ def run( path_dir: str = DEFAULT_DATA_DIR, *, folder_name: str = "default", - callback_url: Optional[str] = None, + callback_url: str | None = None, verbose: bool = True, - solver_version: Optional[str] = None, - pay_type: Union[PayType, str] = "AUTO", - priority: Optional[int] = None, + solver_version: str | None = None, + pay_type: PayType | str = "AUTO", + priority: int | None = None, local_gradient: bool = False, - max_num_adjoint_per_fwd: Optional[int] = None, + max_num_adjoint_per_fwd: int | None = None, ): log.warning( "'ComponentModeler.run()' is deprecated and will be removed in a future release. " diff --git a/tidy3d/plugins/smatrix/component_modelers/modal.py b/tidy3d/plugins/smatrix/component_modelers/modal.py index 699d7e09f2..b5c603d3c0 100644 --- a/tidy3d/plugins/smatrix/component_modelers/modal.py +++ b/tidy3d/plugins/smatrix/component_modelers/modal.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional - import autograd.numpy as np import pydantic.v1 as pd @@ -41,7 +39,7 @@ class ModalComponentModeler(AbstractComponentModeler): "For each input mode, one simulation will be run with a modal source.", ) - run_only: Optional[tuple[MatrixIndex, ...]] = pd.Field( + run_only: tuple[MatrixIndex, ...] | None = pd.Field( None, title="Run Only", description="Set of matrix indices that define the simulations to run. " @@ -245,9 +243,9 @@ def shift_port(self, port: Port) -> Port: @add_ax_if_none def plot_sim( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, ) -> Ax: """Plots the simulation with all sources added for troubleshooting. @@ -283,9 +281,9 @@ def plot_sim( @add_ax_if_none def plot_sim_eps( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **kwargs, ) -> Ax: diff --git a/tidy3d/plugins/smatrix/component_modelers/terminal.py b/tidy3d/plugins/smatrix/component_modelers/terminal.py index 20d1420270..1c1a56cd19 100644 --- a/tidy3d/plugins/smatrix/component_modelers/terminal.py +++ b/tidy3d/plugins/smatrix/component_modelers/terminal.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional, Union - import numpy as np import pydantic.v1 as pd @@ -85,7 +83,7 @@ class TerminalComponentModeler(AbstractComponentModeler, MicrowaveBaseModel): "For each port, one simulation will be run with a source that is associated with the port.", ) - run_only: Optional[tuple[NetworkIndex, ...]] = pd.Field( + run_only: tuple[NetworkIndex, ...] | None = pd.Field( None, title="Run Only", description="Set of matrix indices that define the simulations to run. " @@ -127,7 +125,7 @@ class TerminalComponentModeler(AbstractComponentModeler, MicrowaveBaseModel): description="Whether to compute scattering parameters using the 'pseudo' or 'power' wave definitions.", ) - low_freq_smoothing: Optional[ModelerLowFrequencySmoothingSpec] = pd.Field( + low_freq_smoothing: ModelerLowFrequencySmoothingSpec | None = pd.Field( DEFAULT_LOW_FREQUENCY_SMOOTHING_SPEC, title="Low Frequency Smoothing", description="The low frequency smoothing parameters for the terminal component simulation.", @@ -157,9 +155,9 @@ def _sim_with_sources(self) -> Simulation: @add_ax_if_none def plot_sim( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **kwargs, ) -> Ax: @@ -192,9 +190,9 @@ def plot_sim( @add_ax_if_none def plot_sim_eps( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **kwargs, ) -> Ax: @@ -225,7 +223,7 @@ def plot_sim_eps( return self._sim_with_sources.plot_eps(x=x, y=y, z=z, ax=ax, **kwargs) @staticmethod - def network_index(port: TerminalPortType, mode_index: Optional[int] = None) -> NetworkIndex: + def network_index(port: TerminalPortType, mode_index: int | None = None) -> NetworkIndex: """Converts the port, and a ``mode_index`` when the port is a :class:`.WavePort`, to a unique string specifier. Parameters @@ -471,7 +469,7 @@ def _validate_radiation_monitors(cls, val, values): @staticmethod def _check_grid_size_at_ports( - simulation: Simulation, ports: list[Union[LumpedPort, CoaxialLumpedPort]] + simulation: Simulation, ports: list[LumpedPort | CoaxialLumpedPort] ): """Raises :class:`.SetupError` if the grid is too coarse at port locations""" yee_grid = simulation.grid.yee diff --git a/tidy3d/plugins/smatrix/component_modelers/types.py b/tidy3d/plugins/smatrix/component_modelers/types.py index 4500ab171a..65f9ee94fc 100644 --- a/tidy3d/plugins/smatrix/component_modelers/types.py +++ b/tidy3d/plugins/smatrix/component_modelers/types.py @@ -1,8 +1,6 @@ from __future__ import annotations -from typing import Union - from .modal import ModalComponentModeler from .terminal import TerminalComponentModeler -ComponentModelerType = Union[ModalComponentModeler, TerminalComponentModeler] +ComponentModelerType = ModalComponentModeler | TerminalComponentModeler diff --git a/tidy3d/plugins/smatrix/data/terminal.py b/tidy3d/plugins/smatrix/data/terminal.py index 7e92e26967..8f749edaf2 100644 --- a/tidy3d/plugins/smatrix/data/terminal.py +++ b/tidy3d/plugins/smatrix/data/terminal.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional, Union - import numpy as np import pydantic.v1 as pd @@ -38,7 +36,7 @@ class MicrowaveSMatrixData(MicrowaveBaseModel): """Stores the computed S-matrix and reference impedances for the terminal ports.""" - port_reference_impedances: Optional[PortDataArray] = pd.Field( + port_reference_impedances: PortDataArray | None = pd.Field( None, title="Port Reference Impedances", description="Reference impedance for each port used in the S-parameter calculation. This is optional and may not be present if not specified or computed.", @@ -87,8 +85,8 @@ class TerminalComponentModelerData(AbstractComponentModelerData, MicrowaveBaseMo def smatrix( self, - assume_ideal_excitation: Optional[bool] = None, - s_param_def: Optional[SParamDef] = None, + assume_ideal_excitation: bool | None = None, + s_param_def: SParamDef | None = None, ) -> MicrowaveSMatrixData: """Computes and returns the S-matrix and port reference impedances. @@ -238,7 +236,7 @@ def _monitor_data_at_port_amplitude( self, port: TerminalPortType, monitor_name: str, - a_port: Union[FreqDataArray, complex], + a_port: FreqDataArray | complex, a_raw_port: FreqDataArray, ) -> MonitorData: """Normalize monitor data to a desired complex amplitude at a specific port. @@ -276,8 +274,8 @@ def _monitor_data_at_port_amplitude( def get_antenna_metrics_data( self, - port_amplitudes: Optional[dict[NetworkIndex, complex]] = None, - monitor_name: Optional[str] = None, + port_amplitudes: dict[NetworkIndex, complex] | None = None, + monitor_name: str | None = None, ) -> AntennaMetricsData: """Calculate antenna parameters using superposition of fields from multiple port excitations. @@ -341,7 +339,7 @@ def port_reference_impedances(self) -> PortDataArray: def compute_wave_amplitudes_at_each_port( self, sim_data: SimulationData, - port_reference_impedances: Optional[PortDataArray] = None, + port_reference_impedances: PortDataArray | None = None, s_param_def: SParamDef = "pseudo", ) -> tuple[PortDataArray, PortDataArray]: """Compute the incident and reflected amplitudes at each port. @@ -381,7 +379,7 @@ def compute_wave_amplitudes_at_each_port( def compute_power_wave_amplitudes_at_each_port( self, sim_data: SimulationData, - port_reference_impedances: Optional[PortDataArray] = None, + port_reference_impedances: PortDataArray | None = None, ) -> tuple[PortDataArray, PortDataArray]: """Compute the incident and reflected power wave amplitudes at each port. The computed amplitudes have not been normalized. @@ -405,8 +403,8 @@ def compute_power_wave_amplitudes_at_each_port( def s_to_z( self, - reference: Union[complex, PortDataArray], - assume_ideal_excitation: Optional[bool] = None, + reference: complex | PortDataArray, + assume_ideal_excitation: bool | None = None, s_param_def: SParamDef = "pseudo", ) -> TerminalPortDataArray: """Converts the S-matrix to the Z-matrix using a specified reference impedance. diff --git a/tidy3d/plugins/smatrix/data/types.py b/tidy3d/plugins/smatrix/data/types.py index cdb6f92fa0..784e9691f9 100644 --- a/tidy3d/plugins/smatrix/data/types.py +++ b/tidy3d/plugins/smatrix/data/types.py @@ -1,8 +1,6 @@ from __future__ import annotations -from typing import Union - from tidy3d.plugins.smatrix.data.modal import ModalComponentModelerData from tidy3d.plugins.smatrix.data.terminal import TerminalComponentModelerData -ComponentModelerDataType = Union[TerminalComponentModelerData, ModalComponentModelerData] +ComponentModelerDataType = TerminalComponentModelerData | ModalComponentModelerData diff --git a/tidy3d/plugins/smatrix/ports/base_lumped.py b/tidy3d/plugins/smatrix/ports/base_lumped.py index 2871da304a..9d306fc424 100644 --- a/tidy3d/plugins/smatrix/ports/base_lumped.py +++ b/tidy3d/plugins/smatrix/ports/base_lumped.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import abstractmethod -from typing import Optional import pydantic.v1 as pd @@ -31,7 +30,7 @@ class AbstractLumpedPort(AbstractTerminalPort): units=OHM, ) - num_grid_cells: Optional[pd.PositiveInt] = pd.Field( + num_grid_cells: pd.PositiveInt | None = pd.Field( DEFAULT_PORT_NUM_CELLS, title="Port grid cells", description="Number of mesh grid cells associated with the port along each direction, " @@ -67,23 +66,23 @@ def snapped_center(self, grid: Grid) -> Coordinate: @cached_property @abstractmethod - def to_load(self, snap_center: Optional[float] = None) -> LumpedElementType: + def to_load(self, snap_center: float | None = None) -> LumpedElementType: """Create a load from the lumped port.""" @abstractmethod def to_voltage_monitor( - self, freqs: FreqArray, snap_center: Optional[float] = None + self, freqs: FreqArray, snap_center: float | None = None ) -> FieldMonitor: """Field monitor to compute port voltage.""" @abstractmethod def to_current_monitor( - self, freqs: FreqArray, snap_center: Optional[float] = None + self, freqs: FreqArray, snap_center: float | None = None ) -> FieldMonitor: """Field monitor to compute port current.""" def to_monitors( - self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None + self, freqs: FreqArray, snap_center: float | None = 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 086ac36b91..d6a4fdc0d4 100644 --- a/tidy3d/plugins/smatrix/ports/base_terminal.py +++ b/tidy3d/plugins/smatrix/ports/base_terminal.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Optional, Union import pydantic.v1 as pd @@ -39,13 +38,13 @@ def injection_axis(self): @abstractmethod def to_source( - self, source_time: GaussianPulse, snap_center: Optional[float] = None, grid: Grid = None + self, source_time: GaussianPulse, snap_center: float | None = None, grid: Grid = None ) -> Source: """Create a current source from a terminal-based port.""" def to_field_monitors( - self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None - ) -> Union[list[FieldMonitor], list[ModeMonitor]]: + self, freqs: FreqArray, snap_center: float | None = None, grid: Grid = None + ) -> list[FieldMonitor] | list[ModeMonitor]: """DEPRECATED: Monitors used to compute the port voltage and current.""" log.warning( "'to_field_monitors' method name is deprecated and will be removed in the future. Please use " @@ -55,8 +54,8 @@ def to_field_monitors( @abstractmethod def to_monitors( - self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None - ) -> Union[list[FieldMonitor], list[ModeMonitor]]: + self, freqs: FreqArray, snap_center: float | None = None, grid: Grid = None + ) -> list[FieldMonitor] | list[ModeMonitor]: """Monitors used to compute the port voltage and current.""" @abstractmethod diff --git a/tidy3d/plugins/smatrix/ports/coaxial_lumped.py b/tidy3d/plugins/smatrix/ports/coaxial_lumped.py index ed93899fe3..3402a95337 100644 --- a/tidy3d/plugins/smatrix/ports/coaxial_lumped.py +++ b/tidy3d/plugins/smatrix/ports/coaxial_lumped.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional - import numpy as np import pydantic.v1 as pd @@ -112,7 +110,7 @@ def _ensure_inner_diameter_is_smaller(cls, val, values): return val def to_source( - self, source_time: GaussianPulse, snap_center: Optional[float] = None, grid: Grid = None + self, source_time: GaussianPulse, snap_center: float | None = None, grid: Grid = None ) -> CustomCurrentSource: """Create a current source from the lumped port.""" # Discretized source amps are manually zeroed out later if they @@ -201,7 +199,7 @@ def compute_coax_current(rin, rout, x, y): current_dataset=dataset_E, ) - def to_load(self, snap_center: Optional[float] = None) -> CoaxialLumpedResistor: + def to_load(self, snap_center: float | None = 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. @@ -220,7 +218,7 @@ def to_load(self, snap_center: Optional[float] = None) -> CoaxialLumpedResistor: ) def to_voltage_monitor( - self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None + self, freqs: FreqArray, snap_center: float | None = None, grid: Grid = None ) -> FieldMonitor: """Field monitor to compute port voltage.""" center = list(self.center) @@ -244,7 +242,7 @@ def to_voltage_monitor( ) def to_current_monitor( - self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None + self, freqs: FreqArray, snap_center: float | None = None, grid: Grid = None ) -> FieldMonitor: """Field monitor to compute port current.""" center = list(self.center) diff --git a/tidy3d/plugins/smatrix/ports/rectangular_lumped.py b/tidy3d/plugins/smatrix/ports/rectangular_lumped.py index b81974dbd8..76e0d6f871 100644 --- a/tidy3d/plugins/smatrix/ports/rectangular_lumped.py +++ b/tidy3d/plugins/smatrix/ports/rectangular_lumped.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional - import numpy as np import pydantic.v1 as pd @@ -98,7 +96,7 @@ def current_axis(self) -> Axis: return 3 - self.injection_axis - self.voltage_axis def to_source( - self, source_time: GaussianPulse, snap_center: Optional[float] = None, grid: Grid = None + self, source_time: GaussianPulse, snap_center: float | None = None, grid: Grid = None ) -> UniformCurrentSource: """Create a current source from the lumped port.""" if grid: @@ -125,7 +123,7 @@ def to_source( confine_to_bounds=True, ) - def to_load(self, snap_center: Optional[float] = None) -> LumpedResistor: + def to_load(self, snap_center: float | None = 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 @@ -147,7 +145,7 @@ def to_load(self, snap_center: Optional[float] = None) -> LumpedResistor: ) def to_voltage_monitor( - self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None + self, freqs: FreqArray, snap_center: float | None = None, grid: Grid = None ) -> FieldMonitor: """Field monitor to compute port voltage.""" if grid: @@ -175,7 +173,7 @@ def to_voltage_monitor( ) def to_current_monitor( - self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None + self, freqs: FreqArray, snap_center: float | None = None, grid: Grid = None ) -> FieldMonitor: """Field monitor to compute port current.""" if grid: diff --git a/tidy3d/plugins/smatrix/ports/types.py b/tidy3d/plugins/smatrix/ports/types.py index 3d2dfb0774..f2e30a0bbb 100644 --- a/tidy3d/plugins/smatrix/ports/types.py +++ b/tidy3d/plugins/smatrix/ports/types.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Union - from tidy3d.components.data.data_array import ( CurrentFreqDataArray, CurrentFreqModeDataArray, @@ -12,7 +10,7 @@ from tidy3d.plugins.smatrix.ports.rectangular_lumped import LumpedPort from tidy3d.plugins.smatrix.ports.wave import WavePort -LumpedPortType = Union[LumpedPort, CoaxialLumpedPort] -TerminalPortType = Union[LumpedPortType, WavePort] -PortVoltageType = Union[VoltageFreqDataArray, VoltageFreqModeDataArray] -PortCurrentType = Union[CurrentFreqDataArray, CurrentFreqModeDataArray] +LumpedPortType = LumpedPort | CoaxialLumpedPort +TerminalPortType = LumpedPortType | WavePort +PortVoltageType = VoltageFreqDataArray | VoltageFreqModeDataArray +PortCurrentType = CurrentFreqDataArray | CurrentFreqModeDataArray diff --git a/tidy3d/plugins/smatrix/ports/wave.py b/tidy3d/plugins/smatrix/ports/wave.py index 890c906446..b5e1598968 100644 --- a/tidy3d/plugins/smatrix/ports/wave.py +++ b/tidy3d/plugins/smatrix/ports/wave.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Optional, Union - import numpy as np import pydantic.v1 as pd @@ -62,19 +60,19 @@ class WavePort(AbstractTerminalPort, Box): "``num_modes`` in the solver will be set to ``mode_index + 1``.", ) - voltage_integral: Optional[VoltageIntegralType] = pd.Field( + voltage_integral: VoltageIntegralType | None = pd.Field( None, title="Voltage Integral", description="Definition of voltage integral used to compute voltage and the characteristic impedance.", ) - current_integral: Optional[CurrentIntegralType] = pd.Field( + current_integral: CurrentIntegralType | None = pd.Field( None, title="Current Integral", description="Definition of current integral used to compute current and the characteristic impedance.", ) - num_grid_cells: Optional[int] = pd.Field( + num_grid_cells: int | None = pd.Field( DEFAULT_WAVE_PORT_NUM_CELLS, ge=MIN_WAVE_PORT_NUM_CELLS, title="Number of Grid Cells", @@ -89,13 +87,13 @@ class WavePort(AbstractTerminalPort, Box): description="Use conjugated or non-conjugated dot product for mode decomposition.", ) - frame: Optional[PECFrame] = pd.Field( + frame: PECFrame | None = pd.Field( DEFAULT_WAVE_PORT_FRAME, title="Source Frame", description="Add a thin frame around the source during FDTD run for an improved injection.", ) - absorber: Union[bool, ABCBoundary, ModeABCBoundary] = pd.Field( + absorber: bool | ABCBoundary | ModeABCBoundary = pd.Field( True, title="Absorber", description="Place a mode absorber in the port. If ``True``, an automatically generated mode absorber is placed in the port. " @@ -150,9 +148,7 @@ 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: Optional[float] = None - ) -> ModeSource: + def to_source(self, source_time: GaussianPulse, snap_center: float | None = None) -> ModeSource: """Create a mode source from the wave port.""" center = list(self.center) if snap_center: @@ -169,7 +165,7 @@ def to_source( ) def to_monitors( - self, freqs: FreqArray, snap_center: Optional[float] = None, grid: Grid = None + self, freqs: FreqArray, snap_center: float | None = None, grid: Grid = None ) -> list[ModeMonitor]: """The wave port uses a :class:`.ModeMonitor` to compute the characteristic impedance and the port voltages and currents.""" @@ -201,7 +197,7 @@ def to_mode_solver(self, simulation: Simulation, freqs: FreqArray) -> ModeSolver return mode_solver def to_absorber( - self, snap_center: Optional[float] = None, freq_spec: Optional[pd.NonNegativeFloat] = None + self, snap_center: float | None = None, freq_spec: pd.NonNegativeFloat | None = None ) -> InternalAbsorber: """Create an internal absorber from the wave port.""" center = list(self.center) @@ -249,9 +245,7 @@ def compute_current(self, sim_data: SimulationData) -> FreqDataArray: sign = -1.0 return sign * current_coeffs * (fwd_amps - bwd_amps) - def compute_port_impedance( - self, sim_mode_data: Union[SimulationData, ModeData] - ) -> FreqModeDataArray: + def compute_port_impedance(self, sim_mode_data: SimulationData | ModeData) -> FreqModeDataArray: """Helper to compute impedance of port. The port impedance is computed from the transmission line mode, which should be TEM or at least quasi-TEM.""" impedance_calc = ImpedanceCalculator( diff --git a/tidy3d/plugins/smatrix/utils.py b/tidy3d/plugins/smatrix/utils.py index e53f469508..3f28d013e5 100644 --- a/tidy3d/plugins/smatrix/utils.py +++ b/tidy3d/plugins/smatrix/utils.py @@ -7,8 +7,6 @@ from __future__ import annotations -from typing import Union - import numpy as np from tidy3d.components.data.data_array import ( @@ -216,7 +214,7 @@ def compute_power_delivered_by_port( def s_to_z( s_matrix: TerminalPortDataArray, - reference: Union[complex, PortDataArray], + reference: complex | PortDataArray, s_param_def: SParamDef = "pseudo", ) -> DataArray: """Get the impedance matrix given the scattering matrix and a reference impedance. diff --git a/tidy3d/plugins/waveguide/rectangular_dielectric.py b/tidy3d/plugins/waveguide/rectangular_dielectric.py index 73c4f9b88e..e49c9bfeb6 100644 --- a/tidy3d/plugins/waveguide/rectangular_dielectric.py +++ b/tidy3d/plugins/waveguide/rectangular_dielectric.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Annotated, Any, Literal, Optional, Union +from typing import Annotated, Any, Literal import numpy import pydantic.v1 as pydantic @@ -44,14 +44,14 @@ class RectangularDielectric(Tidy3dBaseModel): - Coupled waveguides """ - wavelength: Union[float, ArrayFloat1D] = pydantic.Field( + wavelength: float | ArrayFloat1D = pydantic.Field( ..., title="Wavelength", description="Wavelength(s) at which to calculate modes (in μm).", units=MICROMETER, ) - core_width: Union[Size1D, ArrayFloat1D] = pydantic.Field( + core_width: Size1D | ArrayFloat1D = pydantic.Field( ..., title="Core width", description="Core width at the top of the waveguide. If set to an array, defines " @@ -73,14 +73,14 @@ class RectangularDielectric(Tidy3dBaseModel): discriminator=TYPE_TAG_STR, ) - clad_medium: Union[AnnotatedMedium, tuple[AnnotatedMedium, ...]] = pydantic.Field( + clad_medium: 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: AnnotatedMedium | tuple[AnnotatedMedium, ...] = pydantic.Field( None, title="Box Medium", description="Medium associated with the lower cladding layer. A sequence of mediums can " @@ -94,7 +94,7 @@ class RectangularDielectric(Tidy3dBaseModel): units=MICROMETER, ) - clad_thickness: Union[Size1D, ArrayFloat1D] = pydantic.Field( + clad_thickness: Size1D | ArrayFloat1D = pydantic.Field( None, title="Clad Thickness", description="Domain size above the core layer. An array can be used to define a layered " @@ -102,7 +102,7 @@ class RectangularDielectric(Tidy3dBaseModel): units=MICROMETER, ) - box_thickness: Union[Size1D, ArrayFloat1D] = pydantic.Field( + box_thickness: Size1D | ArrayFloat1D = pydantic.Field( None, title="Box Thickness", description="Domain size below the core layer. An array can be used to define a layered " @@ -126,7 +126,7 @@ class RectangularDielectric(Tidy3dBaseModel): units=RADIAN, ) - gap: Union[float, ArrayFloat1D] = pydantic.Field( + gap: float | ArrayFloat1D = pydantic.Field( 0.0, title="Gap", description="Distance between adjacent waveguides, measured at the top core edges. " @@ -816,12 +816,12 @@ def mode_area(self) -> FreqModeDataArray: def plot( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, - source_alpha: Optional[float] = None, - monitor_alpha: Optional[float] = None, + source_alpha: float | None = None, + monitor_alpha: float | None = None, **patch_kwargs, ) -> Ax: """Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate. @@ -858,13 +858,13 @@ def plot( def plot_eps( self, - 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, + x: float | None = None, + y: float | None = None, + z: float | None = None, + freq: float | None = None, + alpha: float | None = None, + source_alpha: float | None = None, + monitor_alpha: float | None = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's components on a plane defined by one nonzero x,y,z coordinate. @@ -909,9 +909,9 @@ def plot_eps( def plot_structures( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, ) -> Ax: """Plot each of simulation's structures on a plane defined by one nonzero x,y,z coordinate. @@ -941,11 +941,11 @@ def plot_structures( def plot_structures_eps( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - freq: Optional[float] = None, - alpha: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, + freq: float | None = None, + alpha: float | None = None, cbar: bool = True, reverse: bool = False, ax: Ax = None, @@ -993,9 +993,9 @@ def plot_structures_eps( def plot_grid( self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, + x: float | None = None, + y: float | None = None, + z: float | None = None, ax: Ax = None, **kwargs, ) -> Ax: @@ -1095,10 +1095,10 @@ def plot_field( val: Literal["real", "imag", "abs"] = "real", eps_alpha: float = 0.2, robust: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, + vmin: float | None = None, + vmax: float | None = None, ax: Ax = None, - geometry_edges: Optional[str] = None, + geometry_edges: str | None = 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 bc7f017fab..b048f44cef 100644 --- a/tidy3d/updater.py +++ b/tidy3d/updater.py @@ -4,7 +4,7 @@ import functools import json -from typing import Callable, Optional +from collections.abc import Callable import pydantic.v1 as pd import yaml @@ -24,7 +24,7 @@ class Version(pd.BaseModel): minor: int @classmethod - def from_string(cls, string: Optional[str] = None) -> Version: + def from_string(cls, string: str | None = None) -> Version: """Return Version from a version string.""" if string is None: return cls.from_string(string=__version__) diff --git a/tidy3d/web/api/asynchronous.py b/tidy3d/web/api/asynchronous.py index c5a346abdd..606d922286 100644 --- a/tidy3d/web/api/asynchronous.py +++ b/tidy3d/web/api/asynchronous.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Literal, Optional, Union +from typing import Literal from tidy3d.components.types.workflow import WorkflowType from tidy3d.log import log @@ -12,18 +12,18 @@ def run_async( - simulations: Union[dict[str, WorkflowType], tuple[WorkflowType], list[WorkflowType]], + simulations: dict[str, WorkflowType] | tuple[WorkflowType] | list[WorkflowType], folder_name: str = "default", path_dir: str = DEFAULT_DATA_DIR, - callback_url: Optional[str] = None, - num_workers: Optional[int] = None, + callback_url: str | None = None, + num_workers: int | None = None, verbose: bool = True, simulation_type: str = "tidy3d", - solver_version: Optional[str] = None, - parent_tasks: Optional[dict[str, list[str]]] = None, + solver_version: str | None = None, + parent_tasks: dict[str, list[str]] | None = None, reduce_simulation: Literal["auto", True, False] = "auto", - pay_type: Union[PayType, str] = PayType.AUTO, - priority: Optional[int] = None, + pay_type: PayType | str = PayType.AUTO, + priority: int | None = None, lazy: bool = False, ) -> BatchData: """Submits a set of Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] objects to server, diff --git a/tidy3d/web/api/autograd/autograd.py b/tidy3d/web/api/autograd/autograd.py index 9a29d71af6..18660a97ef 100644 --- a/tidy3d/web/api/autograd/autograd.py +++ b/tidy3d/web/api/autograd/autograd.py @@ -50,7 +50,7 @@ ) -def _resolve_local_gradient(value: typing.Optional[bool]) -> bool: +def _resolve_local_gradient(value: bool | None) -> bool: if value is not None: return bool(value) @@ -100,23 +100,23 @@ def is_valid_for_autograd_async(simulations: dict[str, td.Simulation]) -> bool: def run( simulation: WorkflowType, - task_name: typing.Optional[str] = None, + task_name: str | None = None, folder_name: str = "default", path: str = "simulation_data.hdf5", - callback_url: typing.Optional[str] = None, + callback_url: str | None = None, verbose: bool = True, - 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, + progress_callback_upload: typing.Callable[[float], None] | None = None, + progress_callback_download: typing.Callable[[float], None] | None = None, + solver_version: str | None = None, + worker_group: str | None = None, simulation_type: str = "tidy3d", - parent_tasks: typing.Optional[list[str]] = None, - local_gradient: typing.Optional[bool] = None, - max_num_adjoint_per_fwd: typing.Optional[int] = None, + parent_tasks: list[str] | None = None, + local_gradient: bool | None = None, + max_num_adjoint_per_fwd: int | None = None, reduce_simulation: typing.Literal["auto", True, False] = "auto", - pay_type: typing.Union[PayType, str] = PayType.AUTO, - priority: typing.Optional[int] = None, - lazy: typing.Optional[bool] = None, + pay_type: PayType | str = PayType.AUTO, + priority: int | None = None, + lazy: bool | None = None, ) -> WorkflowDataType: """ Submits a :class:`.Simulation` to server, starts running, monitors progress, downloads, @@ -285,21 +285,21 @@ def run( def run_async( - simulations: typing.Union[dict[str, td.Simulation], tuple[td.Simulation], list[td.Simulation]], + simulations: dict[str, td.Simulation] | tuple[td.Simulation] | list[td.Simulation], folder_name: str = "default", path_dir: str = DEFAULT_DATA_DIR, - callback_url: typing.Optional[str] = None, - num_workers: typing.Optional[int] = None, + callback_url: str | None = None, + num_workers: int | None = None, verbose: bool = True, simulation_type: str = "tidy3d", - solver_version: typing.Optional[str] = None, - parent_tasks: typing.Optional[dict[str, list[str]]] = None, - local_gradient: typing.Optional[bool] = None, - max_num_adjoint_per_fwd: typing.Optional[int] = None, + solver_version: str | None = None, + parent_tasks: dict[str, list[str]] | None = None, + local_gradient: bool | None = None, + max_num_adjoint_per_fwd: int | None = None, reduce_simulation: typing.Literal["auto", True, False] = "auto", - pay_type: typing.Union[PayType, str] = PayType.AUTO, - priority: typing.Optional[int] = None, - lazy: typing.Optional[bool] = None, + pay_type: PayType | str = PayType.AUTO, + priority: int | None = None, + lazy: bool | None = None, ) -> BatchData: """Submits a set of Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] objects to server, starts running, monitors progress, downloads, and loads results as a :class:`.BatchData` object. @@ -416,7 +416,7 @@ def _run( simulation: td.Simulation, task_name: str, local_gradient: bool = False, - max_num_adjoint_per_fwd: typing.Optional[int] = None, + max_num_adjoint_per_fwd: int | None = None, **run_kwargs, ) -> td.SimulationData: """User-facing ``web.run`` function, compatible with ``autograd`` differentiation.""" @@ -459,7 +459,7 @@ def _run( def _run_async( simulations: dict[str, td.Simulation], local_gradient: bool = False, - max_num_adjoint_per_fwd: typing.Optional[int] = None, + max_num_adjoint_per_fwd: int | None = None, **run_async_kwargs, ) -> dict[str, td.SimulationData]: """User-facing ``web.run_async`` function, compatible with ``autograd`` differentiation.""" diff --git a/tidy3d/web/api/autograd/utils.py b/tidy3d/web/api/autograd/utils.py index f58f2f3c34..aff97eeacd 100644 --- a/tidy3d/web/api/autograd/utils.py +++ b/tidy3d/web/api/autograd/utils.py @@ -1,8 +1,6 @@ # utility functions for autograd web API from __future__ import annotations -import typing - import numpy as np import tidy3d as td @@ -58,11 +56,11 @@ def E_to_D(fld_data: td.FieldData, eps_data: td.PermittivityData) -> td.FieldDat def multiply_field_data( - fld_1: td.FieldData, fld_2: typing.Union[td.FieldData, td.PermittivityData], fld_key: str + fld_1: td.FieldData, fld_2: td.FieldData | td.PermittivityData, fld_key: str ) -> td.FieldData: """Elementwise multiply two field data objects, writes data into ``fld_1`` copy.""" - def get_field_key(dim: str, fld_data: typing.Union[td.FieldData, td.PermittivityData]) -> str: + def get_field_key(dim: str, fld_data: td.FieldData | td.PermittivityData) -> str: """Get the key corresponding to the scalar field along this dimension.""" return f"{fld_key}{dim}" if isinstance(fld_data, td.FieldData) else f"eps_{dim}{dim}" diff --git a/tidy3d/web/api/connect_util.py b/tidy3d/web/api/connect_util.py index 5f10d3af3e..4ef9c9f333 100644 --- a/tidy3d/web/api/connect_util.py +++ b/tidy3d/web/api/connect_util.py @@ -4,7 +4,6 @@ import time from functools import wraps -from typing import Optional from requests import ReadTimeout from requests.exceptions import ConnectionError as ConnErr @@ -17,7 +16,7 @@ from tidy3d.web.common import REFRESH_TIME -def wait_for_connection(decorated_fn=None, wait_time_sec: Optional[float] = None): +def wait_for_connection(decorated_fn=None, wait_time_sec: float | None = None): """Causes function to ignore connection errors and retry for ``wait_time_sec`` secs.""" def decorator(web_fn, wait_time_sec=wait_time_sec): diff --git a/tidy3d/web/api/container.py b/tidy3d/web/api/container.py index ed9cb775cc..b375a8c210 100644 --- a/tidy3d/web/api/container.py +++ b/tidy3d/web/api/container.py @@ -8,7 +8,7 @@ from abc import ABC from collections.abc import Mapping from concurrent.futures import ThreadPoolExecutor -from typing import Literal, Optional, Union +from typing import Literal import pydantic.v1 as pd from rich.progress import BarColumn, Progress, TaskProgressColumn, TextColumn, TimeElapsedColumn @@ -256,9 +256,7 @@ def to_file(self, fname: str) -> None: self = self.updated_copy(task_id_cached=task_id_cached) super(Job, self).to_file(fname=fname) # noqa: UP008 - def run( - self, path: str = DEFAULT_DATA_PATH, priority: Optional[int] = None - ) -> WorkflowDataType: + def run(self, path: str = DEFAULT_DATA_PATH, priority: int | None = None) -> WorkflowDataType: """Run :class:`Job` all the way through and return data. Parameters @@ -333,7 +331,7 @@ def postprocess_status(self): ) return - def start(self, priority: Optional[int] = None) -> None: + def start(self, priority: int | None = None) -> None: """Start running a :class:`Job`. Parameters @@ -446,7 +444,7 @@ def estimate_cost(self, verbose: bool = True) -> float: """ return web.estimate_cost(self.task_id, verbose=verbose, solver_version=self.solver_version) - def postprocess_start(self, worker_group: Optional[str] = None, verbose: bool = True) -> None: + def postprocess_start(self, worker_group: str | None = None, verbose: bool = True) -> None: """ If the job is a modeler batch, checks if the run is complete and starts the postprocess phase. @@ -618,9 +616,9 @@ class Batch(WebContainer): * `Inverse taper edge coupler <../../notebooks/EdgeCoupler.html>`_ """ - simulations: Union[ - dict[TaskName, annotate_type(WorkflowType)], tuple[annotate_type(WorkflowType), ...] - ] = pd.Field( + simulations: ( + dict[TaskName, annotate_type(WorkflowType)] | tuple[annotate_type(WorkflowType), ...] + ) = pd.Field( ..., title="Simulations", description="Mapping of task names to Simulations to run as a batch.", @@ -663,7 +661,7 @@ class Batch(WebContainer): description="Collection of parent task ids for each job in batch, used internally only.", ) - num_workers: Optional[pd.PositiveInt] = pd.Field( + num_workers: pd.PositiveInt | None = pd.Field( DEFAULT_NUM_WORKERS, title="Number of Workers", description="Number of workers for multi-threading upload and download of batch. " @@ -704,7 +702,7 @@ class Batch(WebContainer): def run( self, path_dir: str = DEFAULT_DATA_DIR, - priority: Optional[int] = None, + priority: int | None = None, ) -> BatchData: """Upload and run each simulation in :class:`Batch`. @@ -858,7 +856,7 @@ def get_info(self) -> dict[TaskName, TaskInfo]: def start( self, - priority: Optional[int] = None, + priority: int | None = None, ) -> None: """Start running all tasks in the :class:`Batch`. @@ -897,7 +895,7 @@ def get_run_info(self) -> dict[TaskName, RunInfo]: run_info_dict[task_name] = run_info return run_info_dict - def postprocess_start(self, worker_group: Optional[str] = None, verbose: bool = True) -> None: + def postprocess_start(self, worker_group: str | None = None, verbose: bool = True) -> None: """ Start the postprocess phase for all applicable jobs in the batch. @@ -918,7 +916,7 @@ def monitor( download_on_success: bool = False, path_dir: str = DEFAULT_DATA_DIR, replace_existing: bool = False, - postprocess_worker_group: Optional[str] = None, + postprocess_worker_group: str | None = None, ) -> None: """ Monitor progress of each running task. @@ -943,7 +941,7 @@ def monitor( # ----- download scheduling --------------------------------------------------- downloads_started: set[str] = set() download_futures: dict[TaskId, concurrent.futures.Future] = {} - download_executor: Optional[ThreadPoolExecutor] = None + download_executor: ThreadPoolExecutor | None = None if download_on_success: self._check_path_dir(path_dir=path_dir) diff --git a/tidy3d/web/api/material_fitter.py b/tidy3d/web/api/material_fitter.py index f66dce6471..1a12c470cb 100644 --- a/tidy3d/web/api/material_fitter.py +++ b/tidy3d/web/api/material_fitter.py @@ -5,7 +5,6 @@ import os import tempfile from enum import Enum -from typing import Optional from uuid import uuid4 import numpy as np @@ -27,14 +26,14 @@ class ConstraintEnum(str, Enum): class FitterOptions(BaseModel): """Fitter Options.""" - num_poles: Optional[int] = 1 - num_tries: Optional[int] = 50 - tolerance_rms: Optional[float] = 1e-2 - min_wvl: Optional[float] = None - max_wvl: Optional[float] = None - bound_amp: Optional[float] = None - bound_eps_inf: Optional[float] = 1.0 - bound_f: Optional[float] = None + num_poles: int | None = 1 + num_tries: int | None = 50 + tolerance_rms: float | None = 1e-2 + min_wvl: float | None = None + max_wvl: float | None = None + bound_amp: float | None = None + bound_eps_inf: float | None = 1.0 + bound_f: float | None = None constraint: ConstraintEnum = ConstraintEnum.HARD nlopt_maxeval: int = 5000 diff --git a/tidy3d/web/api/material_libray.py b/tidy3d/web/api/material_libray.py index 98bfeaa0b2..606fe079db 100644 --- a/tidy3d/web/api/material_libray.py +++ b/tidy3d/web/api/material_libray.py @@ -4,7 +4,6 @@ import builtins import json -from typing import Optional from pydantic.v1 import Field, parse_obj_as, validator @@ -18,11 +17,11 @@ class MaterialLibray(Queryable, smart_union=True): id: str = Field(title="Material Library ID", description="Material Library ID") name: str = Field(title="Material Library Name", description="Material Library Name") - medium: Optional[MediumType] = Field(title="medium", description="medium", alias="calcResult") - medium_type: Optional[str] = Field( + medium: MediumType | None = Field(title="medium", description="medium", alias="calcResult") + medium_type: str | None = Field( title="medium type", description="medium type", alias="mediumType" ) - json_input: Optional[dict] = Field( + json_input: dict | None = Field( title="json input", description="original input", alias="jsonInput" ) diff --git a/tidy3d/web/api/mode.py b/tidy3d/web/api/mode.py index f69be5cd26..f7ceec5519 100644 --- a/tidy3d/web/api/mode.py +++ b/tidy3d/web/api/mode.py @@ -6,8 +6,9 @@ import pathlib import tempfile import time +from collections.abc import Callable from datetime import datetime -from typing import Callable, Literal, Optional, Union +from typing import Literal import pydantic.v1 as pydantic from botocore.exceptions import ClientError @@ -52,10 +53,10 @@ def run( folder_name: str = "Mode Solver", results_file: str = "mode_solver.hdf5", verbose: bool = True, - progress_callback_upload: Optional[Callable[[float], None]] = None, - progress_callback_download: Optional[Callable[[float], None]] = None, + progress_callback_upload: Callable[[float], None] | None = None, + progress_callback_download: Callable[[float], None] | None = None, reduce_simulation: Literal["auto", True, False] = "auto", - pay_type: Union[PayType, str] = PayType.AUTO, + pay_type: PayType | str = PayType.AUTO, ) -> ModeSolverData: """Submits a :class:`.ModeSolver` to server, starts running, monitors progress, downloads, and loads results as a :class:`.ModeSolverData` object. @@ -149,13 +150,13 @@ def run_batch( mode_solvers: list[ModeSolver], task_name: str = "BatchModeSolver", folder_name: str = "BatchModeSolvers", - results_files: Optional[list[str]] = None, + results_files: list[str] | None = 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: Optional[Callable[[float], None]] = None, - progress_callback_download: Optional[Callable[[float], None]] = None, + progress_callback_upload: Callable[[float], None] | None = None, + progress_callback_download: Callable[[float], None] | None = None, ) -> list[ModeSolverData]: """ Submits a batch of ModeSolver to the server concurrently, manages progress, and retrieves results. @@ -273,7 +274,7 @@ class ModeSolverTask(ResourceLifecycle, Submittable, extra=pydantic.Extra.allow) None, title="real FlexCredits", description="Billed FlexCredits.", alias="charge" ) - created_at: Optional[datetime] = pydantic.Field( + created_at: datetime | None = pydantic.Field( title="created_at", description="Time at which this task was created.", alias="createdAt" ) @@ -375,7 +376,7 @@ def get( to_file: str = "mode_solver.hdf5", sim_file: str = "simulation.hdf5", verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, ) -> ModeSolverTask: """Get mode solver task from the server by id. @@ -417,7 +418,7 @@ def get_info(self) -> ModeSolverTask: return ModeSolverTask(**resp, mode_solver=self.mode_solver) def upload( - self, verbose: bool = True, progress_callback: Optional[Callable[[float], None]] = None + self, verbose: bool = True, progress_callback: Callable[[float], None] | None = None ) -> None: """Upload this task's 'mode_solver' to the server. @@ -464,7 +465,7 @@ def upload( def submit( self, - pay_type: Union[PayType, str] = PayType.AUTO, + pay_type: PayType | str = PayType.AUTO, ): """Start the execution of this task. @@ -500,7 +501,7 @@ def get_modesolver( to_file: str = "mode_solver.hdf5", sim_file: str = "simulation.hdf5", verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, ) -> ModeSolver: """Get mode solver associated with this task from the server. @@ -584,7 +585,7 @@ def get_result( self, to_file: str = "mode_solver_data.hdf5", verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, ) -> ModeSolverData: """Get mode solver results for this task from the server. @@ -647,7 +648,7 @@ def get_log( self, to_file: str = "mode_solver.log", verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, ) -> pathlib.Path: """Get execution log for this task from the server. diff --git a/tidy3d/web/api/run.py b/tidy3d/web/api/run.py index c65f8e94a3..5a4b0904d2 100644 --- a/tidy3d/web/api/run.py +++ b/tidy3d/web/api/run.py @@ -9,19 +9,16 @@ from tidy3d.web.api.container import DEFAULT_DATA_DIR, DEFAULT_DATA_PATH from tidy3d.web.core.types import PayType -RunInput: typing.TypeAlias = typing.Union[ - WorkflowType, - list["RunInput"], - tuple["RunInput", ...], - dict[typing.Hashable, "RunInput"], -] +RunInput: typing.TypeAlias = ( + WorkflowType | list["RunInput"] | tuple["RunInput", ...] | dict[typing.Hashable, "RunInput"] +) -RunOutput: typing.TypeAlias = typing.Union[ - WorkflowDataType, - list["RunOutput"], - tuple["RunOutput", ...], - dict[typing.Hashable, "RunOutput"], -] +RunOutput: typing.TypeAlias = ( + WorkflowDataType + | list["RunOutput"] + | tuple["RunOutput", ...] + | dict[typing.Hashable, "RunOutput"] +) def _collect_by_hash( @@ -79,24 +76,24 @@ def _recur(n: RunInput) -> RunOutput: def run( simulation: RunInput, - task_name: typing.Optional[str] = None, + task_name: str | None = None, folder_name: str = "default", - path: typing.Optional[str] = None, - callback_url: typing.Optional[str] = None, + path: str | None = None, + callback_url: str | None = None, verbose: bool = True, - 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, + progress_callback_upload: typing.Callable[[float], None] | None = None, + progress_callback_download: typing.Callable[[float], None] | None = None, + solver_version: str | None = None, + worker_group: str | None = None, simulation_type: str = "tidy3d", - parent_tasks: typing.Optional[list[str]] = None, - local_gradient: typing.Optional[bool] = None, - max_num_adjoint_per_fwd: typing.Optional[int] = None, + parent_tasks: list[str] | None = None, + local_gradient: bool | None = None, + max_num_adjoint_per_fwd: int | None = None, reduce_simulation: typing.Literal["auto", True, False] = "auto", - pay_type: typing.Union[PayType, str] = PayType.AUTO, - priority: typing.Optional[int] = None, - max_workers: typing.Optional[int] = None, - lazy: typing.Optional[bool] = None, + pay_type: PayType | str = PayType.AUTO, + priority: int | None = None, + max_workers: int | None = None, + lazy: bool | None = None, ) -> RunOutput: """ Submit one or many simulations and return results in the same container shape. diff --git a/tidy3d/web/api/tidy3d_stub.py b/tidy3d/web/api/tidy3d_stub.py index 22152e21ab..34b477ab45 100644 --- a/tidy3d/web/api/tidy3d_stub.py +++ b/tidy3d/web/api/tidy3d_stub.py @@ -3,8 +3,8 @@ from __future__ import annotations import json +from collections.abc import Callable from datetime import datetime -from typing import Callable, Optional import pydantic.v1 as pd from pydantic.v1 import BaseModel @@ -145,7 +145,7 @@ def to_file( """ self.simulation.to_file(file_path) - def to_hdf5_gz(self, fname: str, custom_encoders: Optional[list[Callable]] = None) -> None: + def to_hdf5_gz(self, fname: str, custom_encoders: list[Callable] | None = None) -> None: """Exports Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] instance to .hdf5.gz file. Parameters @@ -210,7 +210,7 @@ class Tidy3dStubData(BaseModel, TaskStubData): @classmethod def from_file( - cls, file_path: str, lazy: bool = False, on_load: Optional[Callable] = None + cls, file_path: str, lazy: bool = False, on_load: Callable | None = None ) -> WorkflowDataType: """Loads a Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`] from .yaml, .json, or .hdf5 file. diff --git a/tidy3d/web/api/webapi.py b/tidy3d/web/api/webapi.py index 7da3e9f7ca..d7d00180b1 100644 --- a/tidy3d/web/api/webapi.py +++ b/tidy3d/web/api/webapi.py @@ -6,7 +6,8 @@ import os import tempfile import time -from typing import Callable, Literal, Optional, Union +from collections.abc import Callable +from typing import Literal from requests import HTTPError from rich.progress import BarColumn, Progress, TaskProgressColumn, TextColumn, TimeElapsedColumn @@ -95,7 +96,7 @@ def _batch_detail(resource_id: str): return BatchTask(resource_id).detail(batch_type="RF_SWEEP") -def _batch_detail_error(resource_id: str) -> Optional[WebError]: +def _batch_detail_error(resource_id: str) -> WebError | None: """Processes a failed batch job to generate a detailed error. This function inspects the status of a batch detail object. If the status @@ -156,7 +157,7 @@ def _batch_detail_error(resource_id: str) -> Optional[WebError]: def _upload_component_modeler_subtasks( - resource_id: str, verbose: bool = True, solver_version: Optional[str] = None + resource_id: str, verbose: bool = True, solver_version: str | None = None ): """Kicks off and monitors the split and validation of component modeler tasks. @@ -319,20 +320,20 @@ def _task_dict_to_url_bullet_list(data_dict: dict) -> str: @wait_for_connection def run( simulation: WorkflowType, - task_name: Optional[str] = None, + task_name: str | None = None, folder_name: str = "default", path: str = "simulation_data.hdf5", - callback_url: Optional[str] = None, + callback_url: str | None = None, verbose: bool = True, - 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, + progress_callback_upload: Callable[[float], None] | None = None, + progress_callback_download: Callable[[float], None] | None = None, + solver_version: str | None = None, + worker_group: str | None = None, simulation_type: str = "tidy3d", - parent_tasks: Optional[list[str]] = None, + parent_tasks: list[str] | None = None, reduce_simulation: Literal["auto", True, False] = "auto", - pay_type: Union[PayType, str] = PayType.AUTO, - priority: Optional[int] = None, + pay_type: PayType | str = PayType.AUTO, + priority: int | None = None, lazy: bool = False, ) -> WorkflowDataType: """ @@ -455,15 +456,15 @@ def run( @wait_for_connection def upload( simulation: WorkflowType, - task_name: Optional[str] = None, + task_name: str | None = None, folder_name: str = "default", - callback_url: Optional[str] = None, + callback_url: str | None = None, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, simulation_type: str = "tidy3d", - parent_tasks: Optional[list[str]] = None, + parent_tasks: list[str] | None = None, source_required: bool = True, - solver_version: Optional[str] = None, + solver_version: str | None = None, reduce_simulation: Literal["auto", True, False] = "auto", ) -> TaskId: """ @@ -696,10 +697,10 @@ def get_info(task_id: TaskId, verbose: bool = True) -> TaskInfo | BatchDetail: def start( task_id: TaskId, verbose: bool = True, - solver_version: Optional[str] = None, - worker_group: Optional[str] = None, - pay_type: Union[PayType, str] = PayType.AUTO, - priority: Optional[int] = None, + solver_version: str | None = None, + worker_group: str | None = None, + pay_type: PayType | str = PayType.AUTO, + priority: int | None = None, ) -> None: """Start running the simulation associated with task. @@ -759,7 +760,7 @@ def start( @wait_for_connection -def get_run_info(task_id: TaskId) -> tuple[Optional[float], Optional[float]]: +def get_run_info(task_id: TaskId) -> tuple[float | None, float | None]: """Gets the % done and field_decay for a running task. Parameters @@ -826,7 +827,7 @@ def get_status(task_id) -> str: return status -def monitor(task_id: TaskId, verbose: bool = True, worker_group: Optional[str] = None) -> None: +def monitor(task_id: TaskId, verbose: bool = True, worker_group: str | None = None) -> None: """ Print the real time task progress until completion. @@ -1043,7 +1044,7 @@ def download( task_id: TaskId, path: str = "simulation_data.hdf5", verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, ) -> None: """Download results of task to file. @@ -1173,7 +1174,7 @@ def download_log( task_id: TaskId, path: str = "tidy3d.log", verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, ) -> None: """Download the tidy3d log file associated with a task. @@ -1202,7 +1203,7 @@ def load( path: str = "simulation_data.hdf5", replace_existing: bool = True, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, lazy: bool = False, ) -> WorkflowDataType: """ @@ -1268,7 +1269,7 @@ def _monitor_modeler_batch( batch_id: str, verbose: bool = True, max_detail_tasks: int = 20, - worker_group: Optional[str] = None, + worker_group: str | None = None, ) -> None: """Monitor modeler batch progress with aggregate and per-task views.""" console = get_logging_console() if verbose else None @@ -1461,7 +1462,7 @@ def download_simulation( task_id: TaskId, path: str = SIM_FILE_HDF5, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, ) -> None: """Download the ``.hdf5`` file associated with the :class:`.Simulation` of a given task. @@ -1492,7 +1493,7 @@ def download_simulation( @wait_for_connection def get_tasks( - num_tasks: Optional[int] = None, order: Literal["new", "old"] = "new", folder: str = "default" + num_tasks: int | None = None, order: Literal["new", "old"] = "new", folder: str = "default" ) -> list[dict]: """Get a list with the metadata of the last ``num_tasks`` tasks. @@ -1524,9 +1525,7 @@ def get_tasks( @wait_for_connection -def estimate_cost( - task_id: str, verbose: bool = True, solver_version: Optional[str] = None -) -> float: +def estimate_cost(task_id: str, verbose: bool = True, solver_version: str | None = None) -> float: """Compute the maximum FlexCredit charge for a given task. Parameters @@ -1790,7 +1789,7 @@ def account(verbose=True) -> Account: def postprocess_start( batch_id: str, verbose: bool = True, - worker_group: Optional[str] = None, + worker_group: str | None = None, ) -> None: """ Checks if a batch run is complete and starts the postprocess phase. diff --git a/tidy3d/web/cli/develop/documentation.py b/tidy3d/web/cli/develop/documentation.py index 9da6926577..4520fb533e 100644 --- a/tidy3d/web/cli/develop/documentation.py +++ b/tidy3d/web/cli/develop/documentation.py @@ -17,7 +17,6 @@ import json import os -from typing import Optional import click @@ -299,7 +298,7 @@ def build_documentation_from_remote_notebooks(args=None): help="Recursively find and replace strings in files based on a JSON configuration.", ) def replace_in_files_command( - directory: str, json_dictionary: Optional[str], selected_version: Optional[str], dry_run: bool + directory: str, json_dictionary: str | None, selected_version: str | None, dry_run: bool ): """ Recursively finds and replaces strings in files within a directory based on a given dictionary loaded from a JSON diff --git a/tidy3d/web/core/account.py b/tidy3d/web/core/account.py index 07cef4caaa..54a0f4e682 100644 --- a/tidy3d/web/core/account.py +++ b/tidy3d/web/core/account.py @@ -3,7 +3,6 @@ from __future__ import annotations from datetime import datetime -from typing import Optional from pydantic.v1 import Extra, Field @@ -14,34 +13,34 @@ class Account(Tidy3DResource, extra=Extra.allow): """Tidy3D User Account.""" - allowance_cycle_type: Optional[str] = Field( + allowance_cycle_type: str | None = Field( None, title="AllowanceCycleType", description="Daily or Monthly", alias="allowanceCycleType", ) - credit: Optional[float] = Field( + credit: float | None = Field( 0, title="credit", description="Current FlexCredit balance", alias="credit" ) - credit_expiration: Optional[datetime] = Field( + credit_expiration: datetime | None = Field( None, title="creditExpiration", description="Expiration date", alias="creditExpiration", ) - allowance_current_cycle_amount: Optional[float] = Field( + allowance_current_cycle_amount: float | None = Field( 0, title="allowanceCurrentCycleAmount", description="Daily/Monthly free simulation balance", alias="allowanceCurrentCycleAmount", ) - allowance_current_cycle_end_date: Optional[datetime] = Field( + allowance_current_cycle_end_date: datetime | None = Field( None, title="allowanceCurrentCycleEndDate", description="Daily/Monthly free simulation balance expiration date", alias="allowanceCurrentCycleEndDate", ) - daily_free_simulation_counts: Optional[int] = Field( + daily_free_simulation_counts: int | None = Field( 0, title="dailyFreeSimulationCounts", description="Daily free simulation counts", diff --git a/tidy3d/web/core/exceptions.py b/tidy3d/web/core/exceptions.py index 4061a8c6f8..fd53ea850e 100644 --- a/tidy3d/web/core/exceptions.py +++ b/tidy3d/web/core/exceptions.py @@ -2,15 +2,13 @@ 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: Optional[str] = None): + def __init__(self, message: str | None = None): """Log just the error message and then raise the Exception.""" log = get_logger() super().__init__(message) diff --git a/tidy3d/web/core/s3utils.py b/tidy3d/web/core/s3utils.py index cb87a69a22..4f172cec24 100644 --- a/tidy3d/web/core/s3utils.py +++ b/tidy3d/web/core/s3utils.py @@ -6,10 +6,9 @@ import pathlib import tempfile import urllib -from collections.abc import Mapping +from collections.abc import Callable, Mapping from datetime import datetime from enum import Enum -from typing import Callable, Optional import boto3 from boto3.s3.transfer import TransferConfig @@ -184,7 +183,7 @@ def _get_progress(action: _S3Action): def get_s3_sts_token( - resource_id: str, file_name: str, extra_arguments: Optional[Mapping[str, str]] = None + resource_id: str, file_name: str, extra_arguments: Mapping[str, str] | None = None ) -> _S3STSToken: """Get s3 sts token for the given resource id and file name. @@ -218,8 +217,8 @@ def upload_file( path: str, remote_filename: str, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, - extra_arguments: Optional[Mapping[str, str]] = None, + progress_callback: Callable[[float], None] | None = None, + extra_arguments: Mapping[str, str] | None = None, ): """Upload a file to S3. @@ -284,9 +283,9 @@ def _callback(bytes_in_chunk): def download_file( resource_id: str, remote_filename: str, - to_file: Optional[str] = None, + to_file: str | None = None, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, ) -> pathlib.Path: """Download file from S3. @@ -374,9 +373,9 @@ def _callback(bytes_in_chunk): def download_gz_file( resource_id: str, remote_filename: str, - to_file: Optional[str] = None, + to_file: str | None = None, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | 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/task_core.py b/tidy3d/web/core/task_core.py index 619ff4bc8d..3d86c1d602 100644 --- a/tidy3d/web/core/task_core.py +++ b/tidy3d/web/core/task_core.py @@ -6,8 +6,8 @@ import pathlib import tempfile import time +from collections.abc import Callable from datetime import datetime -from typing import Callable, Optional, Union from botocore.exceptions import ClientError from pydantic.v1 import Extra, Field, parse_obj_as @@ -146,33 +146,33 @@ def list_tasks(self, projects_endpoint: str = "tidy3d/projects") -> list[Tidy3DR class SimulationTask(ResourceLifecycle, Submittable, extra=Extra.allow): """Interface for managing the running of a :class:`.Simulation` task on server.""" - task_id: Optional[str] = Field( + task_id: str | None = Field( ..., title="task_id", description="Task ID number, set when the task is uploaded, leave as None.", alias="taskId", ) - folder_id: Optional[str] = Field( + folder_id: str | None = Field( None, title="folder_id", description="Folder ID number, set when the task is uploaded, leave as None.", alias="folderId", ) - status: Optional[str] = Field(title="status", description="Simulation task status.") + status: str | None = Field(title="status", description="Simulation task status.") real_flex_unit: float = Field( None, title="real FlexCredits", description="Billed FlexCredits.", alias="realCost" ) - created_at: Optional[datetime] = Field( + created_at: datetime | None = Field( title="created_at", description="Time at which this task was created.", alias="createdAt" ) - task_type: Optional[str] = Field( + task_type: str | None = Field( title="task_type", description="The type of task.", alias="taskType" ) - folder_name: Optional[str] = Field( + folder_name: str | None = Field( "default", title="Folder Name", description="Name of the folder associated with this task.", @@ -205,11 +205,11 @@ def create( task_type: str, task_name: str, folder_name: str = "default", - callback_url: Optional[str] = None, + callback_url: str | None = None, simulation_type: str = "tidy3d", - parent_tasks: Optional[list[str]] = None, + parent_tasks: list[str] | None = None, file_type: str = "Gz", - port_name_list: Optional[list[str]] = None, + port_name_list: list[str] | None = None, projects_endpoint: str = "tidy3d/projects", ) -> SimulationTask: """Create a new task on the server. @@ -358,7 +358,7 @@ def upload_simulation( self, stub: TaskStub, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, remote_sim_file: str = SIM_FILE_HDF5_GZ, ) -> None: """Upload :class:`.Simulation` object to Server. @@ -398,7 +398,7 @@ def upload_file( local_file: str, remote_filename: str, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, ) -> None: """ Upload file to platform. Using this method when the json file is too large to parse @@ -427,10 +427,10 @@ def upload_file( def submit( self, - solver_version: Optional[str] = None, - worker_group: Optional[str] = None, - pay_type: Union[PayType, str] = PayType.AUTO, - priority: Optional[int] = None, + solver_version: str | None = None, + worker_group: str | None = None, + pay_type: PayType | str = PayType.AUTO, + priority: int | None = None, ): """Kick off this task. @@ -504,7 +504,7 @@ def get_sim_data_hdf5( self, to_file: str, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, remote_data_file: str = SIMULATION_DATA_HDF5_GZ, ) -> pathlib.Path: """Get simulation data file from Server. @@ -561,7 +561,7 @@ def get_simulation_hdf5( self, to_file: str, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, remote_sim_file: str = SIM_FILE_HDF5_GZ, ) -> pathlib.Path: """Get simulation.hdf5 file from Server. @@ -615,7 +615,7 @@ def get_log( self, to_file: str, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, ) -> pathlib.Path: """Get log file from Server. @@ -678,7 +678,7 @@ def abort(self): "tidy3d/tasks/abort", json={"taskType": self.task_type, "taskId": self.task_id} ) - def validate_post_upload(self, parent_tasks: Optional[list[str]] = None): + def validate_post_upload(self, parent_tasks: list[str] | None = None): """Perform checks after task is uploaded and metadata is processed.""" if self.task_type == "HEAT_CHARGE" and parent_tasks: try: @@ -780,8 +780,8 @@ def detail(self, batch_type: str) -> BatchDetail: def check( self, - solver_version: Optional[str] = None, - protocol_version: Optional[str] = None, + solver_version: str | None = None, + protocol_version: str | None = None, batch_type: str = "", ): """Submits a request to validate the batch configuration on the server. @@ -813,9 +813,9 @@ def check( def submit( self, - solver_version: Optional[str] = None, - protocol_version: Optional[str] = None, - worker_group: Optional[str] = None, + solver_version: str | None = None, + protocol_version: str | None = None, + worker_group: str | None = None, batch_type: str = "", ): """Submits the batch for execution on the server. @@ -850,9 +850,9 @@ def submit( def postprocess( self, - solver_version: Optional[str] = None, - protocol_version: Optional[str] = None, - worker_group: Optional[str] = None, + solver_version: str | None = None, + protocol_version: str | None = None, + worker_group: str | None = None, batch_type: str = "", ): """Initiates post-processing for a completed batch run. @@ -885,9 +885,7 @@ def postprocess( }, ) - def wait_for_validate( - self, timeout: Optional[float] = None, batch_type: str = "" - ) -> BatchDetail: + def wait_for_validate(self, timeout: float | None = None, batch_type: str = "") -> BatchDetail: """Waits for the batch to complete the validation stage by polling its status. Parameters @@ -920,7 +918,7 @@ def wait_for_validate( return d time.sleep(REFRESH_TIME) - def wait_for_run(self, timeout: Optional[float] = None, batch_type: str = "") -> BatchDetail: + def wait_for_run(self, timeout: float | None = None, batch_type: str = "") -> BatchDetail: """Waits for the batch to complete the execution stage by polling its status. Parameters @@ -963,7 +961,7 @@ def get_data_hdf5( remote_data_file_gz: str, to_file: str, verbose: bool = True, - progress_callback: Optional[Callable[[float], None]] = None, + progress_callback: Callable[[float], None] | None = None, ) -> pathlib.Path: """Downloads a batch data artifact, with a fallback mechanism. diff --git a/tidy3d/web/core/task_info.py b/tidy3d/web/core/task_info.py index 93a68fb4ce..53b5290af1 100644 --- a/tidy3d/web/core/task_info.py +++ b/tidy3d/web/core/task_info.py @@ -5,7 +5,6 @@ from abc import ABC from datetime import datetime from enum import Enum -from typing import Optional import pydantic.v1 as pydantic @@ -87,7 +86,7 @@ class TaskInfo(TaskBase): nodeSize: int = None """Size of the node allocated for the task.""" - completedAt: Optional[datetime] = None + completedAt: datetime | None = None """Timestamp when the task was completed.""" status: str = None @@ -102,7 +101,7 @@ class TaskInfo(TaskBase): solverVersion: str = None """Version of the solver used for the task.""" - createAt: Optional[datetime] = None + createAt: datetime | None = None """Timestamp when the task was created.""" estCostMin: float = None @@ -132,10 +131,10 @@ class TaskInfo(TaskBase): s3Storage: float = None """Amount of S3 storage used by the task.""" - startSolverTime: Optional[datetime] = None + startSolverTime: datetime | None = None """Timestamp when the solver started.""" - finishSolverTime: Optional[datetime] = None + finishSolverTime: datetime | None = None """Timestamp when the solver finished.""" totalSolverTime: int = None @@ -263,8 +262,8 @@ class BatchMember(TaskBase): replaceData: str = None protocolVersion: str = None variable: str = None - createdAt: Optional[datetime] = None - updatedAt: Optional[datetime] = None + createdAt: datetime | None = None + updatedAt: datetime | None = None denormalizeStatus: str = None summary: dict = None @@ -343,13 +342,13 @@ class AsyncJobDetail(TaskBase): asyncId: str status: str - progress: Optional[float] = None - createdAt: Optional[datetime] = None - completedAt: Optional[datetime] = None - tasks: Optional[dict[str, str]] = None - result: Optional[str] = None - taskBlockInfo: Optional[TaskBlockInfo] = None - message: Optional[str] = None + progress: float | None = None + createdAt: datetime | None = None + completedAt: datetime | None = None + tasks: dict[str, str] | None = None + result: str | None = None + taskBlockInfo: TaskBlockInfo | None = None + message: str | None = None AsyncJobDetail.update_forward_refs()