From 27d9f00ff885454e19902177d74a3e883c1c5c9b Mon Sep 17 00:00:00 2001 From: dmarek Date: Mon, 14 Apr 2025 09:12:15 -0400 Subject: [PATCH] feat(rf): add a MicrowaveModeSpec for RF specific mode information add a ImpedanceSpec for controlling how characteristic impedance is calculated from modes add MicrowaveModeMonitor and MicrowaveModeSolverMonitor that accepts the new MicrowaveModeSpec BREAKING CHANGE: changed path integral class names in plugins/microwave --- CHANGELOG.md | 15 + docs/api/microwave/index.rst | 6 + docs/api/microwave/microwave_migration.rst | 130 + docs/api/microwave/ports/wave.rst | 14 +- docs/api/plugins/microwave.rst | 10 +- schemas/EMESimulation.json | 615 +- schemas/ModeSimulation.json | 685 +- schemas/Simulation.json | 1431 +- schemas/TerminalComponentModeler.json | 11651 +++++++++------- tests/test_components/test_geometry.py | 199 +- tests/test_components/test_lumped_element.py | 4 +- tests/test_components/test_microwave.py | 1632 ++- tests/test_components/test_simulation.py | 55 + tests/test_data/test_data_arrays.py | 10 + .../smatrix/terminal_component_modeler_def.py | 5 +- .../test_terminal_component_modeler.py | 17 +- tests/test_plugins/test_microwave.py | 590 +- tidy3d/__init__.py | 54 + tidy3d/components/boundary.py | 8 +- tidy3d/components/data/data_array.py | 22 +- tidy3d/components/data/monitor_data.py | 28 +- tidy3d/components/data/sim_data.py | 3 +- tidy3d/components/eme/simulation.py | 3 +- tidy3d/components/geometry/utils.py | 250 +- tidy3d/components/lumped_element.py | 202 +- tidy3d/components/microwave/base.py | 38 + tidy3d/components/microwave/data/dataset.py | 46 + .../components/microwave/data/monitor_data.py | 175 +- .../microwave/impedance_calculator.py | 167 + tidy3d/components/microwave/mode_spec.py | 98 + tidy3d/components/microwave/monitor.py | 70 + .../microwave/path_integrals/__init__.py | 0 .../microwave/path_integrals/factory.py | 153 + .../path_integrals/integrals/__init__.py | 0 .../path_integrals/integrals/auto.py | 86 + .../path_integrals/integrals/base.py | 213 + .../path_integrals/integrals/current.py | 300 + .../path_integrals/integrals/voltage.py | 91 + .../path_integrals/mode_plane_analyzer.py | 365 + .../path_integrals/specs/__init__.py | 0 .../microwave/path_integrals/specs/base.py | 259 + .../microwave/path_integrals/specs/current.py | 374 + .../path_integrals/specs/impedance.py | 82 + .../microwave/path_integrals/specs/voltage.py | 206 + .../microwave/path_integrals/types.py | 20 + .../microwave/path_integrals/viz.py | 56 + tidy3d/components/mode/data/sim_data.py | 12 +- tidy3d/components/mode/mode_solver.py | 105 +- tidy3d/components/mode/simulation.py | 5 +- tidy3d/components/mode_spec.py | 85 +- tidy3d/components/monitor.py | 32 +- tidy3d/components/simulation.py | 33 +- tidy3d/components/source/field.py | 4 +- tidy3d/components/types/mode_spec.py | 11 + tidy3d/components/types/monitor.py | 43 + tidy3d/components/types/monitor_data.py | 46 + tidy3d/components/types/simulation.py | 2 + tidy3d/config.py | 7 + tidy3d/plugins/microwave/__init__.py | 49 +- tidy3d/plugins/microwave/array_factor.py | 22 +- .../plugins/microwave/auto_path_integrals.py | 86 +- .../microwave/custom_path_integrals.py | 417 +- .../plugins/microwave/impedance_calculator.py | 124 +- tidy3d/plugins/microwave/lobe_measurer.py | 18 +- tidy3d/plugins/microwave/path_integrals.py | 573 +- .../plugins/microwave/rf_material_library.py | 1 - tidy3d/plugins/microwave/viz.py | 47 - .../smatrix/component_modelers/base.py | 18 +- .../smatrix/component_modelers/terminal.py | 11 +- tidy3d/plugins/smatrix/data/data_array.py | 19 - tidy3d/plugins/smatrix/data/terminal.py | 16 +- tidy3d/plugins/smatrix/ports/base_terminal.py | 13 +- .../plugins/smatrix/ports/coaxial_lumped.py | 11 +- .../smatrix/ports/rectangular_lumped.py | 9 +- tidy3d/plugins/smatrix/ports/wave.py | 10 +- tidy3d/utils.py | 10 + tidy3d/web/api/tidy3d_stub.py | 3 + 77 files changed, 14513 insertions(+), 7767 deletions(-) create mode 100644 docs/api/microwave/microwave_migration.rst create mode 100644 tidy3d/components/microwave/base.py create mode 100644 tidy3d/components/microwave/data/dataset.py create mode 100644 tidy3d/components/microwave/impedance_calculator.py create mode 100644 tidy3d/components/microwave/mode_spec.py create mode 100644 tidy3d/components/microwave/monitor.py create mode 100644 tidy3d/components/microwave/path_integrals/__init__.py create mode 100644 tidy3d/components/microwave/path_integrals/factory.py create mode 100644 tidy3d/components/microwave/path_integrals/integrals/__init__.py create mode 100644 tidy3d/components/microwave/path_integrals/integrals/auto.py create mode 100644 tidy3d/components/microwave/path_integrals/integrals/base.py create mode 100644 tidy3d/components/microwave/path_integrals/integrals/current.py create mode 100644 tidy3d/components/microwave/path_integrals/integrals/voltage.py create mode 100644 tidy3d/components/microwave/path_integrals/mode_plane_analyzer.py create mode 100644 tidy3d/components/microwave/path_integrals/specs/__init__.py create mode 100644 tidy3d/components/microwave/path_integrals/specs/base.py create mode 100644 tidy3d/components/microwave/path_integrals/specs/current.py create mode 100644 tidy3d/components/microwave/path_integrals/specs/impedance.py create mode 100644 tidy3d/components/microwave/path_integrals/specs/voltage.py create mode 100644 tidy3d/components/microwave/path_integrals/types.py create mode 100644 tidy3d/components/microwave/path_integrals/viz.py create mode 100644 tidy3d/components/types/mode_spec.py create mode 100644 tidy3d/components/types/monitor.py create mode 100644 tidy3d/components/types/monitor_data.py create mode 100644 tidy3d/utils.py diff --git a/CHANGELOG.md b/CHANGELOG.md index cfdadf5d05..455c978e88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for `tidy3d-extras`, an optional plugin that enables more accurate local mode solving via subpixel averaging. - Added support for `symlog` and `log` scale plotting in `Scene.plot_eps()` and `Scene.plot_structures_property()` methods. The `symlog` scale provides linear behavior near zero and logarithmic behavior elsewhere, while 'log' is a base 10 logarithmic scale. +- Added `MicrowaveModeSpec` for RF-specific mode information with customizable characteristic impedance calculations. +- Added `MicrowaveModeMonitor` and `MicrowaveModeSolverMonitor` for microwave and RF mode analysis with transmission line data. +- Added `MicrowaveModeData` and `MicrowaveModeSolverData` extending mode solver results with characteristic impedance, voltage coefficients, and current coefficients. +- Added `AutoImpedanceSpec` for automatic transmission line impedance calculation based on simulation geometry. +- Added `CustomImpedanceSpec` for user-defined voltage and current path specifications in impedance calculations. +- Added voltage integral specification classes: `AxisAlignedVoltageIntegralSpec` and `Custom2DVoltageIntegralSpec`. +- Added current integral specification classes: `AxisAlignedCurrentIntegralSpec`, `CompositeCurrentIntegralSpec`, and `Custom2DCurrentIntegralSpec`. ### Changed - Improved performance of antenna metrics calculation by utilizing cached wave amplitude calculations instead of recomputing wave amplitudes for each port excitation in the `TerminalComponentModelerData`. - Changed hashing method in `Tidy3dBaseModel` from sha256 to md5. - Allowing for more geometries in a ClipOperation geometry. - Improved the speed of computing `Box` shape derivatives when used inside a `GeometryGroup`. +- All RF and microwave specific components now inherit from `MicrowaveBaseModel`. +- **[BREAKING]** Renamed path integral classes in `tidy3d.plugins.microwave` for improved consistency. Please see our migration guide for details on updating your code. + - `VoltageIntegralAxisAligned` → `AxisAlignedVoltageIntegral` + - `CurrentIntegralAxisAligned` → `AxisAlignedCurrentIntegral` + - `CustomPathIntegral2D` → `Custom2DPathIntegral` + - `CustomVoltageIntegral2D` → `Custom2DVoltageIntegral` + - `CustomCurrentIntegral2D` → `Custom2DCurrentIntegral` + - Path integral and impedance calculator classes have been refactored and moved from `tidy3d.plugins.microwave` to `tidy3d.components.microwave`. They are now publicly exported via the top-level package `__init__.py`, so you can import them directly, e.g. `from tidy3d import ImpedanceCalculator, AxisAlignedVoltageIntegral, AxisAlignedCurrentIntegral, Custom2DVoltageIntegral, Custom2DCurrentIntegral, Custom2DPathIntegral`. ### Fixed - More robust `Sellmeier` and `Debye` material model, and prevent very large pole parameters in `PoleResidue` material model. diff --git a/docs/api/microwave/index.rst b/docs/api/microwave/index.rst index 76952cae0c..19f7240191 100644 --- a/docs/api/microwave/index.rst +++ b/docs/api/microwave/index.rst @@ -8,6 +8,12 @@ Overview RF simulations will be subject to new license requirements in the future. +.. warning:: + + Breaking changes were introduced in ``v2.10.0``, please see the migration guide for help migrating your code. + + + `Migration Guide `_ + This page consolidates Tidy3D features related to microwave and RF simulation. While microwave/RF and optical simulations have many properties in common, there are some differences in the typical RF user workflow that deserve special consideration. The following sections discuss: diff --git a/docs/api/microwave/microwave_migration.rst b/docs/api/microwave/microwave_migration.rst new file mode 100644 index 0000000000..5208d0a7e7 --- /dev/null +++ b/docs/api/microwave/microwave_migration.rst @@ -0,0 +1,130 @@ +.. _microwave_migration: + +v2.10 Refactor Migration +------------------------- + +In version ``v2.10.0``, microwave plugin path integral classes were renamed for improved consistency and clarity. Additionally, these classes (and the impedance calculator) were refactored out of the plugin and moved into ``tidy3d.components.microwave`` and re-exported at the top-level package for convenience. This guide helps you update your scripts to use the new class names and imports. + +Key Changes +~~~~~~~~~~~ + +* **Renamed Classes**: Path integral classes have been renamed to follow a consistent naming pattern. +* **New Import Path (simplified)**: The path integral classes and impedance calculator are now exported at the top level. Prefer importing directly from ``tidy3d`` (e.g., ``from tidy3d import AxisAlignedVoltageIntegral, ImpedanceCalculator``). Existing plugin imports continue to work for backwards compatibility where applicable. + +Class Name Changes +~~~~~~~~~~~~~~~~~~ + +The following classes have been renamed for consistency: + +**Voltage Integrals:** + +* ``VoltageIntegralAxisAligned`` → ``AxisAlignedVoltageIntegral`` +* ``CustomVoltageIntegral2D`` → ``Custom2DVoltageIntegral`` + +**Current Integrals:** + +* ``CurrentIntegralAxisAligned`` → ``AxisAlignedCurrentIntegral`` +* ``CustomCurrentIntegral2D`` → ``Custom2DCurrentIntegral`` + +**Path Integrals:** + +* ``CustomPathIntegral2D`` → ``Custom2DPathIntegral`` + +Migration Examples +~~~~~~~~~~~~~~~~~~ + +**Before (v2.9.x):** + +.. code-block:: python + + from tidy3d.plugins.microwave import ( + VoltageIntegralAxisAligned, + CurrentIntegralAxisAligned, + ) + + voltage_path = VoltageIntegralAxisAligned( + center=(0, 0, 0), + size=(0, 0, 1), + sign="+", + ) + + current_path = CurrentIntegralAxisAligned( + center=(0, 0, 0), + size=(2, 1, 0), + sign="+", + ) + +**After (v2.10+):** + +.. code-block:: python + + from tidy3d import ( + AxisAlignedVoltageIntegral, + AxisAlignedCurrentIntegral, + ) + + voltage_path = AxisAlignedVoltageIntegral( + center=(0, 0, 0), + size=(0, 0, 1), + sign="+", + ) + + current_path = AxisAlignedCurrentIntegral( + center=(0, 0, 0), + size=(2, 1, 0), + sign="+", + ) + +Custom 2D Path Integrals +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Before:** + +.. code-block:: python + + from tidy3d.plugins.microwave import ( + CustomVoltageIntegral2D, + CustomCurrentIntegral2D, + ) + + vertices = [[0, 0], [1, 0], [1, 1], [0, 1]] + + voltage_path = CustomVoltageIntegral2D( + axis=2, + position=0.0, + vertices=vertices, + ) + + current_path = CustomCurrentIntegral2D( + axis=2, + position=0.0, + vertices=vertices, + ) + +**After:** + +.. code-block:: python + + from tidy3d import ( + Custom2DVoltageIntegral, + Custom2DCurrentIntegral, + ) + + vertices = [[0, 0], [1, 0], [1, 1], [0, 1]] + + voltage_path = Custom2DVoltageIntegral( + axis=2, + position=0.0, + vertices=vertices, + ) + + current_path = Custom2DCurrentIntegral( + axis=2, + position=0.0, + vertices=vertices, + ) + +Summary +~~~~~~~ + +All functionality remains the same—only class names and preferred import paths have changed. Update your imports to the top level (``from tidy3d import ...``) and class names according to the table above, and your code will work with v2.10. For impedance calculations, import ``ImpedanceCalculator`` directly via ``from tidy3d import ImpedanceCalculator``. diff --git a/docs/api/microwave/ports/wave.rst b/docs/api/microwave/ports/wave.rst index 77258047ee..5aa95ae9fd 100644 --- a/docs/api/microwave/ports/wave.rst +++ b/docs/api/microwave/ports/wave.rst @@ -46,12 +46,12 @@ If it is desired to only solve for the 2D port mode, one can use the ``to_mode_s :toctree: ../_autosummary/ :template: module.rst - tidy3d.plugins.microwave.VoltageIntegralAxisAligned - tidy3d.plugins.microwave.CurrentIntegralAxisAligned - tidy3d.plugins.microwave.CustomVoltageIntegral2D - tidy3d.plugins.microwave.CustomCurrentIntegral2D + tidy3d.plugins.microwave.AxisAlignedVoltageIntegral + tidy3d.plugins.microwave.AxisAlignedCurrentIntegral + tidy3d.plugins.microwave.Custom2DVoltageIntegral + tidy3d.plugins.microwave.Custom2DCurrentIntegral tidy3d.plugins.microwave.AxisAlignedPathIntegral - tidy3d.plugins.microwave.CustomPathIntegral2D + tidy3d.plugins.microwave.Custom2DPathIntegral tidy3d.plugins.microwave.ImpedanceCalculator The classes above are used to define the voltage/current integration paths for impedance calculation. @@ -59,14 +59,14 @@ The classes above are used to define the voltage/current integration paths for i .. code-block:: python # Define voltage integration line - my_voltage_integral = VoltageIntegralAxisAligned( + my_voltage_integral = AxisAlignedVoltageIntegral( center=(0,0,0), # center of integration line size=(5, 0, 0), # length of integration line sign='+', # sign of integral ) # Define current integration loop - my_current_integral = CurrentIntegralAxisAligned( + my_current_integral = AxisAlignedCurrentIntegral( center=(0,0,0), # center of integration loop size=(20, 20, 0), # size of integration loop sign='+', # sign of integral (should match wave port direction) diff --git a/docs/api/plugins/microwave.rst b/docs/api/plugins/microwave.rst index fe08e44845..a6d8a89ac7 100644 --- a/docs/api/plugins/microwave.rst +++ b/docs/api/plugins/microwave.rst @@ -13,11 +13,11 @@ Microwave :template: module.rst tidy3d.plugins.microwave.AxisAlignedPathIntegral - tidy3d.plugins.microwave.VoltageIntegralAxisAligned - tidy3d.plugins.microwave.CurrentIntegralAxisAligned - tidy3d.plugins.microwave.CustomPathIntegral2D - tidy3d.plugins.microwave.CustomVoltageIntegral2D - tidy3d.plugins.microwave.CustomCurrentIntegral2D + tidy3d.plugins.microwave.AxisAlignedVoltageIntegral + tidy3d.plugins.microwave.AxisAlignedCurrentIntegral + tidy3d.plugins.microwave.Custom2DPathIntegral + tidy3d.plugins.microwave.Custom2DVoltageIntegral + tidy3d.plugins.microwave.Custom2DCurrentIntegral tidy3d.plugins.microwave.ImpedanceCalculator tidy3d.plugins.microwave.RectangularAntennaArrayCalculator tidy3d.plugins.microwave.LobeMeasurer diff --git a/schemas/EMESimulation.json b/schemas/EMESimulation.json index df1713e027..3ba8339704 100644 --- a/schemas/EMESimulation.json +++ b/schemas/EMESimulation.json @@ -691,6 +691,287 @@ }, "type": "object" }, + "AutoImpedanceSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "type": { + "default": "AutoImpedanceSpec", + "enum": [ + "AutoImpedanceSpec" + ], + "type": "string" + } + }, + "type": "object" + }, + "AxisAlignedCurrentIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "extrapolate_to_endpoints": { + "default": false, + "type": "boolean" + }, + "sign": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "snap_contour_to_grid": { + "default": false, + "type": "boolean" + }, + "type": { + "default": "AxisAlignedCurrentIntegralSpec", + "enum": [ + "AxisAlignedCurrentIntegralSpec" + ], + "type": "string" + } + }, + "required": [ + "sign", + "size" + ], + "type": "object" + }, + "AxisAlignedVoltageIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "extrapolate_to_endpoints": { + "default": false, + "type": "boolean" + }, + "sign": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "snap_path_to_grid": { + "default": false, + "type": "boolean" + }, + "type": { + "default": "AxisAlignedVoltageIntegralSpec", + "enum": [ + "AxisAlignedVoltageIntegralSpec" + ], + "type": "string" + } + }, + "required": [ + "sign", + "size" + ], + "type": "object" + }, "BlochBoundary": { "additionalProperties": false, "properties": { @@ -1721,27 +2002,68 @@ "minItems": 2, "type": "array" }, - "type": { - "default": "ComplexPolySlabBase", + "type": { + "default": "ComplexPolySlabBase", + "enum": [ + "ComplexPolySlabBase" + ], + "type": "string" + }, + "vertices": { + "anyOf": [ + { + "type": "ArrayLike" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + }, + "required": [ + "slab_bounds", + "vertices" + ], + "type": "object" + }, + "CompositeCurrentIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "path_specs": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DCurrentIntegralSpec" + } + ] + }, + "type": "array" + }, + "sum_spec": { "enum": [ - "ComplexPolySlabBase" + "split", + "sum" ], "type": "string" }, - "vertices": { - "anyOf": [ - { - "type": "ArrayLike" - }, - { - "type": "autograd.tracer.Box" - } - ] + "type": { + "default": "CompositeCurrentIntegralSpec", + "enum": [ + "CompositeCurrentIntegralSpec" + ], + "type": "string" } }, "required": [ - "slab_bounds", - "vertices" + "path_specs", + "sum_spec" ], "type": "object" }, @@ -2038,6 +2360,78 @@ }, "type": "object" }, + "Custom2DCurrentIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + }, + "position": { + "type": "number" + }, + "type": { + "default": "Custom2DCurrentIntegralSpec", + "enum": [ + "Custom2DCurrentIntegralSpec" + ], + "type": "string" + }, + "vertices": { + "type": "ArrayLike" + } + }, + "required": [ + "axis", + "position", + "vertices" + ], + "type": "object" + }, + "Custom2DVoltageIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + }, + "position": { + "type": "number" + }, + "type": { + "default": "Custom2DVoltageIntegralSpec", + "enum": [ + "Custom2DVoltageIntegralSpec" + ], + "type": "string" + }, + "vertices": { + "type": "ArrayLike" + } + }, + "required": [ + "axis", + "position", + "vertices" + ], + "type": "object" + }, "CustomAnisotropicMedium": { "additionalProperties": false, "properties": { @@ -2919,6 +3313,46 @@ ], "type": "object" }, + "CustomImpedanceSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "current_spec": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/CompositeCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DCurrentIntegralSpec" + } + ] + }, + "type": { + "default": "CustomImpedanceSpec", + "enum": [ + "CustomImpedanceSpec" + ], + "type": "string" + }, + "voltage_spec": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedVoltageIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DVoltageIntegralSpec" + } + ] + } + }, + "type": "object" + }, "CustomLorentz": { "additionalProperties": false, "properties": { @@ -7447,6 +7881,137 @@ ], "type": "object" }, + "MicrowaveModeSpec": { + "additionalProperties": false, + "properties": { + "angle_phi": { + "default": 0.0, + "type": "number" + }, + "angle_rotation": { + "default": false, + "type": "boolean" + }, + "angle_theta": { + "default": 0.0, + "type": "number" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "bend_axis": { + "enum": [ + 0, + 1 + ], + "type": "integer" + }, + "bend_radius": { + "type": "number" + }, + "filter_pol": { + "enum": [ + "te", + "tm" + ], + "type": "string" + }, + "group_index_step": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "number" + }, + { + "type": "boolean" + } + ], + "default": false + }, + "impedance_specs": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/AutoImpedanceSpec" + }, + { + "$ref": "#/definitions/CustomImpedanceSpec" + } + ] + }, + "type": "array" + }, + { + "oneOf": [ + { + "$ref": "#/definitions/AutoImpedanceSpec" + }, + { + "$ref": "#/definitions/CustomImpedanceSpec" + } + ] + } + ] + }, + "num_modes": { + "default": 1, + "exclusiveMinimum": 0, + "type": "integer" + }, + "num_pml": { + "default": [ + 0, + 0 + ], + "items": [ + { + "minimum": 0, + "type": "integer" + }, + { + "minimum": 0, + "type": "integer" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "precision": { + "default": "double", + "enum": [ + "auto", + "double", + "single" + ], + "type": "string" + }, + "target_neff": { + "exclusiveMinimum": 0, + "type": "number" + }, + "track_freq": { + "default": "central", + "enum": [ + "central", + "highest", + "lowest" + ], + "type": "string" + }, + "type": { + "default": "MicrowaveModeSpec", + "enum": [ + "MicrowaveModeSpec" + ], + "type": "string" + } + }, + "type": "object" + }, "ModeABCBoundary": { "additionalProperties": false, "properties": { @@ -7471,11 +8036,6 @@ "type": "integer" }, "mode_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModeSpec" - } - ], "default": { "angle_phi": 0.0, "angle_rotation": false, @@ -7494,7 +8054,22 @@ "target_neff": null, "track_freq": "central", "type": "ModeSpec" - } + }, + "discriminator": { + "mapping": { + "MicrowaveModeSpec": "#/definitions/MicrowaveModeSpec", + "ModeSpec": "#/definitions/ModeSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + }, + { + "$ref": "#/definitions/ModeSpec" + } + ] }, "name": { "type": "string" diff --git a/schemas/ModeSimulation.json b/schemas/ModeSimulation.json index ff8fac73e2..34fde6f98d 100644 --- a/schemas/ModeSimulation.json +++ b/schemas/ModeSimulation.json @@ -691,6 +691,287 @@ }, "type": "object" }, + "AutoImpedanceSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "type": { + "default": "AutoImpedanceSpec", + "enum": [ + "AutoImpedanceSpec" + ], + "type": "string" + } + }, + "type": "object" + }, + "AxisAlignedCurrentIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "extrapolate_to_endpoints": { + "default": false, + "type": "boolean" + }, + "sign": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "snap_contour_to_grid": { + "default": false, + "type": "boolean" + }, + "type": { + "default": "AxisAlignedCurrentIntegralSpec", + "enum": [ + "AxisAlignedCurrentIntegralSpec" + ], + "type": "string" + } + }, + "required": [ + "sign", + "size" + ], + "type": "object" + }, + "AxisAlignedVoltageIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "extrapolate_to_endpoints": { + "default": false, + "type": "boolean" + }, + "sign": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "snap_path_to_grid": { + "default": false, + "type": "boolean" + }, + "type": { + "default": "AxisAlignedVoltageIntegralSpec", + "enum": [ + "AxisAlignedVoltageIntegralSpec" + ], + "type": "string" + } + }, + "required": [ + "sign", + "size" + ], + "type": "object" + }, "BlochBoundary": { "additionalProperties": false, "properties": { @@ -1745,6 +2026,47 @@ ], "type": "object" }, + "CompositeCurrentIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "path_specs": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DCurrentIntegralSpec" + } + ] + }, + "type": "array" + }, + "sum_spec": { + "enum": [ + "split", + "sum" + ], + "type": "string" + }, + "type": { + "default": "CompositeCurrentIntegralSpec", + "enum": [ + "CompositeCurrentIntegralSpec" + ], + "type": "string" + } + }, + "required": [ + "path_specs", + "sum_spec" + ], + "type": "object" + }, "ConstantDoping": { "additionalProperties": false, "properties": { @@ -2024,61 +2346,133 @@ "type": "object" }, "type": { - "default": "ContourPathAveraging", + "default": "ContourPathAveraging", + "enum": [ + "ContourPathAveraging" + ], + "type": "string" + } + }, + "type": "object" + }, + "CornerFinderSpec": { + "additionalProperties": false, + "properties": { + "angle_threshold": { + "default": 0.3141592653589793, + "exclusiveMaximum": 3.141592653589793, + "minimum": 0, + "type": "number" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "concave_resolution": { + "exclusiveMinimum": 0, + "type": "integer" + }, + "convex_resolution": { + "exclusiveMinimum": 0, + "type": "integer" + }, + "distance_threshold": { + "exclusiveMinimum": 0, + "type": "number" + }, + "medium": { + "default": "metal", + "enum": [ + "all", + "dielectric", + "metal" + ], + "type": "string" + }, + "mixed_resolution": { + "exclusiveMinimum": 0, + "type": "integer" + }, + "type": { + "default": "CornerFinderSpec", + "enum": [ + "CornerFinderSpec" + ], + "type": "string" + } + }, + "type": "object" + }, + "Custom2DCurrentIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + }, + "position": { + "type": "number" + }, + "type": { + "default": "Custom2DCurrentIntegralSpec", "enum": [ - "ContourPathAveraging" + "Custom2DCurrentIntegralSpec" ], "type": "string" + }, + "vertices": { + "type": "ArrayLike" } }, + "required": [ + "axis", + "position", + "vertices" + ], "type": "object" }, - "CornerFinderSpec": { + "Custom2DVoltageIntegralSpec": { "additionalProperties": false, "properties": { - "angle_threshold": { - "default": 0.3141592653589793, - "exclusiveMaximum": 3.141592653589793, - "minimum": 0, - "type": "number" - }, "attrs": { "default": {}, "type": "object" }, - "concave_resolution": { - "exclusiveMinimum": 0, - "type": "integer" - }, - "convex_resolution": { - "exclusiveMinimum": 0, - "type": "integer" - }, - "distance_threshold": { - "exclusiveMinimum": 0, - "type": "number" - }, - "medium": { - "default": "metal", + "axis": { "enum": [ - "all", - "dielectric", - "metal" + 0, + 1, + 2 ], - "type": "string" - }, - "mixed_resolution": { - "exclusiveMinimum": 0, "type": "integer" }, + "position": { + "type": "number" + }, "type": { - "default": "CornerFinderSpec", + "default": "Custom2DVoltageIntegralSpec", "enum": [ - "CornerFinderSpec" + "Custom2DVoltageIntegralSpec" ], "type": "string" + }, + "vertices": { + "type": "ArrayLike" } }, + "required": [ + "axis", + "position", + "vertices" + ], "type": "object" }, "CustomAnisotropicMedium": { @@ -2962,6 +3356,46 @@ ], "type": "object" }, + "CustomImpedanceSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "current_spec": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/CompositeCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DCurrentIntegralSpec" + } + ] + }, + "type": { + "default": "CustomImpedanceSpec", + "enum": [ + "CustomImpedanceSpec" + ], + "type": "string" + }, + "voltage_spec": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedVoltageIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DVoltageIntegralSpec" + } + ] + } + }, + "type": "object" + }, "CustomLorentz": { "additionalProperties": false, "properties": { @@ -6678,6 +7112,137 @@ ], "type": "object" }, + "MicrowaveModeSpec": { + "additionalProperties": false, + "properties": { + "angle_phi": { + "default": 0.0, + "type": "number" + }, + "angle_rotation": { + "default": false, + "type": "boolean" + }, + "angle_theta": { + "default": 0.0, + "type": "number" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "bend_axis": { + "enum": [ + 0, + 1 + ], + "type": "integer" + }, + "bend_radius": { + "type": "number" + }, + "filter_pol": { + "enum": [ + "te", + "tm" + ], + "type": "string" + }, + "group_index_step": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "number" + }, + { + "type": "boolean" + } + ], + "default": false + }, + "impedance_specs": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/AutoImpedanceSpec" + }, + { + "$ref": "#/definitions/CustomImpedanceSpec" + } + ] + }, + "type": "array" + }, + { + "oneOf": [ + { + "$ref": "#/definitions/AutoImpedanceSpec" + }, + { + "$ref": "#/definitions/CustomImpedanceSpec" + } + ] + } + ] + }, + "num_modes": { + "default": 1, + "exclusiveMinimum": 0, + "type": "integer" + }, + "num_pml": { + "default": [ + 0, + 0 + ], + "items": [ + { + "minimum": 0, + "type": "integer" + }, + { + "minimum": 0, + "type": "integer" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "precision": { + "default": "double", + "enum": [ + "auto", + "double", + "single" + ], + "type": "string" + }, + "target_neff": { + "exclusiveMinimum": 0, + "type": "number" + }, + "track_freq": { + "default": "central", + "enum": [ + "central", + "highest", + "lowest" + ], + "type": "string" + }, + "type": { + "default": "MicrowaveModeSpec", + "enum": [ + "MicrowaveModeSpec" + ], + "type": "string" + } + }, + "type": "object" + }, "ModeABCBoundary": { "additionalProperties": false, "properties": { @@ -6702,11 +7267,6 @@ "type": "integer" }, "mode_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModeSpec" - } - ], "default": { "angle_phi": 0.0, "angle_rotation": false, @@ -6725,7 +7285,22 @@ "target_neff": null, "track_freq": "central", "type": "ModeSpec" - } + }, + "discriminator": { + "mapping": { + "MicrowaveModeSpec": "#/definitions/MicrowaveModeSpec", + "ModeSpec": "#/definitions/ModeSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + }, + { + "$ref": "#/definitions/ModeSpec" + } + ] }, "name": { "type": "string" @@ -7296,11 +7871,6 @@ "type": "integer" }, "mode_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModeSpec" - } - ], "default": { "angle_phi": 0.0, "angle_rotation": false, @@ -7319,7 +7889,22 @@ "target_neff": null, "track_freq": "central", "type": "ModeSpec" - } + }, + "discriminator": { + "mapping": { + "MicrowaveModeSpec": "#/definitions/MicrowaveModeSpec", + "ModeSpec": "#/definitions/ModeSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + }, + { + "$ref": "#/definitions/ModeSpec" + } + ] }, "name": { "type": "string" @@ -11530,7 +12115,17 @@ ] }, "mode_spec": { - "allOf": [ + "discriminator": { + "mapping": { + "MicrowaveModeSpec": "#/definitions/MicrowaveModeSpec", + "ModeSpec": "#/definitions/ModeSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + }, { "$ref": "#/definitions/ModeSpec" } diff --git a/schemas/Simulation.json b/schemas/Simulation.json index 0e4e86b99c..351ab489ec 100644 --- a/schemas/Simulation.json +++ b/schemas/Simulation.json @@ -894,6 +894,23 @@ }, "type": "object" }, + "AutoImpedanceSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "type": { + "default": "AutoImpedanceSpec", + "enum": [ + "AutoImpedanceSpec" + ], + "type": "string" + } + }, + "type": "object" + }, "AuxFieldTimeMonitor": { "additionalProperties": false, "properties": { @@ -1068,130 +1085,394 @@ ], "type": "object" }, - "BlochBoundary": { + "AxisAlignedCurrentIntegralSpec": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "bloch_vec": { - "type": "number" + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] }, - "name": { + "extrapolate_to_endpoints": { + "default": false, + "type": "boolean" + }, + "sign": { + "enum": [ + "+", + "-" + ], "type": "string" }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "snap_contour_to_grid": { + "default": false, + "type": "boolean" + }, "type": { - "default": "BlochBoundary", + "default": "AxisAlignedCurrentIntegralSpec", "enum": [ - "BlochBoundary" + "AxisAlignedCurrentIntegralSpec" ], "type": "string" } }, "required": [ - "bloch_vec" + "sign", + "size" ], "type": "object" }, - "Boundary": { + "AxisAlignedVoltageIntegralSpec": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "minus": { - "default": { - "attrs": {}, - "name": null, - "num_layers": 12, - "parameters": { - "alpha_max": 0.0, - "alpha_min": 0.0, - "alpha_order": 1, - "attrs": {}, - "kappa_max": 3.0, - "kappa_min": 1.0, - "kappa_order": 3, - "sigma_max": 1.5, - "sigma_min": 0.0, - "sigma_order": 3, - "type": "PMLParams" - }, - "type": "PML" - }, - "discriminator": { - "mapping": { - "ABCBoundary": "#/definitions/ABCBoundary", - "Absorber": "#/definitions/Absorber", - "BlochBoundary": "#/definitions/BlochBoundary", - "ModeABCBoundary": "#/definitions/ModeABCBoundary", - "PECBoundary": "#/definitions/PECBoundary", - "PMCBoundary": "#/definitions/PMCBoundary", - "PML": "#/definitions/PML", - "Periodic": "#/definitions/Periodic", - "StablePML": "#/definitions/StablePML" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/ABCBoundary" - }, - { - "$ref": "#/definitions/Absorber" - }, - { - "$ref": "#/definitions/BlochBoundary" - }, - { - "$ref": "#/definitions/ModeABCBoundary" - }, - { - "$ref": "#/definitions/PECBoundary" - }, - { - "$ref": "#/definitions/PMCBoundary" - }, - { - "$ref": "#/definitions/PML" - }, + "center": { + "anyOf": [ { - "$ref": "#/definitions/Periodic" + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" }, { - "$ref": "#/definitions/StablePML" + "type": "autograd.tracer.Box" } + ], + "default": [ + 0.0, + 0.0, + 0.0 ] }, - "plus": { - "default": { - "attrs": {}, - "name": null, - "num_layers": 12, - "parameters": { - "alpha_max": 0.0, - "alpha_min": 0.0, - "alpha_order": 1, - "attrs": {}, - "kappa_max": 3.0, - "kappa_min": 1.0, - "kappa_order": 3, - "sigma_max": 1.5, - "sigma_min": 0.0, - "sigma_order": 3, - "type": "PMLParams" - }, - "type": "PML" - }, - "discriminator": { - "mapping": { - "ABCBoundary": "#/definitions/ABCBoundary", - "Absorber": "#/definitions/Absorber", - "BlochBoundary": "#/definitions/BlochBoundary", - "ModeABCBoundary": "#/definitions/ModeABCBoundary", - "PECBoundary": "#/definitions/PECBoundary", + "extrapolate_to_endpoints": { + "default": false, + "type": "boolean" + }, + "sign": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "snap_path_to_grid": { + "default": false, + "type": "boolean" + }, + "type": { + "default": "AxisAlignedVoltageIntegralSpec", + "enum": [ + "AxisAlignedVoltageIntegralSpec" + ], + "type": "string" + } + }, + "required": [ + "sign", + "size" + ], + "type": "object" + }, + "BlochBoundary": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "bloch_vec": { + "type": "number" + }, + "name": { + "type": "string" + }, + "type": { + "default": "BlochBoundary", + "enum": [ + "BlochBoundary" + ], + "type": "string" + } + }, + "required": [ + "bloch_vec" + ], + "type": "object" + }, + "Boundary": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "minus": { + "default": { + "attrs": {}, + "name": null, + "num_layers": 12, + "parameters": { + "alpha_max": 0.0, + "alpha_min": 0.0, + "alpha_order": 1, + "attrs": {}, + "kappa_max": 3.0, + "kappa_min": 1.0, + "kappa_order": 3, + "sigma_max": 1.5, + "sigma_min": 0.0, + "sigma_order": 3, + "type": "PMLParams" + }, + "type": "PML" + }, + "discriminator": { + "mapping": { + "ABCBoundary": "#/definitions/ABCBoundary", + "Absorber": "#/definitions/Absorber", + "BlochBoundary": "#/definitions/BlochBoundary", + "ModeABCBoundary": "#/definitions/ModeABCBoundary", + "PECBoundary": "#/definitions/PECBoundary", + "PMCBoundary": "#/definitions/PMCBoundary", + "PML": "#/definitions/PML", + "Periodic": "#/definitions/Periodic", + "StablePML": "#/definitions/StablePML" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/ABCBoundary" + }, + { + "$ref": "#/definitions/Absorber" + }, + { + "$ref": "#/definitions/BlochBoundary" + }, + { + "$ref": "#/definitions/ModeABCBoundary" + }, + { + "$ref": "#/definitions/PECBoundary" + }, + { + "$ref": "#/definitions/PMCBoundary" + }, + { + "$ref": "#/definitions/PML" + }, + { + "$ref": "#/definitions/Periodic" + }, + { + "$ref": "#/definitions/StablePML" + } + ] + }, + "plus": { + "default": { + "attrs": {}, + "name": null, + "num_layers": 12, + "parameters": { + "alpha_max": 0.0, + "alpha_min": 0.0, + "alpha_order": 1, + "attrs": {}, + "kappa_max": 3.0, + "kappa_min": 1.0, + "kappa_order": 3, + "sigma_max": 1.5, + "sigma_min": 0.0, + "sigma_order": 3, + "type": "PMLParams" + }, + "type": "PML" + }, + "discriminator": { + "mapping": { + "ABCBoundary": "#/definitions/ABCBoundary", + "Absorber": "#/definitions/Absorber", + "BlochBoundary": "#/definitions/BlochBoundary", + "ModeABCBoundary": "#/definitions/ModeABCBoundary", + "PECBoundary": "#/definitions/PECBoundary", "PMCBoundary": "#/definitions/PMCBoundary", "PML": "#/definitions/PML", "Periodic": "#/definitions/Periodic", @@ -2122,6 +2403,47 @@ ], "type": "object" }, + "CompositeCurrentIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "path_specs": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DCurrentIntegralSpec" + } + ] + }, + "type": "array" + }, + "sum_spec": { + "enum": [ + "split", + "sum" + ], + "type": "string" + }, + "type": { + "default": "CompositeCurrentIntegralSpec", + "enum": [ + "CompositeCurrentIntegralSpec" + ], + "type": "string" + } + }, + "required": [ + "path_specs", + "sum_spec" + ], + "type": "object" + }, "ConstantDoping": { "additionalProperties": false, "properties": { @@ -2458,40 +2780,112 @@ }, "type": "object" }, - "CustomAnisotropicMedium": { + "Custom2DCurrentIntegralSpec": { "additionalProperties": false, "properties": { - "allow_gain": { - "type": "boolean" - }, "attrs": { "default": {}, "type": "object" }, - "frequency_range": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } + "axis": { + "enum": [ + 0, + 1, + 2 ], - "maxItems": 2, - "minItems": 2, - "type": "array" + "type": "integer" }, - "heat_spec": { - "discriminator": { - "mapping": { - "FluidMedium": "#/definitions/FluidMedium", - "FluidSpec": "#/definitions/FluidSpec", - "SolidMedium": "#/definitions/SolidMedium", - "SolidSpec": "#/definitions/SolidSpec" - }, - "propertyName": "type" - }, - "oneOf": [ + "position": { + "type": "number" + }, + "type": { + "default": "Custom2DCurrentIntegralSpec", + "enum": [ + "Custom2DCurrentIntegralSpec" + ], + "type": "string" + }, + "vertices": { + "type": "ArrayLike" + } + }, + "required": [ + "axis", + "position", + "vertices" + ], + "type": "object" + }, + "Custom2DVoltageIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + }, + "position": { + "type": "number" + }, + "type": { + "default": "Custom2DVoltageIntegralSpec", + "enum": [ + "Custom2DVoltageIntegralSpec" + ], + "type": "string" + }, + "vertices": { + "type": "ArrayLike" + } + }, + "required": [ + "axis", + "position", + "vertices" + ], + "type": "object" + }, + "CustomAnisotropicMedium": { + "additionalProperties": false, + "properties": { + "allow_gain": { + "type": "boolean" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "frequency_range": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "heat_spec": { + "discriminator": { + "mapping": { + "FluidMedium": "#/definitions/FluidMedium", + "FluidSpec": "#/definitions/FluidSpec", + "SolidMedium": "#/definitions/SolidMedium", + "SolidSpec": "#/definitions/SolidSpec" + }, + "propertyName": "type" + }, + "oneOf": [ { "$ref": "#/definitions/FluidMedium" }, @@ -3645,6 +4039,46 @@ ], "type": "object" }, + "CustomImpedanceSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "current_spec": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/CompositeCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DCurrentIntegralSpec" + } + ] + }, + "type": { + "default": "CustomImpedanceSpec", + "enum": [ + "CustomImpedanceSpec" + ], + "type": "string" + }, + "voltage_spec": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedVoltageIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DVoltageIntegralSpec" + } + ] + } + }, + "type": "object" + }, "CustomLorentz": { "additionalProperties": false, "properties": { @@ -10057,109 +10491,672 @@ "$ref": "#/definitions/SolidMedium" }, { - "$ref": "#/definitions/SolidSpec" + "$ref": "#/definitions/SolidSpec" + } + ] + }, + "background_permittivity": { + "minimum": 1.0, + "type": "number" + }, + "dl": { + "items": [ + { + "exclusiveMinimum": 0, + "type": "number" + }, + { + "exclusiveMinimum": 0, + "type": "number" + }, + { + "exclusiveMinimum": 0, + "type": "number" + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "drop_outside_sim": { + "default": true, + "type": "boolean" + }, + "enforce": { + "default": false, + "type": "boolean" + }, + "geometry": { + "discriminator": { + "mapping": { + "Box": "#/definitions/Box", + "ClipOperation": "#/definitions/ClipOperation", + "ComplexPolySlabBase": "#/definitions/ComplexPolySlabBase", + "Cylinder": "#/definitions/Cylinder", + "GeometryGroup": "#/definitions/GeometryGroup", + "PolySlab": "#/definitions/PolySlab", + "Sphere": "#/definitions/Sphere", + "Transformed": "#/definitions/Transformed", + "TriangleMesh": "#/definitions/TriangleMesh" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/Box" + }, + { + "$ref": "#/definitions/ClipOperation" + }, + { + "$ref": "#/definitions/ComplexPolySlabBase" + }, + { + "$ref": "#/definitions/Cylinder" + }, + { + "$ref": "#/definitions/GeometryGroup" + }, + { + "$ref": "#/definitions/PolySlab" + }, + { + "$ref": "#/definitions/Sphere" + }, + { + "$ref": "#/definitions/Transformed" + }, + { + "$ref": "#/definitions/TriangleMesh" + } + ] + }, + "name": { + "type": "string" + }, + "priority": { + "default": 0, + "type": "integer" + }, + "shadow": { + "default": true, + "type": "boolean" + }, + "type": { + "default": "MeshOverrideStructure", + "enum": [ + "MeshOverrideStructure" + ], + "type": "string" + } + }, + "required": [ + "dl", + "geometry" + ], + "type": "object" + }, + "MicrowaveModeMonitor": { + "additionalProperties": false, + "properties": { + "apodization": { + "allOf": [ + { + "$ref": "#/definitions/ApodizationSpec" + } + ], + "default": { + "attrs": {}, + "end": null, + "start": null, + "type": "ApodizationSpec", + "width": null + } + }, + "attrs": { + "default": {}, + "type": "object" + }, + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "colocate": { + "default": true, + "type": "boolean" + }, + "conjugated_dot_product": { + "default": true, + "type": "boolean" + }, + "freqs": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "ArrayLike" + } + ] + }, + "interval_space": { + "default": [ + 1, + 1, + 1 + ], + "items": [ + { + "enum": [ + 1 + ], + "type": "integer" + }, + { + "enum": [ + 1 + ], + "type": "integer" + }, + { + "enum": [ + 1 + ], + "type": "integer" + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "mode_spec": { + "allOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + } + ] + }, + "name": { + "minLength": 1, + "type": "string" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "store_fields_direction": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "type": { + "default": "MicrowaveModeMonitor", + "enum": [ + "MicrowaveModeMonitor" + ], + "type": "string" + } + }, + "required": [ + "freqs", + "name", + "size" + ], + "type": "object" + }, + "MicrowaveModeSolverMonitor": { + "additionalProperties": false, + "properties": { + "apodization": { + "allOf": [ + { + "$ref": "#/definitions/ApodizationSpec" + } + ], + "default": { + "attrs": {}, + "end": null, + "start": null, + "type": "ApodizationSpec", + "width": null + } + }, + "attrs": { + "default": {}, + "type": "object" + }, + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "colocate": { + "default": true, + "type": "boolean" + }, + "conjugated_dot_product": { + "default": true, + "type": "boolean" + }, + "direction": { + "default": "+", + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "fields": { + "default": [ + "Ex", + "Ey", + "Ez", + "Hx", + "Hy", + "Hz" + ], + "items": { + "enum": [ + "Ex", + "Ey", + "Ez", + "Hx", + "Hy", + "Hz" + ], + "type": "string" + }, + "type": "array" + }, + "freqs": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "ArrayLike" + } + ] + }, + "interval_space": { + "default": [ + 1, + 1, + 1 + ], + "items": [ + { + "enum": [ + 1 + ], + "type": "integer" + }, + { + "enum": [ + 1 + ], + "type": "integer" + }, + { + "enum": [ + 1 + ], + "type": "integer" + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "mode_spec": { + "allOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + } + ] + }, + "name": { + "minLength": 1, + "type": "string" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" } ] }, - "background_permittivity": { - "minimum": 1.0, + "store_fields_direction": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "type": { + "default": "MicrowaveModeSolverMonitor", + "enum": [ + "MicrowaveModeSolverMonitor" + ], + "type": "string" + } + }, + "required": [ + "freqs", + "name", + "size" + ], + "type": "object" + }, + "MicrowaveModeSpec": { + "additionalProperties": false, + "properties": { + "angle_phi": { + "default": 0.0, "type": "number" }, - "dl": { - "items": [ - { - "exclusiveMinimum": 0, - "type": "number" - }, + "angle_rotation": { + "default": false, + "type": "boolean" + }, + "angle_theta": { + "default": 0.0, + "type": "number" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "bend_axis": { + "enum": [ + 0, + 1 + ], + "type": "integer" + }, + "bend_radius": { + "type": "number" + }, + "filter_pol": { + "enum": [ + "te", + "tm" + ], + "type": "string" + }, + "group_index_step": { + "anyOf": [ { "exclusiveMinimum": 0, "type": "number" }, { - "exclusiveMinimum": 0, - "type": "number" + "type": "boolean" } ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - "drop_outside_sim": { - "default": true, - "type": "boolean" - }, - "enforce": { - "default": false, - "type": "boolean" + "default": false }, - "geometry": { - "discriminator": { - "mapping": { - "Box": "#/definitions/Box", - "ClipOperation": "#/definitions/ClipOperation", - "ComplexPolySlabBase": "#/definitions/ComplexPolySlabBase", - "Cylinder": "#/definitions/Cylinder", - "GeometryGroup": "#/definitions/GeometryGroup", - "PolySlab": "#/definitions/PolySlab", - "Sphere": "#/definitions/Sphere", - "Transformed": "#/definitions/Transformed", - "TriangleMesh": "#/definitions/TriangleMesh" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/Box" - }, - { - "$ref": "#/definitions/ClipOperation" - }, - { - "$ref": "#/definitions/ComplexPolySlabBase" - }, - { - "$ref": "#/definitions/Cylinder" - }, - { - "$ref": "#/definitions/GeometryGroup" - }, + "impedance_specs": { + "anyOf": [ { - "$ref": "#/definitions/PolySlab" + "items": { + "anyOf": [ + { + "$ref": "#/definitions/AutoImpedanceSpec" + }, + { + "$ref": "#/definitions/CustomImpedanceSpec" + } + ] + }, + "type": "array" }, { - "$ref": "#/definitions/Sphere" - }, + "oneOf": [ + { + "$ref": "#/definitions/AutoImpedanceSpec" + }, + { + "$ref": "#/definitions/CustomImpedanceSpec" + } + ] + } + ] + }, + "num_modes": { + "default": 1, + "exclusiveMinimum": 0, + "type": "integer" + }, + "num_pml": { + "default": [ + 0, + 0 + ], + "items": [ { - "$ref": "#/definitions/Transformed" + "minimum": 0, + "type": "integer" }, { - "$ref": "#/definitions/TriangleMesh" + "minimum": 0, + "type": "integer" } - ] + ], + "maxItems": 2, + "minItems": 2, + "type": "array" }, - "name": { + "precision": { + "default": "double", + "enum": [ + "auto", + "double", + "single" + ], "type": "string" }, - "priority": { - "default": 0, - "type": "integer" + "target_neff": { + "exclusiveMinimum": 0, + "type": "number" }, - "shadow": { - "default": true, - "type": "boolean" + "track_freq": { + "default": "central", + "enum": [ + "central", + "highest", + "lowest" + ], + "type": "string" }, "type": { - "default": "MeshOverrideStructure", + "default": "MicrowaveModeSpec", "enum": [ - "MeshOverrideStructure" + "MicrowaveModeSpec" ], "type": "string" } }, - "required": [ - "dl", - "geometry" - ], "type": "object" }, "ModeABCBoundary": { @@ -10186,11 +11183,6 @@ "type": "integer" }, "mode_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModeSpec" - } - ], "default": { "angle_phi": 0.0, "angle_rotation": false, @@ -10209,7 +11201,22 @@ "target_neff": null, "track_freq": "central", "type": "ModeSpec" - } + }, + "discriminator": { + "mapping": { + "MicrowaveModeSpec": "#/definitions/MicrowaveModeSpec", + "ModeSpec": "#/definitions/ModeSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + }, + { + "$ref": "#/definitions/ModeSpec" + } + ] }, "name": { "type": "string" @@ -10780,11 +11787,6 @@ "type": "integer" }, "mode_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModeSpec" - } - ], "default": { "angle_phi": 0.0, "angle_rotation": false, @@ -10803,7 +11805,22 @@ "target_neff": null, "track_freq": "central", "type": "ModeSpec" - } + }, + "discriminator": { + "mapping": { + "MicrowaveModeSpec": "#/definitions/MicrowaveModeSpec", + "ModeSpec": "#/definitions/ModeSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + }, + { + "$ref": "#/definitions/ModeSpec" + } + ] }, "name": { "type": "string" @@ -15682,6 +16699,8 @@ "FluxMonitor": "#/definitions/FluxMonitor", "FluxTimeMonitor": "#/definitions/FluxTimeMonitor", "MediumMonitor": "#/definitions/MediumMonitor", + "MicrowaveModeMonitor": "#/definitions/MicrowaveModeMonitor", + "MicrowaveModeSolverMonitor": "#/definitions/MicrowaveModeSolverMonitor", "ModeMonitor": "#/definitions/ModeMonitor", "ModeSolverMonitor": "#/definitions/ModeSolverMonitor", "PermittivityMonitor": "#/definitions/PermittivityMonitor" @@ -15722,6 +16741,12 @@ { "$ref": "#/definitions/MediumMonitor" }, + { + "$ref": "#/definitions/MicrowaveModeMonitor" + }, + { + "$ref": "#/definitions/MicrowaveModeSolverMonitor" + }, { "$ref": "#/definitions/ModeMonitor" }, diff --git a/schemas/TerminalComponentModeler.json b/schemas/TerminalComponentModeler.json index 8fe6ce3d51..5a42562d20 100644 --- a/schemas/TerminalComponentModeler.json +++ b/schemas/TerminalComponentModeler.json @@ -894,6 +894,23 @@ }, "type": "object" }, + "AutoImpedanceSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "type": { + "default": "AutoImpedanceSpec", + "enum": [ + "AutoImpedanceSpec" + ], + "type": "string" + } + }, + "type": "object" + }, "AuxFieldTimeMonitor": { "additionalProperties": false, "properties": { @@ -1068,379 +1085,239 @@ ], "type": "object" }, - "BlochBoundary": { + "AxisAlignedCurrentIntegral": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "bloch_vec": { - "type": "number" + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] }, - "name": { + "extrapolate_to_endpoints": { + "default": false, + "type": "boolean" + }, + "sign": { + "enum": [ + "+", + "-" + ], "type": "string" }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "snap_contour_to_grid": { + "default": false, + "type": "boolean" + }, "type": { - "default": "BlochBoundary", + "default": "AxisAlignedCurrentIntegral", "enum": [ - "BlochBoundary" + "AxisAlignedCurrentIntegral" ], "type": "string" } }, "required": [ - "bloch_vec" + "sign", + "size" ], "type": "object" }, - "Boundary": { + "AxisAlignedCurrentIntegralSpec": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "minus": { - "default": { - "attrs": {}, - "name": null, - "num_layers": 12, - "parameters": { - "alpha_max": 0.0, - "alpha_min": 0.0, - "alpha_order": 1, - "attrs": {}, - "kappa_max": 3.0, - "kappa_min": 1.0, - "kappa_order": 3, - "sigma_max": 1.5, - "sigma_min": 0.0, - "sigma_order": 3, - "type": "PMLParams" - }, - "type": "PML" - }, - "discriminator": { - "mapping": { - "ABCBoundary": "#/definitions/ABCBoundary", - "Absorber": "#/definitions/Absorber", - "BlochBoundary": "#/definitions/BlochBoundary", - "ModeABCBoundary": "#/definitions/ModeABCBoundary", - "PECBoundary": "#/definitions/PECBoundary", - "PMCBoundary": "#/definitions/PMCBoundary", - "PML": "#/definitions/PML", - "Periodic": "#/definitions/Periodic", - "StablePML": "#/definitions/StablePML" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/ABCBoundary" - }, - { - "$ref": "#/definitions/Absorber" - }, - { - "$ref": "#/definitions/BlochBoundary" - }, - { - "$ref": "#/definitions/ModeABCBoundary" - }, - { - "$ref": "#/definitions/PECBoundary" - }, - { - "$ref": "#/definitions/PMCBoundary" - }, - { - "$ref": "#/definitions/PML" - }, + "center": { + "anyOf": [ { - "$ref": "#/definitions/Periodic" + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" }, { - "$ref": "#/definitions/StablePML" + "type": "autograd.tracer.Box" } + ], + "default": [ + 0.0, + 0.0, + 0.0 ] }, - "plus": { - "default": { - "attrs": {}, - "name": null, - "num_layers": 12, - "parameters": { - "alpha_max": 0.0, - "alpha_min": 0.0, - "alpha_order": 1, - "attrs": {}, - "kappa_max": 3.0, - "kappa_min": 1.0, - "kappa_order": 3, - "sigma_max": 1.5, - "sigma_min": 0.0, - "sigma_order": 3, - "type": "PMLParams" - }, - "type": "PML" - }, - "discriminator": { - "mapping": { - "ABCBoundary": "#/definitions/ABCBoundary", - "Absorber": "#/definitions/Absorber", - "BlochBoundary": "#/definitions/BlochBoundary", - "ModeABCBoundary": "#/definitions/ModeABCBoundary", - "PECBoundary": "#/definitions/PECBoundary", - "PMCBoundary": "#/definitions/PMCBoundary", - "PML": "#/definitions/PML", - "Periodic": "#/definitions/Periodic", - "StablePML": "#/definitions/StablePML" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/ABCBoundary" - }, - { - "$ref": "#/definitions/Absorber" - }, - { - "$ref": "#/definitions/BlochBoundary" - }, - { - "$ref": "#/definitions/ModeABCBoundary" - }, - { - "$ref": "#/definitions/PECBoundary" - }, - { - "$ref": "#/definitions/PMCBoundary" - }, - { - "$ref": "#/definitions/PML" - }, - { - "$ref": "#/definitions/Periodic" - }, - { - "$ref": "#/definitions/StablePML" - } - ] - }, - "type": { - "default": "Boundary", - "enum": [ - "Boundary" - ], - "type": "string" - } - }, - "type": "object" - }, - "BoundarySpec": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" + "extrapolate_to_endpoints": { + "default": false, + "type": "boolean" }, - "type": { - "default": "BoundarySpec", + "sign": { "enum": [ - "BoundarySpec" + "+", + "-" ], "type": "string" }, - "x": { - "allOf": [ - { - "$ref": "#/definitions/Boundary" - } - ], - "default": { - "attrs": {}, - "minus": { - "attrs": {}, - "name": null, - "num_layers": 12, - "parameters": { - "alpha_max": 0.0, - "alpha_min": 0.0, - "alpha_order": 1, - "attrs": {}, - "kappa_max": 3.0, - "kappa_min": 1.0, - "kappa_order": 3, - "sigma_max": 1.5, - "sigma_min": 0.0, - "sigma_order": 3, - "type": "PMLParams" - }, - "type": "PML" - }, - "plus": { - "attrs": {}, - "name": null, - "num_layers": 12, - "parameters": { - "alpha_max": 0.0, - "alpha_min": 0.0, - "alpha_order": 1, - "attrs": {}, - "kappa_max": 3.0, - "kappa_min": 1.0, - "kappa_order": 3, - "sigma_max": 1.5, - "sigma_min": 0.0, - "sigma_order": 3, - "type": "PMLParams" - }, - "type": "PML" - }, - "type": "Boundary" - } - }, - "y": { - "allOf": [ - { - "$ref": "#/definitions/Boundary" - } - ], - "default": { - "attrs": {}, - "minus": { - "attrs": {}, - "name": null, - "num_layers": 12, - "parameters": { - "alpha_max": 0.0, - "alpha_min": 0.0, - "alpha_order": 1, - "attrs": {}, - "kappa_max": 3.0, - "kappa_min": 1.0, - "kappa_order": 3, - "sigma_max": 1.5, - "sigma_min": 0.0, - "sigma_order": 3, - "type": "PMLParams" - }, - "type": "PML" - }, - "plus": { - "attrs": {}, - "name": null, - "num_layers": 12, - "parameters": { - "alpha_max": 0.0, - "alpha_min": 0.0, - "alpha_order": 1, - "attrs": {}, - "kappa_max": 3.0, - "kappa_min": 1.0, - "kappa_order": 3, - "sigma_max": 1.5, - "sigma_min": 0.0, - "sigma_order": 3, - "type": "PMLParams" - }, - "type": "PML" - }, - "type": "Boundary" - } - }, - "z": { - "allOf": [ - { - "$ref": "#/definitions/Boundary" - } - ], - "default": { - "attrs": {}, - "minus": { - "attrs": {}, - "name": null, - "num_layers": 12, - "parameters": { - "alpha_max": 0.0, - "alpha_min": 0.0, - "alpha_order": 1, - "attrs": {}, - "kappa_max": 3.0, - "kappa_min": 1.0, - "kappa_order": 3, - "sigma_max": 1.5, - "sigma_min": 0.0, - "sigma_order": 3, - "type": "PMLParams" - }, - "type": "PML" - }, - "plus": { - "attrs": {}, - "name": null, - "num_layers": 12, - "parameters": { - "alpha_max": 0.0, - "alpha_min": 0.0, - "alpha_order": 1, - "attrs": {}, - "kappa_max": 3.0, - "kappa_min": 1.0, - "kappa_order": 3, - "sigma_max": 1.5, - "sigma_min": 0.0, - "sigma_order": 3, - "type": "PMLParams" - }, - "type": "PML" - }, - "type": "Boundary" - } - } - }, - "type": "object" - }, - "Box": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "center": { + "size": { "anyOf": [ { "items": [ { "anyOf": [ { - "type": "autograd.tracer.Box" + "minimum": 0, + "type": "number" }, { - "type": "number" + "type": "autograd.tracer.Box" } ] }, { "anyOf": [ { - "type": "autograd.tracer.Box" + "minimum": 0, + "type": "number" }, { - "type": "number" + "type": "autograd.tracer.Box" } ] }, { "anyOf": [ { - "type": "autograd.tracer.Box" + "minimum": 0, + "type": "number" }, { - "type": "number" + "type": "autograd.tracer.Box" } ] } @@ -1452,24 +1329,104 @@ { "type": "autograd.tracer.Box" } - ], - "default": [ - 0.0, - 0.0, - 0.0 ] }, - "size": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { + "snap_contour_to_grid": { + "default": false, + "type": "boolean" + }, + "type": { + "default": "AxisAlignedCurrentIntegralSpec", + "enum": [ + "AxisAlignedCurrentIntegralSpec" + ], + "type": "string" + } + }, + "required": [ + "sign", + "size" + ], + "type": "object" + }, + "AxisAlignedVoltageIntegral": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "extrapolate_to_endpoints": { + "default": false, + "type": "boolean" + }, + "sign": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { "type": "autograd.tracer.Box" } ] @@ -1506,438 +1463,492 @@ } ] }, + "snap_path_to_grid": { + "default": false, + "type": "boolean" + }, "type": { - "default": "Box", + "default": "AxisAlignedVoltageIntegral", "enum": [ - "Box" + "AxisAlignedVoltageIntegral" ], "type": "string" } }, "required": [ + "sign", "size" ], "type": "object" }, - "BroadbandModeABCFitterParam": { + "AxisAlignedVoltageIntegralSpec": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "frequency_sampling_points": { - "default": 15, - "exclusiveMinimum": 0, - "maximum": 101, - "type": "integer" - }, - "max_num_poles": { - "default": 5, - "exclusiveMinimum": 0, - "maximum": 10, - "type": "integer" + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] }, - "tolerance_rms": { - "default": 1e-06, - "minimum": 0, - "type": "number" + "extrapolate_to_endpoints": { + "default": false, + "type": "boolean" }, - "type": { - "default": "BroadbandModeABCFitterParam", + "sign": { "enum": [ - "BroadbandModeABCFitterParam" + "+", + "-" ], "type": "string" - } - }, - "type": "object" - }, - "BroadbandModeABCSpec": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "fit_param": { - "allOf": [ - { - "$ref": "#/definitions/BroadbandModeABCFitterParam" - } - ], - "default": { - "attrs": {}, - "frequency_sampling_points": 15, - "max_num_poles": 5, - "tolerance_rms": 1e-06, - "type": "BroadbandModeABCFitterParam" - } }, - "frequency_range": { - "items": [ + "size": { + "anyOf": [ { - "type": "number" + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" }, { - "type": "number" + "type": "autograd.tracer.Box" } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" + ] + }, + "snap_path_to_grid": { + "default": false, + "type": "boolean" }, "type": { - "default": "BroadbandModeABCSpec", + "default": "AxisAlignedVoltageIntegralSpec", "enum": [ - "BroadbandModeABCSpec" + "AxisAlignedVoltageIntegralSpec" ], "type": "string" } }, "required": [ - "frequency_range" + "sign", + "size" ], "type": "object" }, - "CaugheyThomasMobility": { + "BlochBoundary": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "exp_1": { - "type": "number" - }, - "exp_2": { - "type": "number" - }, - "exp_3": { - "type": "number" - }, - "exp_4": { - "type": "number" - }, - "exp_N": { - "exclusiveMinimum": 0, - "type": "number" - }, - "mu": { - "exclusiveMinimum": 0, - "type": "number" - }, - "mu_min": { - "exclusiveMinimum": 0, + "bloch_vec": { "type": "number" }, - "ref_N": { - "exclusiveMinimum": 0, - "type": "number" + "name": { + "type": "string" }, "type": { - "default": "CaugheyThomasMobility", + "default": "BlochBoundary", "enum": [ - "CaugheyThomasMobility" + "BlochBoundary" ], "type": "string" } }, "required": [ - "exp_1", - "exp_2", - "exp_3", - "exp_4", - "exp_N", - "mu", - "mu_min", - "ref_N" + "bloch_vec" ], "type": "object" }, - "ChargeConductorMedium": { + "Boundary": { "additionalProperties": false, "properties": { - "allow_gain": { - "default": false, - "type": "boolean" - }, "attrs": { "default": {}, "type": "object" }, - "conductivity": { - "exclusiveMinimum": 0, - "type": "number" - }, - "frequency_range": { - "items": [ - { - "type": "number" + "minus": { + "default": { + "attrs": {}, + "name": null, + "num_layers": 12, + "parameters": { + "alpha_max": 0.0, + "alpha_min": 0.0, + "alpha_order": 1, + "attrs": {}, + "kappa_max": 3.0, + "kappa_min": 1.0, + "kappa_order": 3, + "sigma_max": 1.5, + "sigma_min": 0.0, + "sigma_order": 3, + "type": "PMLParams" }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "heat_spec": { + "type": "PML" + }, "discriminator": { "mapping": { - "FluidMedium": "#/definitions/FluidMedium", - "FluidSpec": "#/definitions/FluidSpec", - "SolidMedium": "#/definitions/SolidMedium", - "SolidSpec": "#/definitions/SolidSpec" + "ABCBoundary": "#/definitions/ABCBoundary", + "Absorber": "#/definitions/Absorber", + "BlochBoundary": "#/definitions/BlochBoundary", + "ModeABCBoundary": "#/definitions/ModeABCBoundary", + "PECBoundary": "#/definitions/PECBoundary", + "PMCBoundary": "#/definitions/PMCBoundary", + "PML": "#/definitions/PML", + "Periodic": "#/definitions/Periodic", + "StablePML": "#/definitions/StablePML" }, "propertyName": "type" }, "oneOf": [ { - "$ref": "#/definitions/FluidMedium" + "$ref": "#/definitions/ABCBoundary" }, { - "$ref": "#/definitions/FluidSpec" + "$ref": "#/definitions/Absorber" }, { - "$ref": "#/definitions/SolidMedium" + "$ref": "#/definitions/BlochBoundary" }, { - "$ref": "#/definitions/SolidSpec" - } - ] - }, - "modulation_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModulationSpec" - } - ] - }, - "name": { - "type": "string" - }, - "nonlinear_spec": { - "anyOf": [ + "$ref": "#/definitions/ModeABCBoundary" + }, { - "$ref": "#/definitions/NonlinearSpec" + "$ref": "#/definitions/PECBoundary" }, { - "$ref": "#/definitions/NonlinearSusceptibility" - } - ] - }, - "permittivity": { - "default": 1.0, - "minimum": 1.0, - "type": "number" - }, - "type": { - "default": "ChargeConductorMedium", - "enum": [ - "ChargeConductorMedium" - ], - "type": "string" - }, - "viz_spec": { - "allOf": [ + "$ref": "#/definitions/PMCBoundary" + }, { - "$ref": "#/definitions/VisualizationSpec" - } - ] - } - }, - "required": [ - "conductivity" - ], - "type": "object" - }, - "ChargeInsulatorMedium": { - "additionalProperties": false, - "properties": { - "allow_gain": { - "default": false, - "type": "boolean" - }, - "attrs": { - "default": {}, - "type": "object" - }, - "frequency_range": { - "items": [ + "$ref": "#/definitions/PML" + }, { - "type": "number" + "$ref": "#/definitions/Periodic" }, { - "type": "number" + "$ref": "#/definitions/StablePML" } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" + ] }, - "heat_spec": { + "plus": { + "default": { + "attrs": {}, + "name": null, + "num_layers": 12, + "parameters": { + "alpha_max": 0.0, + "alpha_min": 0.0, + "alpha_order": 1, + "attrs": {}, + "kappa_max": 3.0, + "kappa_min": 1.0, + "kappa_order": 3, + "sigma_max": 1.5, + "sigma_min": 0.0, + "sigma_order": 3, + "type": "PMLParams" + }, + "type": "PML" + }, "discriminator": { "mapping": { - "FluidMedium": "#/definitions/FluidMedium", - "FluidSpec": "#/definitions/FluidSpec", - "SolidMedium": "#/definitions/SolidMedium", - "SolidSpec": "#/definitions/SolidSpec" + "ABCBoundary": "#/definitions/ABCBoundary", + "Absorber": "#/definitions/Absorber", + "BlochBoundary": "#/definitions/BlochBoundary", + "ModeABCBoundary": "#/definitions/ModeABCBoundary", + "PECBoundary": "#/definitions/PECBoundary", + "PMCBoundary": "#/definitions/PMCBoundary", + "PML": "#/definitions/PML", + "Periodic": "#/definitions/Periodic", + "StablePML": "#/definitions/StablePML" }, "propertyName": "type" }, "oneOf": [ { - "$ref": "#/definitions/FluidMedium" + "$ref": "#/definitions/ABCBoundary" }, { - "$ref": "#/definitions/FluidSpec" + "$ref": "#/definitions/Absorber" }, { - "$ref": "#/definitions/SolidMedium" + "$ref": "#/definitions/BlochBoundary" }, { - "$ref": "#/definitions/SolidSpec" - } - ] - }, - "modulation_spec": { - "allOf": [ + "$ref": "#/definitions/ModeABCBoundary" + }, { - "$ref": "#/definitions/ModulationSpec" - } - ] - }, - "name": { - "type": "string" - }, - "nonlinear_spec": { - "anyOf": [ + "$ref": "#/definitions/PECBoundary" + }, { - "$ref": "#/definitions/NonlinearSpec" + "$ref": "#/definitions/PMCBoundary" }, { - "$ref": "#/definitions/NonlinearSusceptibility" + "$ref": "#/definitions/PML" + }, + { + "$ref": "#/definitions/Periodic" + }, + { + "$ref": "#/definitions/StablePML" } ] }, - "permittivity": { - "default": 1.0, - "minimum": 1.0, - "type": "number" - }, "type": { - "default": "ChargeInsulatorMedium", + "default": "Boundary", "enum": [ - "ChargeInsulatorMedium" + "Boundary" ], "type": "string" - }, - "viz_spec": { - "allOf": [ - { - "$ref": "#/definitions/VisualizationSpec" - } - ] } }, "type": "object" }, - "ClipOperation": { + "BoundarySpec": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "geometry_a": { - "anyOf": [ - { - "$ref": "#/definitions/Box" - }, - { - "$ref": "#/definitions/ClipOperation" - }, - { - "$ref": "#/definitions/ComplexPolySlabBase" - }, - { - "$ref": "#/definitions/Cylinder" - }, - { - "$ref": "#/definitions/GeometryGroup" - }, - { - "$ref": "#/definitions/PolySlab" - }, - { - "$ref": "#/definitions/Sphere" - }, - { - "$ref": "#/definitions/Transformed" - }, - { - "$ref": "#/definitions/TriangleMesh" - } - ] + "type": { + "default": "BoundarySpec", + "enum": [ + "BoundarySpec" + ], + "type": "string" }, - "geometry_b": { - "anyOf": [ - { - "$ref": "#/definitions/Box" - }, - { - "$ref": "#/definitions/ClipOperation" - }, - { - "$ref": "#/definitions/ComplexPolySlabBase" - }, - { - "$ref": "#/definitions/Cylinder" - }, + "x": { + "allOf": [ { - "$ref": "#/definitions/GeometryGroup" + "$ref": "#/definitions/Boundary" + } + ], + "default": { + "attrs": {}, + "minus": { + "attrs": {}, + "name": null, + "num_layers": 12, + "parameters": { + "alpha_max": 0.0, + "alpha_min": 0.0, + "alpha_order": 1, + "attrs": {}, + "kappa_max": 3.0, + "kappa_min": 1.0, + "kappa_order": 3, + "sigma_max": 1.5, + "sigma_min": 0.0, + "sigma_order": 3, + "type": "PMLParams" + }, + "type": "PML" }, - { - "$ref": "#/definitions/PolySlab" + "plus": { + "attrs": {}, + "name": null, + "num_layers": 12, + "parameters": { + "alpha_max": 0.0, + "alpha_min": 0.0, + "alpha_order": 1, + "attrs": {}, + "kappa_max": 3.0, + "kappa_min": 1.0, + "kappa_order": 3, + "sigma_max": 1.5, + "sigma_min": 0.0, + "sigma_order": 3, + "type": "PMLParams" + }, + "type": "PML" }, + "type": "Boundary" + } + }, + "y": { + "allOf": [ { - "$ref": "#/definitions/Sphere" + "$ref": "#/definitions/Boundary" + } + ], + "default": { + "attrs": {}, + "minus": { + "attrs": {}, + "name": null, + "num_layers": 12, + "parameters": { + "alpha_max": 0.0, + "alpha_min": 0.0, + "alpha_order": 1, + "attrs": {}, + "kappa_max": 3.0, + "kappa_min": 1.0, + "kappa_order": 3, + "sigma_max": 1.5, + "sigma_min": 0.0, + "sigma_order": 3, + "type": "PMLParams" + }, + "type": "PML" }, - { - "$ref": "#/definitions/Transformed" + "plus": { + "attrs": {}, + "name": null, + "num_layers": 12, + "parameters": { + "alpha_max": 0.0, + "alpha_min": 0.0, + "alpha_order": 1, + "attrs": {}, + "kappa_max": 3.0, + "kappa_min": 1.0, + "kappa_order": 3, + "sigma_max": 1.5, + "sigma_min": 0.0, + "sigma_order": 3, + "type": "PMLParams" + }, + "type": "PML" }, + "type": "Boundary" + } + }, + "z": { + "allOf": [ { - "$ref": "#/definitions/TriangleMesh" + "$ref": "#/definitions/Boundary" } - ] - }, - "operation": { - "enum": [ - "difference", - "intersection", - "symmetric_difference", - "union" - ], - "type": "string" - }, - "type": { - "default": "ClipOperation", - "enum": [ - "ClipOperation" ], - "type": "string" + "default": { + "attrs": {}, + "minus": { + "attrs": {}, + "name": null, + "num_layers": 12, + "parameters": { + "alpha_max": 0.0, + "alpha_min": 0.0, + "alpha_order": 1, + "attrs": {}, + "kappa_max": 3.0, + "kappa_min": 1.0, + "kappa_order": 3, + "sigma_max": 1.5, + "sigma_min": 0.0, + "sigma_order": 3, + "type": "PMLParams" + }, + "type": "PML" + }, + "plus": { + "attrs": {}, + "name": null, + "num_layers": 12, + "parameters": { + "alpha_max": 0.0, + "alpha_min": 0.0, + "alpha_order": 1, + "attrs": {}, + "kappa_max": 3.0, + "kappa_min": 1.0, + "kappa_order": 3, + "sigma_max": 1.5, + "sigma_min": 0.0, + "sigma_order": 3, + "type": "PMLParams" + }, + "type": "PML" + }, + "type": "Boundary" + } } }, - "required": [ - "geometry_a", - "geometry_b", - "operation" - ], "type": "object" }, - "CoaxialLumpedPort": { + "Box": { "additionalProperties": false, "properties": { "attrs": { @@ -1945,624 +1956,637 @@ "type": "object" }, "center": { - "default": [ - 0.0, - 0.0, - 0.0 - ], - "items": [ - { - "type": "number" - }, - { - "type": "number" - }, + "anyOf": [ { - "type": "number" - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - "direction": { - "enum": [ - "+", - "-" - ], - "type": "string" - }, - "enable_snapping_points": { - "default": true, - "type": "boolean" + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] }, - "impedance": { + "size": { "anyOf": [ { - "$ref": "#/definitions/ComplexNumber" - }, - { - "properties": { - "imag": { - "type": "number" + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] }, - "real": { - "type": "number" + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] } - }, - "required": [ - "imag", - "real" ], - "type": "object" + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" } - ], - "default": 50 - }, - "inner_diameter": { - "exclusiveMinimum": 0, - "type": "number" - }, - "name": { - "minLength": 1, - "type": "string" + ] }, - "normal_axis": { + "type": { + "default": "Box", "enum": [ - 0, - 1, - 2 + "Box" ], - "type": "integer" + "type": "string" + } + }, + "required": [ + "size" + ], + "type": "object" + }, + "BroadbandModeABCFitterParam": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" }, - "num_grid_cells": { - "default": 3, + "frequency_sampling_points": { + "default": 15, "exclusiveMinimum": 0, + "maximum": 101, "type": "integer" }, - "outer_diameter": { + "max_num_poles": { + "default": 5, "exclusiveMinimum": 0, + "maximum": 10, + "type": "integer" + }, + "tolerance_rms": { + "default": 1e-06, + "minimum": 0, "type": "number" }, "type": { - "default": "CoaxialLumpedPort", + "default": "BroadbandModeABCFitterParam", "enum": [ - "CoaxialLumpedPort" + "BroadbandModeABCFitterParam" ], "type": "string" } }, - "required": [ - "direction", - "inner_diameter", - "name", - "normal_axis", - "outer_diameter" - ], "type": "object" }, - "CoaxialLumpedResistor": { + "BroadbandModeABCSpec": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "center": { - "default": [ - 0.0, - 0.0, - 0.0 + "fit_param": { + "allOf": [ + { + "$ref": "#/definitions/BroadbandModeABCFitterParam" + } ], + "default": { + "attrs": {}, + "frequency_sampling_points": 15, + "max_num_poles": 5, + "tolerance_rms": 1e-06, + "type": "BroadbandModeABCFitterParam" + } + }, + "frequency_range": { "items": [ { "type": "number" }, - { - "type": "number" - }, { "type": "number" } ], - "maxItems": 3, - "minItems": 3, + "maxItems": 2, + "minItems": 2, "type": "array" }, - "enable_snapping_points": { - "default": true, - "type": "boolean" + "type": { + "default": "BroadbandModeABCSpec", + "enum": [ + "BroadbandModeABCSpec" + ], + "type": "string" + } + }, + "required": [ + "frequency_range" + ], + "type": "object" + }, + "CaugheyThomasMobility": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" }, - "inner_diameter": { - "exclusiveMinimum": 0, + "exp_1": { "type": "number" }, - "name": { - "minLength": 1, - "type": "string" + "exp_2": { + "type": "number" }, - "normal_axis": { - "enum": [ - 0, - 1, - 2 - ], - "type": "integer" + "exp_3": { + "type": "number" }, - "num_grid_cells": { - "default": 1, + "exp_4": { + "type": "number" + }, + "exp_N": { "exclusiveMinimum": 0, - "type": "integer" + "type": "number" }, - "outer_diameter": { + "mu": { "exclusiveMinimum": 0, "type": "number" }, - "resistance": { + "mu_min": { "exclusiveMinimum": 0, - "type": "number", - "unit": "ohm" + "type": "number" + }, + "ref_N": { + "exclusiveMinimum": 0, + "type": "number" }, "type": { - "default": "CoaxialLumpedResistor", + "default": "CaugheyThomasMobility", "enum": [ - "CoaxialLumpedResistor" + "CaugheyThomasMobility" ], "type": "string" } }, "required": [ - "inner_diameter", - "name", - "normal_axis", - "outer_diameter", - "resistance" - ], - "type": "object" - }, - "ComplexNumber": { - "properties": { - "imag": { - "type": "number" - }, - "real": { - "type": "number" - } - }, - "required": [ - "imag", - "real" + "exp_1", + "exp_2", + "exp_3", + "exp_4", + "exp_N", + "mu", + "mu_min", + "ref_N" ], "type": "object" }, - "ComplexPolySlabBase": { + "ChargeConductorMedium": { "additionalProperties": false, "properties": { + "allow_gain": { + "default": false, + "type": "boolean" + }, "attrs": { "default": {}, "type": "object" }, - "axis": { - "default": 2, - "enum": [ - 0, - 1, - 2 - ], - "type": "integer" - }, - "dilation": { - "default": 0.0, + "conductivity": { + "exclusiveMinimum": 0, "type": "number" }, - "reference_plane": { - "default": "middle", - "enum": [ - "bottom", - "middle", - "top" - ], - "type": "string" - }, - "sidewall_angle": { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ], - "default": 0.0 - }, - "slab_bounds": { + "frequency_range": { "items": [ { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] + "type": "number" }, { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] + "type": "number" } ], "maxItems": 2, "minItems": 2, "type": "array" }, - "type": { - "default": "ComplexPolySlabBase", - "enum": [ - "ComplexPolySlabBase" - ], - "type": "string" - }, - "vertices": { - "anyOf": [ + "heat_spec": { + "discriminator": { + "mapping": { + "FluidMedium": "#/definitions/FluidMedium", + "FluidSpec": "#/definitions/FluidSpec", + "SolidMedium": "#/definitions/SolidMedium", + "SolidSpec": "#/definitions/SolidSpec" + }, + "propertyName": "type" + }, + "oneOf": [ { - "type": "ArrayLike" + "$ref": "#/definitions/FluidMedium" }, { - "type": "autograd.tracer.Box" + "$ref": "#/definitions/FluidSpec" + }, + { + "$ref": "#/definitions/SolidMedium" + }, + { + "$ref": "#/definitions/SolidSpec" } ] - } - }, - "required": [ - "slab_bounds", - "vertices" - ], - "type": "object" - }, - "ConstantDoping": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" }, - "center": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, + "modulation_spec": { + "allOf": [ { - "type": "autograd.tracer.Box" + "$ref": "#/definitions/ModulationSpec" } - ], - "default": [ - 0.0, - 0.0, - 0.0 ] }, - "concentration": { - "default": 0, - "minimum": 0, - "type": "number" + "name": { + "type": "string" }, - "size": { + "nonlinear_spec": { "anyOf": [ { - "items": [ - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" + "$ref": "#/definitions/NonlinearSpec" }, { - "type": "autograd.tracer.Box" + "$ref": "#/definitions/NonlinearSusceptibility" } - ], - "default": [ - Infinity, - Infinity, - Infinity ] }, + "permittivity": { + "default": 1.0, + "minimum": 1.0, + "type": "number" + }, "type": { - "default": "ConstantDoping", + "default": "ChargeConductorMedium", "enum": [ - "ConstantDoping" + "ChargeConductorMedium" ], "type": "string" + }, + "viz_spec": { + "allOf": [ + { + "$ref": "#/definitions/VisualizationSpec" + } + ] } }, + "required": [ + "conductivity" + ], "type": "object" }, - "ConstantEffectiveDOS": { + "ChargeInsulatorMedium": { "additionalProperties": false, "properties": { - "N": { - "exclusiveMinimum": 0, - "type": "number" + "allow_gain": { + "default": false, + "type": "boolean" }, "attrs": { "default": {}, "type": "object" }, - "type": { - "default": "ConstantEffectiveDOS", - "enum": [ - "ConstantEffectiveDOS" - ], - "type": "string" - } - }, - "required": [ - "N" - ], - "type": "object" - }, - "ConstantEnergyBandGap": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" + "frequency_range": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" }, - "eg": { - "exclusiveMinimum": 0, - "type": "number" + "heat_spec": { + "discriminator": { + "mapping": { + "FluidMedium": "#/definitions/FluidMedium", + "FluidSpec": "#/definitions/FluidSpec", + "SolidMedium": "#/definitions/SolidMedium", + "SolidSpec": "#/definitions/SolidSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/FluidMedium" + }, + { + "$ref": "#/definitions/FluidSpec" + }, + { + "$ref": "#/definitions/SolidMedium" + }, + { + "$ref": "#/definitions/SolidSpec" + } + ] }, - "type": { - "default": "ConstantEnergyBandGap", - "enum": [ - "ConstantEnergyBandGap" - ], + "modulation_spec": { + "allOf": [ + { + "$ref": "#/definitions/ModulationSpec" + } + ] + }, + "name": { "type": "string" - } - }, - "required": [ - "eg" - ], - "type": "object" - }, - "ConstantMobilityModel": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" }, - "mu": { - "minimum": 0, + "nonlinear_spec": { + "anyOf": [ + { + "$ref": "#/definitions/NonlinearSpec" + }, + { + "$ref": "#/definitions/NonlinearSusceptibility" + } + ] + }, + "permittivity": { + "default": 1.0, + "minimum": 1.0, "type": "number" }, "type": { - "default": "ConstantMobilityModel", + "default": "ChargeInsulatorMedium", "enum": [ - "ConstantMobilityModel" + "ChargeInsulatorMedium" ], "type": "string" + }, + "viz_spec": { + "allOf": [ + { + "$ref": "#/definitions/VisualizationSpec" + } + ] } }, - "required": [ - "mu" - ], "type": "object" }, - "ContinuousWave": { + "ClipOperation": { "additionalProperties": false, "properties": { - "amplitude": { - "default": 1.0, - "minimum": 0, - "type": "number" - }, "attrs": { "default": {}, "type": "object" }, - "freq0": { - "exclusiveMinimum": 0, - "type": "number" - }, - "fwidth": { - "exclusiveMinimum": 0, - "type": "number" - }, - "offset": { - "default": 5.0, - "minimum": 2.5, - "type": "number" + "geometry_a": { + "anyOf": [ + { + "$ref": "#/definitions/Box" + }, + { + "$ref": "#/definitions/ClipOperation" + }, + { + "$ref": "#/definitions/ComplexPolySlabBase" + }, + { + "$ref": "#/definitions/Cylinder" + }, + { + "$ref": "#/definitions/GeometryGroup" + }, + { + "$ref": "#/definitions/PolySlab" + }, + { + "$ref": "#/definitions/Sphere" + }, + { + "$ref": "#/definitions/Transformed" + }, + { + "$ref": "#/definitions/TriangleMesh" + } + ] }, - "phase": { - "default": 0.0, - "type": "number" + "geometry_b": { + "anyOf": [ + { + "$ref": "#/definitions/Box" + }, + { + "$ref": "#/definitions/ClipOperation" + }, + { + "$ref": "#/definitions/ComplexPolySlabBase" + }, + { + "$ref": "#/definitions/Cylinder" + }, + { + "$ref": "#/definitions/GeometryGroup" + }, + { + "$ref": "#/definitions/PolySlab" + }, + { + "$ref": "#/definitions/Sphere" + }, + { + "$ref": "#/definitions/Transformed" + }, + { + "$ref": "#/definitions/TriangleMesh" + } + ] }, - "type": { - "default": "ContinuousWave", + "operation": { "enum": [ - "ContinuousWave" + "difference", + "intersection", + "symmetric_difference", + "union" ], "type": "string" - } - }, - "required": [ - "freq0", - "fwidth" - ], - "type": "object" - }, - "ContinuousWaveTimeModulation": { - "additionalProperties": false, - "properties": { - "amplitude": { - "default": 1.0, - "minimum": 0, - "type": "number" - }, - "attrs": { - "default": {}, - "type": "object" - }, - "freq0": { - "exclusiveMinimum": 0, - "type": "number" - }, - "phase": { - "default": 0.0, - "type": "number" }, "type": { - "default": "ContinuousWaveTimeModulation", + "default": "ClipOperation", "enum": [ - "ContinuousWaveTimeModulation" + "ClipOperation" ], "type": "string" } }, "required": [ - "freq0" + "geometry_a", + "geometry_b", + "operation" ], "type": "object" }, - "ContourPathAveraging": { + "CoaxialLumpedPort": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "type": { - "default": "ContourPathAveraging", - "enum": [ - "ContourPathAveraging" + "center": { + "default": [ + 0.0, + 0.0, + 0.0 ], - "type": "string" - } - }, - "type": "object" - }, - "CornerFinderSpec": { - "additionalProperties": false, - "properties": { - "angle_threshold": { - "default": 0.3141592653589793, - "exclusiveMaximum": 3.141592653589793, - "minimum": 0, - "type": "number" + "items": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" }, - "attrs": { - "default": {}, - "type": "object" + "direction": { + "enum": [ + "+", + "-" + ], + "type": "string" }, - "concave_resolution": { - "exclusiveMinimum": 0, - "type": "integer" + "enable_snapping_points": { + "default": true, + "type": "boolean" }, - "convex_resolution": { - "exclusiveMinimum": 0, - "type": "integer" + "impedance": { + "anyOf": [ + { + "$ref": "#/definitions/ComplexNumber" + }, + { + "properties": { + "imag": { + "type": "number" + }, + "real": { + "type": "number" + } + }, + "required": [ + "imag", + "real" + ], + "type": "object" + } + ], + "default": 50 }, - "distance_threshold": { + "inner_diameter": { "exclusiveMinimum": 0, "type": "number" }, - "medium": { - "default": "metal", + "name": { + "minLength": 1, + "type": "string" + }, + "normal_axis": { "enum": [ - "all", - "dielectric", - "metal" + 0, + 1, + 2 ], - "type": "string" + "type": "integer" }, - "mixed_resolution": { + "num_grid_cells": { + "default": 3, "exclusiveMinimum": 0, "type": "integer" }, + "outer_diameter": { + "exclusiveMinimum": 0, + "type": "number" + }, "type": { - "default": "CornerFinderSpec", + "default": "CoaxialLumpedPort", "enum": [ - "CornerFinderSpec" + "CoaxialLumpedPort" ], "type": "string" } }, + "required": [ + "direction", + "inner_diameter", + "name", + "normal_axis", + "outer_diameter" + ], "type": "object" }, - "CurrentIntegralAxisAligned": { + "CoaxialLumpedResistor": { "additionalProperties": false, "properties": { "attrs": { @@ -2570,435 +2594,266 @@ "type": "object" }, "center": { - "anyOf": [ + "default": [ + 0.0, + 0.0, + 0.0 + ], + "items": [ { - "items": [ - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" + "type": "number" }, { - "type": "autograd.tracer.Box" + "type": "number" + }, + { + "type": "number" } ], - "default": [ - 0.0, - 0.0, - 0.0 - ] + "maxItems": 3, + "minItems": 3, + "type": "array" }, - "extrapolate_to_endpoints": { - "default": false, + "enable_snapping_points": { + "default": true, "type": "boolean" }, - "sign": { + "inner_diameter": { + "exclusiveMinimum": 0, + "type": "number" + }, + "name": { + "minLength": 1, + "type": "string" + }, + "normal_axis": { "enum": [ - "+", - "-" + 0, + 1, + 2 ], - "type": "string" + "type": "integer" }, - "size": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - { - "type": "autograd.tracer.Box" - } - ] + "num_grid_cells": { + "default": 1, + "exclusiveMinimum": 0, + "type": "integer" }, - "snap_contour_to_grid": { - "default": false, - "type": "boolean" + "outer_diameter": { + "exclusiveMinimum": 0, + "type": "number" + }, + "resistance": { + "exclusiveMinimum": 0, + "type": "number", + "unit": "ohm" }, "type": { - "default": "CurrentIntegralAxisAligned", + "default": "CoaxialLumpedResistor", "enum": [ - "CurrentIntegralAxisAligned" + "CoaxialLumpedResistor" ], "type": "string" } }, "required": [ - "sign", - "size" + "inner_diameter", + "name", + "normal_axis", + "outer_diameter", + "resistance" ], "type": "object" }, - "CustomAnisotropicMedium": { - "additionalProperties": false, + "ComplexNumber": { "properties": { - "allow_gain": { - "type": "boolean" - }, - "attrs": { - "default": {}, - "type": "object" + "imag": { + "type": "number" }, - "frequency_range": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "heat_spec": { - "discriminator": { - "mapping": { - "FluidMedium": "#/definitions/FluidMedium", - "FluidSpec": "#/definitions/FluidSpec", - "SolidMedium": "#/definitions/SolidMedium", - "SolidSpec": "#/definitions/SolidSpec" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/FluidMedium" - }, - { - "$ref": "#/definitions/FluidSpec" - }, - { - "$ref": "#/definitions/SolidMedium" - }, - { - "$ref": "#/definitions/SolidSpec" - } - ] + "real": { + "type": "number" + } + }, + "required": [ + "imag", + "real" + ], + "type": "object" + }, + "ComplexPolySlabBase": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" }, - "interp_method": { + "axis": { + "default": 2, "enum": [ - "linear", - "nearest" + 0, + 1, + 2 ], - "type": "string" + "type": "integer" }, - "modulation_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModulationSpec" - } - ] + "dilation": { + "default": 0.0, + "type": "number" }, - "name": { + "reference_plane": { + "default": "middle", + "enum": [ + "bottom", + "middle", + "top" + ], "type": "string" }, - "nonlinear_spec": { + "sidewall_angle": { "anyOf": [ { - "$ref": "#/definitions/NonlinearSpec" + "type": "autograd.tracer.Box" }, { - "$ref": "#/definitions/NonlinearSusceptibility" + "type": "number" } - ] - }, - "subpixel": { - "type": "boolean" - }, - "type": { - "default": "CustomAnisotropicMedium", - "enum": [ - "CustomAnisotropicMedium" ], - "type": "string" - }, - "viz_spec": { - "allOf": [ - { - "$ref": "#/definitions/VisualizationSpec" - } - ] + "default": 0.0 }, - "xx": { - "discriminator": { - "mapping": { - "CustomDebye": "#/definitions/CustomDebye", - "CustomDrude": "#/definitions/CustomDrude", - "CustomLorentz": "#/definitions/CustomLorentz", - "CustomMedium": "#/definitions/CustomMedium", - "CustomPoleResidue": "#/definitions/CustomPoleResidue", - "CustomSellmeier": "#/definitions/CustomSellmeier" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/CustomDebye" - }, - { - "$ref": "#/definitions/CustomDrude" - }, - { - "$ref": "#/definitions/CustomLorentz" - }, - { - "$ref": "#/definitions/CustomMedium" - }, + "slab_bounds": { + "items": [ { - "$ref": "#/definitions/CustomPoleResidue" + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] }, { - "$ref": "#/definitions/CustomSellmeier" + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] } - ] + ], + "maxItems": 2, + "minItems": 2, + "type": "array" }, - "yy": { - "discriminator": { - "mapping": { - "CustomDebye": "#/definitions/CustomDebye", - "CustomDrude": "#/definitions/CustomDrude", - "CustomLorentz": "#/definitions/CustomLorentz", - "CustomMedium": "#/definitions/CustomMedium", - "CustomPoleResidue": "#/definitions/CustomPoleResidue", - "CustomSellmeier": "#/definitions/CustomSellmeier" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/CustomDebye" - }, - { - "$ref": "#/definitions/CustomDrude" - }, - { - "$ref": "#/definitions/CustomLorentz" - }, - { - "$ref": "#/definitions/CustomMedium" - }, - { - "$ref": "#/definitions/CustomPoleResidue" - }, - { - "$ref": "#/definitions/CustomSellmeier" - } - ] + "type": { + "default": "ComplexPolySlabBase", + "enum": [ + "ComplexPolySlabBase" + ], + "type": "string" }, - "zz": { - "discriminator": { - "mapping": { - "CustomDebye": "#/definitions/CustomDebye", - "CustomDrude": "#/definitions/CustomDrude", - "CustomLorentz": "#/definitions/CustomLorentz", - "CustomMedium": "#/definitions/CustomMedium", - "CustomPoleResidue": "#/definitions/CustomPoleResidue", - "CustomSellmeier": "#/definitions/CustomSellmeier" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/CustomDebye" - }, - { - "$ref": "#/definitions/CustomDrude" - }, - { - "$ref": "#/definitions/CustomLorentz" - }, - { - "$ref": "#/definitions/CustomMedium" - }, + "vertices": { + "anyOf": [ { - "$ref": "#/definitions/CustomPoleResidue" + "type": "ArrayLike" }, { - "$ref": "#/definitions/CustomSellmeier" + "type": "autograd.tracer.Box" } ] } }, "required": [ - "xx", - "yy", - "zz" + "slab_bounds", + "vertices" ], "type": "object" }, - "CustomChargePerturbation": { + "CompositeCurrentIntegral": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "electron_range": { - "items": [ - { - "minimum": 0, - "type": "number" - }, - { - "minimum": 0, - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, + "path_specs": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DCurrentIntegralSpec" + } + ] + }, "type": "array" }, - "hole_range": { - "items": [ - { - "minimum": 0, - "type": "number" - }, - { - "minimum": 0, - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "interp_method": { - "default": "linear", - "enum": [ - "linear", - "nearest" + "sum_spec": { + "enum": [ + "split", + "sum" ], "type": "string" }, - "perturbation_values": { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - }, "type": { - "default": "CustomChargePerturbation", + "default": "CompositeCurrentIntegral", "enum": [ - "CustomChargePerturbation" + "CompositeCurrentIntegral" ], "type": "string" } }, "required": [ - "perturbation_values" + "path_specs", + "sum_spec" ], "type": "object" }, - "CustomCurrentIntegral2D": { + "CompositeCurrentIntegralSpec": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "axis": { - "default": 2, + "path_specs": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DCurrentIntegralSpec" + } + ] + }, + "type": "array" + }, + "sum_spec": { "enum": [ - 0, - 1, - 2 + "split", + "sum" ], - "type": "integer" - }, - "position": { - "type": "number" + "type": "string" }, "type": { - "default": "CustomCurrentIntegral2D", + "default": "CompositeCurrentIntegralSpec", "enum": [ - "CustomCurrentIntegral2D" + "CompositeCurrentIntegralSpec" ], "type": "string" - }, - "vertices": { - "type": "ArrayLike" } }, "required": [ - "position", - "vertices" + "path_specs", + "sum_spec" ], "type": "object" }, - "CustomCurrentSource": { + "ConstantDoping": { "additionalProperties": false, "properties": { "attrs": { @@ -3054,23 +2909,10 @@ 0.0 ] }, - "confine_to_bounds": { - "default": false, - "type": "boolean" - }, - "current_dataset": { - "allOf": [ - { - "$ref": "#/definitions/FieldDataset" - } - ] - }, - "interpolate": { - "default": true, - "type": "boolean" - }, - "name": { - "type": "string" + "concentration": { + "default": 0, + "minimum": 0, + "type": "number" }, "size": { "anyOf": [ @@ -3117,458 +2959,389 @@ { "type": "autograd.tracer.Box" } + ], + "default": [ + Infinity, + Infinity, + Infinity ] }, - "source_time": { - "discriminator": { - "mapping": { - "ContinuousWave": "#/definitions/ContinuousWave", - "CustomSourceTime": "#/definitions/CustomSourceTime", - "GaussianPulse": "#/definitions/GaussianPulse" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/ContinuousWave" - }, - { - "$ref": "#/definitions/CustomSourceTime" - }, - { - "$ref": "#/definitions/GaussianPulse" - } - ] + "type": { + "default": "ConstantDoping", + "enum": [ + "ConstantDoping" + ], + "type": "string" + } + }, + "type": "object" + }, + "ConstantEffectiveDOS": { + "additionalProperties": false, + "properties": { + "N": { + "exclusiveMinimum": 0, + "type": "number" + }, + "attrs": { + "default": {}, + "type": "object" }, "type": { - "default": "CustomCurrentSource", + "default": "ConstantEffectiveDOS", "enum": [ - "CustomCurrentSource" + "ConstantEffectiveDOS" ], "type": "string" } }, "required": [ - "current_dataset", - "size", - "source_time" + "N" ], "type": "object" }, - "CustomDebye": { + "ConstantEnergyBandGap": { "additionalProperties": false, "properties": { - "allow_gain": { - "default": false, - "type": "boolean" - }, "attrs": { "default": {}, "type": "object" }, - "coeffs": { - "items": { - "items": [ - { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - } - ] - }, - { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - } - ] - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": "array" - }, - "eps_inf": { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - } - ] + "eg": { + "exclusiveMinimum": 0, + "type": "number" }, - "frequency_range": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } + "type": { + "default": "ConstantEnergyBandGap", + "enum": [ + "ConstantEnergyBandGap" ], - "maxItems": 2, - "minItems": 2, - "type": "array" + "type": "string" + } + }, + "required": [ + "eg" + ], + "type": "object" + }, + "ConstantMobilityModel": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" }, - "heat_spec": { - "discriminator": { - "mapping": { - "FluidMedium": "#/definitions/FluidMedium", - "FluidSpec": "#/definitions/FluidSpec", - "SolidMedium": "#/definitions/SolidMedium", - "SolidSpec": "#/definitions/SolidSpec" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/FluidMedium" - }, - { - "$ref": "#/definitions/FluidSpec" - }, - { - "$ref": "#/definitions/SolidMedium" - }, - { - "$ref": "#/definitions/SolidSpec" - } - ] + "mu": { + "minimum": 0, + "type": "number" }, - "interp_method": { - "default": "nearest", + "type": { + "default": "ConstantMobilityModel", "enum": [ - "linear", - "nearest" + "ConstantMobilityModel" ], "type": "string" + } + }, + "required": [ + "mu" + ], + "type": "object" + }, + "ContinuousWave": { + "additionalProperties": false, + "properties": { + "amplitude": { + "default": 1.0, + "minimum": 0, + "type": "number" }, - "modulation_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModulationSpec" - } - ] + "attrs": { + "default": {}, + "type": "object" }, - "name": { - "type": "string" + "freq0": { + "exclusiveMinimum": 0, + "type": "number" }, - "nonlinear_spec": { - "anyOf": [ - { - "$ref": "#/definitions/NonlinearSpec" - }, - { - "$ref": "#/definitions/NonlinearSusceptibility" - } - ] + "fwidth": { + "exclusiveMinimum": 0, + "type": "number" }, - "subpixel": { - "default": false, - "type": "boolean" + "offset": { + "default": 5.0, + "minimum": 2.5, + "type": "number" + }, + "phase": { + "default": 0.0, + "type": "number" }, "type": { - "default": "CustomDebye", + "default": "ContinuousWave", "enum": [ - "CustomDebye" + "ContinuousWave" ], "type": "string" - }, - "viz_spec": { - "allOf": [ - { - "$ref": "#/definitions/VisualizationSpec" - } - ] } }, "required": [ - "coeffs", - "eps_inf" + "freq0", + "fwidth" ], "type": "object" }, - "CustomDoping": { + "ContinuousWaveTimeModulation": { "additionalProperties": false, "properties": { + "amplitude": { + "default": 1.0, + "minimum": 0, + "type": "number" + }, "attrs": { "default": {}, "type": "object" }, - "center": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - { - "type": "autograd.tracer.Box" - } - ], - "default": [ - 0.0, - 0.0, - 0.0 - ] - }, - "concentration": { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" + "freq0": { + "exclusiveMinimum": 0, + "type": "number" }, - "size": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - { - "type": "autograd.tracer.Box" - } - ], - "default": [ - Infinity, - Infinity, - Infinity - ] + "phase": { + "default": 0.0, + "type": "number" }, "type": { - "default": "CustomDoping", + "default": "ContinuousWaveTimeModulation", "enum": [ - "CustomDoping" + "ContinuousWaveTimeModulation" ], "type": "string" } }, "required": [ - "concentration" + "freq0" ], "type": "object" }, - "CustomDrude": { + "ContourPathAveraging": { "additionalProperties": false, "properties": { - "allow_gain": { - "default": false, - "type": "boolean" + "attrs": { + "default": {}, + "type": "object" + }, + "type": { + "default": "ContourPathAveraging", + "enum": [ + "ContourPathAveraging" + ], + "type": "string" + } + }, + "type": "object" + }, + "CornerFinderSpec": { + "additionalProperties": false, + "properties": { + "angle_threshold": { + "default": 0.3141592653589793, + "exclusiveMaximum": 3.141592653589793, + "minimum": 0, + "type": "number" }, "attrs": { "default": {}, "type": "object" }, - "coeffs": { - "items": { - "items": [ - { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - } - ] - }, - { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - } - ] - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": "array" + "concave_resolution": { + "exclusiveMinimum": 0, + "type": "integer" }, - "eps_inf": { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - } - ] + "convex_resolution": { + "exclusiveMinimum": 0, + "type": "integer" + }, + "distance_threshold": { + "exclusiveMinimum": 0, + "type": "number" + }, + "medium": { + "default": "metal", + "enum": [ + "all", + "dielectric", + "metal" + ], + "type": "string" + }, + "mixed_resolution": { + "exclusiveMinimum": 0, + "type": "integer" + }, + "type": { + "default": "CornerFinderSpec", + "enum": [ + "CornerFinderSpec" + ], + "type": "string" + } + }, + "type": "object" + }, + "Custom2DCurrentIntegral": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + }, + "position": { + "type": "number" + }, + "type": { + "default": "Custom2DCurrentIntegral", + "enum": [ + "Custom2DCurrentIntegral" + ], + "type": "string" + }, + "vertices": { + "type": "ArrayLike" + } + }, + "required": [ + "axis", + "position", + "vertices" + ], + "type": "object" + }, + "Custom2DCurrentIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + }, + "position": { + "type": "number" + }, + "type": { + "default": "Custom2DCurrentIntegralSpec", + "enum": [ + "Custom2DCurrentIntegralSpec" + ], + "type": "string" + }, + "vertices": { + "type": "ArrayLike" + } + }, + "required": [ + "axis", + "position", + "vertices" + ], + "type": "object" + }, + "Custom2DVoltageIntegral": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + }, + "position": { + "type": "number" + }, + "type": { + "default": "Custom2DVoltageIntegral", + "enum": [ + "Custom2DVoltageIntegral" + ], + "type": "string" + }, + "vertices": { + "type": "ArrayLike" + } + }, + "required": [ + "axis", + "position", + "vertices" + ], + "type": "object" + }, + "Custom2DVoltageIntegralSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + }, + "position": { + "type": "number" + }, + "type": { + "default": "Custom2DVoltageIntegralSpec", + "enum": [ + "Custom2DVoltageIntegralSpec" + ], + "type": "string" + }, + "vertices": { + "type": "ArrayLike" + } + }, + "required": [ + "axis", + "position", + "vertices" + ], + "type": "object" + }, + "CustomAnisotropicMedium": { + "additionalProperties": false, + "properties": { + "allow_gain": { + "type": "boolean" + }, + "attrs": { + "default": {}, + "type": "object" }, "frequency_range": { "items": [ @@ -3609,7 +3382,6 @@ ] }, "interp_method": { - "default": "nearest", "enum": [ "linear", "nearest" @@ -3637,13 +3409,12 @@ ] }, "subpixel": { - "default": false, "type": "boolean" }, "type": { - "default": "CustomDrude", + "default": "CustomAnisotropicMedium", "enum": [ - "CustomDrude" + "CustomAnisotropicMedium" ], "type": "string" }, @@ -3653,15 +3424,184 @@ "$ref": "#/definitions/VisualizationSpec" } ] - } - }, - "required": [ - "coeffs", - "eps_inf" + }, + "xx": { + "discriminator": { + "mapping": { + "CustomDebye": "#/definitions/CustomDebye", + "CustomDrude": "#/definitions/CustomDrude", + "CustomLorentz": "#/definitions/CustomLorentz", + "CustomMedium": "#/definitions/CustomMedium", + "CustomPoleResidue": "#/definitions/CustomPoleResidue", + "CustomSellmeier": "#/definitions/CustomSellmeier" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/CustomDebye" + }, + { + "$ref": "#/definitions/CustomDrude" + }, + { + "$ref": "#/definitions/CustomLorentz" + }, + { + "$ref": "#/definitions/CustomMedium" + }, + { + "$ref": "#/definitions/CustomPoleResidue" + }, + { + "$ref": "#/definitions/CustomSellmeier" + } + ] + }, + "yy": { + "discriminator": { + "mapping": { + "CustomDebye": "#/definitions/CustomDebye", + "CustomDrude": "#/definitions/CustomDrude", + "CustomLorentz": "#/definitions/CustomLorentz", + "CustomMedium": "#/definitions/CustomMedium", + "CustomPoleResidue": "#/definitions/CustomPoleResidue", + "CustomSellmeier": "#/definitions/CustomSellmeier" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/CustomDebye" + }, + { + "$ref": "#/definitions/CustomDrude" + }, + { + "$ref": "#/definitions/CustomLorentz" + }, + { + "$ref": "#/definitions/CustomMedium" + }, + { + "$ref": "#/definitions/CustomPoleResidue" + }, + { + "$ref": "#/definitions/CustomSellmeier" + } + ] + }, + "zz": { + "discriminator": { + "mapping": { + "CustomDebye": "#/definitions/CustomDebye", + "CustomDrude": "#/definitions/CustomDrude", + "CustomLorentz": "#/definitions/CustomLorentz", + "CustomMedium": "#/definitions/CustomMedium", + "CustomPoleResidue": "#/definitions/CustomPoleResidue", + "CustomSellmeier": "#/definitions/CustomSellmeier" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/CustomDebye" + }, + { + "$ref": "#/definitions/CustomDrude" + }, + { + "$ref": "#/definitions/CustomLorentz" + }, + { + "$ref": "#/definitions/CustomMedium" + }, + { + "$ref": "#/definitions/CustomPoleResidue" + }, + { + "$ref": "#/definitions/CustomSellmeier" + } + ] + } + }, + "required": [ + "xx", + "yy", + "zz" ], "type": "object" }, - "CustomFieldSource": { + "CustomChargePerturbation": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "electron_range": { + "items": [ + { + "minimum": 0, + "type": "number" + }, + { + "minimum": 0, + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "hole_range": { + "items": [ + { + "minimum": 0, + "type": "number" + }, + { + "minimum": 0, + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "interp_method": { + "default": "linear", + "enum": [ + "linear", + "nearest" + ], + "type": "string" + }, + "perturbation_values": { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + }, + "type": { + "default": "CustomChargePerturbation", + "enum": [ + "CustomChargePerturbation" + ], + "type": "string" + } + }, + "required": [ + "perturbation_values" + ], + "type": "object" + }, + "CustomCurrentSource": { "additionalProperties": false, "properties": { "attrs": { @@ -3717,13 +3657,21 @@ 0.0 ] }, - "field_dataset": { + "confine_to_bounds": { + "default": false, + "type": "boolean" + }, + "current_dataset": { "allOf": [ { "$ref": "#/definitions/FieldDataset" } ] }, + "interpolate": { + "default": true, + "type": "boolean" + }, "name": { "type": "string" }, @@ -3796,133 +3744,26 @@ ] }, "type": { - "default": "CustomFieldSource", + "default": "CustomCurrentSource", "enum": [ - "CustomFieldSource" + "CustomCurrentSource" ], "type": "string" } }, "required": [ - "field_dataset", + "current_dataset", "size", "source_time" ], "type": "object" }, - "CustomGrid": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "custom_offset": { - "type": "number" - }, - "dl": { - "items": { - "exclusiveMinimum": 0, - "type": "number" - }, - "type": "array" - }, - "type": { - "default": "CustomGrid", - "enum": [ - "CustomGrid" - ], - "type": "string" - } - }, - "required": [ - "dl" - ], - "type": "object" - }, - "CustomGridBoundaries": { + "CustomDebye": { "additionalProperties": false, "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "coords": { - "type": "ArrayLike" - }, - "type": { - "default": "CustomGridBoundaries", - "enum": [ - "CustomGridBoundaries" - ], - "type": "string" - } - }, - "required": [ - "coords" - ], - "type": "object" - }, - "CustomHeatPerturbation": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "interp_method": { - "default": "linear", - "enum": [ - "linear", - "nearest" - ], - "type": "string" - }, - "perturbation_values": { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - }, - "temperature_range": { - "items": [ - { - "minimum": 0, - "type": "number" - }, - { - "minimum": 0, - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": { - "default": "CustomHeatPerturbation", - "enum": [ - "CustomHeatPerturbation" - ], - "type": "string" - } - }, - "required": [ - "perturbation_values" - ], - "type": "object" - }, - "CustomLorentz": { - "additionalProperties": false, - "properties": { - "allow_gain": { - "default": false, - "type": "boolean" + "allow_gain": { + "default": false, + "type": "boolean" }, "attrs": { "default": {}, @@ -3956,31 +3797,6 @@ } ] }, - { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - } - ] - }, { "anyOf": [ { @@ -4007,8 +3823,8 @@ ] } ], - "maxItems": 3, - "minItems": 3, + "maxItems": 2, + "minItems": 2, "type": "array" }, "type": "array" @@ -4109,9 +3925,9 @@ "type": "boolean" }, "type": { - "default": "CustomLorentz", + "default": "CustomDebye", "enum": [ - "CustomLorentz" + "CustomDebye" ], "type": "string" }, @@ -4129,172 +3945,209 @@ ], "type": "object" }, - "CustomMedium": { + "CustomDoping": { "additionalProperties": false, "properties": { - "allow_gain": { - "default": false, - "type": "boolean" - }, "attrs": { "default": {}, "type": "object" }, - "conductivity": { + "center": { "anyOf": [ { - "oneOf": [ + "items": [ { - "$ref": "#/definitions/TetrahedralGridDataset" + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] }, { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] } - }, - "required": [ - "_dims" ], - "type": "xr.DataArray" - } - ] - }, - "eps_dataset": { - "allOf": [ + "maxItems": 3, + "minItems": 3, + "type": "array" + }, { - "$ref": "#/definitions/PermittivityDataset" + "type": "autograd.tracer.Box" } + ], + "default": [ + 0.0, + 0.0, + 0.0 ] }, - "frequency_range": { - "items": [ - { - "type": "number" - }, - { - "type": "number" + "concentration": { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" } + }, + "required": [ + "_dims" ], - "maxItems": 2, - "minItems": 2, - "type": "array" + "type": "xr.DataArray" }, - "heat_spec": { - "discriminator": { - "mapping": { - "FluidMedium": "#/definitions/FluidMedium", - "FluidSpec": "#/definitions/FluidSpec", - "SolidMedium": "#/definitions/SolidMedium", - "SolidSpec": "#/definitions/SolidSpec" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/FluidMedium" - }, - { - "$ref": "#/definitions/FluidSpec" - }, + "size": { + "anyOf": [ { - "$ref": "#/definitions/SolidMedium" + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" }, { - "$ref": "#/definitions/SolidSpec" + "type": "autograd.tracer.Box" } + ], + "default": [ + Infinity, + Infinity, + Infinity ] }, - "interp_method": { - "default": "nearest", + "type": { + "default": "CustomDoping", "enum": [ - "linear", - "nearest" + "CustomDoping" ], "type": "string" - }, - "modulation_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModulationSpec" - } - ] - }, - "name": { - "type": "string" - }, - "nonlinear_spec": { - "anyOf": [ - { - "$ref": "#/definitions/NonlinearSpec" - }, - { - "$ref": "#/definitions/NonlinearSusceptibility" - } - ] - }, - "permittivity": { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - } - ] - }, - "subpixel": { - "default": false, - "type": "boolean" - }, - "type": { - "default": "CustomMedium", - "enum": [ - "CustomMedium" - ], - "type": "string" - }, - "viz_spec": { - "allOf": [ - { - "$ref": "#/definitions/VisualizationSpec" - } - ] - } - }, - "type": "object" - }, - "CustomPoleResidue": { - "additionalProperties": false, - "properties": { - "allow_gain": { - "default": false, - "type": "boolean" + } + }, + "required": [ + "concentration" + ], + "type": "object" + }, + "CustomDrude": { + "additionalProperties": false, + "properties": { + "allow_gain": { + "default": false, + "type": "boolean" }, "attrs": { "default": {}, "type": "object" }, + "coeffs": { + "items": { + "items": [ + { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + }, + { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "type": "array" + }, "eps_inf": { "anyOf": [ { @@ -4386,75 +4239,14 @@ } ] }, - "poles": { - "default": [], - "items": { - "items": [ - { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - } - ] - }, - { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - } - ] - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": "array" - }, "subpixel": { "default": false, "type": "boolean" }, "type": { - "default": "CustomPoleResidue", + "default": "CustomDrude", "enum": [ - "CustomPoleResidue" + "CustomDrude" ], "type": "string" }, @@ -4467,304 +4259,1778 @@ } }, "required": [ + "coeffs", "eps_inf" ], "type": "object" }, - "CustomSellmeier": { + "CustomFieldSource": { "additionalProperties": false, "properties": { - "allow_gain": { - "default": false, - "type": "boolean" - }, "attrs": { "default": {}, "type": "object" }, - "coeffs": { - "items": { - "items": [ - { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - } - ] - }, - { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/TetrahedralGridDataset" - }, - { - "$ref": "#/definitions/TriangularGridDataset" - } - ] - }, - { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" }, - "required": [ + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "field_dataset": { + "allOf": [ + { + "$ref": "#/definitions/FieldDataset" + } + ] + }, + "name": { + "type": "string" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "source_time": { + "discriminator": { + "mapping": { + "ContinuousWave": "#/definitions/ContinuousWave", + "CustomSourceTime": "#/definitions/CustomSourceTime", + "GaussianPulse": "#/definitions/GaussianPulse" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/ContinuousWave" + }, + { + "$ref": "#/definitions/CustomSourceTime" + }, + { + "$ref": "#/definitions/GaussianPulse" + } + ] + }, + "type": { + "default": "CustomFieldSource", + "enum": [ + "CustomFieldSource" + ], + "type": "string" + } + }, + "required": [ + "field_dataset", + "size", + "source_time" + ], + "type": "object" + }, + "CustomGrid": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "custom_offset": { + "type": "number" + }, + "dl": { + "items": { + "exclusiveMinimum": 0, + "type": "number" + }, + "type": "array" + }, + "type": { + "default": "CustomGrid", + "enum": [ + "CustomGrid" + ], + "type": "string" + } + }, + "required": [ + "dl" + ], + "type": "object" + }, + "CustomGridBoundaries": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "coords": { + "type": "ArrayLike" + }, + "type": { + "default": "CustomGridBoundaries", + "enum": [ + "CustomGridBoundaries" + ], + "type": "string" + } + }, + "required": [ + "coords" + ], + "type": "object" + }, + "CustomHeatPerturbation": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "interp_method": { + "default": "linear", + "enum": [ + "linear", + "nearest" + ], + "type": "string" + }, + "perturbation_values": { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + }, + "temperature_range": { + "items": [ + { + "minimum": 0, + "type": "number" + }, + { + "minimum": 0, + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "type": { + "default": "CustomHeatPerturbation", + "enum": [ + "CustomHeatPerturbation" + ], + "type": "string" + } + }, + "required": [ + "perturbation_values" + ], + "type": "object" + }, + "CustomImpedanceSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "current_spec": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/CompositeCurrentIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DCurrentIntegralSpec" + } + ] + }, + "type": { + "default": "CustomImpedanceSpec", + "enum": [ + "CustomImpedanceSpec" + ], + "type": "string" + }, + "voltage_spec": { + "anyOf": [ + { + "$ref": "#/definitions/AxisAlignedVoltageIntegralSpec" + }, + { + "$ref": "#/definitions/Custom2DVoltageIntegralSpec" + } + ] + } + }, + "type": "object" + }, + "CustomLorentz": { + "additionalProperties": false, + "properties": { + "allow_gain": { + "default": false, + "type": "boolean" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "coeffs": { + "items": { + "items": [ + { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + }, + { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + }, + { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "type": "array" + }, + "eps_inf": { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + }, + "frequency_range": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "heat_spec": { + "discriminator": { + "mapping": { + "FluidMedium": "#/definitions/FluidMedium", + "FluidSpec": "#/definitions/FluidSpec", + "SolidMedium": "#/definitions/SolidMedium", + "SolidSpec": "#/definitions/SolidSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/FluidMedium" + }, + { + "$ref": "#/definitions/FluidSpec" + }, + { + "$ref": "#/definitions/SolidMedium" + }, + { + "$ref": "#/definitions/SolidSpec" + } + ] + }, + "interp_method": { + "default": "nearest", + "enum": [ + "linear", + "nearest" + ], + "type": "string" + }, + "modulation_spec": { + "allOf": [ + { + "$ref": "#/definitions/ModulationSpec" + } + ] + }, + "name": { + "type": "string" + }, + "nonlinear_spec": { + "anyOf": [ + { + "$ref": "#/definitions/NonlinearSpec" + }, + { + "$ref": "#/definitions/NonlinearSusceptibility" + } + ] + }, + "subpixel": { + "default": false, + "type": "boolean" + }, + "type": { + "default": "CustomLorentz", + "enum": [ + "CustomLorentz" + ], + "type": "string" + }, + "viz_spec": { + "allOf": [ + { + "$ref": "#/definitions/VisualizationSpec" + } + ] + } + }, + "required": [ + "coeffs", + "eps_inf" + ], + "type": "object" + }, + "CustomMedium": { + "additionalProperties": false, + "properties": { + "allow_gain": { + "default": false, + "type": "boolean" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "conductivity": { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + }, + "eps_dataset": { + "allOf": [ + { + "$ref": "#/definitions/PermittivityDataset" + } + ] + }, + "frequency_range": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "heat_spec": { + "discriminator": { + "mapping": { + "FluidMedium": "#/definitions/FluidMedium", + "FluidSpec": "#/definitions/FluidSpec", + "SolidMedium": "#/definitions/SolidMedium", + "SolidSpec": "#/definitions/SolidSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/FluidMedium" + }, + { + "$ref": "#/definitions/FluidSpec" + }, + { + "$ref": "#/definitions/SolidMedium" + }, + { + "$ref": "#/definitions/SolidSpec" + } + ] + }, + "interp_method": { + "default": "nearest", + "enum": [ + "linear", + "nearest" + ], + "type": "string" + }, + "modulation_spec": { + "allOf": [ + { + "$ref": "#/definitions/ModulationSpec" + } + ] + }, + "name": { + "type": "string" + }, + "nonlinear_spec": { + "anyOf": [ + { + "$ref": "#/definitions/NonlinearSpec" + }, + { + "$ref": "#/definitions/NonlinearSusceptibility" + } + ] + }, + "permittivity": { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + }, + "subpixel": { + "default": false, + "type": "boolean" + }, + "type": { + "default": "CustomMedium", + "enum": [ + "CustomMedium" + ], + "type": "string" + }, + "viz_spec": { + "allOf": [ + { + "$ref": "#/definitions/VisualizationSpec" + } + ] + } + }, + "type": "object" + }, + "CustomPoleResidue": { + "additionalProperties": false, + "properties": { + "allow_gain": { + "default": false, + "type": "boolean" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "eps_inf": { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + }, + "frequency_range": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "heat_spec": { + "discriminator": { + "mapping": { + "FluidMedium": "#/definitions/FluidMedium", + "FluidSpec": "#/definitions/FluidSpec", + "SolidMedium": "#/definitions/SolidMedium", + "SolidSpec": "#/definitions/SolidSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/FluidMedium" + }, + { + "$ref": "#/definitions/FluidSpec" + }, + { + "$ref": "#/definitions/SolidMedium" + }, + { + "$ref": "#/definitions/SolidSpec" + } + ] + }, + "interp_method": { + "default": "nearest", + "enum": [ + "linear", + "nearest" + ], + "type": "string" + }, + "modulation_spec": { + "allOf": [ + { + "$ref": "#/definitions/ModulationSpec" + } + ] + }, + "name": { + "type": "string" + }, + "nonlinear_spec": { + "anyOf": [ + { + "$ref": "#/definitions/NonlinearSpec" + }, + { + "$ref": "#/definitions/NonlinearSusceptibility" + } + ] + }, + "poles": { + "default": [], + "items": { + "items": [ + { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + }, + { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "type": "array" + }, + "subpixel": { + "default": false, + "type": "boolean" + }, + "type": { + "default": "CustomPoleResidue", + "enum": [ + "CustomPoleResidue" + ], + "type": "string" + }, + "viz_spec": { + "allOf": [ + { + "$ref": "#/definitions/VisualizationSpec" + } + ] + } + }, + "required": [ + "eps_inf" + ], + "type": "object" + }, + "CustomSellmeier": { + "additionalProperties": false, + "properties": { + "allow_gain": { + "default": false, + "type": "boolean" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "coeffs": { + "items": { + "items": [ + { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ "_dims" ], "type": "xr.DataArray" } ] + }, + { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/TetrahedralGridDataset" + }, + { + "$ref": "#/definitions/TriangularGridDataset" + } + ] + }, + { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + } + ] + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "type": "array" + }, + "frequency_range": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "heat_spec": { + "discriminator": { + "mapping": { + "FluidMedium": "#/definitions/FluidMedium", + "FluidSpec": "#/definitions/FluidSpec", + "SolidMedium": "#/definitions/SolidMedium", + "SolidSpec": "#/definitions/SolidSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/FluidMedium" + }, + { + "$ref": "#/definitions/FluidSpec" + }, + { + "$ref": "#/definitions/SolidMedium" + }, + { + "$ref": "#/definitions/SolidSpec" + } + ] + }, + "interp_method": { + "default": "nearest", + "enum": [ + "linear", + "nearest" + ], + "type": "string" + }, + "modulation_spec": { + "allOf": [ + { + "$ref": "#/definitions/ModulationSpec" + } + ] + }, + "name": { + "type": "string" + }, + "nonlinear_spec": { + "anyOf": [ + { + "$ref": "#/definitions/NonlinearSpec" + }, + { + "$ref": "#/definitions/NonlinearSusceptibility" + } + ] + }, + "subpixel": { + "default": false, + "type": "boolean" + }, + "type": { + "default": "CustomSellmeier", + "enum": [ + "CustomSellmeier" + ], + "type": "string" + }, + "viz_spec": { + "allOf": [ + { + "$ref": "#/definitions/VisualizationSpec" + } + ] + } + }, + "required": [ + "coeffs" + ], + "type": "object" + }, + "CustomSourceTime": { + "additionalProperties": false, + "properties": { + "amplitude": { + "default": 1.0, + "minimum": 0, + "type": "number" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "freq0": { + "exclusiveMinimum": 0, + "type": "number" + }, + "fwidth": { + "exclusiveMinimum": 0, + "type": "number" + }, + "offset": { + "default": 0.0, + "type": "number" + }, + "phase": { + "default": 0.0, + "type": "number" + }, + "source_time_dataset": { + "allOf": [ + { + "$ref": "#/definitions/TimeDataset" + } + ] + }, + "type": { + "default": "CustomSourceTime", + "enum": [ + "CustomSourceTime" + ], + "type": "string" + } + }, + "required": [ + "freq0", + "fwidth", + "source_time_dataset" + ], + "type": "object" + }, + "Cylinder": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "axis": { + "default": 2, + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + }, + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "length": { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "radius": { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "reference_plane": { + "default": "middle", + "enum": [ + "bottom", + "middle", + "top" + ], + "type": "string" + }, + "sidewall_angle": { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ], + "default": 0.0 + }, + "type": { + "default": "Cylinder", + "enum": [ + "Cylinder" + ], + "type": "string" + } + }, + "required": [ + "length", + "radius" + ], + "type": "object" + }, + "Debye": { + "additionalProperties": false, + "properties": { + "allow_gain": { + "default": false, + "type": "boolean" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "coeffs": { + "items": { + "items": [ + { + "type": "number" + }, + { + "exclusiveMinimum": 0, + "type": "number" } ], - "maxItems": 2, - "minItems": 2, - "type": "array" + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "type": "array" + }, + "eps_inf": { + "default": 1.0, + "exclusiveMinimum": 0, + "type": "number" + }, + "frequency_range": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "heat_spec": { + "discriminator": { + "mapping": { + "FluidMedium": "#/definitions/FluidMedium", + "FluidSpec": "#/definitions/FluidSpec", + "SolidMedium": "#/definitions/SolidMedium", + "SolidSpec": "#/definitions/SolidSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/FluidMedium" + }, + { + "$ref": "#/definitions/FluidSpec" + }, + { + "$ref": "#/definitions/SolidMedium" + }, + { + "$ref": "#/definitions/SolidSpec" + } + ] + }, + "modulation_spec": { + "allOf": [ + { + "$ref": "#/definitions/ModulationSpec" + } + ] + }, + "name": { + "type": "string" + }, + "nonlinear_spec": { + "anyOf": [ + { + "$ref": "#/definitions/NonlinearSpec" + }, + { + "$ref": "#/definitions/NonlinearSusceptibility" + } + ] + }, + "type": { + "default": "Debye", + "enum": [ + "Debye" + ], + "type": "string" + }, + "viz_spec": { + "allOf": [ + { + "$ref": "#/definitions/VisualizationSpec" + } + ] + } + }, + "required": [ + "coeffs" + ], + "type": "object" + }, + "DiffractionMonitor": { + "additionalProperties": false, + "properties": { + "apodization": { + "allOf": [ + { + "$ref": "#/definitions/ApodizationSpec" + } + ], + "default": { + "attrs": {}, + "end": null, + "start": null, + "type": "ApodizationSpec", + "width": null + } + }, + "attrs": { + "default": {}, + "type": "object" + }, + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "colocate": { + "default": false, + "enum": [ + false + ], + "type": "boolean" + }, + "freqs": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "ArrayLike" + } + ] + }, + "interval_space": { + "default": [ + 1, + 1, + 1 + ], + "items": [ + { + "enum": [ + 1 + ], + "type": "integer" + }, + { + "enum": [ + 1 + ], + "type": "integer" + }, + { + "enum": [ + 1 + ], + "type": "integer" + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "name": { + "minLength": 1, + "type": "string" + }, + "normal_dir": { + "default": "+", + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + "type": { + "default": "DiffractionMonitor", + "enum": [ + "DiffractionMonitor" + ], + "type": "string" + } + }, + "required": [ + "freqs", + "name", + "size" + ], + "type": "object" + }, + "DirectivityMonitor": { + "additionalProperties": false, + "properties": { + "apodization": { + "allOf": [ + { + "$ref": "#/definitions/ApodizationSpec" + } + ], + "default": { + "attrs": {}, + "end": null, + "start": null, + "type": "ApodizationSpec", + "width": null + } + }, + "attrs": { + "default": {}, + "type": "object" + }, + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "colocate": { + "default": true, + "enum": [ + true + ], + "type": "boolean" + }, + "custom_origin": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "exclude_surfaces": { + "items": { + "enum": [ + "x+", + "x-", + "y+", + "y-", + "z+", + "z-" + ], + "type": "string" }, "type": "array" }, - "frequency_range": { + "far_field_approx": { + "default": true, + "type": "boolean" + }, + "freqs": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "ArrayLike" + } + ] + }, + "interval_space": { + "default": [ + 1, + 1, + 1 + ], "items": [ { - "type": "number" + "exclusiveMinimum": 0, + "type": "integer" }, { - "type": "number" + "exclusiveMinimum": 0, + "type": "integer" + }, + { + "exclusiveMinimum": 0, + "type": "integer" } ], - "maxItems": 2, - "minItems": 2, + "maxItems": 3, + "minItems": 3, "type": "array" }, - "heat_spec": { - "discriminator": { - "mapping": { - "FluidMedium": "#/definitions/FluidMedium", - "FluidSpec": "#/definitions/FluidSpec", - "SolidMedium": "#/definitions/SolidMedium", - "SolidSpec": "#/definitions/SolidSpec" + "medium": { + "anyOf": [ + { + "$ref": "#/definitions/AnisotropicMedium" }, - "propertyName": "type" - }, - "oneOf": [ { - "$ref": "#/definitions/FluidMedium" + "$ref": "#/definitions/AnisotropicMediumFromMedium2D" }, { - "$ref": "#/definitions/FluidSpec" + "$ref": "#/definitions/CustomAnisotropicMedium" }, { - "$ref": "#/definitions/SolidMedium" + "$ref": "#/definitions/CustomDebye" }, { - "$ref": "#/definitions/SolidSpec" - } - ] - }, - "interp_method": { - "default": "nearest", - "enum": [ - "linear", - "nearest" - ], - "type": "string" - }, - "modulation_spec": { - "allOf": [ + "$ref": "#/definitions/CustomDrude" + }, { - "$ref": "#/definitions/ModulationSpec" - } - ] - }, - "name": { - "type": "string" - }, - "nonlinear_spec": { - "anyOf": [ + "$ref": "#/definitions/CustomLorentz" + }, { - "$ref": "#/definitions/NonlinearSpec" + "$ref": "#/definitions/CustomMedium" }, { - "$ref": "#/definitions/NonlinearSusceptibility" - } - ] - }, - "subpixel": { - "default": false, - "type": "boolean" - }, - "type": { - "default": "CustomSellmeier", - "enum": [ - "CustomSellmeier" - ], - "type": "string" - }, - "viz_spec": { - "allOf": [ + "$ref": "#/definitions/CustomPoleResidue" + }, { - "$ref": "#/definitions/VisualizationSpec" - } - ] - } - }, - "required": [ - "coeffs" - ], - "type": "object" - }, - "CustomSourceTime": { - "additionalProperties": false, - "properties": { - "amplitude": { - "default": 1.0, - "minimum": 0, - "type": "number" - }, - "attrs": { - "default": {}, - "type": "object" - }, - "freq0": { - "exclusiveMinimum": 0, - "type": "number" - }, - "fwidth": { - "exclusiveMinimum": 0, - "type": "number" - }, - "offset": { - "default": 0.0, - "type": "number" - }, - "phase": { - "default": 0.0, - "type": "number" - }, - "source_time_dataset": { - "allOf": [ + "$ref": "#/definitions/CustomSellmeier" + }, { - "$ref": "#/definitions/TimeDataset" + "$ref": "#/definitions/Debye" + }, + { + "$ref": "#/definitions/Drude" + }, + { + "$ref": "#/definitions/FullyAnisotropicMedium" + }, + { + "$ref": "#/definitions/Lorentz" + }, + { + "$ref": "#/definitions/LossyMetalMedium" + }, + { + "$ref": "#/definitions/Medium" + }, + { + "$ref": "#/definitions/Medium2D" + }, + { + "$ref": "#/definitions/PECMedium" + }, + { + "$ref": "#/definitions/PMCMedium" + }, + { + "$ref": "#/definitions/PerturbationMedium" + }, + { + "$ref": "#/definitions/PerturbationPoleResidue" + }, + { + "$ref": "#/definitions/PoleResidue" + }, + { + "$ref": "#/definitions/Sellmeier" } ] }, - "type": { - "default": "CustomSourceTime", - "enum": [ - "CustomSourceTime" - ], + "name": { + "minLength": 1, "type": "string" - } - }, - "required": [ - "freq0", - "fwidth", - "source_time_dataset" - ], - "type": "object" - }, - "CustomVoltageIntegral2D": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "axis": { - "default": 2, - "enum": [ - 0, - 1, - 2 - ], - "type": "integer" }, - "position": { - "type": "number" - }, - "type": { - "default": "CustomVoltageIntegral2D", + "normal_dir": { "enum": [ - "CustomVoltageIntegral2D" + "+", + "-" ], "type": "string" }, - "vertices": { - "type": "ArrayLike" - } - }, - "required": [ - "position", - "vertices" - ], - "type": "object" - }, - "Cylinder": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "axis": { - "default": 2, - "enum": [ - 0, - 1, - 2 - ], - "type": "integer" + "phi": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "ArrayLike" + } + ] }, - "center": { + "proj_distance": { + "default": 1000000.0, + "type": "number" + }, + "size": { "anyOf": [ { "items": [ { "anyOf": [ { - "type": "autograd.tracer.Box" + "minimum": 0, + "type": "number" }, { - "type": "number" + "type": "autograd.tracer.Box" } ] }, { "anyOf": [ { - "type": "autograd.tracer.Box" + "minimum": 0, + "type": "number" }, { - "type": "number" + "type": "autograd.tracer.Box" } ] }, { "anyOf": [ { - "type": "autograd.tracer.Box" + "minimum": 0, + "type": "number" }, { - "type": "number" + "type": "autograd.tracer.Box" } ] } @@ -4776,70 +6042,89 @@ { "type": "autograd.tracer.Box" } - ], - "default": [ - 0.0, - 0.0, - 0.0 - ] - }, - "length": { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } ] }, - "radius": { + "theta": { "anyOf": [ { - "minimum": 0, - "type": "number" + "items": { + "type": "number" + }, + "type": "array" }, { - "type": "autograd.tracer.Box" + "type": "ArrayLike" } ] }, - "reference_plane": { - "default": "middle", + "type": { + "default": "DirectivityMonitor", "enum": [ - "bottom", - "middle", - "top" + "DirectivityMonitor" ], "type": "string" }, - "sidewall_angle": { - "anyOf": [ + "window_size": { + "default": [ + 0, + 0 + ], + "items": [ { - "type": "autograd.tracer.Box" + "minimum": 0, + "type": "number" }, { + "minimum": 0, "type": "number" } ], - "default": 0.0 + "maxItems": 2, + "minItems": 2, + "type": "array" + } + }, + "required": [ + "freqs", + "name", + "phi", + "size", + "theta" + ], + "type": "object" + }, + "DistributedGeneration": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "rate": { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" }, "type": { - "default": "Cylinder", + "default": "DistributedGeneration", "enum": [ - "Cylinder" + "DistributedGeneration" ], "type": "string" } }, "required": [ - "length", - "radius" + "rate" ], "type": "object" }, - "Debye": { + "Drude": { "additionalProperties": false, "properties": { "allow_gain": { @@ -4931,9 +6216,9 @@ ] }, "type": { - "default": "Debye", + "default": "Drude", "enum": [ - "Debye" + "Drude" ], "type": "string" }, @@ -4950,7 +6235,119 @@ ], "type": "object" }, - "DiffractionMonitor": { + "DualValleyEffectiveDOS": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "m_eff_hh": { + "exclusiveMinimum": 0, + "type": "number" + }, + "m_eff_lh": { + "exclusiveMinimum": 0, + "type": "number" + }, + "type": { + "default": "DualValleyEffectiveDOS", + "enum": [ + "DualValleyEffectiveDOS" + ], + "type": "string" + } + }, + "required": [ + "m_eff_hh", + "m_eff_lh" + ], + "type": "object" + }, + "FieldDataset": { + "additionalProperties": false, + "properties": { + "Ex": { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + }, + "Ey": { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + }, + "Ez": { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + }, + "Hx": { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + }, + "Hy": { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + }, + "Hz": { + "properties": { + "_dims": { + "type": "Tuple[str, ...]" + } + }, + "required": [ + "_dims" + ], + "type": "xr.DataArray" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "type": { + "default": "FieldDataset", + "enum": [ + "FieldDataset" + ], + "type": "string" + } + }, + "type": "object" + }, + "FieldMonitor": { "additionalProperties": false, "properties": { "apodization": { @@ -5021,12 +6418,31 @@ ] }, "colocate": { - "default": false, - "enum": [ - false - ], + "default": true, "type": "boolean" }, + "fields": { + "default": [ + "Ex", + "Ey", + "Ez", + "Hx", + "Hy", + "Hz" + ], + "items": { + "enum": [ + "Ex", + "Ey", + "Ez", + "Hx", + "Hy", + "Hz" + ], + "type": "string" + }, + "type": "array" + }, "freqs": { "anyOf": [ { @@ -5048,21 +6464,15 @@ ], "items": [ { - "enum": [ - 1 - ], + "exclusiveMinimum": 0, "type": "integer" }, { - "enum": [ - 1 - ], + "exclusiveMinimum": 0, "type": "integer" }, { - "enum": [ - 1 - ], + "exclusiveMinimum": 0, "type": "integer" } ], @@ -5074,14 +6484,6 @@ "minLength": 1, "type": "string" }, - "normal_dir": { - "default": "+", - "enum": [ - "+", - "-" - ], - "type": "string" - }, "size": { "anyOf": [ { @@ -5130,9 +6532,9 @@ ] }, "type": { - "default": "DiffractionMonitor", + "default": "FieldMonitor", "enum": [ - "DiffractionMonitor" + "FieldMonitor" ], "type": "string" } @@ -5144,7 +6546,7 @@ ], "type": "object" }, - "DirectivityMonitor": { + "FieldProjectionAngleMonitor": { "additionalProperties": false, "properties": { "apodization": { @@ -5346,401 +6748,147 @@ "$ref": "#/definitions/PECMedium" }, { - "$ref": "#/definitions/PMCMedium" - }, - { - "$ref": "#/definitions/PerturbationMedium" - }, - { - "$ref": "#/definitions/PerturbationPoleResidue" - }, - { - "$ref": "#/definitions/PoleResidue" - }, - { - "$ref": "#/definitions/Sellmeier" - } - ] - }, - "name": { - "minLength": 1, - "type": "string" - }, - "normal_dir": { - "enum": [ - "+", - "-" - ], - "type": "string" - }, - "phi": { - "anyOf": [ - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "type": "ArrayLike" - } - ] - }, - "proj_distance": { - "default": 1000000.0, - "type": "number" - }, - "size": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - "theta": { - "anyOf": [ - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "type": "ArrayLike" - } - ] - }, - "type": { - "default": "DirectivityMonitor", - "enum": [ - "DirectivityMonitor" - ], - "type": "string" - }, - "window_size": { - "default": [ - 0, - 0 - ], - "items": [ - { - "minimum": 0, - "type": "number" - }, - { - "minimum": 0, - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - } - }, - "required": [ - "freqs", - "name", - "phi", - "size", - "theta" - ], - "type": "object" - }, - "DistributedGeneration": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "rate": { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - }, - "type": { - "default": "DistributedGeneration", - "enum": [ - "DistributedGeneration" - ], - "type": "string" - } - }, - "required": [ - "rate" - ], - "type": "object" - }, - "Drude": { - "additionalProperties": false, - "properties": { - "allow_gain": { - "default": false, - "type": "boolean" - }, - "attrs": { - "default": {}, - "type": "object" - }, - "coeffs": { - "items": { - "items": [ - { - "type": "number" - }, - { - "exclusiveMinimum": 0, - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": "array" - }, - "eps_inf": { - "default": 1.0, - "exclusiveMinimum": 0, - "type": "number" - }, - "frequency_range": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "heat_spec": { - "discriminator": { - "mapping": { - "FluidMedium": "#/definitions/FluidMedium", - "FluidSpec": "#/definitions/FluidSpec", - "SolidMedium": "#/definitions/SolidMedium", - "SolidSpec": "#/definitions/SolidSpec" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/FluidMedium" + "$ref": "#/definitions/PMCMedium" }, { - "$ref": "#/definitions/FluidSpec" + "$ref": "#/definitions/PerturbationMedium" }, { - "$ref": "#/definitions/SolidMedium" + "$ref": "#/definitions/PerturbationPoleResidue" }, { - "$ref": "#/definitions/SolidSpec" - } - ] - }, - "modulation_spec": { - "allOf": [ + "$ref": "#/definitions/PoleResidue" + }, { - "$ref": "#/definitions/ModulationSpec" + "$ref": "#/definitions/Sellmeier" } ] }, "name": { + "minLength": 1, "type": "string" }, - "nonlinear_spec": { + "normal_dir": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "phi": { "anyOf": [ { - "$ref": "#/definitions/NonlinearSpec" + "items": { + "type": "number" + }, + "type": "array" }, { - "$ref": "#/definitions/NonlinearSusceptibility" + "type": "ArrayLike" } ] }, - "type": { - "default": "Drude", - "enum": [ - "Drude" - ], - "type": "string" + "proj_distance": { + "default": 1000000.0, + "type": "number" }, - "viz_spec": { - "allOf": [ + "size": { + "anyOf": [ { - "$ref": "#/definitions/VisualizationSpec" + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" } ] - } - }, - "required": [ - "coeffs" - ], - "type": "object" - }, - "DualValleyEffectiveDOS": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "m_eff_hh": { - "exclusiveMinimum": 0, - "type": "number" }, - "m_eff_lh": { - "exclusiveMinimum": 0, - "type": "number" + "theta": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "ArrayLike" + } + ] }, "type": { - "default": "DualValleyEffectiveDOS", + "default": "FieldProjectionAngleMonitor", "enum": [ - "DualValleyEffectiveDOS" + "FieldProjectionAngleMonitor" ], "type": "string" - } - }, - "required": [ - "m_eff_hh", - "m_eff_lh" - ], - "type": "object" - }, - "FieldDataset": { - "additionalProperties": false, - "properties": { - "Ex": { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - }, - "Ey": { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - }, - "Ez": { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - }, - "Hx": { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" }, - "Hy": { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" - } - }, - "required": [ - "_dims" + "window_size": { + "default": [ + 0, + 0 ], - "type": "xr.DataArray" - }, - "Hz": { - "properties": { - "_dims": { - "type": "Tuple[str, ...]" + "items": [ + { + "minimum": 0, + "type": "number" + }, + { + "minimum": 0, + "type": "number" } - }, - "required": [ - "_dims" - ], - "type": "xr.DataArray" - }, - "attrs": { - "default": {}, - "type": "object" - }, - "type": { - "default": "FieldDataset", - "enum": [ - "FieldDataset" ], - "type": "string" + "maxItems": 2, + "minItems": 2, + "type": "array" } }, + "required": [ + "freqs", + "name", + "phi", + "size", + "theta" + ], "type": "object" }, - "FieldMonitor": { + "FieldProjectionCartesianMonitor": { "additionalProperties": false, "properties": { "apodization": { @@ -5812,30 +6960,45 @@ }, "colocate": { "default": true, + "enum": [ + true + ], "type": "boolean" }, - "fields": { - "default": [ - "Ex", - "Ey", - "Ez", - "Hx", - "Hy", - "Hz" + "custom_origin": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "exclude_surfaces": { "items": { "enum": [ - "Ex", - "Ey", - "Ez", - "Hx", - "Hy", - "Hz" + "x+", + "x-", + "y+", + "y-", + "z+", + "z-" ], "type": "string" }, "type": "array" }, + "far_field_approx": { + "default": true, + "type": "boolean" + }, "freqs": { "anyOf": [ { @@ -5873,10 +7036,99 @@ "minItems": 3, "type": "array" }, + "medium": { + "anyOf": [ + { + "$ref": "#/definitions/AnisotropicMedium" + }, + { + "$ref": "#/definitions/AnisotropicMediumFromMedium2D" + }, + { + "$ref": "#/definitions/CustomAnisotropicMedium" + }, + { + "$ref": "#/definitions/CustomDebye" + }, + { + "$ref": "#/definitions/CustomDrude" + }, + { + "$ref": "#/definitions/CustomLorentz" + }, + { + "$ref": "#/definitions/CustomMedium" + }, + { + "$ref": "#/definitions/CustomPoleResidue" + }, + { + "$ref": "#/definitions/CustomSellmeier" + }, + { + "$ref": "#/definitions/Debye" + }, + { + "$ref": "#/definitions/Drude" + }, + { + "$ref": "#/definitions/FullyAnisotropicMedium" + }, + { + "$ref": "#/definitions/Lorentz" + }, + { + "$ref": "#/definitions/LossyMetalMedium" + }, + { + "$ref": "#/definitions/Medium" + }, + { + "$ref": "#/definitions/Medium2D" + }, + { + "$ref": "#/definitions/PECMedium" + }, + { + "$ref": "#/definitions/PMCMedium" + }, + { + "$ref": "#/definitions/PerturbationMedium" + }, + { + "$ref": "#/definitions/PerturbationPoleResidue" + }, + { + "$ref": "#/definitions/PoleResidue" + }, + { + "$ref": "#/definitions/Sellmeier" + } + ] + }, "name": { "minLength": 1, "type": "string" }, + "normal_dir": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, + "proj_axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + }, + "proj_distance": { + "default": 1000000.0, + "type": "number" + }, "size": { "anyOf": [ { @@ -5925,21 +7177,69 @@ ] }, "type": { - "default": "FieldMonitor", + "default": "FieldProjectionCartesianMonitor", "enum": [ - "FieldMonitor" + "FieldProjectionCartesianMonitor" ], "type": "string" + }, + "window_size": { + "default": [ + 0, + 0 + ], + "items": [ + { + "minimum": 0, + "type": "number" + }, + { + "minimum": 0, + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "x": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "ArrayLike" + } + ] + }, + "y": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "ArrayLike" + } + ] } }, "required": [ "freqs", "name", - "size" + "proj_axis", + "size", + "x", + "y" ], "type": "object" }, - "FieldProjectionAngleMonitor": { + "FieldProjectionKSpaceMonitor": { "additionalProperties": false, "properties": { "apodization": { @@ -6168,18 +7468,13 @@ ], "type": "string" }, - "phi": { - "anyOf": [ - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "type": "ArrayLike" - } - ] + "proj_axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" }, "proj_distance": { "default": 1000000.0, @@ -6232,7 +7527,14 @@ } ] }, - "theta": { + "type": { + "default": "FieldProjectionKSpaceMonitor", + "enum": [ + "FieldProjectionKSpaceMonitor" + ], + "type": "string" + }, + "ux": { "anyOf": [ { "items": { @@ -6245,12 +7547,18 @@ } ] }, - "type": { - "default": "FieldProjectionAngleMonitor", - "enum": [ - "FieldProjectionAngleMonitor" - ], - "type": "string" + "uy": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "ArrayLike" + } + ] }, "window_size": { "default": [ @@ -6275,29 +7583,16 @@ "required": [ "freqs", "name", - "phi", + "proj_axis", "size", - "theta" + "ux", + "uy" ], "type": "object" }, - "FieldProjectionCartesianMonitor": { + "FieldTimeMonitor": { "additionalProperties": false, "properties": { - "apodization": { - "allOf": [ - { - "$ref": "#/definitions/ApodizationSpec" - } - ], - "default": { - "attrs": {}, - "end": null, - "start": null, - "type": "ApodizationSpec", - "width": null - } - }, "attrs": { "default": {}, "type": "object" @@ -6339,71 +7634,47 @@ ], "maxItems": 3, "minItems": 3, - "type": "array" - }, - { - "type": "autograd.tracer.Box" - } - ], - "default": [ - 0.0, - 0.0, - 0.0 - ] - }, - "colocate": { - "default": true, - "enum": [ - true - ], - "type": "boolean" - }, - "custom_origin": { - "items": [ - { - "type": "number" - }, - { - "type": "number" + "type": "array" }, { - "type": "number" + "type": "autograd.tracer.Box" } ], - "maxItems": 3, - "minItems": 3, - "type": "array" + "default": [ + 0.0, + 0.0, + 0.0 + ] }, - "exclude_surfaces": { + "colocate": { + "default": true, + "type": "boolean" + }, + "fields": { + "default": [ + "Ex", + "Ey", + "Ez", + "Hx", + "Hy", + "Hz" + ], "items": { "enum": [ - "x+", - "x-", - "y+", - "y-", - "z+", - "z-" + "Ex", + "Ey", + "Ez", + "Hx", + "Hy", + "Hz" ], "type": "string" }, "type": "array" }, - "far_field_approx": { - "default": true, - "type": "boolean" - }, - "freqs": { - "anyOf": [ - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "type": "ArrayLike" - } - ] + "interval": { + "exclusiveMinimum": 0, + "type": "integer" }, "interval_space": { "default": [ @@ -6429,99 +7700,10 @@ "minItems": 3, "type": "array" }, - "medium": { - "anyOf": [ - { - "$ref": "#/definitions/AnisotropicMedium" - }, - { - "$ref": "#/definitions/AnisotropicMediumFromMedium2D" - }, - { - "$ref": "#/definitions/CustomAnisotropicMedium" - }, - { - "$ref": "#/definitions/CustomDebye" - }, - { - "$ref": "#/definitions/CustomDrude" - }, - { - "$ref": "#/definitions/CustomLorentz" - }, - { - "$ref": "#/definitions/CustomMedium" - }, - { - "$ref": "#/definitions/CustomPoleResidue" - }, - { - "$ref": "#/definitions/CustomSellmeier" - }, - { - "$ref": "#/definitions/Debye" - }, - { - "$ref": "#/definitions/Drude" - }, - { - "$ref": "#/definitions/FullyAnisotropicMedium" - }, - { - "$ref": "#/definitions/Lorentz" - }, - { - "$ref": "#/definitions/LossyMetalMedium" - }, - { - "$ref": "#/definitions/Medium" - }, - { - "$ref": "#/definitions/Medium2D" - }, - { - "$ref": "#/definitions/PECMedium" - }, - { - "$ref": "#/definitions/PMCMedium" - }, - { - "$ref": "#/definitions/PerturbationMedium" - }, - { - "$ref": "#/definitions/PerturbationPoleResidue" - }, - { - "$ref": "#/definitions/PoleResidue" - }, - { - "$ref": "#/definitions/Sellmeier" - } - ] - }, "name": { "minLength": 1, "type": "string" }, - "normal_dir": { - "enum": [ - "+", - "-" - ], - "type": "string" - }, - "proj_axis": { - "enum": [ - 0, - 1, - 2 - ], - "type": "integer" - }, - "proj_distance": { - "default": 1000000.0, - "type": "number" - }, "size": { "anyOf": [ { @@ -6569,70 +7751,144 @@ } ] }, + "start": { + "default": 0.0, + "minimum": 0, + "type": "number" + }, + "stop": { + "minimum": 0, + "type": "number" + }, + "type": { + "default": "FieldTimeMonitor", + "enum": [ + "FieldTimeMonitor" + ], + "type": "string" + } + }, + "required": [ + "name", + "size" + ], + "type": "object" + }, + "FixedAngleSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "type": { + "default": "FixedAngleSpec", + "enum": [ + "FixedAngleSpec" + ], + "type": "string" + } + }, + "type": "object" + }, + "FixedInPlaneKSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "type": { + "default": "FixedInPlaneKSpec", + "enum": [ + "FixedInPlaneKSpec" + ], + "type": "string" + } + }, + "type": "object" + }, + "FluidMedium": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "density": { + "minimum": 0, + "type": "number" + }, + "expansivity": { + "minimum": 0, + "type": "number" + }, + "name": { + "type": "string" + }, + "specific_heat": { + "minimum": 0, + "type": "number" + }, + "thermal_conductivity": { + "minimum": 0, + "type": "number" + }, + "type": { + "default": "FluidMedium", + "enum": [ + "FluidMedium" + ], + "type": "string" + }, + "viscosity": { + "minimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "FluidSpec": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "density": { + "minimum": 0, + "type": "number" + }, + "expansivity": { + "minimum": 0, + "type": "number" + }, + "name": { + "type": "string" + }, + "specific_heat": { + "minimum": 0, + "type": "number" + }, + "thermal_conductivity": { + "minimum": 0, + "type": "number" + }, "type": { - "default": "FieldProjectionCartesianMonitor", + "default": "FluidSpec", "enum": [ - "FieldProjectionCartesianMonitor" + "FluidSpec" ], "type": "string" }, - "window_size": { - "default": [ - 0, - 0 - ], - "items": [ - { - "minimum": 0, - "type": "number" - }, - { - "minimum": 0, - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "x": { - "anyOf": [ - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "type": "ArrayLike" - } - ] - }, - "y": { - "anyOf": [ - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "type": "ArrayLike" - } - ] + "viscosity": { + "minimum": 0, + "type": "number" } }, - "required": [ - "freqs", - "name", - "proj_axis", - "size", - "x", - "y" - ], "type": "object" }, - "FieldProjectionKSpaceMonitor": { + "FluxMonitor": { "additionalProperties": false, "properties": { "apodization": { @@ -6709,22 +7965,6 @@ ], "type": "boolean" }, - "custom_origin": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, "exclude_surfaces": { "items": { "enum": [ @@ -6739,10 +7979,6 @@ }, "type": "array" }, - "far_field_approx": { - "default": true, - "type": "boolean" - }, "freqs": { "anyOf": [ { @@ -6764,15 +8000,21 @@ ], "items": [ { - "exclusiveMinimum": 0, + "enum": [ + 1 + ], "type": "integer" }, { - "exclusiveMinimum": 0, + "enum": [ + 1 + ], "type": "integer" }, { - "exclusiveMinimum": 0, + "enum": [ + 1 + ], "type": "integer" } ], @@ -6780,76 +8022,6 @@ "minItems": 3, "type": "array" }, - "medium": { - "anyOf": [ - { - "$ref": "#/definitions/AnisotropicMedium" - }, - { - "$ref": "#/definitions/AnisotropicMediumFromMedium2D" - }, - { - "$ref": "#/definitions/CustomAnisotropicMedium" - }, - { - "$ref": "#/definitions/CustomDebye" - }, - { - "$ref": "#/definitions/CustomDrude" - }, - { - "$ref": "#/definitions/CustomLorentz" - }, - { - "$ref": "#/definitions/CustomMedium" - }, - { - "$ref": "#/definitions/CustomPoleResidue" - }, - { - "$ref": "#/definitions/CustomSellmeier" - }, - { - "$ref": "#/definitions/Debye" - }, - { - "$ref": "#/definitions/Drude" - }, - { - "$ref": "#/definitions/FullyAnisotropicMedium" - }, - { - "$ref": "#/definitions/Lorentz" - }, - { - "$ref": "#/definitions/LossyMetalMedium" - }, - { - "$ref": "#/definitions/Medium" - }, - { - "$ref": "#/definitions/Medium2D" - }, - { - "$ref": "#/definitions/PECMedium" - }, - { - "$ref": "#/definitions/PMCMedium" - }, - { - "$ref": "#/definitions/PerturbationMedium" - }, - { - "$ref": "#/definitions/PerturbationPoleResidue" - }, - { - "$ref": "#/definitions/PoleResidue" - }, - { - "$ref": "#/definitions/Sellmeier" - } - ] - }, "name": { "minLength": 1, "type": "string" @@ -6861,18 +8033,6 @@ ], "type": "string" }, - "proj_axis": { - "enum": [ - 0, - 1, - 2 - ], - "type": "integer" - }, - "proj_distance": { - "default": 1000000.0, - "type": "number" - }, "size": { "anyOf": [ { @@ -6921,69 +8081,21 @@ ] }, "type": { - "default": "FieldProjectionKSpaceMonitor", + "default": "FluxMonitor", "enum": [ - "FieldProjectionKSpaceMonitor" + "FluxMonitor" ], "type": "string" - }, - "ux": { - "anyOf": [ - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "type": "ArrayLike" - } - ] - }, - "uy": { - "anyOf": [ - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "type": "ArrayLike" - } - ] - }, - "window_size": { - "default": [ - 0, - 0 - ], - "items": [ - { - "minimum": 0, - "type": "number" - }, - { - "minimum": 0, - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" } }, "required": [ "freqs", "name", - "proj_axis", - "size", - "ux", - "uy" + "size" ], "type": "object" }, - "FieldTimeMonitor": { + "FluxTimeMonitor": { "additionalProperties": false, "properties": { "attrs": { @@ -7041,25 +8153,20 @@ }, "colocate": { "default": true, - "type": "boolean" - }, - "fields": { - "default": [ - "Ex", - "Ey", - "Ez", - "Hx", - "Hy", - "Hz" + "enum": [ + true ], + "type": "boolean" + }, + "exclude_surfaces": { "items": { "enum": [ - "Ex", - "Ey", - "Ez", - "Hx", - "Hy", - "Hz" + "x+", + "x-", + "y+", + "y-", + "z+", + "z-" ], "type": "string" }, @@ -7077,15 +8184,21 @@ ], "items": [ { - "exclusiveMinimum": 0, + "enum": [ + 1 + ], "type": "integer" }, { - "exclusiveMinimum": 0, + "enum": [ + 1 + ], "type": "integer" }, { - "exclusiveMinimum": 0, + "enum": [ + 1 + ], "type": "integer" } ], @@ -7097,6 +8210,13 @@ "minLength": 1, "type": "string" }, + "normal_dir": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, "size": { "anyOf": [ { @@ -7154,9 +8274,9 @@ "type": "number" }, "type": { - "default": "FieldTimeMonitor", + "default": "FluxTimeMonitor", "enum": [ - "FieldTimeMonitor" + "FluxTimeMonitor" ], "type": "string" } @@ -7167,136 +8287,191 @@ ], "type": "object" }, - "FixedAngleSpec": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "type": { - "default": "FixedAngleSpec", - "enum": [ - "FixedAngleSpec" - ], - "type": "string" - } - }, - "type": "object" - }, - "FixedInPlaneKSpec": { + "FossumCarrierLifetime": { "additionalProperties": false, "properties": { - "attrs": { - "default": {}, - "type": "object" + "A": { + "type": "number" }, - "type": { - "default": "FixedInPlaneKSpec", - "enum": [ - "FixedInPlaneKSpec" - ], - "type": "string" - } - }, - "type": "object" - }, - "FluidMedium": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" + "B": { + "type": "number" }, - "density": { - "minimum": 0, + "C": { "type": "number" }, - "expansivity": { - "minimum": 0, + "N0": { + "exclusiveMinimum": 0, "type": "number" }, - "name": { - "type": "string" + "alpha": { + "type": "number" }, - "specific_heat": { - "minimum": 0, + "alpha_T": { "type": "number" }, - "thermal_conductivity": { - "minimum": 0, + "attrs": { + "default": {}, + "type": "object" + }, + "tau_300": { + "exclusiveMinimum": 0, "type": "number" }, "type": { - "default": "FluidMedium", + "default": "FossumCarrierLifetime", "enum": [ - "FluidMedium" + "FossumCarrierLifetime" ], "type": "string" - }, - "viscosity": { - "minimum": 0, - "type": "number" } }, + "required": [ + "A", + "B", + "C", + "N0", + "alpha", + "alpha_T", + "tau_300" + ], "type": "object" }, - "FluidSpec": { + "FullyAnisotropicMedium": { "additionalProperties": false, "properties": { + "allow_gain": { + "default": false, + "type": "boolean" + }, "attrs": { "default": {}, "type": "object" }, - "density": { - "minimum": 0, - "type": "number" + "conductivity": { + "default": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0, + 0, + 0 + ] + ], + "type": "ArrayLike" }, - "expansivity": { - "minimum": 0, - "type": "number" + "frequency_range": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "heat_spec": { + "discriminator": { + "mapping": { + "FluidMedium": "#/definitions/FluidMedium", + "FluidSpec": "#/definitions/FluidSpec", + "SolidMedium": "#/definitions/SolidMedium", + "SolidSpec": "#/definitions/SolidSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/FluidMedium" + }, + { + "$ref": "#/definitions/FluidSpec" + }, + { + "$ref": "#/definitions/SolidMedium" + }, + { + "$ref": "#/definitions/SolidSpec" + } + ] + }, + "modulation_spec": { + "allOf": [ + { + "$ref": "#/definitions/ModulationSpec" + } + ] }, "name": { "type": "string" }, - "specific_heat": { - "minimum": 0, - "type": "number" + "nonlinear_spec": { + "anyOf": [ + { + "$ref": "#/definitions/NonlinearSpec" + }, + { + "$ref": "#/definitions/NonlinearSusceptibility" + } + ] }, - "thermal_conductivity": { - "minimum": 0, - "type": "number" + "permittivity": { + "default": [ + [ + 1, + 0, + 0 + ], + [ + 0, + 1, + 0 + ], + [ + 0, + 0, + 1 + ] + ], + "type": "ArrayLike" }, "type": { - "default": "FluidSpec", + "default": "FullyAnisotropicMedium", "enum": [ - "FluidSpec" + "FullyAnisotropicMedium" ], "type": "string" }, - "viscosity": { - "minimum": 0, - "type": "number" + "viz_spec": { + "allOf": [ + { + "$ref": "#/definitions/VisualizationSpec" + } + ] } }, "type": "object" }, - "FluxMonitor": { - "additionalProperties": false, - "properties": { - "apodization": { - "allOf": [ - { - "$ref": "#/definitions/ApodizationSpec" - } - ], - "default": { - "attrs": {}, - "end": null, - "start": null, - "type": "ApodizationSpec", - "width": null - } + "GaussianBeam": { + "additionalProperties": false, + "properties": { + "angle_phi": { + "default": 0.0, + "type": "number" + }, + "angle_theta": { + "default": 0.0, + "type": "number" }, "attrs": { "default": {}, @@ -7351,80 +8526,25 @@ 0.0 ] }, - "colocate": { - "default": true, + "direction": { "enum": [ - true - ], - "type": "boolean" - }, - "exclude_surfaces": { - "items": { - "enum": [ - "x+", - "x-", - "y+", - "y-", - "z+", - "z-" - ], - "type": "string" - }, - "type": "array" - }, - "freqs": { - "anyOf": [ - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "type": "ArrayLike" - } - ] - }, - "interval_space": { - "default": [ - 1, - 1, - 1 - ], - "items": [ - { - "enum": [ - 1 - ], - "type": "integer" - }, - { - "enum": [ - 1 - ], - "type": "integer" - }, - { - "enum": [ - 1 - ], - "type": "integer" - } + "+", + "-" ], - "maxItems": 3, - "minItems": 3, - "type": "array" + "type": "string" }, "name": { - "minLength": 1, "type": "string" }, - "normal_dir": { - "enum": [ - "+", - "-" - ], - "type": "string" + "num_freqs": { + "default": 1, + "maximum": 20, + "minimum": 1, + "type": "integer" + }, + "pol_angle": { + "default": 0, + "type": "number" }, "size": { "anyOf": [ @@ -7473,22 +8593,52 @@ } ] }, + "source_time": { + "discriminator": { + "mapping": { + "ContinuousWave": "#/definitions/ContinuousWave", + "CustomSourceTime": "#/definitions/CustomSourceTime", + "GaussianPulse": "#/definitions/GaussianPulse" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/ContinuousWave" + }, + { + "$ref": "#/definitions/CustomSourceTime" + }, + { + "$ref": "#/definitions/GaussianPulse" + } + ] + }, "type": { - "default": "FluxMonitor", + "default": "GaussianBeam", "enum": [ - "FluxMonitor" + "GaussianBeam" ], "type": "string" + }, + "waist_distance": { + "default": 0.0, + "type": "number" + }, + "waist_radius": { + "default": 1.0, + "exclusiveMinimum": 0, + "type": "number" } }, "required": [ - "freqs", - "name", - "size" + "direction", + "size", + "source_time" ], "type": "object" }, - "FluxTimeMonitor": { + "GaussianDoping": { "additionalProperties": false, "properties": { "attrs": { @@ -7544,71 +8694,13 @@ 0.0 ] }, - "colocate": { - "default": true, - "enum": [ - true - ], - "type": "boolean" - }, - "exclude_surfaces": { - "items": { - "enum": [ - "x+", - "x-", - "y+", - "y-", - "z+", - "z-" - ], - "type": "string" - }, - "type": "array" - }, - "interval": { + "concentration": { "exclusiveMinimum": 0, - "type": "integer" - }, - "interval_space": { - "default": [ - 1, - 1, - 1 - ], - "items": [ - { - "enum": [ - 1 - ], - "type": "integer" - }, - { - "enum": [ - 1 - ], - "type": "integer" - }, - { - "enum": [ - 1 - ], - "type": "integer" - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - "name": { - "minLength": 1, - "type": "string" + "type": "number" }, - "normal_dir": { - "enum": [ - "+", - "-" - ], - "type": "string" + "ref_con": { + "exclusiveMinimum": 0, + "type": "number" }, "size": { "anyOf": [ @@ -7655,389 +8747,540 @@ { "type": "autograd.tracer.Box" } + ], + "default": [ + Infinity, + Infinity, + Infinity ] }, - "start": { - "default": 0.0, + "source": { + "default": "xmin", + "type": "string" + }, + "type": { + "default": "GaussianDoping", + "enum": [ + "GaussianDoping" + ], + "type": "string" + }, + "width": { + "exclusiveMinimum": 0, + "type": "number" + } + }, + "required": [ + "concentration", + "ref_con", + "width" + ], + "type": "object" + }, + "GaussianPulse": { + "additionalProperties": false, + "properties": { + "amplitude": { + "default": 1.0, "minimum": 0, "type": "number" }, - "stop": { - "minimum": 0, + "attrs": { + "default": {}, + "type": "object" + }, + "freq0": { + "exclusiveMinimum": 0, + "type": "number" + }, + "fwidth": { + "exclusiveMinimum": 0, + "type": "number" + }, + "offset": { + "default": 5.0, + "minimum": 2.5, + "type": "number" + }, + "phase": { + "default": 0.0, "type": "number" }, + "remove_dc_component": { + "default": true, + "type": "boolean" + }, "type": { - "default": "FluxTimeMonitor", + "default": "GaussianPulse", "enum": [ - "FluxTimeMonitor" + "GaussianPulse" ], "type": "string" } }, "required": [ - "name", - "size" + "freq0", + "fwidth" ], "type": "object" }, - "FossumCarrierLifetime": { + "GeometryGroup": { "additionalProperties": false, "properties": { - "A": { - "type": "number" - }, - "B": { - "type": "number" - }, - "C": { - "type": "number" - }, - "N0": { - "exclusiveMinimum": 0, - "type": "number" + "attrs": { + "default": {}, + "type": "object" }, - "alpha": { - "type": "number" + "geometries": { + "items": { + "discriminator": { + "mapping": { + "Box": "#/definitions/Box", + "ClipOperation": "#/definitions/ClipOperation", + "ComplexPolySlabBase": "#/definitions/ComplexPolySlabBase", + "Cylinder": "#/definitions/Cylinder", + "GeometryGroup": "#/definitions/GeometryGroup", + "PolySlab": "#/definitions/PolySlab", + "Sphere": "#/definitions/Sphere", + "Transformed": "#/definitions/Transformed", + "TriangleMesh": "#/definitions/TriangleMesh" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/Box" + }, + { + "$ref": "#/definitions/ClipOperation" + }, + { + "$ref": "#/definitions/ComplexPolySlabBase" + }, + { + "$ref": "#/definitions/Cylinder" + }, + { + "$ref": "#/definitions/GeometryGroup" + }, + { + "$ref": "#/definitions/PolySlab" + }, + { + "$ref": "#/definitions/Sphere" + }, + { + "$ref": "#/definitions/Transformed" + }, + { + "$ref": "#/definitions/TriangleMesh" + } + ] + }, + "type": "array" }, - "alpha_T": { - "type": "number" + "type": { + "default": "GeometryGroup", + "enum": [ + "GeometryGroup" + ], + "type": "string" + } + }, + "required": [ + "geometries" + ], + "type": "object" + }, + "GradedMesher": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" }, + "type": { + "default": "GradedMesher", + "enum": [ + "GradedMesher" + ], + "type": "string" + } + }, + "type": "object" + }, + "GridRefinement": { + "additionalProperties": false, + "properties": { "attrs": { "default": {}, "type": "object" }, - "tau_300": { + "dl": { + "exclusiveMinimum": 0, + "type": "number" + }, + "num_cells": { + "default": 3, + "exclusiveMinimum": 0, + "type": "integer" + }, + "refinement_factor": { "exclusiveMinimum": 0, "type": "number" }, "type": { - "default": "FossumCarrierLifetime", + "default": "GridRefinement", "enum": [ - "FossumCarrierLifetime" + "GridRefinement" ], "type": "string" } }, - "required": [ - "A", - "B", - "C", - "N0", - "alpha", - "alpha_T", - "tau_300" - ], "type": "object" }, - "FullyAnisotropicMedium": { + "GridSpec": { "additionalProperties": false, "properties": { - "allow_gain": { - "default": false, - "type": "boolean" - }, "attrs": { "default": {}, "type": "object" }, - "conductivity": { - "default": [ - [ - 0, - 0, - 0 - ], - [ - 0, - 0, - 0 - ], - [ - 0, - 0, - 0 - ] - ], - "type": "ArrayLike" + "grid_x": { + "default": { + "attrs": {}, + "dl_min": null, + "max_scale": 1.4, + "mesher": { + "attrs": {}, + "type": "GradedMesher" + }, + "min_steps_per_sim_size": 10.0, + "min_steps_per_wvl": 10.0, + "type": "AutoGrid" + }, + "discriminator": { + "mapping": { + "AutoGrid": "#/definitions/AutoGrid", + "CustomGrid": "#/definitions/CustomGrid", + "CustomGridBoundaries": "#/definitions/CustomGridBoundaries", + "QuasiUniformGrid": "#/definitions/QuasiUniformGrid", + "UniformGrid": "#/definitions/UniformGrid" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/AutoGrid" + }, + { + "$ref": "#/definitions/CustomGrid" + }, + { + "$ref": "#/definitions/CustomGridBoundaries" + }, + { + "$ref": "#/definitions/QuasiUniformGrid" + }, + { + "$ref": "#/definitions/UniformGrid" + } + ] }, - "frequency_range": { - "items": [ + "grid_y": { + "default": { + "attrs": {}, + "dl_min": null, + "max_scale": 1.4, + "mesher": { + "attrs": {}, + "type": "GradedMesher" + }, + "min_steps_per_sim_size": 10.0, + "min_steps_per_wvl": 10.0, + "type": "AutoGrid" + }, + "discriminator": { + "mapping": { + "AutoGrid": "#/definitions/AutoGrid", + "CustomGrid": "#/definitions/CustomGrid", + "CustomGridBoundaries": "#/definitions/CustomGridBoundaries", + "QuasiUniformGrid": "#/definitions/QuasiUniformGrid", + "UniformGrid": "#/definitions/UniformGrid" + }, + "propertyName": "type" + }, + "oneOf": [ { - "type": "number" + "$ref": "#/definitions/AutoGrid" }, { - "type": "number" + "$ref": "#/definitions/CustomGrid" + }, + { + "$ref": "#/definitions/CustomGridBoundaries" + }, + { + "$ref": "#/definitions/QuasiUniformGrid" + }, + { + "$ref": "#/definitions/UniformGrid" } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" + ] }, - "heat_spec": { + "grid_z": { + "default": { + "attrs": {}, + "dl_min": null, + "max_scale": 1.4, + "mesher": { + "attrs": {}, + "type": "GradedMesher" + }, + "min_steps_per_sim_size": 10.0, + "min_steps_per_wvl": 10.0, + "type": "AutoGrid" + }, "discriminator": { "mapping": { - "FluidMedium": "#/definitions/FluidMedium", - "FluidSpec": "#/definitions/FluidSpec", - "SolidMedium": "#/definitions/SolidMedium", - "SolidSpec": "#/definitions/SolidSpec" + "AutoGrid": "#/definitions/AutoGrid", + "CustomGrid": "#/definitions/CustomGrid", + "CustomGridBoundaries": "#/definitions/CustomGridBoundaries", + "QuasiUniformGrid": "#/definitions/QuasiUniformGrid", + "UniformGrid": "#/definitions/UniformGrid" }, "propertyName": "type" }, "oneOf": [ { - "$ref": "#/definitions/FluidMedium" + "$ref": "#/definitions/AutoGrid" }, { - "$ref": "#/definitions/FluidSpec" + "$ref": "#/definitions/CustomGrid" }, { - "$ref": "#/definitions/SolidMedium" + "$ref": "#/definitions/CustomGridBoundaries" }, { - "$ref": "#/definitions/SolidSpec" - } - ] - }, - "modulation_spec": { - "allOf": [ + "$ref": "#/definitions/QuasiUniformGrid" + }, { - "$ref": "#/definitions/ModulationSpec" + "$ref": "#/definitions/UniformGrid" } ] }, - "name": { - "type": "string" + "layer_refinement_specs": { + "default": [], + "items": { + "$ref": "#/definitions/LayerRefinementSpec" + }, + "type": "array" }, - "nonlinear_spec": { - "anyOf": [ - { - "$ref": "#/definitions/NonlinearSpec" + "override_structures": { + "default": [], + "items": { + "discriminator": { + "mapping": { + "MeshOverrideStructure": "#/definitions/MeshOverrideStructure", + "Structure": "#/definitions/Structure" + }, + "propertyName": "type" }, - { - "$ref": "#/definitions/NonlinearSusceptibility" - } - ] + "oneOf": [ + { + "$ref": "#/definitions/MeshOverrideStructure" + }, + { + "$ref": "#/definitions/Structure" + } + ] + }, + "type": "array" }, - "permittivity": { - "default": [ - [ - 1, - 0, - 0 - ], - [ - 0, - 1, - 0 + "snapping_points": { + "default": [], + "items": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } ], - [ - 0, - 0, - 1 - ] - ], - "type": "ArrayLike" + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "type": "array" }, "type": { - "default": "FullyAnisotropicMedium", + "default": "GridSpec", "enum": [ - "FullyAnisotropicMedium" + "GridSpec" ], "type": "string" }, - "viz_spec": { - "allOf": [ - { - "$ref": "#/definitions/VisualizationSpec" - } - ] + "wavelength": { + "type": "number" } }, "type": "object" }, - "GaussianBeam": { + "HammerstadSurfaceRoughness": { "additionalProperties": false, "properties": { - "angle_phi": { - "default": 0.0, + "attrs": { + "default": {}, + "type": "object" + }, + "roughness_factor": { + "default": 2.0, + "exclusiveMinimum": 1.0, "type": "number" }, - "angle_theta": { - "default": 0.0, + "rq": { + "exclusiveMinimum": 0, "type": "number" }, + "type": { + "default": "HammerstadSurfaceRoughness", + "enum": [ + "HammerstadSurfaceRoughness" + ], + "type": "string" + } + }, + "required": [ + "rq" + ], + "type": "object" + }, + "HeuristicPECStaircasing": { + "additionalProperties": false, + "properties": { "attrs": { "default": {}, "type": "object" }, - "center": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - { - "type": "autograd.tracer.Box" - } - ], - "default": [ - 0.0, - 0.0, - 0.0 - ] - }, - "direction": { + "type": { + "default": "HeuristicPECStaircasing", "enum": [ - "+", - "-" + "HeuristicPECStaircasing" ], "type": "string" + } + }, + "type": "object" + }, + "HuraySurfaceRoughness": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" }, - "name": { - "type": "string" + "coeffs": { + "items": { + "items": [ + { + "exclusiveMinimum": 0, + "type": "number" + }, + { + "exclusiveMinimum": 0, + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "type": "array" }, - "num_freqs": { + "relative_area": { "default": 1, - "maximum": 20, - "minimum": 1, - "type": "integer" - }, - "pol_angle": { - "default": 0, + "exclusiveMinimum": 0, "type": "number" }, - "size": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, + "type": { + "default": "HuraySurfaceRoughness", + "enum": [ + "HuraySurfaceRoughness" + ], + "type": "string" + } + }, + "required": [ + "coeffs" + ], + "type": "object" + }, + "IndexPerturbation": { + "additionalProperties": false, + "properties": { + "attrs": { + "default": {}, + "type": "object" + }, + "delta_k": { + "allOf": [ { - "type": "autograd.tracer.Box" + "$ref": "#/definitions/ParameterPerturbation" } ] }, - "source_time": { - "discriminator": { - "mapping": { - "ContinuousWave": "#/definitions/ContinuousWave", - "CustomSourceTime": "#/definitions/CustomSourceTime", - "GaussianPulse": "#/definitions/GaussianPulse" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/ContinuousWave" - }, - { - "$ref": "#/definitions/CustomSourceTime" - }, + "delta_n": { + "allOf": [ { - "$ref": "#/definitions/GaussianPulse" + "$ref": "#/definitions/ParameterPerturbation" } ] }, + "freq": { + "minimum": 0, + "type": "number" + }, "type": { - "default": "GaussianBeam", + "default": "IndexPerturbation", "enum": [ - "GaussianBeam" + "IndexPerturbation" ], "type": "string" - }, - "waist_distance": { - "default": 0.0, - "type": "number" - }, - "waist_radius": { - "default": 1.0, - "exclusiveMinimum": 0, - "type": "number" } }, "required": [ - "direction", - "size", - "source_time" + "freq" ], "type": "object" }, - "GaussianDoping": { + "InternalAbsorber": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, + "boundary_spec": { + "discriminator": { + "mapping": { + "ABCBoundary": "#/definitions/ABCBoundary", + "ModeABCBoundary": "#/definitions/ModeABCBoundary" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/ABCBoundary" + }, + { + "$ref": "#/definitions/ModeABCBoundary" + } + ] + }, "center": { "anyOf": [ { @@ -8087,13 +9330,16 @@ 0.0 ] }, - "concentration": { - "exclusiveMinimum": 0, - "type": "number" + "direction": { + "enum": [ + "+", + "-" + ], + "type": "string" }, - "ref_con": { - "exclusiveMinimum": 0, - "type": "number" + "grid_shift": { + "default": 0, + "type": "integer" }, "size": { "anyOf": [ @@ -8140,571 +9386,584 @@ { "type": "autograd.tracer.Box" } - ], - "default": [ - Infinity, - Infinity, - Infinity ] }, - "source": { - "default": "xmin", - "type": "string" - }, - "type": { - "default": "GaussianDoping", - "enum": [ - "GaussianDoping" - ], - "type": "string" - }, - "width": { - "exclusiveMinimum": 0, - "type": "number" - } - }, - "required": [ - "concentration", - "ref_con", - "width" - ], - "type": "object" - }, - "GaussianPulse": { - "additionalProperties": false, - "properties": { - "amplitude": { - "default": 1.0, - "minimum": 0, - "type": "number" - }, - "attrs": { - "default": {}, - "type": "object" - }, - "freq0": { - "exclusiveMinimum": 0, - "type": "number" - }, - "fwidth": { - "exclusiveMinimum": 0, - "type": "number" - }, - "offset": { - "default": 5.0, - "minimum": 2.5, - "type": "number" - }, - "phase": { - "default": 0.0, - "type": "number" - }, - "remove_dc_component": { - "default": true, - "type": "boolean" - }, - "type": { - "default": "GaussianPulse", - "enum": [ - "GaussianPulse" - ], - "type": "string" - } - }, - "required": [ - "freq0", - "fwidth" - ], - "type": "object" - }, - "GeometryGroup": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "geometries": { - "items": { - "discriminator": { - "mapping": { - "Box": "#/definitions/Box", - "ClipOperation": "#/definitions/ClipOperation", - "ComplexPolySlabBase": "#/definitions/ComplexPolySlabBase", - "Cylinder": "#/definitions/Cylinder", - "GeometryGroup": "#/definitions/GeometryGroup", - "PolySlab": "#/definitions/PolySlab", - "Sphere": "#/definitions/Sphere", - "Transformed": "#/definitions/Transformed", - "TriangleMesh": "#/definitions/TriangleMesh" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/Box" - }, - { - "$ref": "#/definitions/ClipOperation" - }, - { - "$ref": "#/definitions/ComplexPolySlabBase" - }, - { - "$ref": "#/definitions/Cylinder" - }, - { - "$ref": "#/definitions/GeometryGroup" - }, - { - "$ref": "#/definitions/PolySlab" - }, - { - "$ref": "#/definitions/Sphere" - }, - { - "$ref": "#/definitions/Transformed" - }, - { - "$ref": "#/definitions/TriangleMesh" - } - ] - }, - "type": "array" - }, "type": { - "default": "GeometryGroup", + "default": "InternalAbsorber", "enum": [ - "GeometryGroup" + "InternalAbsorber" ], "type": "string" } }, "required": [ - "geometries" + "boundary_spec", + "direction", + "size" ], "type": "object" }, - "GradedMesher": { + "IsotropicEffectiveDOS": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, + "m_eff": { + "exclusiveMinimum": 0, + "type": "number" + }, "type": { - "default": "GradedMesher", + "default": "IsotropicEffectiveDOS", "enum": [ - "GradedMesher" + "IsotropicEffectiveDOS" ], "type": "string" } }, + "required": [ + "m_eff" + ], "type": "object" }, - "GridRefinement": { + "KerrNonlinearity": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "dl": { - "exclusiveMinimum": 0, - "type": "number" - }, - "num_cells": { - "default": 3, - "exclusiveMinimum": 0, - "type": "integer" + "n0": { + "anyOf": [ + { + "$ref": "#/definitions/ComplexNumber" + }, + { + "properties": { + "imag": { + "type": "number" + }, + "real": { + "type": "number" + } + }, + "required": [ + "imag", + "real" + ], + "type": "object" + } + ] }, - "refinement_factor": { - "exclusiveMinimum": 0, - "type": "number" + "n2": { + "anyOf": [ + { + "$ref": "#/definitions/ComplexNumber" + }, + { + "properties": { + "imag": { + "type": "number" + }, + "real": { + "type": "number" + } + }, + "required": [ + "imag", + "real" + ], + "type": "object" + } + ], + "default": 0 }, "type": { - "default": "GridRefinement", + "default": "KerrNonlinearity", "enum": [ - "GridRefinement" + "KerrNonlinearity" ], "type": "string" + }, + "use_complex_fields": { + "default": false, + "type": "boolean" } }, "type": "object" }, - "GridSpec": { + "LayerRefinementSpec": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "grid_x": { - "default": { - "attrs": {}, - "dl_min": null, - "max_scale": 1.4, - "mesher": { - "attrs": {}, - "type": "GradedMesher" - }, - "min_steps_per_sim_size": 10.0, - "min_steps_per_wvl": 10.0, - "type": "AutoGrid" - }, - "discriminator": { - "mapping": { - "AutoGrid": "#/definitions/AutoGrid", - "CustomGrid": "#/definitions/CustomGrid", - "CustomGridBoundaries": "#/definitions/CustomGridBoundaries", - "QuasiUniformGrid": "#/definitions/QuasiUniformGrid", - "UniformGrid": "#/definitions/UniformGrid" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/AutoGrid" - }, - { - "$ref": "#/definitions/CustomGrid" - }, - { - "$ref": "#/definitions/CustomGridBoundaries" - }, - { - "$ref": "#/definitions/QuasiUniformGrid" - }, + "axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + }, + "bounds_refinement": { + "allOf": [ { - "$ref": "#/definitions/UniformGrid" + "$ref": "#/definitions/GridRefinement" } ] }, - "grid_y": { - "default": { - "attrs": {}, - "dl_min": null, - "max_scale": 1.4, - "mesher": { - "attrs": {}, - "type": "GradedMesher" - }, - "min_steps_per_sim_size": 10.0, - "min_steps_per_wvl": 10.0, - "type": "AutoGrid" - }, - "discriminator": { - "mapping": { - "AutoGrid": "#/definitions/AutoGrid", - "CustomGrid": "#/definitions/CustomGrid", - "CustomGridBoundaries": "#/definitions/CustomGridBoundaries", - "QuasiUniformGrid": "#/definitions/QuasiUniformGrid", - "UniformGrid": "#/definitions/UniformGrid" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/AutoGrid" - }, - { - "$ref": "#/definitions/CustomGrid" - }, - { - "$ref": "#/definitions/CustomGridBoundaries" - }, + "bounds_snapping": { + "default": "lower", + "enum": [ + "bounds", + "center", + "lower", + "upper" + ], + "type": "string" + }, + "center": { + "anyOf": [ { - "$ref": "#/definitions/QuasiUniformGrid" + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" }, { - "$ref": "#/definitions/UniformGrid" + "type": "autograd.tracer.Box" } + ], + "default": [ + 0.0, + 0.0, + 0.0 ] }, - "grid_z": { - "default": { - "attrs": {}, - "dl_min": null, - "max_scale": 1.4, - "mesher": { - "attrs": {}, - "type": "GradedMesher" - }, - "min_steps_per_sim_size": 10.0, - "min_steps_per_wvl": 10.0, - "type": "AutoGrid" - }, - "discriminator": { - "mapping": { - "AutoGrid": "#/definitions/AutoGrid", - "CustomGrid": "#/definitions/CustomGrid", - "CustomGridBoundaries": "#/definitions/CustomGridBoundaries", - "QuasiUniformGrid": "#/definitions/QuasiUniformGrid", - "UniformGrid": "#/definitions/UniformGrid" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/AutoGrid" - }, - { - "$ref": "#/definitions/CustomGrid" - }, - { - "$ref": "#/definitions/CustomGridBoundaries" - }, - { - "$ref": "#/definitions/QuasiUniformGrid" - }, + "corner_finder": { + "allOf": [ { - "$ref": "#/definitions/UniformGrid" + "$ref": "#/definitions/CornerFinderSpec" } - ] - }, - "layer_refinement_specs": { - "default": [], - "items": { - "$ref": "#/definitions/LayerRefinementSpec" - }, - "type": "array" + ], + "default": { + "angle_threshold": 0.3141592653589793, + "attrs": {}, + "concave_resolution": null, + "convex_resolution": null, + "distance_threshold": null, + "medium": "metal", + "mixed_resolution": null, + "type": "CornerFinderSpec" + } }, - "override_structures": { - "default": [], - "items": { - "discriminator": { - "mapping": { - "MeshOverrideStructure": "#/definitions/MeshOverrideStructure", - "Structure": "#/definitions/Structure" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/MeshOverrideStructure" - }, - { - "$ref": "#/definitions/Structure" - } - ] - }, - "type": "array" + "corner_refinement": { + "allOf": [ + { + "$ref": "#/definitions/GridRefinement" + } + ], + "default": { + "attrs": {}, + "dl": null, + "num_cells": 3, + "refinement_factor": null, + "type": "GridRefinement" + } }, - "snapping_points": { - "default": [], - "items": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - "type": "array" + "corner_snapping": { + "default": true, + "type": "boolean" }, - "type": { - "default": "GridSpec", - "enum": [ - "GridSpec" - ], - "type": "string" + "dl_min_from_gap_width": { + "default": true, + "type": "boolean" }, - "wavelength": { - "type": "number" - } - }, - "type": "object" - }, - "HammerstadSurfaceRoughness": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" + "gap_meshing_iters": { + "default": 1, + "minimum": 0, + "type": "integer" }, - "roughness_factor": { - "default": 2.0, - "exclusiveMinimum": 1.0, - "type": "number" + "interior_disjoint_geometries": { + "default": true, + "type": "boolean" }, - "rq": { + "min_steps_along_axis": { "exclusiveMinimum": 0, "type": "number" }, + "refinement_inside_sim_only": { + "default": true, + "type": "boolean" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, "type": { - "default": "HammerstadSurfaceRoughness", + "default": "LayerRefinementSpec", "enum": [ - "HammerstadSurfaceRoughness" + "LayerRefinementSpec" ], "type": "string" } }, "required": [ - "rq" + "axis", + "size" ], "type": "object" }, - "HeuristicPECStaircasing": { + "LinearChargePerturbation": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "type": { - "default": "HeuristicPECStaircasing", - "enum": [ - "HeuristicPECStaircasing" + "electron_coeff": { + "type": "number" + }, + "electron_range": { + "default": [ + 0, + Infinity ], - "type": "string" - } - }, - "type": "object" - }, - "HuraySurfaceRoughness": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" + "items": [ + { + "minimum": 0, + "type": "number" + }, + { + "minimum": 0, + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" }, - "coeffs": { - "items": { - "items": [ - { - "exclusiveMinimum": 0, - "type": "number" - }, - { - "exclusiveMinimum": 0, - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, + "electron_ref": { + "minimum": 0, + "type": "number" + }, + "hole_coeff": { + "type": "number" + }, + "hole_range": { + "default": [ + 0, + Infinity + ], + "items": [ + { + "minimum": 0, + "type": "number" + }, + { + "minimum": 0, + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, "type": "array" }, - "relative_area": { - "default": 1, - "exclusiveMinimum": 0, + "hole_ref": { + "minimum": 0, "type": "number" }, "type": { - "default": "HuraySurfaceRoughness", + "default": "LinearChargePerturbation", "enum": [ - "HuraySurfaceRoughness" + "LinearChargePerturbation" ], "type": "string" } }, "required": [ - "coeffs" + "electron_coeff", + "electron_ref", + "hole_coeff", + "hole_ref" ], "type": "object" }, - "IndexPerturbation": { + "LinearHeatPerturbation": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "delta_k": { - "allOf": [ + "coeff": { + "anyOf": [ { - "$ref": "#/definitions/ParameterPerturbation" + "$ref": "#/definitions/ComplexNumber" + }, + { + "properties": { + "imag": { + "type": "number" + }, + "real": { + "type": "number" + } + }, + "required": [ + "imag", + "real" + ], + "type": "object" + }, + { + "type": "number" } ] }, - "delta_n": { - "allOf": [ + "temperature_range": { + "default": [ + 0, + Infinity + ], + "items": [ { - "$ref": "#/definitions/ParameterPerturbation" + "minimum": 0, + "type": "number" + }, + { + "minimum": 0, + "type": "number" } - ] + ], + "maxItems": 2, + "minItems": 2, + "type": "array" }, - "freq": { + "temperature_ref": { "minimum": 0, "type": "number" }, "type": { - "default": "IndexPerturbation", + "default": "LinearHeatPerturbation", "enum": [ - "IndexPerturbation" + "LinearHeatPerturbation" ], "type": "string" } }, "required": [ - "freq" + "coeff", + "temperature_ref" ], "type": "object" }, - "InternalAbsorber": { + "LinearLumpedElement": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "boundary_spec": { + "center": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "dist_type": { + "default": "on", + "enum": [ + "laterally_only", + "off", + "on" + ], + "type": "string" + }, + "enable_snapping_points": { + "default": true, + "type": "boolean" + }, + "name": { + "minLength": 1, + "type": "string" + }, + "network": { "discriminator": { "mapping": { - "ABCBoundary": "#/definitions/ABCBoundary", - "ModeABCBoundary": "#/definitions/ModeABCBoundary" + "AdmittanceNetwork": "#/definitions/AdmittanceNetwork", + "RLCNetwork": "#/definitions/RLCNetwork" }, "propertyName": "type" }, "oneOf": [ { - "$ref": "#/definitions/ABCBoundary" + "$ref": "#/definitions/AdmittanceNetwork" }, { - "$ref": "#/definitions/ModeABCBoundary" + "$ref": "#/definitions/RLCNetwork" } ] }, - "center": { + "num_grid_cells": { + "default": 1, + "exclusiveMinimum": 0, + "type": "integer" + }, + "size": { "anyOf": [ { "items": [ { "anyOf": [ { - "type": "autograd.tracer.Box" + "minimum": 0, + "type": "number" }, { - "type": "number" + "type": "autograd.tracer.Box" } ] }, { "anyOf": [ { - "type": "autograd.tracer.Box" + "minimum": 0, + "type": "number" }, { - "type": "number" + "type": "autograd.tracer.Box" } ] }, { "anyOf": [ { - "type": "autograd.tracer.Box" + "minimum": 0, + "type": "number" }, { - "type": "number" + "type": "autograd.tracer.Box" } ] } @@ -8714,210 +9973,305 @@ "type": "array" }, { - "type": "autograd.tracer.Box" + "type": "autograd.tracer.Box" + } + ] + }, + "snap_perimeter_to_grid": { + "default": true, + "type": "boolean" + }, + "type": { + "default": "LinearLumpedElement", + "enum": [ + "LinearLumpedElement" + ], + "type": "string" + }, + "voltage_axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" + } + }, + "required": [ + "name", + "network", + "size", + "voltage_axis" + ], + "type": "object" + }, + "Lorentz": { + "additionalProperties": false, + "properties": { + "allow_gain": { + "default": false, + "type": "boolean" + }, + "attrs": { + "default": {}, + "type": "object" + }, + "coeffs": { + "items": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "minimum": 0, + "type": "number" + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "type": "array" + }, + "eps_inf": { + "default": 1.0, + "exclusiveMinimum": 0, + "type": "number" + }, + "frequency_range": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "heat_spec": { + "discriminator": { + "mapping": { + "FluidMedium": "#/definitions/FluidMedium", + "FluidSpec": "#/definitions/FluidSpec", + "SolidMedium": "#/definitions/SolidMedium", + "SolidSpec": "#/definitions/SolidSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/FluidMedium" + }, + { + "$ref": "#/definitions/FluidSpec" + }, + { + "$ref": "#/definitions/SolidMedium" + }, + { + "$ref": "#/definitions/SolidSpec" } - ], - "default": [ - 0.0, - 0.0, - 0.0 ] }, - "direction": { - "enum": [ - "+", - "-" - ], - "type": "string" + "modulation_spec": { + "allOf": [ + { + "$ref": "#/definitions/ModulationSpec" + } + ] }, - "grid_shift": { - "default": 0, - "type": "integer" + "name": { + "type": "string" }, - "size": { + "nonlinear_spec": { "anyOf": [ { - "items": [ - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" + "$ref": "#/definitions/NonlinearSpec" }, { - "type": "autograd.tracer.Box" + "$ref": "#/definitions/NonlinearSusceptibility" } ] }, "type": { - "default": "InternalAbsorber", + "default": "Lorentz", "enum": [ - "InternalAbsorber" + "Lorentz" ], "type": "string" + }, + "viz_spec": { + "allOf": [ + { + "$ref": "#/definitions/VisualizationSpec" + } + ] } }, "required": [ - "boundary_spec", - "direction", - "size" + "coeffs" ], "type": "object" }, - "IsotropicEffectiveDOS": { + "LossyMetalMedium": { "additionalProperties": false, "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "m_eff": { - "exclusiveMinimum": 0, - "type": "number" - }, - "type": { - "default": "IsotropicEffectiveDOS", + "allow_gain": { + "default": false, "enum": [ - "IsotropicEffectiveDOS" + false ], - "type": "string" - } - }, - "required": [ - "m_eff" - ], - "type": "object" - }, - "KerrNonlinearity": { - "additionalProperties": false, - "properties": { + "type": "boolean" + }, "attrs": { "default": {}, "type": "object" }, - "n0": { + "conductivity": { "anyOf": [ { - "$ref": "#/definitions/ComplexNumber" + "type": "autograd.tracer.Box" }, { - "properties": { - "imag": { - "type": "number" - }, - "real": { - "type": "number" - } - }, - "required": [ - "imag", - "real" - ], - "type": "object" + "type": "number" + } + ], + "default": 0.0 + }, + "fit_param": { + "allOf": [ + { + "$ref": "#/definitions/SurfaceImpedanceFitterParam" + } + ], + "default": { + "attrs": {}, + "frequency_sampling_points": 20, + "log_sampling": true, + "max_num_poles": 5, + "tolerance_rms": 0.001, + "type": "SurfaceImpedanceFitterParam" + } + }, + "frequency_range": { + "items": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "heat_spec": { + "discriminator": { + "mapping": { + "FluidMedium": "#/definitions/FluidMedium", + "FluidSpec": "#/definitions/FluidSpec", + "SolidMedium": "#/definitions/SolidMedium", + "SolidSpec": "#/definitions/SolidSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/FluidMedium" + }, + { + "$ref": "#/definitions/FluidSpec" + }, + { + "$ref": "#/definitions/SolidMedium" + }, + { + "$ref": "#/definitions/SolidSpec" } ] }, - "n2": { + "modulation_spec": { + "allOf": [ + { + "$ref": "#/definitions/ModulationSpec" + } + ] + }, + "name": { + "type": "string" + }, + "nonlinear_spec": { "anyOf": [ { - "$ref": "#/definitions/ComplexNumber" + "$ref": "#/definitions/NonlinearSpec" }, { - "properties": { - "imag": { - "type": "number" - }, - "real": { - "type": "number" - } - }, - "required": [ - "imag", - "real" - ], - "type": "object" + "$ref": "#/definitions/NonlinearSusceptibility" } + ] + }, + "permittivity": { + "default": 1.0, + "enum": [ + 1 ], - "default": 0 + "type": "integer" + }, + "roughness": { + "discriminator": { + "mapping": { + "HammerstadSurfaceRoughness": "#/definitions/HammerstadSurfaceRoughness", + "HuraySurfaceRoughness": "#/definitions/HuraySurfaceRoughness" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/HammerstadSurfaceRoughness" + }, + { + "$ref": "#/definitions/HuraySurfaceRoughness" + } + ] + }, + "thickness": { + "exclusiveMinimum": 0, + "type": "number" }, "type": { - "default": "KerrNonlinearity", + "default": "LossyMetalMedium", "enum": [ - "KerrNonlinearity" + "LossyMetalMedium" ], "type": "string" }, - "use_complex_fields": { - "default": false, - "type": "boolean" + "viz_spec": { + "allOf": [ + { + "$ref": "#/definitions/VisualizationSpec" + } + ] } }, + "required": [ + "frequency_range" + ], "type": "object" }, - "LayerRefinementSpec": { + "LumpedPort": { "additionalProperties": false, "properties": { "attrs": { "default": {}, "type": "object" }, - "axis": { - "enum": [ - 0, - 1, - 2 - ], - "type": "integer" - }, - "bounds_refinement": { - "allOf": [ - { - "$ref": "#/definitions/GridRefinement" - } - ] - }, - "bounds_snapping": { - "default": "lower", - "enum": [ - "bounds", - "center", - "lower", - "upper" - ], - "type": "string" - }, "center": { "anyOf": [ { @@ -8967,61 +10321,50 @@ 0.0 ] }, - "corner_finder": { - "allOf": [ - { - "$ref": "#/definitions/CornerFinderSpec" - } - ], - "default": { - "angle_threshold": 0.3141592653589793, - "attrs": {}, - "concave_resolution": null, - "convex_resolution": null, - "distance_threshold": null, - "medium": "metal", - "mixed_resolution": null, - "type": "CornerFinderSpec" - } - }, - "corner_refinement": { - "allOf": [ - { - "$ref": "#/definitions/GridRefinement" - } + "dist_type": { + "default": "on", + "enum": [ + "laterally_only", + "off", + "on" ], - "default": { - "attrs": {}, - "dl": null, - "num_cells": 3, - "refinement_factor": null, - "type": "GridRefinement" - } - }, - "corner_snapping": { - "default": true, - "type": "boolean" + "type": "string" }, - "dl_min_from_gap_width": { + "enable_snapping_points": { "default": true, "type": "boolean" }, - "gap_meshing_iters": { - "default": 1, - "minimum": 0, - "type": "integer" + "impedance": { + "anyOf": [ + { + "$ref": "#/definitions/ComplexNumber" + }, + { + "properties": { + "imag": { + "type": "number" + }, + "real": { + "type": "number" + } + }, + "required": [ + "imag", + "real" + ], + "type": "object" + } + ], + "default": 50 }, - "interior_disjoint_geometries": { - "default": true, - "type": "boolean" + "name": { + "minLength": 1, + "type": "string" }, - "min_steps_along_axis": { + "num_grid_cells": { + "default": 3, "exclusiveMinimum": 0, - "type": "number" - }, - "refinement_inside_sim_only": { - "default": true, - "type": "boolean" + "type": "integer" }, "size": { "anyOf": [ @@ -9033,202 +10376,71 @@ "minimum": 0, "type": "number" }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - "type": { - "default": "LayerRefinementSpec", - "enum": [ - "LayerRefinementSpec" - ], - "type": "string" - } - }, - "required": [ - "axis", - "size" - ], - "type": "object" - }, - "LinearChargePerturbation": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "electron_coeff": { - "type": "number" - }, - "electron_range": { - "default": [ - 0, - Infinity - ], - "items": [ - { - "minimum": 0, - "type": "number" - }, - { - "minimum": 0, - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "electron_ref": { - "minimum": 0, - "type": "number" - }, - "hole_coeff": { - "type": "number" - }, - "hole_range": { - "default": [ - 0, - Infinity - ], - "items": [ - { - "minimum": 0, - "type": "number" - }, - { - "minimum": 0, - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "hole_ref": { - "minimum": 0, - "type": "number" - }, - "type": { - "default": "LinearChargePerturbation", - "enum": [ - "LinearChargePerturbation" - ], - "type": "string" - } - }, - "required": [ - "electron_coeff", - "electron_ref", - "hole_coeff", - "hole_ref" - ], - "type": "object" - }, - "LinearHeatPerturbation": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "coeff": { - "anyOf": [ - { - "$ref": "#/definitions/ComplexNumber" - }, - { - "properties": { - "imag": { - "type": "number" + { + "type": "autograd.tracer.Box" + } + ] }, - "real": { - "type": "number" + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] } - }, - "required": [ - "imag", - "real" ], - "type": "object" + "maxItems": 3, + "minItems": 3, + "type": "array" }, { - "type": "number" + "type": "autograd.tracer.Box" } ] }, - "temperature_range": { - "default": [ - 0, - Infinity - ], - "items": [ - { - "minimum": 0, - "type": "number" - }, - { - "minimum": 0, - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "temperature_ref": { - "minimum": 0, - "type": "number" + "snap_perimeter_to_grid": { + "default": true, + "type": "boolean" }, "type": { - "default": "LinearHeatPerturbation", + "default": "LumpedPort", "enum": [ - "LinearHeatPerturbation" + "LumpedPort" ], "type": "string" + }, + "voltage_axis": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer" } }, "required": [ - "coeff", - "temperature_ref" + "name", + "size", + "voltage_axis" ], "type": "object" }, - "LinearLumpedElement": { + "LumpedResistor": { "additionalProperties": false, "properties": { "attrs": { @@ -9284,15 +10496,6 @@ 0.0 ] }, - "dist_type": { - "default": "on", - "enum": [ - "laterally_only", - "off", - "on" - ], - "type": "string" - }, "enable_snapping_points": { "default": true, "type": "boolean" @@ -9301,28 +10504,16 @@ "minLength": 1, "type": "string" }, - "network": { - "discriminator": { - "mapping": { - "AdmittanceNetwork": "#/definitions/AdmittanceNetwork", - "RLCNetwork": "#/definitions/RLCNetwork" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/AdmittanceNetwork" - }, - { - "$ref": "#/definitions/RLCNetwork" - } - ] - }, "num_grid_cells": { "default": 1, "exclusiveMinimum": 0, "type": "integer" }, + "resistance": { + "exclusiveMinimum": 0, + "type": "number", + "unit": "ohm" + }, "size": { "anyOf": [ { @@ -9375,9 +10566,9 @@ "type": "boolean" }, "type": { - "default": "LinearLumpedElement", + "default": "LumpedResistor", "enum": [ - "LinearLumpedElement" + "LumpedResistor" ], "type": "string" }, @@ -9392,13 +10583,13 @@ }, "required": [ "name", - "network", + "resistance", "size", "voltage_axis" ], "type": "object" }, - "Lorentz": { + "Medium": { "additionalProperties": false, "properties": { "allow_gain": { @@ -9409,30 +10600,16 @@ "default": {}, "type": "object" }, - "coeffs": { - "items": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - }, - { - "minimum": 0, - "type": "number" - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - "type": "array" - }, - "eps_inf": { - "default": 1.0, - "exclusiveMinimum": 0, - "type": "number" + "conductivity": { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ], + "default": 0.0 }, "frequency_range": { "items": [ @@ -9492,10 +10669,22 @@ } ] }, + "permittivity": { + "anyOf": [ + { + "minimum": 1.0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ], + "default": 1.0 + }, "type": { - "default": "Lorentz", + "default": "Medium", "enum": [ - "Lorentz" + "Medium" ], "type": "string" }, @@ -9507,51 +10696,19 @@ ] } }, - "required": [ - "coeffs" - ], "type": "object" }, - "LossyMetalMedium": { + "Medium2D": { "additionalProperties": false, "properties": { "allow_gain": { "default": false, - "enum": [ - false - ], "type": "boolean" }, "attrs": { "default": {}, "type": "object" }, - "conductivity": { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ], - "default": 0.0 - }, - "fit_param": { - "allOf": [ - { - "$ref": "#/definitions/SurfaceImpedanceFitterParam" - } - ], - "default": { - "attrs": {}, - "frequency_sampling_points": 20, - "log_sampling": true, - "max_num_poles": 5, - "tolerance_rms": 0.001, - "type": "SurfaceImpedanceFitterParam" - } - }, "frequency_range": { "items": [ { @@ -9580,68 +10737,122 @@ "$ref": "#/definitions/FluidMedium" }, { - "$ref": "#/definitions/FluidSpec" + "$ref": "#/definitions/FluidSpec" + }, + { + "$ref": "#/definitions/SolidMedium" + }, + { + "$ref": "#/definitions/SolidSpec" + } + ] + }, + "modulation_spec": { + "allOf": [ + { + "$ref": "#/definitions/ModulationSpec" + } + ] + }, + "name": { + "type": "string" + }, + "nonlinear_spec": { + "anyOf": [ + { + "$ref": "#/definitions/NonlinearSpec" + }, + { + "$ref": "#/definitions/NonlinearSusceptibility" + } + ] + }, + "ss": { + "discriminator": { + "mapping": { + "Debye": "#/definitions/Debye", + "Drude": "#/definitions/Drude", + "Lorentz": "#/definitions/Lorentz", + "LossyMetalMedium": "#/definitions/LossyMetalMedium", + "Medium": "#/definitions/Medium", + "PECMedium": "#/definitions/PECMedium", + "PoleResidue": "#/definitions/PoleResidue", + "Sellmeier": "#/definitions/Sellmeier" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/Debye" + }, + { + "$ref": "#/definitions/Drude" + }, + { + "$ref": "#/definitions/Lorentz" }, { - "$ref": "#/definitions/SolidMedium" + "$ref": "#/definitions/LossyMetalMedium" }, { - "$ref": "#/definitions/SolidSpec" - } - ] - }, - "modulation_spec": { - "allOf": [ + "$ref": "#/definitions/Medium" + }, { - "$ref": "#/definitions/ModulationSpec" - } - ] - }, - "name": { - "type": "string" - }, - "nonlinear_spec": { - "anyOf": [ + "$ref": "#/definitions/PECMedium" + }, { - "$ref": "#/definitions/NonlinearSpec" + "$ref": "#/definitions/PoleResidue" }, { - "$ref": "#/definitions/NonlinearSusceptibility" + "$ref": "#/definitions/Sellmeier" } ] }, - "permittivity": { - "default": 1.0, - "enum": [ - 1 - ], - "type": "integer" - }, - "roughness": { + "tt": { "discriminator": { "mapping": { - "HammerstadSurfaceRoughness": "#/definitions/HammerstadSurfaceRoughness", - "HuraySurfaceRoughness": "#/definitions/HuraySurfaceRoughness" + "Debye": "#/definitions/Debye", + "Drude": "#/definitions/Drude", + "Lorentz": "#/definitions/Lorentz", + "LossyMetalMedium": "#/definitions/LossyMetalMedium", + "Medium": "#/definitions/Medium", + "PECMedium": "#/definitions/PECMedium", + "PoleResidue": "#/definitions/PoleResidue", + "Sellmeier": "#/definitions/Sellmeier" }, "propertyName": "type" }, "oneOf": [ { - "$ref": "#/definitions/HammerstadSurfaceRoughness" + "$ref": "#/definitions/Debye" }, { - "$ref": "#/definitions/HuraySurfaceRoughness" + "$ref": "#/definitions/Drude" + }, + { + "$ref": "#/definitions/Lorentz" + }, + { + "$ref": "#/definitions/LossyMetalMedium" + }, + { + "$ref": "#/definitions/Medium" + }, + { + "$ref": "#/definitions/PECMedium" + }, + { + "$ref": "#/definitions/PoleResidue" + }, + { + "$ref": "#/definitions/Sellmeier" } ] }, - "thickness": { - "exclusiveMinimum": 0, - "type": "number" - }, "type": { - "default": "LossyMetalMedium", + "default": "Medium2D", "enum": [ - "LossyMetalMedium" + "Medium2D" ], "type": "string" }, @@ -9654,13 +10865,28 @@ } }, "required": [ - "frequency_range" + "ss", + "tt" ], "type": "object" }, - "LumpedPort": { + "MediumMonitor": { "additionalProperties": false, "properties": { + "apodization": { + "allOf": [ + { + "$ref": "#/definitions/ApodizationSpec" + } + ], + "default": { + "attrs": {}, + "end": null, + "start": null, + "type": "ApodizationSpec", + "width": null + } + }, "attrs": { "default": {}, "type": "object" @@ -9714,199 +10940,54 @@ 0.0 ] }, - "dist_type": { - "default": "on", + "colocate": { + "default": false, "enum": [ - "laterally_only", - "off", - "on" + false ], - "type": "string" - }, - "enable_snapping_points": { - "default": true, "type": "boolean" }, - "impedance": { + "freqs": { "anyOf": [ { - "$ref": "#/definitions/ComplexNumber" - }, - { - "properties": { - "imag": { - "type": "number" - }, - "real": { - "type": "number" - } + "items": { + "type": "number" }, - "required": [ - "imag", - "real" - ], - "type": "object" - } - ], - "default": 50 - }, - "name": { - "minLength": 1, - "type": "string" - }, - "num_grid_cells": { - "default": 3, - "exclusiveMinimum": 0, - "type": "integer" - }, - "size": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, "type": "array" }, { - "type": "autograd.tracer.Box" + "type": "ArrayLike" } ] }, - "snap_perimeter_to_grid": { - "default": true, - "type": "boolean" - }, - "type": { - "default": "LumpedPort", - "enum": [ - "LumpedPort" - ], - "type": "string" - }, - "voltage_axis": { - "enum": [ - 0, + "interval_space": { + "default": [ 1, - 2 - ], - "type": "integer" - } - }, - "required": [ - "name", - "size", - "voltage_axis" - ], - "type": "object" - }, - "LumpedResistor": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "center": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" + 1, + 1 + ], + "items": [ + { + "exclusiveMinimum": 0, + "type": "integer" }, { - "type": "autograd.tracer.Box" + "exclusiveMinimum": 0, + "type": "integer" + }, + { + "exclusiveMinimum": 0, + "type": "integer" } ], - "default": [ - 0.0, - 0.0, - 0.0 - ] - }, - "enable_snapping_points": { - "default": true, - "type": "boolean" + "maxItems": 3, + "minItems": 3, + "type": "array" }, "name": { "minLength": 1, "type": "string" }, - "num_grid_cells": { - "default": 1, - "exclusiveMinimum": 0, - "type": "integer" - }, - "resistance": { - "exclusiveMinimum": 0, - "type": "number", - "unit": "ohm" - }, "size": { "anyOf": [ { @@ -9954,80 +11035,69 @@ } ] }, - "snap_perimeter_to_grid": { - "default": true, - "type": "boolean" - }, "type": { - "default": "LumpedResistor", + "default": "MediumMonitor", "enum": [ - "LumpedResistor" + "MediumMonitor" ], "type": "string" - }, - "voltage_axis": { - "enum": [ - 0, - 1, - 2 - ], - "type": "integer" } }, "required": [ + "freqs", "name", - "resistance", - "size", - "voltage_axis" + "size" ], "type": "object" }, - "Medium": { + "MeshOverrideStructure": { "additionalProperties": false, "properties": { - "allow_gain": { - "default": false, - "type": "boolean" - }, "attrs": { "default": {}, "type": "object" }, - "conductivity": { + "background_medium": { "anyOf": [ { - "type": "autograd.tracer.Box" + "$ref": "#/definitions/AnisotropicMedium" }, { - "type": "number" - } - ], - "default": 0.0 - }, - "frequency_range": { - "items": [ + "$ref": "#/definitions/AnisotropicMediumFromMedium2D" + }, { - "type": "number" + "$ref": "#/definitions/ChargeConductorMedium" }, { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "heat_spec": { - "discriminator": { - "mapping": { - "FluidMedium": "#/definitions/FluidMedium", - "FluidSpec": "#/definitions/FluidSpec", - "SolidMedium": "#/definitions/SolidMedium", - "SolidSpec": "#/definitions/SolidSpec" + "$ref": "#/definitions/ChargeInsulatorMedium" + }, + { + "$ref": "#/definitions/CustomAnisotropicMedium" + }, + { + "$ref": "#/definitions/CustomDebye" + }, + { + "$ref": "#/definitions/CustomDrude" + }, + { + "$ref": "#/definitions/CustomLorentz" + }, + { + "$ref": "#/definitions/CustomMedium" + }, + { + "$ref": "#/definitions/CustomPoleResidue" + }, + { + "$ref": "#/definitions/CustomSellmeier" + }, + { + "$ref": "#/definitions/Debye" + }, + { + "$ref": "#/definitions/Drude" }, - "propertyName": "type" - }, - "oneOf": [ { "$ref": "#/definitions/FluidMedium" }, @@ -10035,102 +11105,43 @@ "$ref": "#/definitions/FluidSpec" }, { - "$ref": "#/definitions/SolidMedium" + "$ref": "#/definitions/FullyAnisotropicMedium" }, { - "$ref": "#/definitions/SolidSpec" - } - ] - }, - "modulation_spec": { - "allOf": [ + "$ref": "#/definitions/Lorentz" + }, { - "$ref": "#/definitions/ModulationSpec" - } - ] - }, - "name": { - "type": "string" - }, - "nonlinear_spec": { - "anyOf": [ + "$ref": "#/definitions/LossyMetalMedium" + }, { - "$ref": "#/definitions/NonlinearSpec" + "$ref": "#/definitions/Medium" }, { - "$ref": "#/definitions/NonlinearSusceptibility" - } - ] - }, - "permittivity": { - "anyOf": [ + "$ref": "#/definitions/Medium2D" + }, { - "minimum": 1.0, - "type": "number" + "$ref": "#/definitions/MultiPhysicsMedium" }, { - "type": "autograd.tracer.Box" - } - ], - "default": 1.0 - }, - "type": { - "default": "Medium", - "enum": [ - "Medium" - ], - "type": "string" - }, - "viz_spec": { - "allOf": [ + "$ref": "#/definitions/PECMedium" + }, { - "$ref": "#/definitions/VisualizationSpec" - } - ] - } - }, - "type": "object" - }, - "Medium2D": { - "additionalProperties": false, - "properties": { - "allow_gain": { - "default": false, - "type": "boolean" - }, - "attrs": { - "default": {}, - "type": "object" - }, - "frequency_range": { - "items": [ + "$ref": "#/definitions/PMCMedium" + }, { - "type": "number" + "$ref": "#/definitions/PerturbationMedium" }, { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "heat_spec": { - "discriminator": { - "mapping": { - "FluidMedium": "#/definitions/FluidMedium", - "FluidSpec": "#/definitions/FluidSpec", - "SolidMedium": "#/definitions/SolidMedium", - "SolidSpec": "#/definitions/SolidSpec" + "$ref": "#/definitions/PerturbationPoleResidue" }, - "propertyName": "type" - }, - "oneOf": [ { - "$ref": "#/definitions/FluidMedium" + "$ref": "#/definitions/PoleResidue" }, { - "$ref": "#/definitions/FluidSpec" + "$ref": "#/definitions/Sellmeier" + }, + { + "$ref": "#/definitions/SemiconductorMedium" }, { "$ref": "#/definitions/SolidMedium" @@ -10140,130 +11151,309 @@ } ] }, - "modulation_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModulationSpec" - } - ] - }, - "name": { - "type": "string" + "background_permittivity": { + "minimum": 1.0, + "type": "number" }, - "nonlinear_spec": { - "anyOf": [ + "dl": { + "items": [ { - "$ref": "#/definitions/NonlinearSpec" + "exclusiveMinimum": 0, + "type": "number" }, { - "$ref": "#/definitions/NonlinearSusceptibility" + "exclusiveMinimum": 0, + "type": "number" + }, + { + "exclusiveMinimum": 0, + "type": "number" } - ] + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "drop_outside_sim": { + "default": true, + "type": "boolean" + }, + "enforce": { + "default": false, + "type": "boolean" }, - "ss": { + "geometry": { "discriminator": { "mapping": { - "Debye": "#/definitions/Debye", - "Drude": "#/definitions/Drude", - "Lorentz": "#/definitions/Lorentz", - "LossyMetalMedium": "#/definitions/LossyMetalMedium", - "Medium": "#/definitions/Medium", - "PECMedium": "#/definitions/PECMedium", - "PoleResidue": "#/definitions/PoleResidue", - "Sellmeier": "#/definitions/Sellmeier" + "Box": "#/definitions/Box", + "ClipOperation": "#/definitions/ClipOperation", + "ComplexPolySlabBase": "#/definitions/ComplexPolySlabBase", + "Cylinder": "#/definitions/Cylinder", + "GeometryGroup": "#/definitions/GeometryGroup", + "PolySlab": "#/definitions/PolySlab", + "Sphere": "#/definitions/Sphere", + "Transformed": "#/definitions/Transformed", + "TriangleMesh": "#/definitions/TriangleMesh" }, "propertyName": "type" }, "oneOf": [ { - "$ref": "#/definitions/Debye" + "$ref": "#/definitions/Box" }, { - "$ref": "#/definitions/Drude" + "$ref": "#/definitions/ClipOperation" }, { - "$ref": "#/definitions/Lorentz" + "$ref": "#/definitions/ComplexPolySlabBase" }, { - "$ref": "#/definitions/LossyMetalMedium" + "$ref": "#/definitions/Cylinder" }, { - "$ref": "#/definitions/Medium" + "$ref": "#/definitions/GeometryGroup" }, { - "$ref": "#/definitions/PECMedium" + "$ref": "#/definitions/PolySlab" }, { - "$ref": "#/definitions/PoleResidue" + "$ref": "#/definitions/Sphere" }, { - "$ref": "#/definitions/Sellmeier" + "$ref": "#/definitions/Transformed" + }, + { + "$ref": "#/definitions/TriangleMesh" } ] }, - "tt": { - "discriminator": { - "mapping": { - "Debye": "#/definitions/Debye", - "Drude": "#/definitions/Drude", - "Lorentz": "#/definitions/Lorentz", - "LossyMetalMedium": "#/definitions/LossyMetalMedium", - "Medium": "#/definitions/Medium", - "PECMedium": "#/definitions/PECMedium", - "PoleResidue": "#/definitions/PoleResidue", - "Sellmeier": "#/definitions/Sellmeier" - }, - "propertyName": "type" - }, - "oneOf": [ + "name": { + "type": "string" + }, + "priority": { + "default": 0, + "type": "integer" + }, + "shadow": { + "default": true, + "type": "boolean" + }, + "type": { + "default": "MeshOverrideStructure", + "enum": [ + "MeshOverrideStructure" + ], + "type": "string" + } + }, + "required": [ + "dl", + "geometry" + ], + "type": "object" + }, + "MicrowaveModeMonitor": { + "additionalProperties": false, + "properties": { + "apodization": { + "allOf": [ { - "$ref": "#/definitions/Debye" - }, + "$ref": "#/definitions/ApodizationSpec" + } + ], + "default": { + "attrs": {}, + "end": null, + "start": null, + "type": "ApodizationSpec", + "width": null + } + }, + "attrs": { + "default": {}, + "type": "object" + }, + "center": { + "anyOf": [ { - "$ref": "#/definitions/Drude" + "items": [ + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + }, + { + "anyOf": [ + { + "type": "autograd.tracer.Box" + }, + { + "type": "number" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" }, { - "$ref": "#/definitions/Lorentz" - }, + "type": "autograd.tracer.Box" + } + ], + "default": [ + 0.0, + 0.0, + 0.0 + ] + }, + "colocate": { + "default": true, + "type": "boolean" + }, + "conjugated_dot_product": { + "default": true, + "type": "boolean" + }, + "freqs": { + "anyOf": [ { - "$ref": "#/definitions/LossyMetalMedium" + "items": { + "type": "number" + }, + "type": "array" }, { - "$ref": "#/definitions/Medium" + "type": "ArrayLike" + } + ] + }, + "interval_space": { + "default": [ + 1, + 1, + 1 + ], + "items": [ + { + "enum": [ + 1 + ], + "type": "integer" }, { - "$ref": "#/definitions/PECMedium" + "enum": [ + 1 + ], + "type": "integer" }, { - "$ref": "#/definitions/PoleResidue" + "enum": [ + 1 + ], + "type": "integer" + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "mode_spec": { + "allOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + } + ] + }, + "name": { + "minLength": 1, + "type": "string" + }, + "size": { + "anyOf": [ + { + "items": [ + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + }, + { + "anyOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "autograd.tracer.Box" + } + ] + } + ], + "maxItems": 3, + "minItems": 3, + "type": "array" }, { - "$ref": "#/definitions/Sellmeier" + "type": "autograd.tracer.Box" } ] }, + "store_fields_direction": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, "type": { - "default": "Medium2D", + "default": "MicrowaveModeMonitor", "enum": [ - "Medium2D" + "MicrowaveModeMonitor" ], "type": "string" - }, - "viz_spec": { - "allOf": [ - { - "$ref": "#/definitions/VisualizationSpec" - } - ] } }, "required": [ - "ss", - "tt" + "freqs", + "name", + "size" ], "type": "object" }, - "MediumMonitor": { + "MicrowaveModeSolverMonitor": { "additionalProperties": false, "properties": { "apodization": { @@ -10334,11 +11524,42 @@ ] }, "colocate": { - "default": false, + "default": true, + "type": "boolean" + }, + "conjugated_dot_product": { + "default": true, + "type": "boolean" + }, + "direction": { + "default": "+", "enum": [ - false + "+", + "-" ], - "type": "boolean" + "type": "string" + }, + "fields": { + "default": [ + "Ex", + "Ey", + "Ez", + "Hx", + "Hy", + "Hz" + ], + "items": { + "enum": [ + "Ex", + "Ey", + "Ez", + "Hx", + "Hy", + "Hz" + ], + "type": "string" + }, + "type": "array" }, "freqs": { "anyOf": [ @@ -10361,15 +11582,21 @@ ], "items": [ { - "exclusiveMinimum": 0, + "enum": [ + 1 + ], "type": "integer" }, { - "exclusiveMinimum": 0, + "enum": [ + 1 + ], "type": "integer" }, { - "exclusiveMinimum": 0, + "enum": [ + 1 + ], "type": "integer" } ], @@ -10377,6 +11604,13 @@ "minItems": 3, "type": "array" }, + "mode_spec": { + "allOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + } + ] + }, "name": { "minLength": 1, "type": "string" @@ -10428,10 +11662,17 @@ } ] }, + "store_fields_direction": { + "enum": [ + "+", + "-" + ], + "type": "string" + }, "type": { - "default": "MediumMonitor", + "default": "MicrowaveModeSolverMonitor", "enum": [ - "MediumMonitor" + "MicrowaveModeSolverMonitor" ], "type": "string" } @@ -10443,206 +11684,135 @@ ], "type": "object" }, - "MeshOverrideStructure": { + "MicrowaveModeSpec": { "additionalProperties": false, "properties": { + "angle_phi": { + "default": 0.0, + "type": "number" + }, + "angle_rotation": { + "default": false, + "type": "boolean" + }, + "angle_theta": { + "default": 0.0, + "type": "number" + }, "attrs": { "default": {}, "type": "object" }, - "background_medium": { - "anyOf": [ - { - "$ref": "#/definitions/AnisotropicMedium" - }, - { - "$ref": "#/definitions/AnisotropicMediumFromMedium2D" - }, - { - "$ref": "#/definitions/ChargeConductorMedium" - }, - { - "$ref": "#/definitions/ChargeInsulatorMedium" - }, - { - "$ref": "#/definitions/CustomAnisotropicMedium" - }, - { - "$ref": "#/definitions/CustomDebye" - }, - { - "$ref": "#/definitions/CustomDrude" - }, - { - "$ref": "#/definitions/CustomLorentz" - }, - { - "$ref": "#/definitions/CustomMedium" - }, - { - "$ref": "#/definitions/CustomPoleResidue" - }, - { - "$ref": "#/definitions/CustomSellmeier" - }, - { - "$ref": "#/definitions/Debye" - }, - { - "$ref": "#/definitions/Drude" - }, - { - "$ref": "#/definitions/FluidMedium" - }, - { - "$ref": "#/definitions/FluidSpec" - }, - { - "$ref": "#/definitions/FullyAnisotropicMedium" - }, - { - "$ref": "#/definitions/Lorentz" - }, - { - "$ref": "#/definitions/LossyMetalMedium" - }, - { - "$ref": "#/definitions/Medium" - }, - { - "$ref": "#/definitions/Medium2D" - }, - { - "$ref": "#/definitions/MultiPhysicsMedium" - }, - { - "$ref": "#/definitions/PECMedium" - }, - { - "$ref": "#/definitions/PMCMedium" - }, - { - "$ref": "#/definitions/PerturbationMedium" - }, - { - "$ref": "#/definitions/PerturbationPoleResidue" - }, - { - "$ref": "#/definitions/PoleResidue" - }, - { - "$ref": "#/definitions/Sellmeier" - }, - { - "$ref": "#/definitions/SemiconductorMedium" - }, - { - "$ref": "#/definitions/SolidMedium" - }, - { - "$ref": "#/definitions/SolidSpec" - } - ] + "bend_axis": { + "enum": [ + 0, + 1 + ], + "type": "integer" }, - "background_permittivity": { - "minimum": 1.0, + "bend_radius": { "type": "number" }, - "dl": { - "items": [ - { - "exclusiveMinimum": 0, - "type": "number" - }, - { - "exclusiveMinimum": 0, - "type": "number" - }, - { - "exclusiveMinimum": 0, - "type": "number" - } + "filter_pol": { + "enum": [ + "te", + "tm" ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - "drop_outside_sim": { - "default": true, - "type": "boolean" - }, - "enforce": { - "default": false, - "type": "boolean" + "type": "string" }, - "geometry": { - "discriminator": { - "mapping": { - "Box": "#/definitions/Box", - "ClipOperation": "#/definitions/ClipOperation", - "ComplexPolySlabBase": "#/definitions/ComplexPolySlabBase", - "Cylinder": "#/definitions/Cylinder", - "GeometryGroup": "#/definitions/GeometryGroup", - "PolySlab": "#/definitions/PolySlab", - "Sphere": "#/definitions/Sphere", - "Transformed": "#/definitions/Transformed", - "TriangleMesh": "#/definitions/TriangleMesh" - }, - "propertyName": "type" - }, - "oneOf": [ - { - "$ref": "#/definitions/Box" - }, - { - "$ref": "#/definitions/ClipOperation" - }, - { - "$ref": "#/definitions/ComplexPolySlabBase" - }, + "group_index_step": { + "anyOf": [ { - "$ref": "#/definitions/Cylinder" + "exclusiveMinimum": 0, + "type": "number" }, { - "$ref": "#/definitions/GeometryGroup" - }, + "type": "boolean" + } + ], + "default": false + }, + "impedance_specs": { + "anyOf": [ { - "$ref": "#/definitions/PolySlab" + "items": { + "anyOf": [ + { + "$ref": "#/definitions/AutoImpedanceSpec" + }, + { + "$ref": "#/definitions/CustomImpedanceSpec" + } + ] + }, + "type": "array" }, { - "$ref": "#/definitions/Sphere" - }, + "oneOf": [ + { + "$ref": "#/definitions/AutoImpedanceSpec" + }, + { + "$ref": "#/definitions/CustomImpedanceSpec" + } + ] + } + ] + }, + "num_modes": { + "default": 1, + "exclusiveMinimum": 0, + "type": "integer" + }, + "num_pml": { + "default": [ + 0, + 0 + ], + "items": [ { - "$ref": "#/definitions/Transformed" + "minimum": 0, + "type": "integer" }, { - "$ref": "#/definitions/TriangleMesh" + "minimum": 0, + "type": "integer" } - ] + ], + "maxItems": 2, + "minItems": 2, + "type": "array" }, - "name": { + "precision": { + "default": "double", + "enum": [ + "auto", + "double", + "single" + ], "type": "string" }, - "priority": { - "default": 0, - "type": "integer" + "target_neff": { + "exclusiveMinimum": 0, + "type": "number" }, - "shadow": { - "default": true, - "type": "boolean" + "track_freq": { + "default": "central", + "enum": [ + "central", + "highest", + "lowest" + ], + "type": "string" }, "type": { - "default": "MeshOverrideStructure", + "default": "MicrowaveModeSpec", "enum": [ - "MeshOverrideStructure" + "MicrowaveModeSpec" ], "type": "string" } }, - "required": [ - "dl", - "geometry" - ], "type": "object" }, "ModeABCBoundary": { @@ -10669,11 +11839,6 @@ "type": "integer" }, "mode_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModeSpec" - } - ], "default": { "angle_phi": 0.0, "angle_rotation": false, @@ -10692,7 +11857,22 @@ "target_neff": null, "track_freq": "central", "type": "ModeSpec" - } + }, + "discriminator": { + "mapping": { + "MicrowaveModeSpec": "#/definitions/MicrowaveModeSpec", + "ModeSpec": "#/definitions/ModeSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + }, + { + "$ref": "#/definitions/ModeSpec" + } + ] }, "name": { "type": "string" @@ -11263,11 +12443,6 @@ "type": "integer" }, "mode_spec": { - "allOf": [ - { - "$ref": "#/definitions/ModeSpec" - } - ], "default": { "angle_phi": 0.0, "angle_rotation": false, @@ -11286,7 +12461,22 @@ "target_neff": null, "track_freq": "central", "type": "ModeSpec" - } + }, + "discriminator": { + "mapping": { + "MicrowaveModeSpec": "#/definitions/MicrowaveModeSpec", + "ModeSpec": "#/definitions/ModeSpec" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/definitions/MicrowaveModeSpec" + }, + { + "$ref": "#/definitions/ModeSpec" + } + ] }, "name": { "type": "string" @@ -14383,6 +15573,8 @@ "FluxMonitor": "#/definitions/FluxMonitor", "FluxTimeMonitor": "#/definitions/FluxTimeMonitor", "MediumMonitor": "#/definitions/MediumMonitor", + "MicrowaveModeMonitor": "#/definitions/MicrowaveModeMonitor", + "MicrowaveModeSolverMonitor": "#/definitions/MicrowaveModeSolverMonitor", "ModeMonitor": "#/definitions/ModeMonitor", "ModeSolverMonitor": "#/definitions/ModeSolverMonitor", "PermittivityMonitor": "#/definitions/PermittivityMonitor" @@ -14423,6 +15615,12 @@ { "$ref": "#/definitions/MediumMonitor" }, + { + "$ref": "#/definitions/MicrowaveModeMonitor" + }, + { + "$ref": "#/definitions/MicrowaveModeSolverMonitor" + }, { "$ref": "#/definitions/ModeMonitor" }, @@ -16472,138 +17670,6 @@ }, "type": "object" }, - "VoltageIntegralAxisAligned": { - "additionalProperties": false, - "properties": { - "attrs": { - "default": {}, - "type": "object" - }, - "center": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - }, - { - "anyOf": [ - { - "type": "autograd.tracer.Box" - }, - { - "type": "number" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - { - "type": "autograd.tracer.Box" - } - ], - "default": [ - 0.0, - 0.0, - 0.0 - ] - }, - "extrapolate_to_endpoints": { - "default": false, - "type": "boolean" - }, - "sign": { - "enum": [ - "+", - "-" - ], - "type": "string" - }, - "size": { - "anyOf": [ - { - "items": [ - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - { - "anyOf": [ - { - "minimum": 0, - "type": "number" - }, - { - "type": "autograd.tracer.Box" - } - ] - } - ], - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - { - "type": "autograd.tracer.Box" - } - ] - }, - "snap_path_to_grid": { - "default": false, - "type": "boolean" - }, - "type": { - "default": "VoltageIntegralAxisAligned", - "enum": [ - "VoltageIntegralAxisAligned" - ], - "type": "string" - } - }, - "required": [ - "sign", - "size" - ], - "type": "object" - }, "VolumetricAveraging": { "additionalProperties": false, "properties": { @@ -16702,10 +17768,13 @@ "current_integral": { "anyOf": [ { - "$ref": "#/definitions/CurrentIntegralAxisAligned" + "$ref": "#/definitions/AxisAlignedCurrentIntegral" + }, + { + "$ref": "#/definitions/CompositeCurrentIntegral" }, { - "$ref": "#/definitions/CustomCurrentIntegral2D" + "$ref": "#/definitions/Custom2DCurrentIntegral" } ] }, @@ -16825,10 +17894,10 @@ "voltage_integral": { "anyOf": [ { - "$ref": "#/definitions/CustomVoltageIntegral2D" + "$ref": "#/definitions/AxisAlignedVoltageIntegral" }, { - "$ref": "#/definitions/VoltageIntegralAxisAligned" + "$ref": "#/definitions/Custom2DVoltageIntegral" } ] } diff --git a/tests/test_components/test_geometry.py b/tests/test_components/test_geometry.py index 0edab2fd35..8bf9347753 100644 --- a/tests/test_components/test_geometry.py +++ b/tests/test_components/test_geometry.py @@ -12,6 +12,15 @@ import pytest import shapely import trimesh +from shapely.geometry import ( + GeometryCollection, + LineString, + MultiLineString, + MultiPoint, + MultiPolygon, + Point, + Polygon, +) import tidy3d as td from tidy3d.compat import _shapely_is_older_than @@ -22,6 +31,7 @@ SnapLocation, SnappingSpec, flatten_groups, + flatten_shapely_geometries, snap_box_to_grid, traverse_geometries, ) @@ -1137,7 +1147,14 @@ def test_subdivide(): @pytest.mark.parametrize("snap_location", [SnapLocation.Boundary, SnapLocation.Center]) @pytest.mark.parametrize( "snap_behavior", - [SnapBehavior.Off, SnapBehavior.Closest, SnapBehavior.Expand, SnapBehavior.Contract], + [ + SnapBehavior.Off, + SnapBehavior.Closest, + SnapBehavior.Expand, + SnapBehavior.Contract, + SnapBehavior.StrictExpand, + SnapBehavior.StrictContract, + ], ) def test_snap_box_to_grid(snap_location, snap_behavior): """ "Test that all combinations of SnappingSpec correctly modify a test box without error.""" @@ -1158,12 +1175,78 @@ def test_snap_box_to_grid(snap_location, snap_behavior): new_box = snap_box_to_grid(grid, box, snap_spec) if snap_behavior != SnapBehavior.Off and snap_location == SnapLocation.Boundary: - # Check that the box boundary slightly off from 0.1 was correctly snapped to 0.1 - assert math.isclose(new_box.bounds[0][1], xyz[1]) - # Check that the box boundary slightly off from 0.3 was correctly snapped to 0.3 - assert math.isclose(new_box.bounds[1][1], xyz[3]) - # Check that the box boundary outside the grid was snapped to the smallest grid coordinate - assert math.isclose(new_box.bounds[0][2], xyz[0]) + # Strict behaviors have different snapping rules, so skip these specific assertions + if snap_behavior not in (SnapBehavior.StrictExpand, SnapBehavior.StrictContract): + # Check that the box boundary slightly off from 0.1 was correctly snapped to 0.1 + assert math.isclose(new_box.bounds[0][1], xyz[1]) + # Check that the box boundary slightly off from 0.3 was correctly snapped to 0.3 + assert math.isclose(new_box.bounds[1][1], xyz[3]) + # Check that the box boundary outside the grid was snapped to the smallest grid coordinate + assert math.isclose(new_box.bounds[0][2], xyz[0]) + + +def test_snap_box_to_grid_strict_behaviors(): + """Test StrictExpand and StrictContract behaviors specifically.""" + xyz = np.linspace(0, 1, 11) # Grid points at 0.0, 0.1, 0.2, ..., 1.0 + coords = td.Coords(x=xyz, y=xyz, z=xyz) + grid = td.Grid(boundaries=coords) + + # Test StrictExpand: should always move endpoints outwards, even if coincident + box_coincident = td.Box( + center=(0.1, 0.2, 0.3), size=(0, 0, 0) + ) # Centered exactly on grid points + snap_spec_strict_expand = SnappingSpec( + location=[SnapLocation.Boundary] * 3, behavior=[SnapBehavior.StrictExpand] * 3 + ) + + expanded_box = snap_box_to_grid(grid, box_coincident, snap_spec_strict_expand) + + # StrictExpand should move bounds outwards even when already on grid + assert np.isclose(expanded_box.bounds[0][0], 0.0) # Left bound moved left from 0.1 + assert np.isclose(expanded_box.bounds[1][0], 0.2) # Right bound moved right from 0.1 + assert np.isclose(expanded_box.bounds[0][1], 0.1) # Bottom bound moved down from 0.2 + assert np.isclose(expanded_box.bounds[1][1], 0.3) # Top bound moved up from 0.2 + + # Test StrictContract: should always move endpoints inwards, even if coincident + box_large = td.Box(center=(0.5, 0.5, 0.5), size=(0.4, 0.4, 0.4)) # Spans multiple grid cells + snap_spec_strict_contract = SnappingSpec( + location=[SnapLocation.Boundary] * 3, behavior=[SnapBehavior.StrictContract] * 3 + ) + + contracted_box = snap_box_to_grid(grid, box_large, snap_spec_strict_contract) + + # StrictContract should make the box smaller than the original + assert contracted_box.size[0] < box_large.size[0] + assert contracted_box.size[1] < box_large.size[1] + assert contracted_box.size[2] < box_large.size[2] + + # Test edge case: box coincident with grid boundaries + box_on_grid = td.Box( + center=(0.15, 0.25, 0.35), size=(0.1, 0.1, 0.1) + ) # Boundaries at 0.1,0.2 and 0.2,0.3 + + # Regular Expand shouldn't change a box already coincident with grid + snap_spec_regular_expand = SnappingSpec( + location=[SnapLocation.Boundary] * 3, behavior=[SnapBehavior.Expand] * 3 + ) + regular_expanded = snap_box_to_grid(grid, box_on_grid, snap_spec_regular_expand) + assert np.allclose(regular_expanded.bounds, box_on_grid.bounds) # Should be unchanged + + # StrictExpand should still expand even when coincident + strict_expanded = snap_box_to_grid(grid, box_on_grid, snap_spec_strict_expand) + assert not np.allclose(strict_expanded.bounds, box_on_grid.bounds) # Should be changed + assert strict_expanded.size[0] > box_on_grid.size[0] # Should be larger + + # Test with margin parameter for strict behaviors + snap_spec_strict_expand_margin = SnappingSpec( + location=[SnapLocation.Boundary] * 3, + behavior=[SnapBehavior.StrictExpand] * 3, + margin=(1, 1, 1), # Consider 1 additional grid point when expanding + ) + + margin_expanded = snap_box_to_grid(grid, box_coincident, snap_spec_strict_expand_margin) + # With margin=1, should expand even further than without margin + assert margin_expanded.size[0] >= expanded_box.size[0] def test_triangulation_with_collinear_vertices(): @@ -1298,3 +1381,105 @@ def test_cleanup_shapely_object(): orig_polygon = shapely.Polygon(exterior_coords) new_polygon = cleanup_shapely_object(orig_polygon, tolerance_ratio=1e-12) assert len(new_polygon.exterior.coords) == 0 # empty / collinear polygons should get deleted + + +def test_flatten_shapely_geometries(): + """Test the flatten_shapely_geometries utility function comprehensively.""" + # Test 1: Single polygon (should be wrapped in list and returned) + single_polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + result = flatten_shapely_geometries(single_polygon) + assert len(result) == 1 + assert result[0] == single_polygon + + # Test 2: List of polygons (should return as-is) + poly1 = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + poly2 = Polygon([(2, 0), (3, 0), (3, 1), (2, 1)]) + polygon_list = [poly1, poly2] + result = flatten_shapely_geometries(polygon_list) + assert len(result) == 2 + assert result == polygon_list + + # Test 3: MultiPolygon (should be flattened) + multi_polygon = MultiPolygon([poly1, poly2]) + result = flatten_shapely_geometries(multi_polygon) + assert len(result) == 2 + assert result[0] == poly1 + assert result[1] == poly2 + + # Test 4: Empty geometries (should be filtered out) + empty_polygon = Polygon() + mixed_list = [poly1, empty_polygon, poly2] + result = flatten_shapely_geometries(mixed_list) + assert len(result) == 2 + assert empty_polygon not in result + + # Test 5: GeometryCollection (should be recursively flattened) + line = LineString([(0, 0), (1, 1)]) + point = Point(0, 0) + collection = GeometryCollection([poly1, line, point, poly2]) + result = flatten_shapely_geometries(collection) + assert len(result) == 2 # Only polygons kept by default + assert poly1 in result + assert poly2 in result + + # Test 6: Custom keep_types parameter + result_with_lines = flatten_shapely_geometries(collection, keep_types=(Polygon, LineString)) + assert len(result_with_lines) == 3 # 2 polygons + 1 line + assert poly1 in result_with_lines + assert poly2 in result_with_lines + assert line in result_with_lines + + # Test 7: Nested collections and multi-geometries + line1 = LineString([(0, 0), (1, 1)]) + line2 = LineString([(2, 2), (3, 3)]) + multi_line = MultiLineString([line1, line2]) + nested_collection = GeometryCollection( + [ + collection, # Contains poly1, line, point, poly2 + multi_line, + poly1, + ] + ) + result = flatten_shapely_geometries(nested_collection) + assert len(result) == 3 # poly1 (from collection), poly2 (from collection), poly1 (direct) + + # Test 8: MultiPoint (should be handled) + point1 = Point(0, 0) + point2 = Point(1, 1) + multi_point = MultiPoint([point1, point2]) + result = flatten_shapely_geometries(multi_point, keep_types=(Point,)) + assert len(result) == 2 + assert point1 in result + assert point2 in result + + # Test 9: MultiLineString (should be handled) + result = flatten_shapely_geometries(multi_line, keep_types=(LineString,)) + assert len(result) == 2 + assert line1 in result + assert line2 in result + + # Test 10: Mixed empty and non-empty geometries + empty_multi = MultiPolygon([]) + mixed_with_empty = [poly1, empty_multi, empty_polygon, poly2] + result = flatten_shapely_geometries(mixed_with_empty) + assert len(result) == 2 + assert poly1 in result + assert poly2 in result + + # Test 11: Deeply nested structure + inner_collection = GeometryCollection([poly1, line]) + outer_multi = MultiPolygon([poly2]) + deep_collection = GeometryCollection([inner_collection, outer_multi]) + result = flatten_shapely_geometries(deep_collection) + assert len(result) == 2 + assert poly1 in result + assert poly2 in result + + # Test 12: All geometry types filtered out + points_and_lines = GeometryCollection([Point(0, 0), LineString([(0, 0), (1, 1)])]) + result = flatten_shapely_geometries(points_and_lines) # Default keeps only Polygons + assert len(result) == 0 + + # Test 13: Edge case - single empty geometry + result = flatten_shapely_geometries(empty_polygon) + assert len(result) == 0 diff --git a/tests/test_components/test_lumped_element.py b/tests/test_components/test_lumped_element.py index fb15221366..957a4e8aeb 100644 --- a/tests/test_components/test_lumped_element.py +++ b/tests/test_components/test_lumped_element.py @@ -7,7 +7,7 @@ import pytest import tidy3d as td -from tidy3d.components.lumped_element import NetworkConversions +from tidy3d.components.lumped_element import network_complex_permittivity def test_lumped_resistor(): @@ -261,7 +261,7 @@ def test_RLC_and_lumped_network_agreement(Rval, Lval, Cval, topology): (a, b) = RLC._as_admittance_function eps_from_RLC_med = med_RLC.eps_model(freqs) - eps_direct = 1 + sf * NetworkConversions.complex_permittivity(a=a, b=b, freqs=freqs) + eps_direct = 1 + sf * network_complex_permittivity(a=a, b=b, freqs=freqs) assert np.allclose(eps_from_RLC_med, eps_direct, rtol=rtol) diff --git a/tests/test_components/test_microwave.py b/tests/test_components/test_microwave.py index 5fb08815e9..b8a882f1b3 100644 --- a/tests/test_components/test_microwave.py +++ b/tests/test_components/test_microwave.py @@ -3,13 +3,18 @@ from __future__ import annotations from math import isclose +from typing import Literal +import matplotlib.pyplot as plt import numpy as np +import pydantic.v1 as pd import pytest import xarray as xr +from shapely import LineString +import tidy3d as td +from tidy3d.components.data.data_array import FreqModeDataArray from tidy3d.components.data.monitor_data import FreqDataArray -from tidy3d.components.microwave.data.monitor_data import AntennaMetricsData from tidy3d.components.microwave.formulas.circuit_parameters import ( capacitance_colinear_cylindrical_wire_segments, capacitance_rectangular_sheets, @@ -17,9 +22,370 @@ mutual_inductance_colinear_wire_segments, total_inductance_colinear_rectangular_wire_segments, ) +from tidy3d.components.microwave.path_integrals.factory import ( + make_current_integral, + make_path_integrals, + make_voltage_integral, +) +from tidy3d.components.microwave.path_integrals.mode_plane_analyzer import ( + ModePlaneAnalyzer, +) +from tidy3d.components.mode.mode_solver import ModeSolver from tidy3d.constants import EPSILON_0 +from tidy3d.exceptions import DataError, SetupError, ValidationError from ..test_data.test_monitor_data import make_directivity_data +from ..utils import AssertLogLevel, get_spatial_coords_dict, run_emulated + +MAKE_PLOTS = False +if MAKE_PLOTS: + # Interactive plotting for debugging + from matplotlib import use + + use("TkAgg") + +# Constant parameters and simulation for path integral tests +mm = 1e3 +# Using similar code as "test_data/test_data_arrays.py" +MON_SIZE = (2, 1, 0) +FIELDS = ("Ex", "Ey", "Hx", "Hy") +FSTART = 0.5e9 +FSTOP = 1.5e9 +F0 = (FSTART + FSTOP) / 2 +FWIDTH = FSTOP - FSTART +FS = np.linspace(FSTART, FSTOP, 3) +FIELD_MONITOR = td.FieldMonitor(size=MON_SIZE, fields=FIELDS, name="strip_field", freqs=FS) +STRIP_WIDTH = 1.5 +STRIP_HEIGHT = 0.5 +COAX_R1 = 0.04 +COAX_R2 = 0.5 + +SIM_Z = td.Simulation( + size=(2, 1, 1), + grid_spec=td.GridSpec.uniform(dl=0.04), + monitors=[ + FIELD_MONITOR, + td.FieldMonitor(center=(0, 0, 0), size=(1, 1, 1), freqs=FS, name="field", colocate=False), + td.FieldMonitor( + center=(0, 0, 0), size=(1, 1, 1), freqs=FS, fields=["Ex", "Hx"], name="ExHx" + ), + td.FieldTimeMonitor(center=(0, 0, 0), size=(1, 1, 0), colocate=False, name="field_time"), + td.ModeSolverMonitor( + center=(0, 0, 0), + size=(1, 1, 0), + freqs=FS, + mode_spec=td.ModeSpec(num_modes=2), + name="mode_solver", + ), + td.ModeMonitor( + center=(0, 0, 0), + size=(1, 1, 0), + freqs=FS, + mode_spec=td.ModeSpec(num_modes=3), + store_fields_direction="+", + name="mode", + ), + ], + sources=[ + td.PointDipole( + center=(0, 0, 0), + polarization="Ex", + source_time=td.GaussianPulse(freq0=F0, fwidth=FWIDTH), + ) + ], + run_time=5e-16, +) + +SIM_Z_DATA = run_emulated(SIM_Z) + +""" Generate the data arrays for testing path integral computations """ + + +def make_stripline_scalar_field_data_array(grid_key: str): + """Populate FIELD_MONITOR with a idealized stripline mode, where fringing fields are assumed 0.""" + XS, YS, ZS = get_spatial_coords_dict(SIM_Z, FIELD_MONITOR, grid_key).values() + XGRID, YGRID = np.meshgrid(XS, YS, indexing="ij") + XGRID = XGRID.reshape((len(XS), len(YS), 1, 1)) + YGRID = YGRID.reshape((len(XS), len(YS), 1, 1)) + values = np.zeros((len(XS), len(YS), len(ZS), len(FS))) + ones = np.ones((len(XS), len(YS), len(ZS), len(FS))) + XGRID = np.broadcast_to(XGRID, values.shape) + YGRID = np.broadcast_to(YGRID, values.shape) + + # Numpy masks for quickly determining location + above_in_strip = np.logical_and(YGRID >= 0, YGRID <= STRIP_HEIGHT / 2) + below_in_strip = np.logical_and(YGRID < 0, YGRID >= -STRIP_HEIGHT / 2) + within_strip_width = np.logical_and(XGRID >= -STRIP_WIDTH / 2, XGRID < STRIP_WIDTH / 2) + above_and_within = np.logical_and(above_in_strip, within_strip_width) + below_and_within = np.logical_and(below_in_strip, within_strip_width) + # E field is perpendicular to strip surface and magnetic field is parallel + if grid_key == "Ey": + values = np.where(above_and_within, ones, values) + values = np.where(below_and_within, -ones, values) + elif grid_key == "Hx": + values = np.where(above_and_within, -ones / td.ETA_0, values) + values = np.where(below_and_within, ones / td.ETA_0, values) + + return td.ScalarFieldDataArray(values, coords={"x": XS, "y": YS, "z": ZS, "f": FS}) + + +def make_coaxial_field_data_array(grid_key: str): + """Populate FIELD_MONITOR with a coaxial transmission line mode.""" + + # Get a normalized electric field that represents the electric field within a coaxial cable transmission line. + def compute_coax_radial_electric(rin, rout, x, y, is_x): + # Radial distance + r = np.sqrt((x) ** 2 + (y) ** 2) + # Remove division by 0 + r_valid = np.where(r == 0.0, 1, r) + # Compute current density so that the total current + # is 1 flowing through surfaces of constant r + # Extra r is for changing to Cartesian coordinates + denominator = 2 * np.pi * r_valid**2 + if is_x: + Exy = np.where(r <= rin, 0, (x / denominator)) + Exy = np.where(r >= rout, 0, Exy) + else: + Exy = np.where(r <= rin, 0, (y / denominator)) + Exy = np.where(r >= rout, 0, Exy) + return Exy + + XS, YS, ZS = get_spatial_coords_dict(SIM_Z, FIELD_MONITOR, grid_key).values() + XGRID, YGRID = np.meshgrid(XS, YS, indexing="ij") + XGRID = XGRID.reshape((len(XS), len(YS), 1, 1)) + YGRID = YGRID.reshape((len(XS), len(YS), 1, 1)) + values = np.zeros((len(XS), len(YS), len(ZS), len(FS))) + + XGRID = np.broadcast_to(XGRID, values.shape) + YGRID = np.broadcast_to(YGRID, values.shape) + + is_x = grid_key[1] == "x" + if grid_key[0] == "E": + field = compute_coax_radial_electric(COAX_R1, COAX_R2, XGRID, YGRID, is_x) + else: + field = compute_coax_radial_electric(COAX_R1, COAX_R2, XGRID, YGRID, not is_x) + # H field is perpendicular and oriented for positive z propagation + # We want to compute Hx which is -Ey/eta0, Hy which is Ex/eta0 + if is_x: + field /= -td.ETA_0 + else: + field /= td.ETA_0 + + return td.ScalarFieldDataArray(field, coords={"x": XS, "y": YS, "z": ZS, "f": FS}) + + +def make_field_data(): + return td.FieldData( + monitor=FIELD_MONITOR, + Ex=make_stripline_scalar_field_data_array("Ex"), + Ey=make_stripline_scalar_field_data_array("Ey"), + Hx=make_stripline_scalar_field_data_array("Hx"), + Hy=make_stripline_scalar_field_data_array("Hy"), + symmetry=SIM_Z.symmetry, + symmetry_center=SIM_Z.center, + grid_expanded=SIM_Z.discretize_monitor(FIELD_MONITOR), + ) + + +def make_coax_field_data(): + return td.FieldData( + monitor=FIELD_MONITOR, + Ex=make_coaxial_field_data_array("Ex"), + Ey=make_coaxial_field_data_array("Ey"), + Hx=make_coaxial_field_data_array("Hx"), + Hy=make_coaxial_field_data_array("Hy"), + symmetry=SIM_Z.symmetry, + symmetry_center=SIM_Z.center, + grid_expanded=SIM_Z.discretize_monitor(FIELD_MONITOR), + ) + + +def make_mw_sim( + use_2D: bool = False, + colocate: bool = False, + transmission_line_type: Literal["microstrip", "cpw", "coax", "stripline"] = "microstrip", + width=3 * mm, + height=1 * mm, + metal_thickness=0.2 * mm, +) -> td.Simulation: + """Helper to create a microwave simulation with a single type of transmission line present.""" + + freq_start = 1e9 + freq_stop = 10e9 + + freq0 = (freq_start + freq_stop) / 2 + fwidth = freq_stop - freq_start + freqs = np.arange(freq_start, freq_stop, 1e9) + + run_time = 60 / fwidth + + length = 40 * mm + sim_width = length + + pec = td.PEC + if use_2D: + metal_thickness = 0.0 + pec = td.PEC2D + + epsr = 4.4 + diel = td.Medium(permittivity=epsr) + + metal_geos = [] + + if transmission_line_type == "microstrip": + substrate = td.Structure( + geometry=td.Box( + center=[0, 0, 0], + size=[td.inf, td.inf, 2 * height], + ), + medium=diel, + ) + metal_geos.append( + td.Box( + center=[0, 0, height + metal_thickness / 2], + size=[td.inf, width, metal_thickness], + ) + ) + elif transmission_line_type == "cpw": + substrate = td.Structure( + geometry=td.Box( + center=[0, 0, 0], + size=[td.inf, td.inf, 2 * height], + ), + medium=diel, + ) + metal_geos.append( + td.Box( + center=[0, 0, height + metal_thickness / 2], + size=[td.inf, width, metal_thickness], + ) + ) + gnd_width = 10 * width + gap = width / 5 + gnd_shift = gnd_width / 2 + gap + width / 2 + metal_geos.append( + td.Box( + center=[0, -gnd_shift, height + metal_thickness / 2], + size=[td.inf, gnd_width, metal_thickness], + ) + ) + metal_geos.append( + td.Box( + center=[0, gnd_shift, height + metal_thickness / 2], + size=[td.inf, gnd_width, metal_thickness], + ) + ) + elif transmission_line_type == "coax": + substrate = td.Structure( + geometry=td.Box( + center=[0, 0, 0], + size=[td.inf, td.inf, 2 * height], + ), + medium=diel, + ) + metal_geos.append( + td.GeometryGroup( + geometries=( + td.ClipOperation( + operation="difference", + geometry_a=td.Cylinder( + axis=0, radius=2 * mm, center=(0, 0, 5 * mm), length=td.inf + ), + geometry_b=td.Cylinder( + axis=0, radius=1.8 * mm, center=(0, 0, 5 * mm), length=td.inf + ), + ), + td.Cylinder(axis=0, radius=0.6 * mm, center=(0, 0, 5 * mm), length=td.inf), + ) + ) + ) + elif transmission_line_type == "stripline": + substrate = td.Structure( + geometry=td.Box( + center=[0, 0, 0], + size=[td.inf, td.inf, 2 * height + metal_thickness], + ), + medium=diel, + ) + metal_geos.append( + td.Box( + center=[0, 0, 0], + size=[td.inf, width, metal_thickness], + ) + ) + gnd_width = 10 * width + metal_geos.append( + td.Box( + center=[0, 0, height + metal_thickness], + size=[td.inf, gnd_width, metal_thickness], + ) + ) + metal_geos.append( + td.Box( + center=[0, 0, -height - metal_thickness], + size=[td.inf, gnd_width, metal_thickness], + ) + ) + else: + raise AssertionError("Incorrect argument") + + metal_structures = [td.Structure(geometry=geo, medium=pec) for geo in metal_geos] + structures = [substrate, *metal_structures] + boundary_spec = td.BoundarySpec( + x=td.Boundary(plus=td.PML(), minus=td.PML()), + y=td.Boundary(plus=td.PML(), minus=td.PML()), + z=td.Boundary(plus=td.PML(), minus=td.PECBoundary()), + ) + + size_sim = [ + length + 2 * width, + sim_width, + 20 * mm + height + metal_thickness, + ] + center_sim = [0, 0, size_sim[2] / 2] + # Slightly different setup for stripline substrate sandwiched between ground planes + if transmission_line_type == "stripline": + center_sim[2] = 0 + boundary_spec = td.BoundarySpec( + x=td.Boundary(plus=td.PML(), minus=td.PML()), + y=td.Boundary(plus=td.PML(), minus=td.PML()), + z=td.Boundary(plus=td.PML(), minus=td.PML()), + ) + size_port = [0, sim_width, size_sim[2]] + center_port = [0, 0, center_sim[2]] + impedance_specs = (td.AutoImpedanceSpec(),) * 4 + mode_spec = td.MicrowaveModeSpec( + num_modes=4, + target_neff=1.8, + impedance_specs=impedance_specs, + ) + + mode_monitor = td.MicrowaveModeMonitor( + center=center_port, size=size_port, freqs=freqs, name="mode_1", colocate=colocate + ) + + gaussian = td.GaussianPulse(freq0=freq0, fwidth=fwidth) + mode_src = td.ModeSource( + center=(-length / 2, 0, center_sim[2]), + size=size_port, + direction="+", + mode_spec=mode_spec, + mode_index=0, + source_time=gaussian, + ) + sim = td.Simulation( + center=center_sim, + size=size_sim, + grid_spec=td.GridSpec.uniform(dl=0.1 * mm), + structures=structures, + sources=[mode_src], + monitors=[mode_monitor], + run_time=run_time, + boundary_spec=boundary_spec, + plot_length_units="mm", + symmetry=(0, 0, 0), + ) + return sim def test_inductance_formulas(): @@ -68,7 +434,7 @@ def test_antenna_parameters(): f = directivity_data.coords["f"] power_inc = FreqDataArray(0.8 * np.ones(len(f)), coords={"f": f}) power_refl = 0.25 * power_inc - antenna_params = AntennaMetricsData.from_directivity_data( + antenna_params = td.AntennaMetricsData.from_directivity_data( directivity_data, power_inc, power_refl ) @@ -112,3 +478,1265 @@ def test_antenna_parameters(): antenna_params.partial_gain("invalid") with pytest.raises(ValueError): antenna_params.partial_realized_gain("invalid") + + +def test_path_spec_plotting(): + """Test that all types of path specification correctly plot themselves.""" + + mean_radius = (COAX_R2 + COAX_R1) * 0.5 + size = [COAX_R2 - COAX_R1, 0, 0] + center = [mean_radius, 0, 0] + + voltage_integral = td.AxisAlignedVoltageIntegralSpec( + center=center, size=size, sign="-", extrapolate_to_endpoints=True, snap_path_to_grid=True + ) + + current_integral = td.Custom2DCurrentIntegralSpec.from_circular_path( + center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=2, clockwise=False + ) + + ax = voltage_integral.plot(z=0) + current_integral.plot(z=0, ax=ax) + plt.close() + + # Test off center plotting + ax = voltage_integral.plot(z=2) + current_integral.plot(z=2, ax=ax) + plt.close() + + # Plot + voltage_integral = td.Custom2DVoltageIntegralSpec( + axis=1, position=0, vertices=[(-1, -1), (0, 0), (1, 1)] + ) + + current_integral = td.AxisAlignedCurrentIntegralSpec( + center=(0, 0, 0), + size=(2, 0, 1), + sign="-", + extrapolate_to_endpoints=False, + snap_contour_to_grid=False, + ) + + ax = voltage_integral.plot(y=0) + current_integral.plot(y=0, ax=ax) + plt.close() + + # Test off center plotting + ax = voltage_integral.plot(y=2) + current_integral.plot(y=2, ax=ax) + plt.close() + + current_integral = td.CompositeCurrentIntegralSpec( + path_specs=( + td.AxisAlignedCurrentIntegralSpec(center=(-1, -1, 0), size=(1, 1, 0), sign="-"), + td.AxisAlignedCurrentIntegralSpec(center=(1, 1, 0), size=(1, 1, 0), sign="-"), + ), + sum_spec="sum", + ) + ax = current_integral.plot(z=0) + plt.close() + + +@pytest.mark.parametrize("clockwise", [False, True]) +def test_custom_current_specification_sign(clockwise): + """Make sure the sign is correctly calculated for custom current specs.""" + current_integral = td.Custom2DCurrentIntegralSpec.from_circular_path( + center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=2, clockwise=clockwise + ) + if clockwise: + assert current_integral.sign == "-" + else: + assert current_integral.sign == "+" + + # When aligned with y, the sign has to be handled carefully + current_integral = td.Custom2DCurrentIntegralSpec.from_circular_path( + center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=1, clockwise=clockwise + ) + if clockwise: + assert current_integral.sign == "-" + else: + assert current_integral.sign == "+" + + +def test_composite_current_integral_validation(): + """Ensures that the CompositeCurrentIntegralSpec is validated correctly.""" + + current_spec = td.AxisAlignedCurrentIntegralSpec(center=(1, 2, 3), size=(0, 1, 1), sign="-") + voltage_spec = td.AxisAlignedVoltageIntegralSpec(center=(1, 2, 3), size=(0, 0, 1), sign="-") + path_spec = td.CompositeCurrentIntegralSpec(path_specs=[current_spec], sum_spec="sum") + + with pytest.raises(pd.ValidationError): + path_spec.updated_copy(path_specs=[]) + + with pytest.raises(pd.ValidationError): + path_spec.updated_copy(path_specs=[voltage_spec]) + + +def test_path_integral_creation(): + """Check that path integrals are correctly constructed from path specifications.""" + + path_spec = td.AxisAlignedVoltageIntegralSpec(center=(1, 2, 3), size=(0, 0, 1), sign="-") + voltage_integral = make_voltage_integral(path_spec) + + path_spec = td.AxisAlignedCurrentIntegralSpec(center=(1, 2, 3), size=(0, 1, 1), sign="-") + current_integral = make_current_integral(path_spec) + + path_spec = td.Custom2DVoltageIntegralSpec(vertices=[(0, 1), (0, 4)], axis=1, position=2) + voltage_integral = make_voltage_integral(path_spec) + + path_spec = td.Custom2DCurrentIntegralSpec( + vertices=[ + (0, 1), + (0, 4), + (3, 4), + (3, 1), + ], + axis=1, + position=2, + ) + _ = make_current_integral(path_spec) + + with pytest.raises(pd.ValidationError): + path_spec = td.Custom2DCurrentIntegralSpec( + vertices=[ + (0, 1, 3), + (0, 4, 5), + (3, 4, 5), + (3, 1, 5), + ], + axis=1, + position=2, + ) + + +def test_mode_plane_analyzer_errors(): + """Check that the ModePlaneAnalyzer reports errors properly.""" + + path_spec_gen = ModePlaneAnalyzer(size=(0, 2, 2), field_data_colocated=False) + + # First some quick sanity checks with the helper + test_path = td.Box(center=(0, 0, 0), size=(0, 0.9, 0.1)) + test_shapely = [LineString([(-1, 0), (1, 0)])] + assert path_spec_gen._check_box_intersects_with_conductors(test_shapely, test_path) + + test_path = td.Box(center=(0, 0, 0), size=(0, 2.1, 0.1)) + test_shapely = [LineString([(-1, 0), (1, 0)])] + assert not path_spec_gen._check_box_intersects_with_conductors(test_shapely, test_path) + + sim = make_mw_sim(False, False, "microstrip") + coax = td.GeometryGroup( + geometries=( + td.ClipOperation( + operation="difference", + geometry_a=td.Cylinder(axis=0, radius=2 * mm, center=(0, 0, 5 * mm), length=td.inf), + geometry_b=td.Cylinder( + axis=0, radius=1.4 * mm, center=(0, 0, 5 * mm), length=td.inf + ), + ), + td.Cylinder(axis=0, radius=1 * mm, center=(0, 0, 5 * mm), length=td.inf), + ) + ) + coax_struct = td.Structure(geometry=coax, medium=td.PEC) + sim = sim.updated_copy(structures=[coax_struct]) + mode_monitor = sim.monitors[0] + modal_plane = td.Box(center=mode_monitor.center, size=mode_monitor.size) + path_spec_gen = ModePlaneAnalyzer( + center=modal_plane.center, + size=modal_plane.size, + field_data_colocated=mode_monitor.colocate, + ) + with pytest.raises(SetupError): + path_spec_gen.get_conductor_bounding_boxes( + sim.structures, + sim.grid, + sim.symmetry, + sim.bounding_box, + ) + + # Error when no conductors intersecting mode plane + path_spec_gen = path_spec_gen.updated_copy(size=(0, 0.1, 0.1), center=(0, 0, 1.5)) + with pytest.raises(SetupError): + path_spec_gen.get_conductor_bounding_boxes( + sim.structures, + sim.grid, + sim.symmetry, + sim.bounding_box, + ) + + +@pytest.mark.parametrize("colocate", [False, True]) +@pytest.mark.parametrize("tline_type", ["microstrip", "cpw", "coax"]) +def test_mode_plane_analyzer_canonical_shapes(colocate, tline_type): + """Test canonical transmission line types to make sure the correct path integrals are generated.""" + sim = make_mw_sim(False, colocate, tline_type) + mode_monitor = sim.monitors[0] + modal_plane = td.Box(center=mode_monitor.center, size=mode_monitor.size) + mode_plane_analyzer = ModePlaneAnalyzer( + center=modal_plane.center, + size=modal_plane.size, + field_data_colocated=mode_monitor.colocate, + ) + bounding_boxes, geos = mode_plane_analyzer.get_conductor_bounding_boxes( + sim.structures, + sim.grid, + sim.symmetry, + sim.bounding_box, + ) + + if tline_type == "coax": + assert len(bounding_boxes) == 2 + for path_spec in bounding_boxes: + assert np.all(np.isclose(path_spec.center, (0, 0, 5 * mm))) + else: + assert len(bounding_boxes) == 1 + assert np.all(np.isclose(bounding_boxes[0].center, (0, 0, 1.1 * mm))) + + +@pytest.mark.parametrize("use_2D", [False, True]) +@pytest.mark.parametrize("symmetry", [(0, 0, 1), (0, 1, 1), (0, 1, 0)]) +def test_mode_plane_analyzer_advanced(use_2D, symmetry): + """The various symmetry permutations as well as with and without 2D structures.""" + sim = make_mw_sim(use_2D, False, "stripline") + + # Add shapes outside the portion considered for the symmetric simulation + bottom_left = td.Structure( + geometry=td.Box( + center=[0, -5 * mm, -5 * mm], + size=[td.inf, 1 * mm, 1 * mm], + ), + medium=td.PEC, + ) + # Add shape only in the symmetric portion + top_right = td.Structure( + geometry=td.Box( + center=[0, 5 * mm, 5 * mm], + size=[td.inf, 1 * mm, 1 * mm], + ), + medium=td.PEC, + ) + + structures = [*list(sim.structures), bottom_left, top_right] + sim = sim.updated_copy(symmetry=symmetry, structures=structures) + mode_monitor = sim.monitors[0] + + modal_plane = td.Box(center=mode_monitor.center, size=mode_monitor.size) + mode_plane_analyzer = ModePlaneAnalyzer( + center=modal_plane.center, + size=modal_plane.size, + field_data_colocated=mode_monitor.colocate, + ) + bounding_boxes, geos = mode_plane_analyzer.get_conductor_bounding_boxes( + sim.structures, + sim.grid, + sim.symmetry, + sim.bounding_box, + ) + + if symmetry[1] == 1 and symmetry[2] == 1: + assert len(bounding_boxes) == 7 + else: + assert len(bounding_boxes) == 5 + + +@pytest.mark.parametrize( + "mode_size", [(1.4 * mm, 1.0 * mm, 0), (1.4 * mm, 2 * mm, 0), (1.4 * mm - 1, 1.0 * mm + 1, 0)] +) +@pytest.mark.parametrize("symmetry", [(0, 0, 0), (0, 1, 0), (1, 1, 0)]) +def test_mode_plane_analyzer_mode_bounds(mode_size, symmetry): + """Test that the the mode plane bounds matches the mode solver grid bounds exactly.""" + + dl = 0.1 * mm + + freq0 = (5e9) / 2 + fwidth = 4e9 + run_time = 60 / fwidth + + boundary_spec = td.BoundarySpec( + x=td.Boundary(plus=td.PECBoundary(), minus=td.PECBoundary()), + y=td.Boundary(plus=td.PECBoundary(), minus=td.PECBoundary()), + z=td.Boundary(plus=td.PECBoundary(), minus=td.PECBoundary()), + ) + impedance_specs = (td.AutoImpedanceSpec(),) * 4 + mode_spec = td.MicrowaveModeSpec( + num_modes=4, + target_neff=1.8, + impedance_specs=impedance_specs, + ) + + metal_box = td.Structure( + geometry=td.Box.from_bounds( + rmin=(0.5 * mm, 0.5 * mm, 0.5 * mm), rmax=(1 * mm - 1 * dl, 0.5 * mm, 0.5 * mm) + ), + medium=td.PEC, + ) + sim = td.Simulation( + center=(0, 0, 0), + size=(2 * mm, 2 * mm, 2 * mm), + grid_spec=td.GridSpec.uniform(dl=dl), + structures=(metal_box,), + run_time=run_time, + boundary_spec=boundary_spec, + plot_length_units="mm", + symmetry=symmetry, + ) + + mode_center = [0, 0, 0] + mode_plane = td.Box(center=mode_center, size=mode_size) + + mms = ModeSolver( + simulation=sim, + plane=mode_plane, + mode_spec=mode_spec, + colocate=True, + freqs=[freq0], + ) + mode_solver_boundaries = mms._solver_grid.boundaries.to_list + mode_plane_analyzer = ModePlaneAnalyzer( + center=mode_center, + size=mode_size, + field_data_colocated=False, + ) + mode_plane_limits = mode_plane_analyzer._get_mode_limits(sim.grid, sim.symmetry) + + for dim in (0, 1): + solver_dim_boundaries = mode_solver_boundaries[dim] + # TODO: Need the second check because the mode solver erroneously adds + # an extra grid cell even when touching the simulation boundary + assert ( + solver_dim_boundaries[0] == mode_plane_limits[0][dim] + or mode_plane_limits[0][dim] == sim.bounds[0][dim] + ) + assert ( + solver_dim_boundaries[-1] == mode_plane_limits[1][dim] + or mode_plane_limits[1][dim] == sim.bounds[1][dim] + ) + + +def test_impedance_spec_validation(): + """Check that the various allowed methods for supplying path specifications are validated.""" + + _ = td.AutoImpedanceSpec() + + v_spec = td.AxisAlignedVoltageIntegralSpec(center=(1, 2, 3), size=(0, 0, 1), sign="-") + i_spec = td.AxisAlignedCurrentIntegralSpec(center=(1, 2, 3), size=(0, 1, 1), sign="-") + + # All valid methods + both = td.CustomImpedanceSpec(voltage_spec=v_spec, current_spec=i_spec) + voltage_only = td.CustomImpedanceSpec( + voltage_spec=v_spec, + ) + current_only = td.CustomImpedanceSpec( + current_spec=i_spec, + ) + + # Invalid + with pytest.raises(pd.ValidationError): + _ = td.CustomImpedanceSpec(voltage_spec=None, current_spec=None) + + _ = td.MicrowaveModeSpec(num_modes=4, impedance_specs=(both, voltage_only, current_only, None)) + + +def test_path_integral_factory_voltage_validation(): + """Test make_voltage_integral validation and error handling.""" + + # Valid voltage specs + axis_aligned_spec = td.AxisAlignedVoltageIntegralSpec( + center=(1, 2, 3), size=(0, 0, 1), sign="-" + ) + custom_2d_spec = td.Custom2DVoltageIntegralSpec(vertices=[(0, 1), (0, 4)], axis=1, position=2) + + # Test successful creation with axis-aligned spec + voltage_integral = make_voltage_integral(axis_aligned_spec) + assert voltage_integral is not None + assert voltage_integral.center == (1, 2, 3) + assert voltage_integral.size == (0, 0, 1) + + # Test successful creation with custom 2D spec + voltage_integral = make_voltage_integral(custom_2d_spec) + assert voltage_integral is not None + assert voltage_integral.axis == 1 + assert voltage_integral.position == 2 + + # Test ValidationError with unsupported type + class UnsupportedVoltageSpec: + def dict(self, exclude=None): + return {} + + with pytest.raises(ValidationError, match="Unsupported voltage path specification type"): + make_voltage_integral(UnsupportedVoltageSpec()) + + +def test_path_integral_factory_current_validation(): + """Test make_current_integral validation and error handling.""" + + # Valid current specs + axis_aligned_spec = td.AxisAlignedCurrentIntegralSpec( + center=(1, 2, 3), size=(0, 1, 1), sign="-" + ) + custom_2d_spec = td.Custom2DCurrentIntegralSpec( + vertices=[(0, 1), (0, 4), (3, 4), (3, 1)], axis=1, position=2 + ) + composite_spec = td.CompositeCurrentIntegralSpec(path_specs=[axis_aligned_spec], sum_spec="sum") + + # Test successful creation with axis-aligned spec + current_integral = make_current_integral(axis_aligned_spec) + assert current_integral is not None + assert current_integral.center == (1, 2, 3) + assert current_integral.size == (0, 1, 1) + + # Test successful creation with custom 2D spec + current_integral = make_current_integral(custom_2d_spec) + assert current_integral is not None + assert current_integral.axis == 1 + assert current_integral.position == 2 + + # Test successful creation with composite spec + current_integral = make_current_integral(composite_spec) + assert current_integral is not None + assert len(current_integral.path_specs) == 1 + + # Test ValidationError with unsupported type + class UnsupportedCurrentSpec: + def dict(self, exclude=None): + return {} + + with pytest.raises(ValidationError, match="Unsupported current path specification type"): + make_current_integral(UnsupportedCurrentSpec()) + + +def test_make_path_integrals_validation(): + """Test make_path_integrals validation and error handling.""" + + # Create a basic simulation setup + sim = make_mw_sim(False, False, "microstrip") + mode_monitor = sim.monitors[0] + + # Valid microwave mode spec with explicit specs + v_spec = td.AxisAlignedVoltageIntegralSpec(center=(1, 2, 3), size=(0, 0, 1), sign="-") + i_spec = td.AxisAlignedCurrentIntegralSpec(center=(1, 2, 3), size=(0, 1, 1), sign="-") + + impedance_spec = td.CustomImpedanceSpec( + voltage_spec=v_spec, + current_spec=i_spec, + ) + microwave_spec = td.MicrowaveModeSpec(impedance_specs=(impedance_spec,)) + + # Test successful creation + voltage_integrals, current_integrals = make_path_integrals(microwave_spec) + assert len(voltage_integrals) == 1 + assert len(current_integrals) == 1 + assert voltage_integrals[0] is not None + assert current_integrals[0] is not None + + # Test with None specs - when both are None, use_automatic_setup is True + # This means current integrals will be auto-generated, not None + microwave_spec_none = td.MicrowaveModeSpec(impedance_specs=(None,)) + voltage_integrals, current_integrals = make_path_integrals(microwave_spec_none) + assert len(voltage_integrals) == mode_monitor.mode_spec.num_modes + assert len(current_integrals) == mode_monitor.mode_spec.num_modes + assert all(vi is None for vi in voltage_integrals) + assert all(ci is None for ci in current_integrals) + + +def test_make_path_integrals_construction_errors(monkeypatch): + """Test that make_path_integrals handles construction errors properly.""" + + # Create a valid spec + v_spec = td.AxisAlignedVoltageIntegralSpec(center=(1, 2, 3), size=(0, 0, 1), sign="-") + + impedance_spec = td.CustomImpedanceSpec(voltage_spec=v_spec, current_spec=None) + microwave_spec = td.MicrowaveModeSpec(impedance_specs=(impedance_spec,)) + + # Mock make_voltage_integral to raise an exception + def mock_make_voltage_integral(path_spec): + raise RuntimeError("Intentional construction failure") + + monkeypatch.setattr( + "tidy3d.components.microwave.path_integrals.factory.make_voltage_integral", + mock_make_voltage_integral, + ) + + # This should raise a SetupError due to construction failure + with pytest.raises(SetupError, match="Failed to construct path integrals"): + make_path_integrals(microwave_spec) + + +def test_path_integral_factory_composite_current(): + """Test make_current_integral with CompositeCurrentIntegralSpec.""" + + # Create base specs for the composite + axis_aligned_spec1 = td.AxisAlignedCurrentIntegralSpec( + center=(1, 2, 3), size=(0, 1, 1), sign="-" + ) + axis_aligned_spec2 = td.AxisAlignedCurrentIntegralSpec( + center=(2, 2, 3), size=(0, 1, 1), sign="+" + ) + + # Test creation of CompositeCurrentIntegralSpec + composite_spec = td.CompositeCurrentIntegralSpec( + path_specs=[axis_aligned_spec1, axis_aligned_spec2], + sum_spec="sum", + ) + + # Test successful creation with composite spec + current_integral = make_current_integral(composite_spec) + assert current_integral is not None + assert len(current_integral.path_specs) == 2 + + # Test with different sum_spec options + composite_spec_split = td.CompositeCurrentIntegralSpec( + path_specs=[axis_aligned_spec1, axis_aligned_spec2], + sum_spec="split", + ) + current_integral_split = make_current_integral(composite_spec_split) + assert current_integral_split is not None + assert current_integral_split.sum_spec == "split" + + +def test_path_integral_factory_mixed_specs(): + """Test make_path_integrals with mixed voltage and current specs (some None).""" + + # Create specs where some are None + v_spec = td.AxisAlignedVoltageIntegralSpec(center=(1, 2, 3), size=(0, 0, 1), sign="-") + i_spec = td.AxisAlignedCurrentIntegralSpec(center=(1, 2, 3), size=(0, 1, 1), sign="-") + + # Test with mixed specs - some None, some specified + impedance_spec1 = td.CustomImpedanceSpec(voltage_spec=v_spec, current_spec=None) + impedance_spec2 = td.CustomImpedanceSpec(voltage_spec=None, current_spec=i_spec) + microwave_spec = td.MicrowaveModeSpec( + num_modes=2, + impedance_specs=( + impedance_spec1, + impedance_spec2, + ), + ) + + voltage_integrals, current_integrals = make_path_integrals(microwave_spec) + + assert len(voltage_integrals) == 2 + assert len(current_integrals) == 2 + assert voltage_integrals[0] is not None # First mode has voltage spec + assert voltage_integrals[1] is None # Second mode has no voltage spec + assert current_integrals[0] is None # First mode has no current spec + assert current_integrals[1] is not None # Second mode has current spec + + +def test_mode_spec_with_microwave_mode_spec(): + """Test that the number of impedance specs is validated against the number of modes in MicrowaveModeSpec.""" + + impedance_specs = (td.AutoImpedanceSpec(),) + + # Should work when impedance_specs matches num_modes + mw_spec = td.MicrowaveModeSpec(num_modes=1, impedance_specs=impedance_specs) + assert mw_spec.num_modes == 1 + assert len(mw_spec.impedance_specs) == 1 + + # Should fail when impedance_specs doesn't match num_modes + with pytest.raises(pd.ValidationError): + td.MicrowaveModeSpec(num_modes=2, impedance_specs=impedance_specs) + + +def test_mode_solver_with_microwave_mode_spec(): + """Test running the mode locally and see if impedance is close to correct.""" + + width = 1.0 * mm + height = 0.5 * mm + metal_thickness = 0.1 * mm + + stripline_sim = make_mw_sim( + transmission_line_type="stripline", + width=width, + height=height, + metal_thickness=metal_thickness, + ) + dl = 0.05 * mm + stripline_sim = stripline_sim.updated_copy(grid_spec=td.GridSpec.uniform(dl=dl)) + + plane = td.Box(center=(0, 0, 0), size=(0, 10 * width, 2 * height + metal_thickness)) + num_modes = 3 + impedance_specs = td.AutoImpedanceSpec() + mode_spec = td.MicrowaveModeSpec( + num_modes=num_modes, + target_neff=2.2, + impedance_specs=impedance_specs, + ) + mms = ModeSolver( + simulation=stripline_sim, + plane=plane, + mode_spec=mode_spec, + colocate=False, + freqs=[1e9, 5e9, 10e9], + ) + + # _, ax = plt.subplots(1, 1, tight_layout=True, figsize=(15, 15)) + # mms.plot(ax=ax) + # mms.plot_grid(ax=ax) + # ax.set_aspect("equal") + # plt.show() + + # This should raise a SetupError because the auto impedance spec cannot be used with the local mode solver + with pytest.raises(SetupError, match="Auto path specification is not available"): + mms_data: td.MicrowaveModeSolverData = mms.data + + # Manually defined impedance spec will work + custom_spec = td.CustomImpedanceSpec( + voltage_spec=None, + current_spec=td.AxisAlignedCurrentIntegralSpec( + size=(0, width + dl, metal_thickness + dl), sign="+" + ), + ) + impedance_specs = (custom_spec, None, None) + microwave_spec_custom = td.MicrowaveModeSpec( + num_modes=num_modes, target_neff=2.2, impedance_specs=impedance_specs + ) + mms = mms.updated_copy(mode_spec=microwave_spec_custom) + mms_data: td.MicrowaveModeSolverData = mms.data + + # _, ax = plt.subplots(1, 1, tight_layout=True, figsize=(15, 15)) + # mms_data.field_components["Ez"].isel(mode_index=0, f=0).real.plot(ax=ax) + # ax.set_aspect("equal") + # plt.show() + mms_data.to_dataframe() + + assert np.all( + np.isclose(mms_data.transmission_line_data.Z0.real.sel(mode_index=0), 28.6, rtol=0.2) + ) + + # Make sure a single spec can be used + microwave_spec_custom = td.MicrowaveModeSpec( + num_modes=num_modes, target_neff=2.2, impedance_specs=custom_spec + ) + mms = mms.updated_copy(mode_spec=microwave_spec_custom) + mms_data: td.MicrowaveModeSolverData = mms.data + assert np.all( + np.isclose(mms_data.transmission_line_data.Z0.real.sel(mode_index=0), 28.6, rtol=0.2) + ) + + +@pytest.mark.parametrize("axis", [0, 1, 2]) +def test_voltage_integral_axes(axis): + """Check AxisAlignedVoltageIntegral runs.""" + length = 0.5 + size = [0, 0, 0] + size[axis] = length + center = [0, 0, 0] + voltage_integral = td.AxisAlignedVoltageIntegral( + center=center, + size=size, + sign="+", + ) + + _ = voltage_integral.compute_voltage(SIM_Z_DATA["field"]) + + +@pytest.mark.parametrize("axis", [0, 1, 2]) +def test_current_integral_axes(axis): + """Check AxisAlignedCurrentIntegral runs.""" + length = 0.5 + size = [length, length, length] + size[axis] = 0.0 + center = [0, 0, 0] + current_integral = td.AxisAlignedCurrentIntegral( + center=center, + size=size, + sign="+", + ) + _ = current_integral.compute_current(SIM_Z_DATA["field"]) + + +def test_voltage_integral_toggles(): + """Check AxisAlignedVoltageIntegral runs with toggles.""" + length = 0.5 + size = [0, 0, 0] + size[0] = length + center = [0, 0, 0] + voltage_integral = td.AxisAlignedVoltageIntegral( + center=center, + size=size, + extrapolate_to_endpoints=True, + snap_path_to_grid=True, + sign="-", + ) + _ = voltage_integral.compute_voltage(SIM_Z_DATA["field"]) + + +def test_current_integral_toggles(): + """Check AxisAlignedCurrentIntegral runs with toggles.""" + length = 0.5 + size = [length, length, length] + size[0] = 0.0 + center = [0, 0, 0] + current_integral = td.AxisAlignedCurrentIntegral( + center=center, + size=size, + extrapolate_to_endpoints=True, + snap_contour_to_grid=True, + sign="-", + ) + _ = current_integral.compute_current(SIM_Z_DATA["field"]) + + +def test_voltage_missing_fields(): + """Check validation of AxisAlignedVoltageIntegral with missing fields.""" + length = 0.5 + size = [0, 0, 0] + size[1] = length + center = [0, 0, 0] + voltage_integral = td.AxisAlignedVoltageIntegral( + center=center, + size=size, + sign="+", + ) + + with pytest.raises(DataError): + _ = voltage_integral.compute_voltage(SIM_Z_DATA["ExHx"]) + + +def test_current_missing_fields(): + """Check validation of AxisAlignedCurrentIntegral with missing fields.""" + length = 0.5 + size = [length, length, length] + size[0] = 0.0 + center = [0, 0, 0] + current_integral = td.AxisAlignedCurrentIntegral( + center=center, + size=size, + sign="+", + ) + + with pytest.raises(DataError): + _ = current_integral.compute_current(SIM_Z_DATA["ExHx"]) + + current_integral = td.AxisAlignedCurrentIntegral( + center=center, + size=[length, length, 0], + sign="+", + ) + + with pytest.raises(DataError): + _ = current_integral.compute_current(SIM_Z_DATA["ExHx"]) + + +def test_time_monitor_voltage_integral(): + """Check AxisAlignedVoltageIntegral runs on time domain data.""" + length = 0.5 + size = [0, 0, 0] + size[1] = length + center = [0, 0, 0] + voltage_integral = td.AxisAlignedVoltageIntegral( + center=center, + size=size, + sign="+", + ) + + voltage_integral.compute_voltage(SIM_Z_DATA["field_time"]) + + +def test_mode_solver_monitor_voltage_integral(): + """Check AxisAlignedVoltageIntegral runs on ModeData and ModeSolverData.""" + length = 0.5 + size = [0, 0, 0] + size[1] = length + center = [0, 0, 0] + voltage_integral = td.AxisAlignedVoltageIntegral( + center=center, + size=size, + sign="+", + ) + + voltage_integral.compute_voltage(SIM_Z_DATA["mode_solver"]) + voltage_integral.compute_voltage(SIM_Z_DATA["mode"]) + + +def test_tiny_voltage_path(): + """Check AxisAlignedVoltageIntegral runs when given a very short path.""" + length = 0.02 + size = [0, 0, 0] + size[1] = length + center = [0, 0, 0] + voltage_integral = td.AxisAlignedVoltageIntegral( + center=center, size=size, sign="+", extrapolate_to_endpoints=True + ) + + _ = voltage_integral.compute_voltage(SIM_Z_DATA["field"]) + + +def test_impedance_calculator(): + """Check validation of ImpedanceCalculator when integrals are missing.""" + with pytest.raises(pd.ValidationError): + _ = td.ImpedanceCalculator(voltage_integral=None, current_integral=None) + + +def test_impedance_calculator_on_time_data(): + """Check ImpedanceCalculator runs on time domain data.""" + # Setup path integrals + length = 0.5 + size = [0, length, 0] + size[1] = length + center = [0, 0, 0] + voltage_integral = td.AxisAlignedVoltageIntegral( + center=center, size=size, sign="+", extrapolate_to_endpoints=True + ) + + size = [length, length, 0] + current_integral = td.AxisAlignedCurrentIntegral(center=center, size=size, sign="+") + + # Compute impedance using the tool + Z_calc = td.ImpedanceCalculator( + voltage_integral=voltage_integral, current_integral=current_integral + ) + _ = Z_calc.compute_impedance(SIM_Z_DATA["field_time"]) + Z_calc = td.ImpedanceCalculator(voltage_integral=voltage_integral, current_integral=None) + _ = Z_calc.compute_impedance(SIM_Z_DATA["field_time"]) + Z_calc = td.ImpedanceCalculator(voltage_integral=None, current_integral=current_integral) + _ = Z_calc.compute_impedance(SIM_Z_DATA["field_time"]) + + +def test_impedance_accuracy(): + """Test the accuracy of the ImpedanceCalculator.""" + field_data = make_field_data() + # Setup path integrals + size = [0, STRIP_HEIGHT / 2, 0] + center = [0, -STRIP_HEIGHT / 4, 0] + voltage_integral = td.AxisAlignedVoltageIntegral( + center=center, size=size, sign="+", extrapolate_to_endpoints=True + ) + + size = [STRIP_WIDTH * 1.25, STRIP_HEIGHT / 2, 0] + center = [0, 0, 0] + current_integral = td.AxisAlignedCurrentIntegral(center=center, size=size, sign="+") + + def impedance_of_stripline(width, height): + # Assuming no fringing fields, is the same as a parallel plate + # with half the height and carrying twice the current + Z0_parallel_plate = 0.5 * height / width * td.ETA_0 + return Z0_parallel_plate / 2 + + analytic_impedance = impedance_of_stripline(STRIP_WIDTH, STRIP_HEIGHT) + + # Compute impedance using the tool + Z_calc = td.ImpedanceCalculator( + voltage_integral=voltage_integral, current_integral=current_integral + ) + Z1 = Z_calc.compute_impedance(field_data) + Z_calc = td.ImpedanceCalculator(voltage_integral=voltage_integral, current_integral=None) + Z2 = Z_calc.compute_impedance(field_data) + Z_calc = td.ImpedanceCalculator(voltage_integral=None, current_integral=current_integral) + Z3 = Z_calc.compute_impedance(field_data) + + # Computation that uses the flux is less accurate, due to staircasing the field + assert np.all(np.isclose(Z1, analytic_impedance, rtol=0.02)) + assert np.all(np.isclose(Z2, analytic_impedance, atol=3.5)) + assert np.all(np.isclose(Z3, analytic_impedance, atol=3.5)) + + +def test_frequency_monitor_custom_voltage_integral(): + length = 0.5 + size = [0, 0, 0] + size[1] = length + # Make line + vertices = [(0, 0), (0, 0.2), (0, 0.4)] + voltage_integral = td.Custom2DVoltageIntegral(axis=2, position=0, vertices=vertices) + voltage_integral.compute_voltage(SIM_Z_DATA["field"]) + + +def test_vertices_validator_custom_current_integral(): + length = 0.5 + size = [0, 0, 0] + size[1] = length + # Make wrong box + vertices = [(0.2, -0.2, 0.5), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)] + + with pytest.raises(pd.ValidationError): + _ = td.Custom2DCurrentIntegral(axis=2, position=0, vertices=vertices) + + # Make wrong box shape + vertices = [(0.2, 0.2, -0.2, -0.2, 0.2), (-0.2, 0.2, 0.2, -0.2, 0.2)] + with pytest.raises(pd.ValidationError): + _ = td.Custom2DCurrentIntegral(axis=2, position=0, vertices=vertices) + + +def test_fields_missing_custom_current_integral(): + length = 0.5 + size = [0, 0, 0] + size[1] = length + # Make box + vertices = [(0.2, -0.2), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)] + current_integral = td.Custom2DCurrentIntegral(axis=2, position=0, vertices=vertices) + with pytest.raises(DataError): + current_integral.compute_current(SIM_Z_DATA["ExHx"]) + + +def test_wrong_monitor_data(): + """Check that the function arguments to integrals are correctly validated.""" + length = 0.5 + size = [0, 0, 0] + size[1] = length + # Make box + vertices = [(0.2, -0.2), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)] + current_integral = td.Custom2DCurrentIntegral(axis=2, position=0, vertices=vertices) + with pytest.raises(DataError): + current_integral.compute_current(SIM_Z.sources[0].source_time) + with pytest.raises(DataError): + current_integral.compute_current(em_field=SIM_Z.sources[0].source_time) + + +def test_time_monitor_custom_current_integral(): + length = 0.5 + size = [0, 0, 0] + size[1] = length + # Make box + vertices = [(0.2, -0.2), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)] + current_integral = td.Custom2DCurrentIntegral(axis=2, position=0, vertices=vertices) + current_integral.compute_current(SIM_Z_DATA["field_time"]) + + +def test_mode_solver_custom_current_integral(): + """Test that both ModeData and ModeSolverData are allowed types.""" + length = 0.5 + size = [0, 0, 0] + size[1] = length + # Make box + vertices = [(0.2, -0.2), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)] + current_integral = td.Custom2DCurrentIntegral(axis=2, position=0, vertices=vertices) + current_integral.compute_current(SIM_Z_DATA["mode_solver"]) + current_integral.compute_current(SIM_Z_DATA["mode"]) + + +def test_custom_current_integral_normal_y(): + current_integral = td.Custom2DCurrentIntegral.from_circular_path( + center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=1, clockwise=False + ) + current_integral.compute_current(SIM_Z_DATA["field"]) + + +def test_composite_current_integral_warnings(): + """Ensures that the checks function correctly on some test data.""" + f = [2e9, 3e9, 4e9] + mode_index = list(np.arange(5)) + coords = {"f": f, "mode_index": mode_index} + values = np.ones((3, 5)) + + path_spec = td.AxisAlignedCurrentIntegralSpec(center=(0, 0, 0), size=(2, 2, 0), sign="+") + composite_integral = td.CompositeCurrentIntegral(path_specs=[path_spec], sum_spec="split") + + phase_diff = FreqModeDataArray(np.angle(values), coords=coords) + with AssertLogLevel(None): + assert composite_integral._check_phase_sign_consistency(phase_diff) + + values[1, 2:] = -1 + phase_diff = FreqModeDataArray(np.angle(values), coords=coords) + with AssertLogLevel("WARNING"): + assert not composite_integral._check_phase_sign_consistency(phase_diff) + + values = np.ones((3, 5)) + in_phase = FreqModeDataArray(values, coords=coords) + values = 0.5 * np.ones((3, 5)) + out_phase = FreqModeDataArray(values, coords=coords) + with AssertLogLevel(None): + assert composite_integral._check_phase_amplitude_consistency(in_phase, out_phase) + + values = 0.5 * np.ones((3, 5)) + values[2, 4:] = 1.5 + out_phase = FreqModeDataArray(values, coords=coords) + with AssertLogLevel("WARNING"): + assert not composite_integral._check_phase_amplitude_consistency(in_phase, out_phase) + + +def test_custom_path_integral_accuracy(): + """Test the accuracy of the custom path integral.""" + field_data = make_coax_field_data() + + current_integral = td.Custom2DCurrentIntegral.from_circular_path( + center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=2, clockwise=False + ) + current = current_integral.compute_current(field_data) + assert np.allclose(current.values, 1.0 / td.ETA_0, rtol=0.01) + + current_integral = td.Custom2DCurrentIntegral.from_circular_path( + center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=2, clockwise=True + ) + current = current_integral.compute_current(field_data) + assert np.allclose(current.values, -1.0 / td.ETA_0, rtol=0.01) + + +def test_impedance_accuracy_on_coaxial(): + """Test the accuracy of the ImpedanceCalculator.""" + field_data = make_coax_field_data() + # Setup path integrals + mean_radius = (COAX_R2 + COAX_R1) * 0.5 + size = [COAX_R2 - COAX_R1, 0, 0] + center = [mean_radius, 0, 0] + + voltage_integral = td.AxisAlignedVoltageIntegral( + center=center, size=size, sign="-", extrapolate_to_endpoints=True, snap_path_to_grid=True + ) + current_integral = td.Custom2DCurrentIntegral.from_circular_path( + center=(0, 0, 0), radius=mean_radius, num_points=31, normal_axis=2, clockwise=False + ) + + def impedance_of_coaxial_cable(r1, r2, wave_impedance=td.ETA_0): + return np.log(r2 / r1) * wave_impedance / 2 / np.pi + + Z_analytic = impedance_of_coaxial_cable(COAX_R1, COAX_R2) + + # Compute impedance using the tool + Z_calculator = td.ImpedanceCalculator( + voltage_integral=voltage_integral, current_integral=current_integral + ) + Z_calc = Z_calculator.compute_impedance(field_data) + assert np.allclose(Z_calc, Z_analytic, rtol=0.04) + + +def test_creation_from_terminal_positions(): + """Test creating an AxisAlignedVoltageIntegral using terminal positions.""" + _ = td.AxisAlignedVoltageIntegral.from_terminal_positions( + plus_terminal=2, minus_terminal=1, y=2.2, z=1 + ) + _ = td.AxisAlignedVoltageIntegral.from_terminal_positions( + plus_terminal=1, minus_terminal=2, y=2.2, z=1 + ) + + +def test_auto_path_integrals_for_lumped_element(): + """Test the auto creation of path integrals around lumped elements.""" + Rval = 75 + Cval = 0.2 * 1e-12 + fstart = 1e9 + fstop = 30e9 + freqs = np.linspace(fstart, fstop, 100) + + RLC = td.RLCNetwork( + resistance=Rval, + capacitance=Cval, + network_topology="series", + ) + + linear_element = td.LinearLumpedElement( + center=[0, 0, 0], + size=[0.2, 0, 0.5], + voltage_axis=2, + network=RLC, + name="RLC", + ) + + SIM_Z_with_element = SIM_Z.updated_copy(lumped_elements=[linear_element]) + + _, _ = td.path_integrals_from_lumped_element(linear_element, SIM_Z_with_element.grid, "+") + + +def test_composite_current_integral_compute_current(): + """Test CompositeCurrentIntegral.compute_current method with different sum_spec behaviors.""" + + # Create individual path specs for the composite + path_spec1 = td.AxisAlignedCurrentIntegral(center=(0, 0, 0), size=(0.5, 0.5, 0), sign="+") + path_spec2 = td.AxisAlignedCurrentIntegral(center=(0.25, 0, 0), size=(0.5, 0.5, 0), sign="-") + + # Test with sum_spec="sum" + composite_integral_sum = td.CompositeCurrentIntegral( + path_specs=[path_spec1, path_spec2], sum_spec="sum" + ) + + current_sum = composite_integral_sum.compute_current(SIM_Z_DATA["field"]) + assert current_sum is not None + assert hasattr(current_sum, "values") + + # Test with sum_spec="split" + composite_integral_split = td.CompositeCurrentIntegral( + path_specs=[path_spec1, path_spec2], sum_spec="split" + ) + + current_split = composite_integral_split.compute_current(SIM_Z_DATA["field"]) + assert current_split is not None + assert hasattr(current_split, "values") + + # Test that both methods return results with the same dimensions + assert current_sum.dims == current_split.dims + + +def test_composite_current_integral_time_domain_error(): + """Test that CompositeCurrentIntegral raises error for time domain data with split sum_spec.""" + + path_spec = td.AxisAlignedCurrentIntegral(center=(0, 0, 0), size=(0.5, 0.5, 0), sign="+") + + composite_integral = td.CompositeCurrentIntegral(path_specs=[path_spec], sum_spec="split") + + # Should raise DataError for time domain data with split sum_spec + with pytest.raises( + td.exceptions.DataError, match="Only frequency domain field data is supported" + ): + composite_integral.compute_current(SIM_Z_DATA["field_time"]) + + +def test_composite_current_integral_phase_consistency_warnings(): + """Test CompositeCurrentIntegral phase consistency warning methods.""" + from tidy3d.components.data.data_array import FreqModeDataArray + + # Create a composite integral for testing + path_spec = td.AxisAlignedCurrentIntegral(center=(0, 0, 0), size=(0.5, 0.5, 0), sign="+") + + composite_integral = td.CompositeCurrentIntegral(path_specs=[path_spec], sum_spec="split") + + # Test _check_phase_sign_consistency with consistent data + f = [2e9, 3e9, 4e9] + mode_index = list(np.arange(3)) + coords = {"f": f, "mode_index": mode_index} + + # Phase difference data that is consistent (all in phase) + consistent_phase_values = np.zeros((3, 3)) # All zeros = in phase + consistent_phase_diff = FreqModeDataArray(consistent_phase_values, coords=coords) + + # This should return True (no warning) + result = composite_integral._check_phase_sign_consistency(consistent_phase_diff) + assert result is True + + # Phase difference data that is inconsistent + inconsistent_phase_values = np.array([[0, 0, 0], [0, np.pi, 0], [0, 0, np.pi]]) # Mixed phases + inconsistent_phase_diff = FreqModeDataArray(inconsistent_phase_values, coords=coords) + + # This should return False and emit a warning + # Note: The warning is logged, but we'll just test the return value here + result = composite_integral._check_phase_sign_consistency(inconsistent_phase_diff) + assert result is False + + # Test _check_phase_amplitude_consistency + current_values = np.ones((3, 3)) + current_in_phase = FreqModeDataArray(current_values, coords=coords) + current_out_phase = FreqModeDataArray(0.5 * current_values, coords=coords) + + # Consistent amplitudes (in_phase always larger) + result = composite_integral._check_phase_amplitude_consistency( + current_in_phase, current_out_phase + ) + assert result is True + + # Inconsistent amplitudes (mix of which is larger) + inconsistent_out_phase = FreqModeDataArray( + np.array([[0.5, 0.5, 0.5], [1.5, 0.5, 0.5], [0.5, 1.5, 0.5]]), coords=coords + ) + + # This should return False and emit a warning + # Note: The warning is logged, but we'll just test the return value here + result = composite_integral._check_phase_amplitude_consistency( + current_in_phase, inconsistent_out_phase + ) + assert result is False + + +def test_impedance_calculator_compute_impedance_with_return_extras(): + """Test ImpedanceCalculator.compute_impedance with return_voltage_and_current=True.""" + + # Setup path integrals + voltage_integral = td.AxisAlignedVoltageIntegral( + center=(0, 0, 0), size=(0, 0.5, 0), sign="+", extrapolate_to_endpoints=True + ) + current_integral = td.AxisAlignedCurrentIntegral(center=(0, 0, 0), size=(0.5, 0.5, 0), sign="+") + + # Test with both voltage and current integrals + Z_calc = td.ImpedanceCalculator( + voltage_integral=voltage_integral, current_integral=current_integral + ) + + # Test with mode data that supports flux calculations + result = Z_calc.compute_impedance(SIM_Z_DATA["mode"], return_voltage_and_current=True) + + # Should return a tuple of (impedance, voltage, current) + assert isinstance(result, tuple) + assert len(result) == 3 + impedance, voltage, current = result + + assert impedance is not None + assert voltage is not None + assert current is not None + assert hasattr(impedance, "values") + assert hasattr(voltage, "values") + assert hasattr(current, "values") + + # Test with only voltage integral (current computed from flux) + Z_calc_voltage_only = td.ImpedanceCalculator(voltage_integral=voltage_integral) + + result_voltage_only = Z_calc_voltage_only.compute_impedance( + SIM_Z_DATA["mode"], return_voltage_and_current=True + ) + + assert isinstance(result_voltage_only, tuple) + assert len(result_voltage_only) == 3 + impedance_v, voltage_v, current_v = result_voltage_only + + assert impedance_v is not None + assert voltage_v is not None + assert current_v is not None # Should be computed from flux + + # Test with only current integral (voltage computed from flux) + Z_calc_current_only = td.ImpedanceCalculator(current_integral=current_integral) + + result_current_only = Z_calc_current_only.compute_impedance( + SIM_Z_DATA["mode"], return_voltage_and_current=True + ) + + assert isinstance(result_current_only, tuple) + assert len(result_current_only) == 3 + impedance_c, voltage_c, current_c = result_current_only + + assert impedance_c is not None + assert voltage_c is not None # Should be computed from flux + assert current_c is not None + + +def test_composite_current_integral_freq_mode_data(): + """Test CompositeCurrentIntegral works correctly with FreqModeDataArray.""" + + # Create individual path specs for the composite + path_spec1 = td.AxisAlignedCurrentIntegral(center=(0, 0, 0), size=(0.5, 0.5, 0), sign="+") + path_spec2 = td.AxisAlignedCurrentIntegral(center=(0.25, 0, 0), size=(0.5, 0.5, 0), sign="-") + + # Test with sum_spec="sum" - should work with FreqModeDataArray + composite_integral_sum = td.CompositeCurrentIntegral( + path_specs=[path_spec1, path_spec2], sum_spec="sum" + ) + + # Use mode data which provides FreqModeDataArray + current_sum = composite_integral_sum.compute_current(SIM_Z_DATA["mode"]) + assert current_sum is not None + assert hasattr(current_sum, "values") + + # Verify it's a FreqModeDataArray by checking dimensions + assert "f" in current_sum.dims + assert "mode_index" in current_sum.dims + + # Test with sum_spec="split" - should also work with FreqModeDataArray + composite_integral_split = td.CompositeCurrentIntegral( + path_specs=[path_spec1, path_spec2], sum_spec="split" + ) + + current_split = composite_integral_split.compute_current(SIM_Z_DATA["mode"]) + assert current_split is not None + assert hasattr(current_split, "values") + + # Verify it's a FreqModeDataArray by checking dimensions + assert "f" in current_split.dims + assert "mode_index" in current_split.dims + + # Test that both methods return compatible results + assert current_sum.dims == current_split.dims + assert current_sum.shape == current_split.shape + + +def test_impedance_calculator_mode_direction_handling(): + """Test that ImpedanceCalculator properly handles mode direction for flux calculation.""" + + current_integral = td.AxisAlignedCurrentIntegral(center=(0, 0, 0), size=(0.5, 0.5, 0), sign="+") + + # Test with ModeSolverMonitor data + Z_calc = td.ImpedanceCalculator(current_integral=current_integral) + + impedance_mode_solver = Z_calc.compute_impedance(SIM_Z_DATA["mode_solver"]) + assert impedance_mode_solver is not None + + # Test with ModeMonitor data + impedance_mode = Z_calc.compute_impedance(SIM_Z_DATA["mode"]) + assert impedance_mode is not None + + # Both should produce valid impedance values + assert hasattr(impedance_mode_solver, "values") + assert hasattr(impedance_mode, "values") diff --git a/tests/test_components/test_simulation.py b/tests/test_components/test_simulation.py index c9d22d7d96..e36888d3ba 100644 --- a/tests/test_components/test_simulation.py +++ b/tests/test_components/test_simulation.py @@ -3785,3 +3785,58 @@ def test_structures_per_medium(monkeypatch): grid_spec=td.GridSpec.uniform(dl=0.02), structures=structs, ) + + +def test_validate_microwave_mode_spec_generation(): + """Test that auto generation of path specs is correctly validated for currently unsupported structures.""" + freq0 = 10e9 + mm = 1e3 + run_time_spec = td.RunTimeSpec(quality_factor=3.0) + size = (10 * mm, 10 * mm, 10 * mm) + size_mon = (0, 8 * mm, 8 * mm) + + # Currently limited to generation of axis aligned boxes around conductors, + # so the path may intersect other nearby conductors, like in this coaxial cable + coaxial = td.Structure( + geometry=td.GeometryGroup( + geometries=( + td.ClipOperation( + operation="difference", + geometry_a=td.Cylinder( + axis=0, radius=2.5 * mm, center=(0, 0, 0), length=td.inf + ), + geometry_b=td.Cylinder( + axis=0, radius=1.3 * mm, center=(0, 0, 0), length=td.inf + ), + ), + td.Cylinder(axis=0, radius=1 * mm, center=(0, 0, 0), length=td.inf), + ) + ), + medium=td.PEC, + ) + mode_spec = td.MicrowaveModeSpec( + num_modes=2, + target_neff=1.8, + impedance_specs=(td.AutoImpedanceSpec(), td.AutoImpedanceSpec()), + ) + + mode_mon = td.MicrowaveModeMonitor( + center=(0, 0, 0), + size=size_mon, + freqs=[freq0], + name="mode_1", + colocate=False, + mode_spec=mode_spec, + ) + sim = td.Simulation( + run_time=run_time_spec, + size=size, + sources=[], + structures=[coaxial], + grid_spec=td.GridSpec.uniform(dl=0.1 * mm), + monitors=[mode_mon], + ) + + # check that validation error is caught + with pytest.raises(SetupError): + sim._validate_microwave_mode_specs() diff --git a/tests/test_data/test_data_arrays.py b/tests/test_data/test_data_arrays.py index 185ee4c2dc..1ba507455f 100644 --- a/tests/test_data/test_data_arrays.py +++ b/tests/test_data/test_data_arrays.py @@ -319,6 +319,16 @@ def test_abs(): _ = data.abs +def test_angle(): + # Make sure works on real data and the type is correct + data = make_scalar_field_time_data_array("Ex") + angle_data = data.angle + assert type(data) is type(angle_data) + data = make_mode_amps_data_array() + angle_data = data.angle + assert type(data) is type(angle_data) + + def test_heat_data_array(): T = [0, 1e-12, 2e-12] _ = td.HeatDataArray((1 + 1j) * np.random.random((3,)), coords={"T": T}) diff --git a/tests/test_plugins/smatrix/terminal_component_modeler_def.py b/tests/test_plugins/smatrix/terminal_component_modeler_def.py index fece3de50b..72941d6580 100644 --- a/tests/test_plugins/smatrix/terminal_component_modeler_def.py +++ b/tests/test_plugins/smatrix/terminal_component_modeler_def.py @@ -5,7 +5,6 @@ import numpy as np import tidy3d as td -import tidy3d.plugins.microwave as microwave from tidy3d.plugins.smatrix import ( CoaxialLumpedPort, LumpedPort, @@ -288,7 +287,7 @@ def make_port(center, direction, type, name) -> Union[CoaxialLumpedPort, WavePor voltage_integral = None if use_voltage: - voltage_integral = microwave.VoltageIntegralAxisAligned( + voltage_integral = td.AxisAlignedVoltageIntegral( center=voltage_center, size=voltage_size, extrapolate_to_endpoints=True, @@ -297,7 +296,7 @@ def make_port(center, direction, type, name) -> Union[CoaxialLumpedPort, WavePor ) current_integral = None if use_current: - current_integral = microwave.CustomCurrentIntegral2D.from_circular_path( + current_integral = td.Custom2DCurrentIntegral.from_circular_path( center=center, radius=mean_radius, num_points=41, diff --git a/tests/test_plugins/smatrix/test_terminal_component_modeler.py b/tests/test_plugins/smatrix/test_terminal_component_modeler.py index e7ca3b3f00..ea1984c8ff 100644 --- a/tests/test_plugins/smatrix/test_terminal_component_modeler.py +++ b/tests/test_plugins/smatrix/test_terminal_component_modeler.py @@ -15,11 +15,6 @@ from tidy3d.components.boundary import BroadbandModeABCSpec from tidy3d.components.data.data_array import FreqDataArray from tidy3d.exceptions import SetupError, Tidy3dError, Tidy3dKeyError -from tidy3d.plugins.microwave import ( - CurrentIntegralAxisAligned, - CustomCurrentIntegral2D, - VoltageIntegralAxisAligned, -) from tidy3d.plugins.smatrix import ( CoaxialLumpedPort, LumpedPort, @@ -830,7 +825,7 @@ def test_wave_port_path_integral_validation(): size_port = [2, 2, 0] center_port = [0, 0, -10] - voltage_path = VoltageIntegralAxisAligned( + voltage_path = td.AxisAlignedVoltageIntegral( center=(0.5, 0, -10), size=(1.0, 0, 0), extrapolate_to_endpoints=True, @@ -838,7 +833,7 @@ def test_wave_port_path_integral_validation(): sign="+", ) - custom_current_path = CustomCurrentIntegral2D.from_circular_path( + custom_current_path = td.Custom2DCurrentIntegral.from_circular_path( center=center_port, radius=0.5, num_points=21, normal_axis=2, clockwise=False ) @@ -887,7 +882,7 @@ def test_wave_port_path_integral_validation(): current_integral=None, ) - custom_current_path = CustomCurrentIntegral2D.from_circular_path( + custom_current_path = td.Custom2DCurrentIntegral.from_circular_path( center=center_port, radius=3, num_points=21, normal_axis=2, clockwise=False ) with pytest.raises(pd.ValidationError): @@ -922,7 +917,7 @@ def test_wave_port_grid_validation(tmp_path): size_port = [2, 2, 0] center_port = [0, 0, -10] - voltage_path = VoltageIntegralAxisAligned( + voltage_path = td.AxisAlignedVoltageIntegral( center=(0.5, 0, -10), size=(1.0, 0, 0), extrapolate_to_endpoints=True, @@ -930,7 +925,7 @@ def test_wave_port_grid_validation(tmp_path): sign="+", ) - current_path = CurrentIntegralAxisAligned( + current_path = td.AxisAlignedCurrentIntegral( center=(0.5, 0, -10), size=(0.25, 0.5, 0), snap_contour_to_grid=True, @@ -990,7 +985,7 @@ def test_port_source_snapped_to_PML(tmp_path): """ modeler = make_component_modeler(planar_pec=True) port_pos = 5e4 - voltage_path = VoltageIntegralAxisAligned( + voltage_path = td.AxisAlignedVoltageIntegral( center=(port_pos, 0, 0), size=(0, 1e3, 0), sign="+", diff --git a/tests/test_plugins/test_microwave.py b/tests/test_plugins/test_microwave.py index a769ac700b..3132bfdd5d 100644 --- a/tests/test_plugins/test_microwave.py +++ b/tests/test_plugins/test_microwave.py @@ -11,383 +11,14 @@ from skrf import Frequency from skrf.media import MLine -import tidy3d as td import tidy3d.plugins.microwave as mw -from tidy3d import FieldData -from tidy3d.constants import ETA_0 -from tidy3d.exceptions import DataError - -from ..utils import get_spatial_coords_dict, run_emulated - -# Using similar code as "test_data/test_data_arrays.py" -MON_SIZE = (2, 1, 0) -FIELDS = ("Ex", "Ey", "Hx", "Hy") -FSTART = 0.5e9 -FSTOP = 1.5e9 -F0 = (FSTART + FSTOP) / 2 -FWIDTH = FSTOP - FSTART -FS = np.linspace(FSTART, FSTOP, 3) -FIELD_MONITOR = td.FieldMonitor(size=MON_SIZE, fields=FIELDS, name="strip_field", freqs=FS) -STRIP_WIDTH = 1.5 -STRIP_HEIGHT = 0.5 -COAX_R1 = 0.04 -COAX_R2 = 0.5 - -SIM_Z = td.Simulation( - size=(2, 1, 1), - grid_spec=td.GridSpec.uniform(dl=0.04), - monitors=[ - FIELD_MONITOR, - td.FieldMonitor(center=(0, 0, 0), size=(1, 1, 1), freqs=FS, name="field", colocate=False), - td.FieldMonitor( - center=(0, 0, 0), size=(1, 1, 1), freqs=FS, fields=["Ex", "Hx"], name="ExHx" - ), - td.FieldTimeMonitor(center=(0, 0, 0), size=(1, 1, 0), colocate=False, name="field_time"), - td.ModeSolverMonitor( - center=(0, 0, 0), - size=(1, 1, 0), - freqs=FS, - mode_spec=td.ModeSpec(num_modes=2), - name="mode_solver", - ), - td.ModeMonitor( - center=(0, 0, 0), - size=(1, 1, 0), - freqs=FS, - mode_spec=td.ModeSpec(num_modes=3), - store_fields_direction="+", - name="mode", - ), - ], - sources=[ - td.PointDipole( - center=(0, 0, 0), - polarization="Ex", - source_time=td.GaussianPulse(freq0=F0, fwidth=FWIDTH), - ) - ], - run_time=5e-16, -) - -SIM_Z_DATA = run_emulated(SIM_Z) - -""" Generate the data arrays for testing path integral computations """ - - -def make_stripline_scalar_field_data_array(grid_key: str): - """Populate FIELD_MONITOR with a idealized stripline mode, where fringing fields are assumed 0.""" - XS, YS, ZS = get_spatial_coords_dict(SIM_Z, FIELD_MONITOR, grid_key).values() - XGRID, YGRID = np.meshgrid(XS, YS, indexing="ij") - XGRID = XGRID.reshape((len(XS), len(YS), 1, 1)) - YGRID = YGRID.reshape((len(XS), len(YS), 1, 1)) - values = np.zeros((len(XS), len(YS), len(ZS), len(FS))) - ones = np.ones((len(XS), len(YS), len(ZS), len(FS))) - XGRID = np.broadcast_to(XGRID, values.shape) - YGRID = np.broadcast_to(YGRID, values.shape) - - # Numpy masks for quickly determining location - above_in_strip = np.logical_and(YGRID >= 0, YGRID <= STRIP_HEIGHT / 2) - below_in_strip = np.logical_and(YGRID < 0, YGRID >= -STRIP_HEIGHT / 2) - within_strip_width = np.logical_and(XGRID >= -STRIP_WIDTH / 2, XGRID < STRIP_WIDTH / 2) - above_and_within = np.logical_and(above_in_strip, within_strip_width) - below_and_within = np.logical_and(below_in_strip, within_strip_width) - # E field is perpendicular to strip surface and magnetic field is parallel - if grid_key == "Ey": - values = np.where(above_and_within, ones, values) - values = np.where(below_and_within, -ones, values) - elif grid_key == "Hx": - values = np.where(above_and_within, -ones / ETA_0, values) - values = np.where(below_and_within, ones / ETA_0, values) - - return td.ScalarFieldDataArray(values, coords={"x": XS, "y": YS, "z": ZS, "f": FS}) - - -def make_coaxial_field_data_array(grid_key: str): - """Populate FIELD_MONITOR with a coaxial transmission line mode.""" - - # Get a normalized electric field that represents the electric field within a coaxial cable transmission line. - def compute_coax_radial_electric(rin, rout, x, y, is_x): - # Radial distance - r = np.sqrt((x) ** 2 + (y) ** 2) - # Remove division by 0 - r_valid = np.where(r == 0.0, 1, r) - # Compute current density so that the total current - # is 1 flowing through surfaces of constant r - # Extra r is for changing to Cartesian coordinates - denominator = 2 * np.pi * r_valid**2 - if is_x: - Exy = np.where(r <= rin, 0, (x / denominator)) - Exy = np.where(r >= rout, 0, Exy) - else: - Exy = np.where(r <= rin, 0, (y / denominator)) - Exy = np.where(r >= rout, 0, Exy) - return Exy - - XS, YS, ZS = get_spatial_coords_dict(SIM_Z, FIELD_MONITOR, grid_key).values() - XGRID, YGRID = np.meshgrid(XS, YS, indexing="ij") - XGRID = XGRID.reshape((len(XS), len(YS), 1, 1)) - YGRID = YGRID.reshape((len(XS), len(YS), 1, 1)) - values = np.zeros((len(XS), len(YS), len(ZS), len(FS))) - - XGRID = np.broadcast_to(XGRID, values.shape) - YGRID = np.broadcast_to(YGRID, values.shape) - - is_x = grid_key[1] == "x" - if grid_key[0] == "E": - field = compute_coax_radial_electric(COAX_R1, COAX_R2, XGRID, YGRID, is_x) - else: - field = compute_coax_radial_electric(COAX_R1, COAX_R2, XGRID, YGRID, not is_x) - # H field is perpendicular and oriented for positive z propagation - # We want to compute Hx which is -Ey/eta0, Hy which is Ex/eta0 - if is_x: - field /= -ETA_0 - else: - field /= ETA_0 - - return td.ScalarFieldDataArray(field, coords={"x": XS, "y": YS, "z": ZS, "f": FS}) - - -def make_field_data(): - return FieldData( - monitor=FIELD_MONITOR, - Ex=make_stripline_scalar_field_data_array("Ex"), - Ey=make_stripline_scalar_field_data_array("Ey"), - Hx=make_stripline_scalar_field_data_array("Hx"), - Hy=make_stripline_scalar_field_data_array("Hy"), - symmetry=SIM_Z.symmetry, - symmetry_center=SIM_Z.center, - grid_expanded=SIM_Z.discretize_monitor(FIELD_MONITOR), - ) - - -def make_coax_field_data(): - return FieldData( - monitor=FIELD_MONITOR, - Ex=make_coaxial_field_data_array("Ex"), - Ey=make_coaxial_field_data_array("Ey"), - Hx=make_coaxial_field_data_array("Hx"), - Hy=make_coaxial_field_data_array("Hy"), - symmetry=SIM_Z.symmetry, - symmetry_center=SIM_Z.center, - grid_expanded=SIM_Z.discretize_monitor(FIELD_MONITOR), - ) - - -@pytest.mark.parametrize("axis", [0, 1, 2]) -def test_voltage_integral_axes(axis): - """Check VoltageIntegralAxisAligned runs.""" - length = 0.5 - size = [0, 0, 0] - size[axis] = length - center = [0, 0, 0] - voltage_integral = mw.VoltageIntegralAxisAligned( - center=center, - size=size, - sign="+", - ) - - _ = voltage_integral.compute_voltage(SIM_Z_DATA["field"]) - - -@pytest.mark.parametrize("axis", [0, 1, 2]) -def test_current_integral_axes(axis): - """Check CurrentIntegralAxisAligned runs.""" - length = 0.5 - size = [length, length, length] - size[axis] = 0.0 - center = [0, 0, 0] - current_integral = mw.CurrentIntegralAxisAligned( - center=center, - size=size, - sign="+", - ) - _ = current_integral.compute_current(SIM_Z_DATA["field"]) - - -def test_voltage_integral_toggles(): - """Check VoltageIntegralAxisAligned runs with toggles.""" - length = 0.5 - size = [0, 0, 0] - size[0] = length - center = [0, 0, 0] - voltage_integral = mw.VoltageIntegralAxisAligned( - center=center, - size=size, - extrapolate_to_endpoints=True, - snap_path_to_grid=True, - sign="-", - ) - _ = voltage_integral.compute_voltage(SIM_Z_DATA["field"]) - - -def test_current_integral_toggles(): - """Check CurrentIntegralAxisAligned runs with toggles.""" - length = 0.5 - size = [length, length, length] - size[0] = 0.0 - center = [0, 0, 0] - current_integral = mw.CurrentIntegralAxisAligned( - center=center, - size=size, - extrapolate_to_endpoints=True, - snap_contour_to_grid=True, - sign="-", - ) - _ = current_integral.compute_current(SIM_Z_DATA["field"]) - - -def test_voltage_missing_fields(): - """Check validation of VoltageIntegralAxisAligned with missing fields.""" - length = 0.5 - size = [0, 0, 0] - size[1] = length - center = [0, 0, 0] - voltage_integral = mw.VoltageIntegralAxisAligned( - center=center, - size=size, - sign="+", - ) - - with pytest.raises(DataError): - _ = voltage_integral.compute_voltage(SIM_Z_DATA["ExHx"]) - - -def test_current_missing_fields(): - """Check validation of CurrentIntegralAxisAligned with missing fields.""" - length = 0.5 - size = [length, length, length] - size[0] = 0.0 - center = [0, 0, 0] - current_integral = mw.CurrentIntegralAxisAligned( - center=center, - size=size, - sign="+", - ) - with pytest.raises(DataError): - _ = current_integral.compute_current(SIM_Z_DATA["ExHx"]) - - current_integral = mw.CurrentIntegralAxisAligned( - center=center, - size=[length, length, 0], - sign="+", - ) - - with pytest.raises(DataError): - _ = current_integral.compute_current(SIM_Z_DATA["ExHx"]) - - -def test_time_monitor_voltage_integral(): - """Check VoltageIntegralAxisAligned runs on time domain data.""" - length = 0.5 - size = [0, 0, 0] - size[1] = length - center = [0, 0, 0] - voltage_integral = mw.VoltageIntegralAxisAligned( - center=center, - size=size, - sign="+", - ) - - voltage_integral.compute_voltage(SIM_Z_DATA["field_time"]) - - -def test_mode_solver_monitor_voltage_integral(): - """Check VoltageIntegralAxisAligned runs on ModeData and ModeSolverData.""" - length = 0.5 - size = [0, 0, 0] - size[1] = length - center = [0, 0, 0] - voltage_integral = mw.VoltageIntegralAxisAligned( - center=center, - size=size, - sign="+", - ) - - voltage_integral.compute_voltage(SIM_Z_DATA["mode_solver"]) - voltage_integral.compute_voltage(SIM_Z_DATA["mode"]) - - -def test_tiny_voltage_path(): - """Check VoltageIntegralAxisAligned runs when given a very short path.""" - length = 0.02 - size = [0, 0, 0] - size[1] = length - center = [0, 0, 0] - voltage_integral = mw.VoltageIntegralAxisAligned( - center=center, size=size, sign="+", extrapolate_to_endpoints=True - ) - - _ = voltage_integral.compute_voltage(SIM_Z_DATA["field"]) - - -def test_impedance_calculator(): - """Check validation of ImpedanceCalculator when integrals are missing.""" - with pytest.raises(pd.ValidationError): - _ = mw.ImpedanceCalculator(voltage_integral=None, current_integral=None) - - -def test_impedance_calculator_on_time_data(): - """Check ImpedanceCalculator runs on time domain data.""" - # Setup path integrals - length = 0.5 - size = [0, length, 0] - size[1] = length - center = [0, 0, 0] - voltage_integral = mw.VoltageIntegralAxisAligned( - center=center, size=size, sign="+", extrapolate_to_endpoints=True - ) - - size = [length, length, 0] - current_integral = mw.CurrentIntegralAxisAligned(center=center, size=size, sign="+") - - # Compute impedance using the tool - Z_calc = mw.ImpedanceCalculator( - voltage_integral=voltage_integral, current_integral=current_integral - ) - _ = Z_calc.compute_impedance(SIM_Z_DATA["field_time"]) - Z_calc = mw.ImpedanceCalculator(voltage_integral=voltage_integral, current_integral=None) - _ = Z_calc.compute_impedance(SIM_Z_DATA["field_time"]) - Z_calc = mw.ImpedanceCalculator(voltage_integral=None, current_integral=current_integral) - _ = Z_calc.compute_impedance(SIM_Z_DATA["field_time"]) - - -def test_impedance_accuracy(): - """Test the accuracy of the ImpedanceCalculator.""" - field_data = make_field_data() - # Setup path integrals - size = [0, STRIP_HEIGHT / 2, 0] - center = [0, -STRIP_HEIGHT / 4, 0] - voltage_integral = mw.VoltageIntegralAxisAligned( - center=center, size=size, sign="+", extrapolate_to_endpoints=True - ) - - size = [STRIP_WIDTH * 1.25, STRIP_HEIGHT / 2, 0] - center = [0, 0, 0] - current_integral = mw.CurrentIntegralAxisAligned(center=center, size=size, sign="+") - - def impedance_of_stripline(width, height): - # Assuming no fringing fields, is the same as a parallel plate - # with half the height and carrying twice the current - Z0_parallel_plate = 0.5 * height / width * td.ETA_0 - return Z0_parallel_plate / 2 - - analytic_impedance = impedance_of_stripline(STRIP_WIDTH, STRIP_HEIGHT) - - # Compute impedance using the tool - Z_calc = mw.ImpedanceCalculator( - voltage_integral=voltage_integral, current_integral=current_integral - ) - Z1 = Z_calc.compute_impedance(field_data) - Z_calc = mw.ImpedanceCalculator(voltage_integral=voltage_integral, current_integral=None) - Z2 = Z_calc.compute_impedance(field_data) - Z_calc = mw.ImpedanceCalculator(voltage_integral=None, current_integral=current_integral) - Z3 = Z_calc.compute_impedance(field_data) +MAKE_PLOTS = False +if MAKE_PLOTS: + # Interative plotting for debugging + from matplotlib import use - # Computation that uses the flux is less accurate, due to staircasing the field - assert np.all(np.isclose(Z1, analytic_impedance, rtol=0.02)) - assert np.all(np.isclose(Z2, analytic_impedance, atol=3.5)) - assert np.all(np.isclose(Z3, analytic_impedance, atol=3.5)) + use("TkAgg") def test_microstrip_models(): @@ -447,212 +78,6 @@ def test_coupled_microstrip_model(): assert np.isclose(eps_odd, 2.80, rtol=0.01) -def test_frequency_monitor_custom_voltage_integral(): - length = 0.5 - size = [0, 0, 0] - size[1] = length - # Make line - vertices = [(0, 0), (0, 0.2), (0, 0.4)] - voltage_integral = mw.CustomVoltageIntegral2D(axis=2, position=0, vertices=vertices) - voltage_integral.compute_voltage(SIM_Z_DATA["field"]) - - -def test_vertices_validator_custom_current_integral(): - length = 0.5 - size = [0, 0, 0] - size[1] = length - # Make wrong box - vertices = [(0.2, -0.2, 0.5), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)] - - with pytest.raises(pd.ValidationError): - _ = mw.CustomCurrentIntegral2D(axis=2, position=0, vertices=vertices) - - # Make wrong box shape - vertices = [(0.2, 0.2, -0.2, -0.2, 0.2), (-0.2, 0.2, 0.2, -0.2, 0.2)] - with pytest.raises(pd.ValidationError): - _ = mw.CustomCurrentIntegral2D(axis=2, position=0, vertices=vertices) - - -def test_fields_missing_custom_current_integral(): - length = 0.5 - size = [0, 0, 0] - size[1] = length - # Make box - vertices = [(0.2, -0.2), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)] - current_integral = mw.CustomCurrentIntegral2D(axis=2, position=0, vertices=vertices) - with pytest.raises(DataError): - current_integral.compute_current(SIM_Z_DATA["ExHx"]) - - -def test_wrong_monitor_data(): - """Check that the function arguments to integrals are correctly validated.""" - length = 0.5 - size = [0, 0, 0] - size[1] = length - # Make box - vertices = [(0.2, -0.2), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)] - current_integral = mw.CustomCurrentIntegral2D(axis=2, position=0, vertices=vertices) - with pytest.raises(DataError): - current_integral.compute_current(SIM_Z.sources[0].source_time) - with pytest.raises(DataError): - current_integral.compute_current(em_field=SIM_Z.sources[0].source_time) - - -def test_time_monitor_custom_current_integral(): - length = 0.5 - size = [0, 0, 0] - size[1] = length - # Make box - vertices = [(0.2, -0.2), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)] - current_integral = mw.CustomCurrentIntegral2D(axis=2, position=0, vertices=vertices) - current_integral.compute_current(SIM_Z_DATA["field_time"]) - - -def test_mode_solver_custom_current_integral(): - """Test that both ModeData and ModeSolverData are allowed types.""" - length = 0.5 - size = [0, 0, 0] - size[1] = length - # Make box - vertices = [(0.2, -0.2), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)] - current_integral = mw.CustomCurrentIntegral2D(axis=2, position=0, vertices=vertices) - current_integral.compute_current(SIM_Z_DATA["mode_solver"]) - current_integral.compute_current(SIM_Z_DATA["mode"]) - - -def test_custom_current_integral_normal_y(): - current_integral = mw.CustomCurrentIntegral2D.from_circular_path( - center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=1, clockwise=False - ) - current_integral.compute_current(SIM_Z_DATA["field"]) - - -def test_custom_path_integral_accuracy(): - """Test the accuracy of the custom path integral.""" - field_data = make_coax_field_data() - - current_integral = mw.CustomCurrentIntegral2D.from_circular_path( - center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=2, clockwise=False - ) - current = current_integral.compute_current(field_data) - assert np.allclose(current.values, 1.0 / ETA_0, rtol=0.01) - - current_integral = mw.CustomCurrentIntegral2D.from_circular_path( - center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=2, clockwise=True - ) - current = current_integral.compute_current(field_data) - assert np.allclose(current.values, -1.0 / ETA_0, rtol=0.01) - - -def test_impedance_accuracy_on_coaxial(): - """Test the accuracy of the ImpedanceCalculator.""" - field_data = make_coax_field_data() - # Setup path integrals - mean_radius = (COAX_R2 + COAX_R1) * 0.5 - size = [COAX_R2 - COAX_R1, 0, 0] - center = [mean_radius, 0, 0] - - voltage_integral = mw.VoltageIntegralAxisAligned( - center=center, size=size, sign="-", extrapolate_to_endpoints=True, snap_path_to_grid=True - ) - current_integral = mw.CustomCurrentIntegral2D.from_circular_path( - center=(0, 0, 0), radius=mean_radius, num_points=31, normal_axis=2, clockwise=False - ) - - def impedance_of_coaxial_cable(r1, r2, wave_impedance=td.ETA_0): - return np.log(r2 / r1) * wave_impedance / 2 / np.pi - - Z_analytic = impedance_of_coaxial_cable(COAX_R1, COAX_R2) - - # Compute impedance using the tool - Z_calculator = mw.ImpedanceCalculator( - voltage_integral=voltage_integral, current_integral=current_integral - ) - Z_calc = Z_calculator.compute_impedance(field_data) - assert np.allclose(Z_calc, Z_analytic, rtol=0.04) - - -def test_path_integral_plotting(): - """Test that all types of path integrals correctly plot themselves.""" - - mean_radius = (COAX_R2 + COAX_R1) * 0.5 - size = [COAX_R2 - COAX_R1, 0, 0] - center = [mean_radius, 0, 0] - - voltage_integral = mw.VoltageIntegralAxisAligned( - center=center, size=size, sign="-", extrapolate_to_endpoints=True, snap_path_to_grid=True - ) - - current_integral = mw.CustomCurrentIntegral2D.from_circular_path( - center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=2, clockwise=False - ) - - ax = voltage_integral.plot(z=0) - current_integral.plot(z=0, ax=ax) - plt.close() - - # Test off center plotting - ax = voltage_integral.plot(z=2) - current_integral.plot(z=2, ax=ax) - plt.close() - - # Plot - voltage_integral = mw.CustomVoltageIntegral2D( - axis=1, position=0, vertices=[(-1, -1), (0, 0), (1, 1)] - ) - - current_integral = mw.CurrentIntegralAxisAligned( - center=(0, 0, 0), - size=(2, 0, 1), - sign="-", - extrapolate_to_endpoints=False, - snap_contour_to_grid=False, - ) - - ax = voltage_integral.plot(y=0) - current_integral.plot(y=0, ax=ax) - plt.close() - - # Test off center plotting - ax = voltage_integral.plot(y=2) - current_integral.plot(y=2, ax=ax) - plt.close() - - -def test_creation_from_terminal_positions(): - """Test creating an VoltageIntegralAxisAligned using terminal positions.""" - _ = mw.VoltageIntegralAxisAligned.from_terminal_positions( - plus_terminal=2, minus_terminal=1, y=2.2, z=1 - ) - - -def test_auto_path_integrals_for_lumped_element(): - """Test the auto creation of path integrals around lumped elements.""" - Rval = 75 - Cval = 0.2 * 1e-12 - fstart = 1e9 - fstop = 30e9 - freqs = np.linspace(fstart, fstop, 100) - - RLC = td.RLCNetwork( - resistance=Rval, - capacitance=Cval, - network_topology="series", - ) - - linear_element = td.LinearLumpedElement( - center=[0, 0, 0], - size=[0.2, 0, 0.5], - voltage_axis=2, - network=RLC, - name="RLC", - ) - - SIM_Z_with_element = SIM_Z.updated_copy(lumped_elements=[linear_element]) - - _, _ = mw.path_integrals_from_lumped_element(linear_element, SIM_Z_with_element.grid, "+") - - def test_lobe_measurer_validation(): """ "Make sure the lobe measurer validates inputs correctly.""" theta = np.linspace(0, 2 * np.pi, 101, endpoint=False) @@ -785,12 +210,11 @@ def test_lobe_measurements(apply_cyclic_extension, include_endpoint): @pytest.mark.parametrize("min_value", [0.0, 1.0]) def test_lobe_plots(min_value): """Run the lobe measurer on some test data and plot the results.""" - # Interative plotting for debugging - # from matplotlib import use - # use("TkAgg") theta = np.linspace(0, 2 * np.pi, 301) Urad = np.cos(theta) ** 2 * np.cos(3 * theta) ** 2 + min_value lobe_measurer = mw.LobeMeasurer(angle=theta, radiation_pattern=Urad) _, ax = plt.subplots(1, 1, subplot_kw={"projection": "polar"}) ax.plot(theta, Urad, "k") lobe_measurer.plot(0, ax) + if MAKE_PLOTS: + plt.show() diff --git a/tidy3d/__init__.py b/tidy3d/__init__.py index 8741cec4fc..8472e89793 100644 --- a/tidy3d/__init__.py +++ b/tidy3d/__init__.py @@ -19,6 +19,41 @@ ) from tidy3d.components.microwave.data.monitor_data import ( AntennaMetricsData, + MicrowaveModeData, + MicrowaveModeSolverData, +) +from tidy3d.components.microwave.impedance_calculator import ImpedanceCalculator +from tidy3d.components.microwave.mode_spec import ( + MicrowaveModeSpec, +) +from tidy3d.components.microwave.monitor import ( + MicrowaveModeMonitor, + MicrowaveModeSolverMonitor, +) +from tidy3d.components.microwave.path_integrals.integrals.auto import ( + path_integrals_from_lumped_element, +) +from tidy3d.components.microwave.path_integrals.integrals.current import ( + AxisAlignedCurrentIntegral, + CompositeCurrentIntegral, + Custom2DCurrentIntegral, +) +from tidy3d.components.microwave.path_integrals.integrals.voltage import ( + AxisAlignedVoltageIntegral, + Custom2DVoltageIntegral, +) +from tidy3d.components.microwave.path_integrals.specs.current import ( + AxisAlignedCurrentIntegralSpec, + CompositeCurrentIntegralSpec, + Custom2DCurrentIntegralSpec, +) +from tidy3d.components.microwave.path_integrals.specs.impedance import ( + AutoImpedanceSpec, + CustomImpedanceSpec, +) +from tidy3d.components.microwave.path_integrals.specs.voltage import ( + AxisAlignedVoltageIntegralSpec, + Custom2DVoltageIntegralSpec, ) from tidy3d.components.spice.analysis.dc import ( ChargeToleranceSpec, @@ -456,8 +491,13 @@ def set_logging_level(level: str) -> None: "AstigmaticGaussianBeamProfile", "AugerRecombination", "AutoGrid", + "AutoImpedanceSpec", "AuxFieldTimeData", "AuxFieldTimeMonitor", + "AxisAlignedCurrentIntegral", + "AxisAlignedCurrentIntegralSpec", + "AxisAlignedVoltageIntegral", + "AxisAlignedVoltageIntegralSpec", "BlochBoundary", "Boundary", "BoundaryEdge", @@ -474,6 +514,8 @@ def set_logging_level(level: str) -> None: "ChargeToleranceSpec", "ClipOperation", "CoaxialLumpedResistor", + "CompositeCurrentIntegral", + "CompositeCurrentIntegralSpec", "ConstantDoping", "ConstantEffectiveDOS", "ConstantEnergyBandGap", @@ -486,6 +528,10 @@ def set_logging_level(level: str) -> None: "Coords1D", "CornerFinderSpec", "CurrentBC", + "Custom2DCurrentIntegral", + "Custom2DCurrentIntegralSpec", + "Custom2DVoltageIntegral", + "Custom2DVoltageIntegralSpec", "CustomAnisotropicMedium", "CustomChargePerturbation", "CustomCurrentSource", @@ -496,6 +542,7 @@ def set_logging_level(level: str) -> None: "CustomGrid", "CustomGridBoundaries", "CustomHeatPerturbation", + "CustomImpedanceSpec", "CustomLorentz", "CustomMedium", "CustomPoleResidue", @@ -603,6 +650,7 @@ def set_logging_level(level: str) -> None: "HeatSource", "HeuristicPECStaircasing", "HuraySurfaceRoughness", + "ImpedanceCalculator", "IndexPerturbation", "IndexedDataArray", "IndexedFieldVoltageDataArray", @@ -628,6 +676,11 @@ def set_logging_level(level: str) -> None: "MediumMediumInterface", "MediumMonitor", "MeshOverrideStructure", + "MicrowaveModeData", + "MicrowaveModeMonitor", + "MicrowaveModeSolverData", + "MicrowaveModeSolverMonitor", + "MicrowaveModeSpec", "ModeABCBoundary", "ModeAmpsDataArray", "ModeData", @@ -757,6 +810,7 @@ def set_logging_level(level: str) -> None: "log", "material_library", "medium_from_nk", + "path_integrals_from_lumped_element", "restore_matplotlib_rcparams", "set_logging_console", "set_logging_file", diff --git a/tidy3d/components/boundary.py b/tidy3d/components/boundary.py index 9a548995fc..974f3ba8ec 100644 --- a/tidy3d/components/boundary.py +++ b/tidy3d/components/boundary.py @@ -26,6 +26,7 @@ from .monitor import ModeMonitor, ModeSolverMonitor from .source.field import TFSF, GaussianBeam, ModeSource, PlaneWave from .types import TYPE_TAG_STR, Ax, Axis, Complex, Direction, FreqBound +from .types.mode_spec import ModeSpecType MIN_NUM_PML_LAYERS = 6 MIN_NUM_STABLE_PML_LAYERS = 6 @@ -246,10 +247,11 @@ def _frequency_grid(self) -> np.ndarray: class ModeABCBoundary(AbstractABCBoundary): """One-way wave equation absorbing boundary conditions for absorbing a waveguide mode.""" - mode_spec: ModeSpec = pd.Field( + mode_spec: ModeSpecType = pd.Field( DEFAULT_MODE_SPEC_MODE_ABC, title="Mode Specification", description="Parameters that determine the modes computed by the mode solver.", + discriminator=TYPE_TAG_STR, ) mode_index: pd.NonNegativeInt = pd.Field( @@ -1117,7 +1119,7 @@ def abc( def mode_abc( cls, plane: Box, - mode_spec: ModeSpec = DEFAULT_MODE_SPEC_MODE_ABC, + mode_spec: ModeSpecType = DEFAULT_MODE_SPEC_MODE_ABC, mode_index: pd.NonNegativeInt = 0, freq_spec: Optional[Union[pd.PositiveFloat, BroadbandModeABCSpec]] = None, ): @@ -1127,7 +1129,7 @@ def mode_abc( ---------- plane: Box Cross-sectional plane in which the absorbed mode will be computed. - mode_spec: ModeSpec = ModeSpec() + mode_spec: ModeSpecType = ModeSpec() Parameters that determine the modes computed by the mode solver. mode_index : pd.NonNegativeInt = 0 Mode index. diff --git a/tidy3d/components/data/data_array.py b/tidy3d/components/data/data_array.py index 1cbae1d8e4..b7f8d0d9a8 100644 --- a/tidy3d/components/data/data_array.py +++ b/tidy3d/components/data/data_array.py @@ -208,6 +208,12 @@ def abs(self): """Absolute value of data array.""" return abs(self) + @property + def angle(self): + """Angle or phase value of data array.""" + values = np.angle(self.values) + return type(self)(values, coords=self.coords) + @property def is_uniform(self): """Whether each element is of equal value in the data array""" @@ -1500,7 +1506,7 @@ class ImpedanceFreqModeDataArray(ImpedanceArray, FreqModeDataArray): __slots__ = () -def _make_base_result_data_array(result: DataArray) -> IntegralResultTypes: +def _make_base_result_data_array(result: DataArray) -> IntegralResultType: """Helper for creating the proper base result type.""" cls = FreqDataArray if "t" in result.coords: @@ -1510,7 +1516,7 @@ def _make_base_result_data_array(result: DataArray) -> IntegralResultTypes: return cls.assign_data_attrs(cls(data=result.data, coords=result.coords)) -def _make_voltage_data_array(result: DataArray) -> VoltageIntegralResultTypes: +def _make_voltage_data_array(result: DataArray) -> VoltageIntegralResultType: """Helper for creating the proper voltage array type.""" cls = VoltageFreqDataArray if "t" in result.coords: @@ -1520,7 +1526,7 @@ def _make_voltage_data_array(result: DataArray) -> VoltageIntegralResultTypes: return cls.assign_data_attrs(cls(data=result.data, coords=result.coords)) -def _make_current_data_array(result: DataArray) -> CurrentIntegralResultTypes: +def _make_current_data_array(result: DataArray) -> CurrentIntegralResultType: """Helper for creating the proper current array type.""" cls = CurrentFreqDataArray if "t" in result.coords: @@ -1530,7 +1536,7 @@ def _make_current_data_array(result: DataArray) -> CurrentIntegralResultTypes: return cls.assign_data_attrs(cls(data=result.data, coords=result.coords)) -def _make_impedance_data_array(result: DataArray) -> ImpedanceResultTypes: +def _make_impedance_data_array(result: DataArray) -> ImpedanceResultType: """Helper for creating the proper impedance array type.""" cls = ImpedanceFreqDataArray if "t" in result.coords: @@ -1597,13 +1603,13 @@ def _make_impedance_data_array(result: DataArray) -> ImpedanceResultTypes: PointDataArray, ] -IntegralResultTypes = Union[FreqDataArray, FreqModeDataArray, TimeDataArray] -VoltageIntegralResultTypes = Union[ +IntegralResultType = Union[FreqDataArray, FreqModeDataArray, TimeDataArray] +VoltageIntegralResultType = Union[ VoltageFreqDataArray, VoltageFreqModeDataArray, VoltageTimeDataArray ] -CurrentIntegralResultTypes = Union[ +CurrentIntegralResultType = Union[ CurrentFreqDataArray, CurrentFreqModeDataArray, CurrentTimeDataArray ] -ImpedanceResultTypes = Union[ +ImpedanceResultType = Union[ ImpedanceFreqDataArray, ImpedanceFreqModeDataArray, ImpedanceTimeDataArray ] diff --git a/tidy3d/components/data/monitor_data.py b/tidy3d/components/data/monitor_data.py index 9e0a967334..ccb31ca278 100644 --- a/tidy3d/components/data/monitor_data.py +++ b/tidy3d/components/data/monitor_data.py @@ -33,7 +33,6 @@ MediumMonitor, ModeMonitor, ModeSolverMonitor, - MonitorType, PermittivityMonitor, ) from tidy3d.components.source.base import Source @@ -54,6 +53,7 @@ TrackFreq, UnitsZBF, ) +from tidy3d.components.types.monitor import MonitorType from tidy3d.components.validators import ( enforce_monitor_fields_present, required_if_symmetry_present, @@ -1645,7 +1645,7 @@ class ModeData(ModeSolverDataset, ElectromagneticFieldData): eps_spec: list[EpsSpecType] = pd.Field( None, - title="Permettivity Specification", + title="Permittivity Specification", description="Characterization of the permittivity profile on the plane where modes are " "computed. Possible values are 'diagonal', 'tensorial_real', 'tensorial_complex'.", ) @@ -3544,9 +3544,9 @@ class DirectivityData(FieldProjectionAngleData): >>> values = (1+1j) * np.random.random((len(r), len(theta), len(phi), len(f))) >>> flux_data = FluxDataArray(np.random.random(len(f)), coords=coords_flux) >>> scalar_field = FieldProjectionAngleDataArray(values, coords=coords) - >>> monitor = DirectivityMonitor(center=(1,2,3), size=(2,2,2), freqs=f, name='n2f_monitor', phi=phi, theta=theta) # doctest: +SKIP + >>> monitor = DirectivityMonitor(center=(1,2,3), size=(2,2,2), freqs=f, name='n2f_monitor', phi=phi, theta=theta) >>> data = DirectivityData(monitor=monitor, flux=flux_data, Er=scalar_field, Etheta=scalar_field, Ephi=scalar_field, - ... Hr=scalar_field, Htheta=scalar_field, Hphi=scalar_field, projection_surfaces=monitor.projection_surfaces) # doctest: +SKIP + ... Hr=scalar_field, Htheta=scalar_field, Hphi=scalar_field, projection_surfaces=monitor.projection_surfaces) """ monitor: DirectivityMonitor = pd.Field( @@ -3942,23 +3942,3 @@ def fields_circular_polarization(self) -> xr.Dataset: keys = ("Eleft", "Eright", "Hleft", "Hright") data_arrays = (Eleft, Eright, Hleft, Hright) return xr.Dataset(dict(zip(keys, data_arrays))) - - -MonitorDataTypes = ( - FieldData, - FieldTimeData, - PermittivityData, - MediumData, - ModeSolverData, - ModeData, - FluxData, - FluxTimeData, - AuxFieldTimeData, - FieldProjectionKSpaceData, - FieldProjectionCartesianData, - FieldProjectionAngleData, - DiffractionData, - DirectivityData, -) - -MonitorDataType = Union[MonitorDataTypes] diff --git a/tidy3d/components/data/sim_data.py b/tidy3d/components/data/sim_data.py index 3fdc9954d3..9c81bdbfae 100644 --- a/tidy3d/components/data/sim_data.py +++ b/tidy3d/components/data/sim_data.py @@ -25,12 +25,13 @@ from tidy3d.components.source.utils import SourceType from tidy3d.components.structure import Structure from tidy3d.components.types import Ax, Axis, ColormapType, FieldVal, PlotScale, annotate_type +from tidy3d.components.types.monitor_data import MonitorDataType, MonitorDataTypes from tidy3d.components.viz import add_ax_if_none, equal_aspect from tidy3d.exceptions import DataError, FileError, SetupError, Tidy3dKeyError from tidy3d.log import log from .data_array import FreqDataArray, TimeDataArray -from .monitor_data import AbstractFieldData, FieldTimeData, MonitorDataType, MonitorDataTypes +from .monitor_data import AbstractFieldData, FieldTimeData DATA_TYPE_MAP = {data.__fields__["monitor"].type_: data for data in MonitorDataTypes} diff --git a/tidy3d/components/eme/simulation.py b/tidy3d/components/eme/simulation.py index cf04801252..b01b928e4f 100644 --- a/tidy3d/components/eme/simulation.py +++ b/tidy3d/components/eme/simulation.py @@ -17,7 +17,7 @@ from tidy3d.components.grid.grid import Grid from tidy3d.components.grid.grid_spec import GridSpec from tidy3d.components.medium import FullyAnisotropicMedium -from tidy3d.components.monitor import AbstractModeMonitor, ModeSolverMonitor, Monitor, MonitorType +from tidy3d.components.monitor import AbstractModeMonitor, ModeSolverMonitor, Monitor from tidy3d.components.scene import Scene from tidy3d.components.simulation import ( AbstractYeeGridSimulation, @@ -25,6 +25,7 @@ validate_boundaries_for_zero_dims, ) from tidy3d.components.types import Ax, Axis, FreqArray, Symmetry, annotate_type +from tidy3d.components.types.monitor import MonitorType from tidy3d.components.validators import MIN_FREQUENCY, validate_freqs_min, validate_freqs_not_empty from tidy3d.components.viz import add_ax_if_none, equal_aspect from tidy3d.constants import C_0, inf diff --git a/tidy3d/components/geometry/utils.py b/tidy3d/components/geometry/utils.py index fa46911297..86b46829a6 100644 --- a/tidy3d/components/geometry/utils.py +++ b/tidy3d/components/geometry/utils.py @@ -3,13 +3,20 @@ from __future__ import annotations from collections import defaultdict +from collections.abc import Iterable from enum import Enum from math import isclose from typing import Any, Optional, Union import numpy as np -import pydantic +import pydantic.v1 as pydantic import shapely +from shapely.geometry import ( + Polygon, +) +from shapely.geometry.base import ( + BaseMultipartGeometry, +) from tidy3d.components.autograd.utils import get_static from tidy3d.components.base import Tidy3dBaseModel @@ -43,6 +50,44 @@ ] +def flatten_shapely_geometries( + geoms: Union[Shapely, Iterable[Shapely]], keep_types: tuple[type, ...] = (Polygon,) +) -> list[Shapely]: + """ + Flatten nested geometries into a flat list, while only keeping the specified types. + + Recursively extracts and returns non-empty geometries of the given types from input geometries, + expanding any GeometryCollections or Multi* types. + + Parameters + ---------- + geoms : Union[Shapely, Iterable[Shapely]] + Input geometries to flatten. + + keep_types : tuple[type, ...] + Geometry types to keep (e.g., (Polygon, LineString)). Default is + (Polygon). + + Returns + ------- + list[Shapely] + Flat list of non-empty geometries matching the specified types. + """ + # Handle single Shapely object by wrapping it in a list + if isinstance(geoms, Shapely): + geoms = [geoms] + + flat = [] + for geom in geoms: + if geom.is_empty: + continue + if isinstance(geom, keep_types): + flat.append(geom) + elif isinstance(geom, BaseMultipartGeometry): + flat.extend(flatten_shapely_geometries(geom.geoms, keep_types)) + return flat + + def merging_geometries_on_plane( geometries: list[GeometryType], plane: Box, @@ -65,7 +110,7 @@ def merging_geometries_on_plane( Returns ------- - List[Tuple[Any, shapely]] + List[Tuple[Any, Shapely]] List of shapes and their property value on the plane after merging. """ @@ -375,7 +420,15 @@ class SnapBehavior(Enum): Snaps the interval's endpoints to the closest grid points, while guaranteeing that the snapping location will never move endpoints outwards. """ - Off = 4 + StrictExpand = 4 + """ + Same as Expand, but will always move endpoints outwards, even if already coincident with grid. + """ + StrictContract = 5 + """ + Same as Contract, but will always move endpoints inwards, even if already coincident with grid. + """ + Off = 6 """ Do not use snapping. """ @@ -396,6 +449,15 @@ class SnappingSpec(Tidy3dBaseModel): description="Describes how snapping positions will be chosen.", ) + margin: Optional[ + tuple[pydantic.NonNegativeInt, pydantic.NonNegativeInt, pydantic.NonNegativeInt] + ] = pydantic.Field( + (0, 0, 0), + title="Margin", + description="Number of additional grid points to consider when expanding or contracting " + "during snapping. Only applies when ``SnapBehavior`` is ``Expand`` or ``Contract``.", + ) + def get_closest_value(test: float, coords: np.ArrayLike, upper_bound_idx: int) -> float: """Helper to choose the closest value in an array to a given test value, @@ -420,34 +482,130 @@ def snap_box_to_grid(grid: Grid, box: Box, snap_spec: SnappingSpec, rtol=fp_eps) The way in which each dimension of the `box` is snapped to the grid is controlled by ``snap_spec``. """ + def _clamp_index(idx: int, length: int) -> int: + return max(0, min(idx, length - 1)) + def get_lower_bound( - test: float, coords: np.ArrayLike, upper_bound_idx: int, rel_tol: float + test: float, + coords: np.ArrayLike, + upper_bound_idx: int, + rel_tol: float, + strict_bounds: bool, + margin: int = 0, ) -> float: - """Helper to choose the lower bound in an array for a given test value, - using the index of the upper bound. If the test value is close to the upper - bound, it assumes they are equal, and in that case the upper bound is returned. + """Choose the lower bound from coords for snapping a test value downward. + + Returns a coordinate value from ``coords`` that satisfies ``result <= test`` when + ``strict_bounds=False``, or ``result < test`` when ``strict_bounds=True``. The + ``rel_tol`` parameter is used to determine floating-point equality: if ``test`` is + close to a grid point (within ``rel_tol``). + + Parameters + ---------- + test : float + The value to snap. + coords : np.ArrayLike + Sorted array of coordinate values to snap to. + upper_bound_idx : int + Index from ``np.searchsorted(coords, test, side="left")`` - the first index where + ``coords[upper_bound_idx] >= test``. + rel_tol : float + Relative tolerance for floating-point equality comparison. + strict_bounds : bool + If ``False``: Return value satisfies ``result <= test`` (using ``rel_tol`` for equality). + If ``True``: Return value satisfies ``result < test`` (using ``rel_tol`` for equality). + margin : int, optional + Additional offset in grid cells (can be negative). Applied after determining the + snap index. Default is 0. + + Returns + ------- + float + The selected coordinate value from ``coords``. """ - if upper_bound_idx == len(coords): - return coords[upper_bound_idx - 1] - if upper_bound_idx == 0 or isclose(coords[upper_bound_idx], test, rel_tol=rel_tol): - return coords[upper_bound_idx] - return coords[upper_bound_idx - 1] + snap_idx = upper_bound_idx - 1 + if ( + not strict_bounds + and upper_bound_idx != len(coords) + and isclose(coords[upper_bound_idx], test, rel_tol=rel_tol) + ): + snap_idx = upper_bound_idx + elif ( + strict_bounds + and upper_bound_idx >= 2 + and isclose(coords[upper_bound_idx - 1], test, rel_tol=rel_tol) + ): + snap_idx = upper_bound_idx - 2 + + # Apply margin and clamp + snap_idx += margin + snap_idx = _clamp_index(snap_idx, len(coords)) + return coords[snap_idx] def get_upper_bound( - test: float, coords: np.ArrayLike, upper_bound_idx: int, rel_tol: float + test: float, + coords: np.ArrayLike, + upper_bound_idx: int, + rel_tol: float, + strict_bounds: bool, + margin: int = 0, ) -> float: - """Helper to choose the upper bound in an array for a given test value, - using the index of the upper bound. If the test value is close to the lower - bound, it assumes they are equal, and in that case the lower bound is returned. + """Choose the upper bound from coords for snapping a test value upward. + + Returns a coordinate value from ``coords`` that satisfies ``result >= test`` when + ``strict_bounds=False``, or ``result > test`` when ``strict_bounds=True``. The + ``rel_tol`` parameter is used to determine floating-point equality: if ``test`` is + close to a grid point (within ``rel_tol``). + + Parameters + ---------- + test : float + The value to snap. + coords : np.ArrayLike + Sorted array of coordinate values to snap to. + upper_bound_idx : int + Index from ``np.searchsorted(coords, test, side="left")`` - the first index where + ``coords[upper_bound_idx] >= test``. + rel_tol : float + Relative tolerance for floating-point equality comparison. + strict_bounds : bool + If ``False``: Return value satisfies ``result >= test`` (using ``rel_tol`` for equality). + If ``True``: Return value satisfies ``result > test`` (using ``rel_tol`` for equality). + margin : int, optional + Additional offset in grid cells (can be negative). Applied after determining the + snap index. Default is 0. + + Returns + ------- + float + The selected coordinate value from ``coords``. """ - if upper_bound_idx == len(coords): - return coords[upper_bound_idx - 1] - if upper_bound_idx > 0 and isclose(coords[upper_bound_idx - 1], test, rel_tol=rel_tol): - return coords[upper_bound_idx - 1] - return coords[upper_bound_idx] + snap_idx = upper_bound_idx + + if ( + not strict_bounds + and upper_bound_idx > 0 + and (isclose(coords[upper_bound_idx - 1], test, rel_tol=rel_tol)) + ): + snap_idx = upper_bound_idx - 1 + elif ( + strict_bounds + and upper_bound_idx < len(coords) + and isclose(coords[upper_bound_idx], test, rel_tol=rel_tol) + ): + snap_idx = upper_bound_idx + 1 + + # Apply margin and clamp + snap_idx += margin + snap_idx = _clamp_index(snap_idx, len(coords)) + return coords[snap_idx] def find_snapping_locations( - interval_min: float, interval_max: float, coords: np.ndarray, snap_type: SnapBehavior + interval_min: float, + interval_max: float, + coords: np.ndarray, + snap_type: SnapBehavior, + snap_margin: pydantic.NonNegativeInt, ) -> tuple[float, float]: """Helper that snaps a supplied interval [interval_min, interval_max] to a sorted array representing coordinate values. @@ -455,15 +613,48 @@ def find_snapping_locations( # Locate the interval that includes the min and max min_upper_bound_idx = np.searchsorted(coords, interval_min, side="left") max_upper_bound_idx = np.searchsorted(coords, interval_max, side="left") + strict_bounds = ( + snap_type == SnapBehavior.StrictExpand or snap_type == SnapBehavior.StrictContract + ) if snap_type == SnapBehavior.Closest: min_snap = get_closest_value(interval_min, coords, min_upper_bound_idx) max_snap = get_closest_value(interval_max, coords, max_upper_bound_idx) - elif snap_type == SnapBehavior.Expand: - min_snap = get_lower_bound(interval_min, coords, min_upper_bound_idx, rel_tol=rtol) - max_snap = get_upper_bound(interval_max, coords, max_upper_bound_idx, rel_tol=rtol) + elif snap_type == SnapBehavior.Expand or snap_type == SnapBehavior.StrictExpand: + min_snap = get_lower_bound( + interval_min, + coords, + min_upper_bound_idx, + rel_tol=rtol, + strict_bounds=strict_bounds, + margin=-snap_margin, + ) + max_snap = get_upper_bound( + interval_max, + coords, + max_upper_bound_idx, + rel_tol=rtol, + strict_bounds=strict_bounds, + margin=+snap_margin, + ) else: # SnapType.Contract - min_snap = get_upper_bound(interval_min, coords, min_upper_bound_idx, rel_tol=rtol) - max_snap = get_lower_bound(interval_max, coords, max_upper_bound_idx, rel_tol=rtol) + min_snap = get_upper_bound( + interval_min, + coords, + min_upper_bound_idx, + rel_tol=rtol, + strict_bounds=strict_bounds, + margin=+snap_margin, + ) + max_snap = get_lower_bound( + interval_max, + coords, + max_upper_bound_idx, + rel_tol=rtol, + strict_bounds=strict_bounds, + margin=-snap_margin, + ) + if max_snap < min_snap: + raise SetupError("The supplied 'snap_margin' is too large for this contraction.") return (min_snap, max_snap) # Iterate over each axis and apply the specified snapping behavior. @@ -473,6 +664,7 @@ def find_snapping_locations( for axis in range(3): snap_location = snap_spec.location[axis] snap_type = snap_spec.behavior[axis] + snap_margin = snap_spec.margin[axis] if snap_type == SnapBehavior.Off: continue if snap_location == SnapLocation.Boundary: @@ -483,7 +675,9 @@ def find_snapping_locations( box_min = min_b[axis] box_max = max_b[axis] - (new_min, new_max) = find_snapping_locations(box_min, box_max, snap_coords, snap_type) + (new_min, new_max) = find_snapping_locations( + box_min, box_max, snap_coords, snap_type, snap_margin + ) min_b[axis] = new_min max_b[axis] = new_max return Box.from_bounds(min_b, max_b) diff --git a/tidy3d/components/lumped_element.py b/tidy3d/components/lumped_element.py index 9e2d8a9ce8..bf8a71b039 100644 --- a/tidy3d/components/lumped_element.py +++ b/tidy3d/components/lumped_element.py @@ -16,9 +16,8 @@ from tidy3d.components.validators import assert_line_or_plane, assert_plane, validate_name_str from tidy3d.constants import EPSILON_0, FARAD, HENRY, MICROMETER, OHM, fp_eps from tidy3d.exceptions import ValidationError -from tidy3d.log import log -from .base import Tidy3dBaseModel, cached_property, skip_if_fields_missing +from .base import cached_property, skip_if_fields_missing from .geometry.base import Box, ClipOperation, Geometry, GeometryGroup from .geometry.primitives import Cylinder from .geometry.utils import ( @@ -29,6 +28,7 @@ snap_point_to_grid, ) from .geometry.utils_2d import increment_float +from .microwave.base import MicrowaveBaseModel from .microwave.formulas.circuit_parameters import ( capacitance_colinear_cylindrical_wire_segments, capacitance_rectangular_sheets, @@ -50,7 +50,7 @@ LOSS_FACTOR_INDUCTOR = 1e6 -class LumpedElement(Tidy3dBaseModel, ABC): +class LumpedElement(MicrowaveBaseModel, ABC): """Base class describing the interface all lumped elements obey.""" name: str = pd.Field( @@ -104,14 +104,6 @@ def to_structures(self, grid: Grid = None) -> list[Structure]: which are ready to be added to the :class:`.Simulation`""" return [self.to_structure(grid)] - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values - class RectangularLumpedElement(LumpedElement, Box): """Class representing a rectangular element with zero thickness. A :class:`RectangularLumpedElement` @@ -464,98 +456,100 @@ def geometry(self) -> ClipOperation: return self.to_geometry() -class NetworkConversions(Tidy3dBaseModel): - """Helper functionality for directly computing complex conductivity and permittivities using - equations in _`[1]`. Useful for testing the direct translations of lumped network parameters into - an equivalent PoleResidue medium. +def network_complex_conductivity( + a: tuple[float, ...], b: tuple[float, ...], freqs: np.ndarray +) -> np.ndarray: + """Returns the equivalent conductivity of the lumped network over the range of frequencies + provided in ``freqs`` using the expression in _`[1]`. - Notes - ----- + This implementation follows a similar approach as _`[1]` with a couple small differences. Instead of + scaling the complex conductivity by the size of a single grid cell, we later scale the quantities by the + size of the lumped element in the FDTD simulation. In many cases, we will assume the time step is small, + so that the complex conductivity can be expressed more simply as a rational expression. - This implementation follows a similar approach as _`[1]` with a couple small differences. Instead of - scaling the complex conductivity by the size of a single grid cell, we later scale the quantities by the - size of the lumped element in the FDTD simulation. In many cases, we will assume the time step is small, - so that the complex conductivity can be expressed more simply as a rational expression. + Parameters + ---------- + a : tuple[float, ...] + Coefficients of the numerator polynomial + b : tuple[float, ...] + Coefficients of the denominator polynomial. + freqs: np.ndarray + Frequencies at which to evaluate model. - **References** + Returns + ------- + np.ndarray + The resulting complex conductivity. - .. [1] J. A. Pereda, F. Alimenti, P. Mezzanotte, L. Roselli and R. Sorrentino, "A new algorithm - for the incorporation of arbitrary linear lumped networks into FDTD simulators," IEEE - Trans. Microw. Theory Tech., vol. 47, no. 6, pp. 943-949, Jun. 1999. - """ + Notes + ----- - @staticmethod - def complex_conductivity(a: tuple[float, ...], b: tuple[float, ...], freqs: np.ndarray): - """Returns the equivalent conductivity of the lumped network over the range of frequencies - provided in ``freqs`` using the expression in _`[1]`. - - Parameters - ---------- - a : tuple[float, ...] - Coefficients of the numerator polynomial - b : tuple[float, ...] - Coefficients of the denominator polynomial. - freqs: np.ndarray - Frequencies at which to evaluate model. + **References** - Returns - ------- - np.ndarray - The resulting complex conductivity. - """ + .. [1] J. A. Pereda, F. Alimenti, P. Mezzanotte, L. Roselli and R. Sorrentino, "A new algorithm + for the incorporation of arbitrary linear lumped networks into FDTD simulators," IEEE + Trans. Microw. Theory Tech., vol. 47, no. 6, pp. 943-949, Jun. 1999. + """ - # This is the original term from [1], instead we use the limiting case of dt -> 0. - # After time-discretization, the PoleResidue medium should model the original term. - # K_tan = -1j * (2 / dt) * np.tan(2 * np.pi * freqs * dt / 2) - K_tan = -1j * 2 * np.pi * freqs - numer = 0 - denom = 0 - for a_m, m in zip(a, range(len(a))): - numer += a_m * K_tan ** (m) - for b_m, m in zip(b, range(len(b))): - denom += b_m * K_tan ** (m) - # We do not include the scaling factor associated with the cell size, since we will - # distribute the network over more than one cell. - return numer / denom + # This is the original term from [1], instead we use the limiting case of dt -> 0. + # After time-discretization, the PoleResidue medium should model the original term. + # K_tan = -1j * (2 / dt) * np.tan(2 * np.pi * freqs * dt / 2) + K_tan = -1j * 2 * np.pi * freqs + numer = 0 + denom = 0 + for a_m, m in zip(a, range(len(a))): + numer += a_m * K_tan ** (m) + for b_m, m in zip(b, range(len(b))): + denom += b_m * K_tan ** (m) + # We do not include the scaling factor associated with the cell size, since we will + # distribute the network over more than one cell. + return numer / denom + + +def network_complex_permittivity( + a: tuple[float, ...], b: tuple[float, ...], freqs: np.ndarray +) -> np.ndarray: + """Returns an equivalent complex permittivity of the lumped network over the range of frequencies + provided in ``freqs`` using the expression in _`[1]`. The result needs to be combined with a + :math:`\\epsilon_\\infty`, e.g., 1 or the existing background medium, before being added to an + FDTD simulation. + + This implementation follows a similar approach as _`[1]` with a couple small differences. Instead of + scaling the complex conductivity by the size of a single grid cell, we later scale the quantities by the + size of the lumped element in the FDTD simulation. In many cases, we will assume the time step is small, + so that the complex conductivity can be expressed more simply as a rational expression. + + Parameters + ---------- + a : tuple[float, ...] + Coefficients of the numerator polynomial + b : tuple[float, ...] + Coefficients of the denominator polynomial. + freqs: np.ndarray + Frequencies at which to evaluate model. + + Returns + ------- + np.ndarray + The equivalent frequency-dependent portion of the electric permittivity. - @staticmethod - def complex_permittivity(a: tuple[float, ...], b: tuple[float, ...], freqs: np.ndarray): - """ - Returns an equivalent complex permittivity of the lumped network over the range of frequencies - provided in ``freqs`` using the expression in _`[1]`. The result needs to be combined with a - :math:`\\epsilon_\\infty`, e.g., 1 or the existing background medium, before being added to an - FDTD simulation. - - Parameters - ---------- - a : tuple[float, ...] - Coefficients of the numerator polynomial - b : tuple[float, ...] - Coefficients of the denominator polynomial. - freqs: np.ndarray - Frequencies at which to evaluate model. + Notes + ----- - Returns - ------- - np.ndarray - The equivalent frequency-dependent portion of the electric permittivity. - """ + **References** - # For fitting with a pole-residue model, we provide a convenience function for - # converting the complex conductivity to a complex permittivity. - sigma = NetworkConversions.complex_conductivity(a, b, freqs) - return 1j * sigma / (2 * np.pi * freqs * EPSILON_0) + .. [1] J. A. Pereda, F. Alimenti, P. Mezzanotte, L. Roselli and R. Sorrentino, "A new algorithm + for the incorporation of arbitrary linear lumped networks into FDTD simulators," IEEE + Trans. Microw. Theory Tech., vol. 47, no. 6, pp. 943-949, Jun. 1999. + """ - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values + # For fitting with a pole-residue model, we provide a convenience function for + # converting the complex conductivity to a complex permittivity. + sigma = network_complex_conductivity(a, b, freqs) + return 1j * sigma / (2 * np.pi * freqs * EPSILON_0) -class RLCNetwork(Tidy3dBaseModel): +class RLCNetwork(MicrowaveBaseModel): """Class for representing a simple network consisting of a resistor, capacitor, and inductor. Provides additional functionality for representing the network as an equivalent medium. @@ -575,7 +569,7 @@ class RLCNetwork(Tidy3dBaseModel): >>> RL_series = RLCNetwork(resistance=75, ... inductance=1e-9, ... network_topology="series" - ... ) # doctest: +SKIP + ... ) """ @@ -804,16 +798,8 @@ def _validate_single_element(cls, val, values): raise ValueError("At least one element must be defined in the 'RLCNetwork'.") return val - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values - -class AdmittanceNetwork(Tidy3dBaseModel): +class AdmittanceNetwork(MicrowaveBaseModel): """Class for representing a network consisting of an arbitrary number of resistors, capacitors, and inductors. The network is represented in the Laplace domain as an admittance function. Provides additional functionality for representing the network @@ -857,7 +843,7 @@ class AdmittanceNetwork(Tidy3dBaseModel): >>> b = (R, 0) >>> RC_parallel = AdmittanceNetwork(a=a, ... b=b - ... ) # doctest: +SKIP + ... ) """ @@ -889,14 +875,6 @@ def _as_admittance_function(self) -> tuple[tuple[float, ...], tuple[float, ...]] """ return (self.a, self.b) - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values - class LinearLumpedElement(RectangularLumpedElement): """Lumped element representing a network consisting of resistors, capacitors, and inductors. @@ -919,14 +897,14 @@ class LinearLumpedElement(RectangularLumpedElement): >>> RL_series = RLCNetwork(resistance=75, ... inductance=1e-9, ... network_topology="series" - ... ) # doctest: +SKIP + ... ) >>> linear_element = LinearLumpedElement( ... center=[0, 0, 0], ... size=[2, 0, 3], ... voltage_axis=0, ... network=RL_series, ... name="LumpedRL" - ... ) # doctest: +SKIP + ... ) See Also @@ -1174,7 +1152,7 @@ def admittance(self, freqs: np.ndarray) -> np.ndarray: an opposite sign compared to the expected value when using the engineering convention. """ a, b = self.network._as_admittance_function - return NetworkConversions.complex_conductivity(a=a, b=b, freqs=freqs) + return network_complex_conductivity(a=a, b=b, freqs=freqs) def impedance(self, freqs: np.ndarray) -> np.ndarray: """Returns the impedance of this lumped element at the frequencies specified by ``freqs``. diff --git a/tidy3d/components/microwave/base.py b/tidy3d/components/microwave/base.py new file mode 100644 index 0000000000..7156f6bc89 --- /dev/null +++ b/tidy3d/components/microwave/base.py @@ -0,0 +1,38 @@ +"""Base class for configuring RF and microwave models.""" + +from __future__ import annotations + +import pydantic.v1 as pd + +from tidy3d.components.base import Tidy3dBaseModel +from tidy3d.config import config +from tidy3d.log import log +from tidy3d.utils import is_running_pytest + + +class MicrowaveBaseModel(Tidy3dBaseModel): + """Base model that all RF and microwave specific components inherit from.""" + + @pd.root_validator(pre=False) + def _warn_rf_license(cls, values): + from tidy3d.config import config + + # Skip warning during test runs or when globally suppressed via config + if not is_running_pytest() and not config.suppress_rf_license_warning: + log.warning( + "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. " + "You have instantiated at least one RF-specific component.", + log_once=True, + ) + return values + + @classmethod + def _default_without_license_warning(cls) -> MicrowaveBaseModel: + """Internal helper factory function for classes inheriting from ``MicrowaveBaseModel``.""" + if config.suppress_rf_license_warning is True: + return cls() + else: + config.suppress_rf_license_warning = True + default_contructed = cls() + config.suppress_rf_license_warning = False + return default_contructed diff --git a/tidy3d/components/microwave/data/dataset.py b/tidy3d/components/microwave/data/dataset.py new file mode 100644 index 0000000000..8810df1cc5 --- /dev/null +++ b/tidy3d/components/microwave/data/dataset.py @@ -0,0 +1,46 @@ +"""Dataset for microwave and RF transmission line mode data, including impedance, voltage, and current coefficients.""" + +from __future__ import annotations + +import pydantic.v1 as pd + +from tidy3d.components.data.data_array import ( + CurrentFreqModeDataArray, + ImpedanceFreqModeDataArray, + VoltageFreqModeDataArray, +) +from tidy3d.components.data.dataset import Dataset + + +class TransmissionLineDataset(Dataset): + """Holds mode data that is specific to transmission lines in microwave and RF applications, + like characteristic impedance. + + Notes + ----- + The data in this class is only calculated when a :class:`MicrowaveModeSpec` + is provided to the :class:`ModeMonitor`, :class:`ModeSolverMonitor`, :class:`ModeSolver`, + or :class:`ModeSimulation`. + """ + + Z0: ImpedanceFreqModeDataArray = pd.Field( + ..., + title="Characteristic Impedance", + description="The characteristic impedance of the transmission line.", + ) + + voltage_coeffs: VoltageFreqModeDataArray = pd.Field( + ..., + title="Mode Voltage Coefficients", + description="Quantity calculated for transmission lines, which associates " + "a voltage-like quantity with each mode profile that scales linearly with the " + "complex-valued mode amplitude.", + ) + + current_coeffs: CurrentFreqModeDataArray = pd.Field( + ..., + title="Mode Current Coefficients", + description="Quantity calculated for transmission lines, which associates " + "a current-like quantity with each mode profile that scales linearly with the " + "complex-valued mode amplitude.", + ) diff --git a/tidy3d/components/microwave/data/monitor_data.py b/tidy3d/components/microwave/data/monitor_data.py index 72a34c0d37..0a31435689 100644 --- a/tidy3d/components/microwave/data/monitor_data.py +++ b/tidy3d/components/microwave/data/monitor_data.py @@ -10,12 +10,14 @@ import xarray as xr from tidy3d.components.data.data_array import FieldProjectionAngleDataArray, FreqDataArray -from tidy3d.components.data.monitor_data import DirectivityData +from tidy3d.components.data.monitor_data import DirectivityData, ModeData, ModeSolverData +from tidy3d.components.microwave.base import MicrowaveBaseModel +from tidy3d.components.microwave.data.dataset import TransmissionLineDataset +from tidy3d.components.microwave.monitor import MicrowaveModeMonitor, MicrowaveModeSolverMonitor from tidy3d.components.types import PolarizationBasis -from tidy3d.log import log -class AntennaMetricsData(DirectivityData): +class AntennaMetricsData(DirectivityData, MicrowaveBaseModel): """Data representing the main parameters and figures of merit for antennas. Example @@ -39,8 +41,8 @@ class AntennaMetricsData(DirectivityData): ... name="rad_monitor", ... phi=phi, ... theta=theta - ... ) # doctest: +SKIP - >>> power_data = FreqDataArray(np.random.random(len(f)), coords=coords_flux) # doctest: +SKIP + ... ) + >>> power_data = FreqDataArray(np.random.random(len(f)), coords=coords_flux) >>> data = AntennaMetricsData( ... monitor=monitor, ... projection_surfaces=monitor.projection_surfaces, @@ -53,7 +55,7 @@ class AntennaMetricsData(DirectivityData): ... Hphi=scalar_field, ... power_incident=power_data, ... power_reflected=power_data - ... ) # doctest: +SKIP + ... ) Notes ----- @@ -198,10 +200,157 @@ def realized_gain(self) -> FieldProjectionAngleDataArray: partial_G = self.partial_realized_gain() return partial_G.Gtheta + partial_G.Gphi - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values + +class MicrowaveModeData(ModeData, MicrowaveBaseModel): + """ + Data associated with a :class:`.ModeMonitor` for microwave and RF applications: modal amplitudes, + propagation indices, mode profiles, and transmission line data. + + Notes + ----- + + This class extends :class:`.ModeData` with additional microwave-specific data including + characteristic impedance, voltage coefficients, and current coefficients. The data is + stored as `DataArray `_ + objects using the `xarray `_ package. + + The microwave mode data contains all the information from :class:`.ModeData` plus additional + microwave dataset with impedance calculations performed using voltage and current line integrals + as specified in the :class:`.MicrowaveModeSpec`. + + Example + ------- + >>> import tidy3d as td + >>> import numpy as np + >>> from tidy3d.components.data.data_array import ( + ... CurrentFreqModeDataArray, + ... ImpedanceFreqModeDataArray, + ... ModeAmpsDataArray, + ... ModeIndexDataArray, + ... VoltageFreqModeDataArray, + ... ) + >>> from tidy3d.components.microwave.data.dataset import TransmissionLineDataset + >>> direction = ["+", "-"] + >>> f = [1e14, 2e14, 3e14] + >>> mode_index = np.arange(3) + >>> index_coords = dict(f=f, mode_index=mode_index) + >>> index_data = ModeIndexDataArray((1+1j) * np.random.random((3, 3)), coords=index_coords) + >>> amp_coords = dict(direction=direction, f=f, mode_index=mode_index) + >>> amp_data = ModeAmpsDataArray((1+1j) * np.random.random((2, 3, 3)), coords=amp_coords) + >>> impedance_data = ImpedanceFreqModeDataArray(50 * np.ones((3, 3)), coords=index_coords) + >>> voltage_data = VoltageFreqModeDataArray((1+1j) * np.random.random((3, 3)), coords=index_coords) + >>> current_data = CurrentFreqModeDataArray((0.02+0.01j) * np.random.random((3, 3)), coords=index_coords) + >>> tl_data = TransmissionLineDataset( + ... Z0=impedance_data, + ... voltage_coeffs=voltage_data, + ... current_coeffs=current_data + ... ) + >>> monitor = td.MicrowaveModeMonitor( + ... center=(0, 0, 0), + ... size=(2, 0, 6), + ... freqs=[2e14, 3e14], + ... mode_spec=td.MicrowaveModeSpec(num_modes=3, impedance_specs=td.AutoImpedanceSpec()), + ... name='microwave_mode', + ... ) + >>> data = MicrowaveModeData( + ... monitor=monitor, + ... amps=amp_data, + ... n_complex=index_data, + ... transmission_line_data=tl_data + ... ) + """ + + monitor: MicrowaveModeMonitor = pd.Field( + ..., title="Monitor", description="Mode monitor associated with the data." + ) + + transmission_line_data: Optional[TransmissionLineDataset] = pd.Field( + None, + title="Transmission Line Data", + description="Additional data relevant to transmission lines in RF and microwave applications, " + "like characteristic impedance. This field is populated when a :class:`MicrowaveModeSpec` has " + "been used to set up the monitor or mode solver.", + ) + + @property + def modes_info(self) -> xr.Dataset: + """Dataset collecting various properties of the stored modes.""" + super_info = super().modes_info + if self.transmission_line_data is not None: + super_info["Re(Z0)"] = self.transmission_line_data.Z0.real + super_info["Im(Z0)"] = self.transmission_line_data.Z0.imag + return super_info + + +class MicrowaveModeSolverData(ModeSolverData, MicrowaveModeData): + """ + Data associated with a :class:`.ModeSolverMonitor` for microwave and RF applications: scalar components + of E and H fields plus characteristic impedance data. + + Notes + ----- + + This class extends :class:`.ModeSolverData` with additional microwave-specific data including + characteristic impedance, voltage coefficients, and current coefficients. The data is + stored as `DataArray `_ + objects using the `xarray `_ package. + + The microwave mode solver data contains all field components (Ex, Ey, Ez, Hx, Hy, Hz) and + effective indices from :class:`.ModeSolverData`, plus impedance calculations performed using + voltage and current line integrals as specified in the :class:`.MicrowaveModeSpec`. + + Example + ------- + >>> import tidy3d as td + >>> import numpy as np + >>> from tidy3d import Grid, Coords + >>> from tidy3d.components.data.data_array import ( + ... CurrentFreqModeDataArray, + ... ImpedanceFreqModeDataArray, + ... ScalarModeFieldDataArray, + ... ModeIndexDataArray, + ... VoltageFreqModeDataArray, + ... ) + >>> from tidy3d.components.microwave.data.dataset import TransmissionLineDataset + >>> x = [-1, 1, 3] + >>> y = [-2, 0] + >>> z = [-3, -1, 1, 3, 5] + >>> f = [2e14, 3e14] + >>> mode_index = np.arange(3) + >>> grid = Grid(boundaries=Coords(x=x, y=y, z=z)) + >>> field_coords = dict(x=x[:-1], y=y[:-1], z=z[:-1], f=f, mode_index=mode_index) + >>> field = ScalarModeFieldDataArray((1+1j)*np.random.random((2,1,4,2,3)), coords=field_coords) + >>> index_coords = dict(f=f, mode_index=mode_index) + >>> index_data = ModeIndexDataArray((1+1j) * np.random.random((2,3)), coords=index_coords) + >>> impedance_data = ImpedanceFreqModeDataArray(50 * np.ones((2, 3)), coords=index_coords) + >>> voltage_data = VoltageFreqModeDataArray((1+1j) * np.random.random((2, 3)), coords=index_coords) + >>> current_data = CurrentFreqModeDataArray((0.02+0.01j) * np.random.random((2, 3)), coords=index_coords) + >>> tl_data = TransmissionLineDataset( + ... Z0=impedance_data, + ... voltage_coeffs=voltage_data, + ... current_coeffs=current_data + ... ) + >>> monitor = td.MicrowaveModeSolverMonitor( + ... center=(0, 0, 0), + ... size=(2, 0, 6), + ... freqs=[2e14, 3e14], + ... mode_spec=td.MicrowaveModeSpec(num_modes=3, impedance_specs=td.AutoImpedanceSpec()), + ... name='microwave_mode_solver', + ... ) + >>> data = MicrowaveModeSolverData( + ... monitor=monitor, + ... Ex=field, + ... Ey=field, + ... Ez=field, + ... Hx=field, + ... Hy=field, + ... Hz=field, + ... n_complex=index_data, + ... grid_expanded=grid, + ... transmission_line_data=tl_data + ... ) + """ + + monitor: MicrowaveModeSolverMonitor = pd.Field( + ..., title="Monitor", description="Mode monitor associated with the data." + ) diff --git a/tidy3d/components/microwave/impedance_calculator.py b/tidy3d/components/microwave/impedance_calculator.py new file mode 100644 index 0000000000..8f80c57320 --- /dev/null +++ b/tidy3d/components/microwave/impedance_calculator.py @@ -0,0 +1,167 @@ +"""Class for computing characteristic impedance of transmission lines.""" + +from __future__ import annotations + +from typing import Optional, Union + +import numpy as np +import pydantic.v1 as pd + +from tidy3d.components.data.data_array import ( + CurrentIntegralResultType, + ImpedanceResultType, + VoltageIntegralResultType, + _make_current_data_array, + _make_impedance_data_array, + _make_voltage_data_array, +) +from tidy3d.components.data.monitor_data import FieldTimeData +from tidy3d.components.microwave.base import MicrowaveBaseModel +from tidy3d.components.microwave.path_integrals.integrals.base import ( + AxisAlignedPathIntegral, + IntegrableMonitorDataType, +) +from tidy3d.components.microwave.path_integrals.integrals.current import ( + AxisAlignedCurrentIntegral, + CompositeCurrentIntegral, + Custom2DCurrentIntegral, +) +from tidy3d.components.microwave.path_integrals.integrals.voltage import ( + AxisAlignedVoltageIntegral, + Custom2DVoltageIntegral, +) +from tidy3d.components.monitor import ModeMonitor, ModeSolverMonitor +from tidy3d.exceptions import ValidationError + +VoltageIntegralType = Union[AxisAlignedVoltageIntegral, Custom2DVoltageIntegral] +CurrentIntegralType = Union[ + AxisAlignedCurrentIntegral, Custom2DCurrentIntegral, CompositeCurrentIntegral +] + + +class ImpedanceCalculator(MicrowaveBaseModel): + """Tool for computing the characteristic impedance of a transmission line. + + Example + ------- + Create a calculator with both voltage and current path integrals defined. + + >>> v_int = AxisAlignedVoltageIntegral( + ... center=(0, 0, 0), + ... size=(0, 0, 2), + ... sign="+", + ... extrapolate_to_endpoints=True, + ... snap_path_to_grid=True, + ... ) + >>> i_int = AxisAlignedCurrentIntegral( + ... center=(0, 0, 0), + ... size=(1, 1, 0), + ... sign="+", + ... extrapolate_to_endpoints=True, + ... snap_contour_to_grid=True, + ... ) + >>> calc = ImpedanceCalculator(voltage_integral=v_int, current_integral=i_int) + + You can also define only one of the integrals. At least one is required. + + >>> _ = ImpedanceCalculator(voltage_integral=v_int) + """ + + voltage_integral: Optional[VoltageIntegralType] = pd.Field( + None, + title="Voltage Integral", + description="Definition of path integral for computing voltage.", + ) + + current_integral: Optional[CurrentIntegralType] = pd.Field( + None, + title="Current Integral", + description="Definition of contour integral for computing current.", + ) + + def compute_impedance( + self, em_field: IntegrableMonitorDataType, return_voltage_and_current=False + ) -> Union[ + 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``. + + Parameters + ---------- + em_field : :class:`.IntegrableMonitorDataType` + The electromagnetic field data that will be used for computing the characteristic + impedance. + return_voltage_and_current: bool + When ``True``, returns additional :class:`.IntegralResultType` that represent the voltage + and current associated with the supplied fields. + + Returns + ------- + :class:`.IntegralResultType` or tuple[VoltageIntegralResultType, CurrentIntegralResultType, ImpedanceResultType] + If ``return_voltage_and_current=False``, single result of impedance computation + over remaining dimensions (frequency, time, mode indices). If ``return_voltage_and_current=True``, + tuple of (impedance, voltage, current). + """ + + AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) + + voltage = None + current = None + # If both voltage and current integrals have been defined then impedance is computed directly + if self.voltage_integral is not None: + voltage = self.voltage_integral.compute_voltage(em_field) + if self.current_integral is not None: + current = self.current_integral.compute_current(em_field) + + # If only one of the integrals has been provided, then the computation falls back to using + # total power (flux) with Ohm's law to compute the missing quantity. The input field should + # cover an area large enough to render the flux computation accurate. If the input field is + # a time signal, then it is real and flux corresponds to the instantaneous power. Otherwise + # the input field is in frequency domain, where flux indicates the time-averaged power + # 0.5*Re(V*conj(I)). + # We explicitly take the real part, in case Bloch BCs were used in the simulation. + flux_sign = 1.0 + # Determine flux sign + if isinstance(em_field.monitor, ModeSolverMonitor): + flux_sign = 1 if em_field.monitor.direction == "+" else -1 + if isinstance(em_field.monitor, ModeMonitor): + flux_sign = 1 if em_field.monitor.store_fields_direction == "+" else -1 + + if self.voltage_integral is None: + flux = flux_sign * em_field.complex_flux + if isinstance(em_field, FieldTimeData): + impedance = flux / np.real(current) ** 2 + else: + impedance = 2 * flux / (current * np.conj(current)) + elif self.current_integral is None: + flux = flux_sign * em_field.complex_flux + if isinstance(em_field, FieldTimeData): + impedance = np.real(voltage) ** 2 / flux + else: + impedance = (voltage * np.conj(voltage)) / (2 * np.conj(flux)) + else: + if isinstance(em_field, FieldTimeData): + impedance = np.real(voltage) / np.real(current) + else: + impedance = voltage / current + impedance = _make_impedance_data_array(impedance) + if return_voltage_and_current: + if voltage is None: + voltage = _make_voltage_data_array(impedance * current) + if current is None: + current = _make_current_data_array(voltage / impedance) + return (impedance, voltage, current) + return impedance + + @pd.validator("current_integral", always=True) + def check_voltage_or_current(cls, val, values): + """Raise validation error if both ``voltage_integral`` and ``current_integral`` + are not provided.""" + if not values.get("voltage_integral") and not val: + raise ValidationError( + "At least one of 'voltage_integral' or 'current_integral' must be provided." + ) + return val diff --git a/tidy3d/components/microwave/mode_spec.py b/tidy3d/components/microwave/mode_spec.py new file mode 100644 index 0000000000..ee7dcddeb6 --- /dev/null +++ b/tidy3d/components/microwave/mode_spec.py @@ -0,0 +1,98 @@ +"""Specification for modes associated with transmission lines.""" + +from __future__ import annotations + +from typing import Optional, Union + +import pydantic.v1 as pd + +from tidy3d.components.base import cached_property +from tidy3d.components.microwave.base import MicrowaveBaseModel +from tidy3d.components.microwave.path_integrals.specs.impedance import ( + AutoImpedanceSpec, + ImpedanceSpecType, +) +from tidy3d.components.mode_spec import AbstractModeSpec +from tidy3d.components.types import annotate_type + + +class MicrowaveModeSpec(AbstractModeSpec, MicrowaveBaseModel): + """ + The :class:`.MicrowaveModeSpec` class specifies how quantities related to transmission line + modes and microwave waveguides are computed. For example, it defines the paths for line integrals, which are used to + compute voltage, current, and characteristic impedance of the transmission line. + + Example + ------- + >>> import tidy3d as td + >>> # Using automatic impedance calculation (single spec, will be duplicated for all modes) + >>> mode_spec_auto = td.MicrowaveModeSpec( + ... num_modes=2, + ... impedance_specs=td.AutoImpedanceSpec() + ... ) + >>> # Using custom impedance specification for multiple modes + >>> voltage_spec = td.AxisAlignedVoltageIntegralSpec( + ... center=(0, 0, 0), size=(0, 0, 1), sign="+" + ... ) + >>> current_spec = td.AxisAlignedCurrentIntegralSpec( + ... center=(0, 0, 0), size=(2, 1, 0), sign="+" + ... ) + >>> custom_impedance = td.CustomImpedanceSpec( + ... voltage_spec=voltage_spec, current_spec=current_spec + ... ) + >>> mode_spec_custom = td.MicrowaveModeSpec( + ... num_modes=1, + ... impedance_specs=custom_impedance + ... ) + """ + + impedance_specs: Union[ + annotate_type(ImpedanceSpecType), + tuple[Optional[annotate_type(ImpedanceSpecType)], ...], + ] = 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. " + "Can be a single impedance specification (which will be applied to all modes) or a tuple of specifications " + "(one per mode). The number of impedance specifications should match the number of modes field. " + "When an impedance specification of ``None`` is used, the impedance calculation will be " + "ignored for the associated mode.", + ) + + @cached_property + def _impedance_specs_as_tuple(self) -> tuple[Optional[ImpedanceSpecType]]: + """Gets the impedance_specs field converted to a tuple.""" + if isinstance(self.impedance_specs, Union[tuple, list]): + return tuple(self.impedance_specs) + return (self.impedance_specs,) + + @cached_property + def _using_auto_current_spec(self) -> bool: + """Checks whether at least one of the modes will require an auto setup of the current path specification.""" + return any( + isinstance(impedance_spec, AutoImpedanceSpec) + for impedance_spec in self._impedance_specs_as_tuple + ) + + @pd.validator("impedance_specs", always=True) + 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]): + num_impedance_specs = len(val) + else: + return val + + # Otherwise, check that the count matches + if num_impedance_specs != num_modes: + from tidy3d.exceptions import SetupError + + raise SetupError( + f"Given {num_impedance_specs} impedance specifications in the 'MicrowaveModeSpec', " + f"but the number of modes requested is {num_modes}. Please ensure that the " + "number of impedance specifications is equal to the number of modes, or provide " + "a single specification to apply to all modes." + ) + + return val diff --git a/tidy3d/components/microwave/monitor.py b/tidy3d/components/microwave/monitor.py new file mode 100644 index 0000000000..da7aec42a0 --- /dev/null +++ b/tidy3d/components/microwave/monitor.py @@ -0,0 +1,70 @@ +"""Objects that define how data is recorded from simulation.""" + +from __future__ import annotations + +import pydantic.v1 as pydantic + +from tidy3d.components.microwave.base import MicrowaveBaseModel +from tidy3d.components.microwave.mode_spec import MicrowaveModeSpec +from tidy3d.components.monitor import ModeMonitor, ModeSolverMonitor + + +class MicrowaveModeMonitor(MicrowaveBaseModel, ModeMonitor): + """:class:`Monitor` that records amplitudes from modal decomposition of fields on plane. + + Notes + ------ + + The fields recorded by frequency monitors (and hence also mode monitors) are automatically + normalized by the power amplitude spectrum of the source. For multiple sources, the user can + select which source to use for the normalization too. + + We can also use the mode amplitudes recorded in the mode monitor to reveal the decomposition + of the radiated power into forward- and backward-propagating modes, respectively. + + .. TODO give an example of how to extract the data from this mode. + + .. TODO add derivation in the notebook. + + .. TODO add link to method + + .. TODO add links to notebooks correspondingly + + Example + ------- + >>> mode_spec = MicrowaveModeSpec(num_modes=3) + >>> monitor = MicrowaveModeMonitor( + ... center=(1,2,3), + ... size=(2,2,0), + ... freqs=[200e12, 210e12], + ... mode_spec=mode_spec, + ... name='mode_monitor') + + See Also + -------- + + **Notebooks**: + * `ModalSourcesMonitors <../../notebooks/ModalSourcesMonitors.html>`_ + """ + + mode_spec: MicrowaveModeSpec = pydantic.Field( + default_factory=MicrowaveModeSpec._default_without_license_warning, + title="Mode Specification", + description="Parameters to feed to mode solver which determine modes measured by monitor.", + ) + + +class MicrowaveModeSolverMonitor(MicrowaveModeMonitor, ModeSolverMonitor): + """:class:`Monitor` that stores the mode field profiles returned by the mode solver in the + monitor plane. + + Example + ------- + >>> mode_spec = MicrowaveModeSpec(num_modes=3) + >>> monitor = MicrowaveModeSolverMonitor( + ... center=(1,2,3), + ... size=(2,2,0), + ... freqs=[200e12, 210e12], + ... mode_spec=mode_spec, + ... name='mode_monitor') + """ diff --git a/tidy3d/components/microwave/path_integrals/__init__.py b/tidy3d/components/microwave/path_integrals/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tidy3d/components/microwave/path_integrals/factory.py b/tidy3d/components/microwave/path_integrals/factory.py new file mode 100644 index 0000000000..9100dcc206 --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/factory.py @@ -0,0 +1,153 @@ +"""Factory functions for creating current and voltage path integrals from path specifications.""" + +from __future__ import annotations + +from typing import Optional + +from tidy3d.components.microwave.impedance_calculator import ( + CurrentIntegralType, + VoltageIntegralType, +) +from tidy3d.components.microwave.mode_spec import MicrowaveModeSpec +from tidy3d.components.microwave.path_integrals.integrals.current import ( + AxisAlignedCurrentIntegral, + CompositeCurrentIntegral, + Custom2DCurrentIntegral, +) +from tidy3d.components.microwave.path_integrals.integrals.voltage import ( + AxisAlignedVoltageIntegral, + Custom2DVoltageIntegral, +) +from tidy3d.components.microwave.path_integrals.specs.current import ( + AxisAlignedCurrentIntegralSpec, + CompositeCurrentIntegralSpec, + Custom2DCurrentIntegralSpec, +) +from tidy3d.components.microwave.path_integrals.specs.impedance import ( + AutoImpedanceSpec, + CustomImpedanceSpec, +) +from tidy3d.components.microwave.path_integrals.specs.voltage import ( + AxisAlignedVoltageIntegralSpec, + Custom2DVoltageIntegralSpec, +) +from tidy3d.components.microwave.path_integrals.types import ( + CurrentPathSpecType, + VoltagePathSpecType, +) +from tidy3d.exceptions import SetupError, ValidationError + + +def make_voltage_integral(path_spec: VoltagePathSpecType) -> VoltageIntegralType: + """Create a voltage path integral from a path specification. + + Parameters + ---------- + path_spec : VoltagePathSpecType + Specification defining the path for voltage integration. Can be either an axis-aligned or + custom path specification. + + Returns + ------- + VoltageIntegralType + Voltage path integral instance corresponding to the provided specification type. + """ + v_integral = None + if isinstance(path_spec, AxisAlignedVoltageIntegralSpec): + v_integral = AxisAlignedVoltageIntegral(**path_spec.dict(exclude={"type"})) + elif isinstance(path_spec, Custom2DVoltageIntegralSpec): + v_integral = Custom2DVoltageIntegral(**path_spec.dict(exclude={"type"})) + else: + raise ValidationError(f"Unsupported voltage path specification type: {type(path_spec)}") + return v_integral + + +def make_current_integral(path_spec: CurrentPathSpecType) -> CurrentIntegralType: + """Create a current path integral from a path specification. + + Parameters + ---------- + path_spec : CurrentPathSpecType + Specification defining the path for current integration. Can be either an axis-aligned, + custom, or composite path specification. + + Returns + ------- + CurrentIntegralType + Current path integral instance corresponding to the provided specification type. + """ + i_integral = None + if isinstance(path_spec, AxisAlignedCurrentIntegralSpec): + i_integral = AxisAlignedCurrentIntegral(**path_spec.dict(exclude={"type"})) + elif isinstance(path_spec, Custom2DCurrentIntegralSpec): + i_integral = Custom2DCurrentIntegral(**path_spec.dict(exclude={"type"})) + elif isinstance(path_spec, CompositeCurrentIntegralSpec): + i_integral = CompositeCurrentIntegral(**path_spec.dict(exclude={"type"})) + else: + raise ValidationError(f"Unsupported current path specification type: {type(path_spec)}") + return i_integral + + +def make_path_integrals( + microwave_mode_spec: MicrowaveModeSpec, auto_spec: Optional[CustomImpedanceSpec] = None +) -> tuple[tuple[Optional[VoltageIntegralType]], tuple[Optional[CurrentIntegralType]]]: + """ + Given a microwave mode specification and monitor, create the voltage and + current path integrals used for the impedance computation. + + Parameters + ---------- + microwave_mode_spec : MicrowaveModeSpec + Microwave mode specification for creating voltage and current path specifications. + auto_spec: Optional[CustomImpedanceSpec] + The automatically created impedance specification, if available. + + Returns + ------- + tuple[tuple[Optional[VoltageIntegralType]], tuple[Optional[CurrentIntegralType]]] + Tuple containing the voltage and current path integral instances for each mode. + + Raises + ------ + SetupError + If path specifications cannot be auto-generated or path integrals cannot be constructed. + """ + + if microwave_mode_spec._using_auto_current_spec and auto_spec is None: + raise SetupError("Auto path specification is not available for the local mode solver.") + + v_integrals = [] + i_integrals = [] + + # Handle case where impedance spec is a single ImpedanceSpecType + impedance_specs = microwave_mode_spec._impedance_specs_as_tuple + + for idx, impedance_spec in enumerate(impedance_specs): + if impedance_spec is None: + # Do not calculate impedance for this mode + v_integrals.append(None) + i_integrals.append(None) + continue + elif isinstance(impedance_spec, AutoImpedanceSpec): + v_spec = None + i_spec = auto_spec.current_spec + else: + v_spec = impedance_spec.voltage_spec + i_spec = impedance_spec.current_spec + + try: + v_integral = None + i_integral = None + if v_spec is not None: + v_integral = make_voltage_integral(v_spec) + if i_spec is not None: + i_integral = make_current_integral(i_spec) + v_integrals.append(v_integral) + i_integrals.append(i_integral) + except Exception as e: + raise SetupError( + f"Failed to construct path integrals for the mode with index {idx} " + "from the impedance specification. " + "Please create a github issue so that the problem can be investigated." + ) from e + return (tuple(v_integrals), tuple(i_integrals)) diff --git a/tidy3d/components/microwave/path_integrals/integrals/__init__.py b/tidy3d/components/microwave/path_integrals/integrals/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tidy3d/components/microwave/path_integrals/integrals/auto.py b/tidy3d/components/microwave/path_integrals/integrals/auto.py new file mode 100644 index 0000000000..2af7caefaa --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/integrals/auto.py @@ -0,0 +1,86 @@ +"""Helpers for automatic setup of path integrals.""" + +from __future__ import annotations + +from tidy3d.components.geometry.base import Box +from tidy3d.components.geometry.utils import ( + SnapBehavior, + SnapLocation, + SnappingSpec, + snap_box_to_grid, +) +from tidy3d.components.grid.grid import Grid +from tidy3d.components.lumped_element import LinearLumpedElement +from tidy3d.components.microwave.path_integrals.integrals.current import ( + AxisAlignedCurrentIntegral, +) +from tidy3d.components.microwave.path_integrals.integrals.voltage import ( + AxisAlignedVoltageIntegral, +) +from tidy3d.components.types import Direction + + +def path_integrals_from_lumped_element( + lumped_element: LinearLumpedElement, grid: Grid, polarity: Direction = "+" +) -> tuple[AxisAlignedVoltageIntegral, AxisAlignedCurrentIntegral]: + """Helper to create a :class:`.AxisAlignedVoltageIntegral` and :class:`.AxisAlignedCurrentIntegral` + from a supplied :class:`.LinearLumpedElement`. Takes into account any snapping the lumped element + undergoes using the supplied :class:`.Grid`. + + Parameters + ---------- + lumped_element : :class:`.LinearLumpedElement` + The lumped element for which to create path integrals. + grid : :class:`.Grid` + The simulation grid used for snapping the lumped element. + polarity : Direction + Choice for defining voltage. When positive, the terminal of the lumped element with + the greatest coordinate is considered the positive terminal. + Returns + ------- + AxisAlignedVoltageIntegral + The created path integral for computing voltage between the two terminals of the :class:`.LinearLumpedElement`. + AxisAlignedCurrentIntegral + The created path integral for computing current flowing through the :class:`.LinearLumpedElement`. + """ + + # Quick access to voltage and the primary current axis + V_axis = lumped_element.voltage_axis + I_axis = lumped_element.lateral_axis + + # The exact position of the lumped element after any possible snapping + lumped_element_box = lumped_element._create_box_for_network(grid=grid) + + V_size = [0, 0, 0] + V_size[V_axis] = lumped_element_box.size[V_axis] + voltage_integral = AxisAlignedVoltageIntegral( + center=lumped_element_box.center, + size=V_size, + sign=polarity, + extrapolate_to_endpoints=True, + snap_path_to_grid=True, + ) + + # Snap the current integral to a box that encloses the element along the lateral and normal axes + # using the closest positions of the magnetic field + snap_location = [SnapLocation.Center] * 3 + snap_behavior = [SnapBehavior.Expand] * 3 + # Don't need to snap along voltage axis, since it will already be snapped from the lumped element's box + snap_behavior[V_axis] = SnapBehavior.Off + snap_spec = SnappingSpec(location=snap_location, behavior=snap_behavior) + + I_size = [0, 0, 0] + I_size[I_axis] = lumped_element_box.size[I_axis] + current_box = Box(center=lumped_element_box.center, size=I_size) + current_box = snap_box_to_grid(grid, current_box, snap_spec) + # Convention is current flows from plus to minus terminals + current_sign = "-" if polarity == "+" else "+" + current_integral = AxisAlignedCurrentIntegral( + center=current_box.center, + size=current_box.size, + sign=current_sign, + snap_contour_to_grid=True, + extrapolate_to_endpoints=True, + ) + + return (voltage_integral, current_integral) diff --git a/tidy3d/components/microwave/path_integrals/integrals/base.py b/tidy3d/components/microwave/path_integrals/integrals/base.py new file mode 100644 index 0000000000..04fa762596 --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/integrals/base.py @@ -0,0 +1,213 @@ +"""Base classes for performing path integrals with fields on the Yee grid""" + +from __future__ import annotations + +from typing import Literal, Union + +import numpy as np +import xarray as xr + +from tidy3d.components.data.data_array import ( + IntegralResultType, + ScalarFieldDataArray, + ScalarFieldTimeDataArray, + ScalarModeFieldDataArray, + _make_base_result_data_array, +) +from tidy3d.components.data.monitor_data import FieldData, FieldTimeData, ModeData, ModeSolverData +from tidy3d.components.microwave.path_integrals.specs.base import ( + AxisAlignedPathIntegralSpec, + Custom2DPathIntegralSpec, +) +from tidy3d.constants import fp_eps +from tidy3d.exceptions import DataError + +IntegrableMonitorDataType = Union[FieldData, FieldTimeData, ModeData, ModeSolverData] +EMScalarFieldType = Union[ScalarFieldDataArray, ScalarFieldTimeDataArray, ScalarModeFieldDataArray] +FieldParameter = Literal["E", "H"] + + +class AxisAlignedPathIntegral(AxisAlignedPathIntegralSpec): + """Class for defining the simplest type of path integral, which is aligned with Cartesian axes. + + Example + ------- + >>> path = AxisAlignedPathIntegral( + ... center=(0, 0, 1), + ... size=(0, 0, 2), + ... extrapolate_to_endpoints=True, + ... snap_path_to_grid=False, + ... ) + """ + + def compute_integral(self, scalar_field: EMScalarFieldType) -> IntegralResultType: + """Computes the defined integral given the input ``scalar_field``.""" + + if not scalar_field.does_cover(self.bounds, fp_eps, np.finfo(np.float32).smallest_normal): + raise DataError("Scalar field does not cover the integration domain.") + coord = "xyz"[self.main_axis] + + scalar_field = self._get_field_along_path(scalar_field) + # Get the boundaries + min_bound = self.bounds[0][self.main_axis] + max_bound = self.bounds[1][self.main_axis] + + if self.extrapolate_to_endpoints: + # Remove field outside the boundaries + scalar_field = scalar_field.sel({coord: slice(min_bound, max_bound)}) + # Ignore values on the boundary (sel is inclusive) + scalar_field = scalar_field.drop_sel({coord: (min_bound, max_bound)}, errors="ignore") + coordinates = scalar_field.coords[coord].values + else: + coordinates = scalar_field.coords[coord].sel({coord: slice(min_bound, max_bound)}) + + # Integration is along the original coordinates plus ensure that + # endpoints corresponding to the precise bounds of the port are included + coords_interp = np.array([min_bound]) + coords_interp = np.concatenate((coords_interp, coordinates)) + coords_interp = np.concatenate((coords_interp, [max_bound])) + coords_interp = {coord: coords_interp} + + # Use extrapolation for the 2 additional endpoints, unless there is only a single sample point + method = "linear" + if len(coordinates) == 1 and self.extrapolate_to_endpoints: + method = "nearest" + scalar_field = scalar_field.interp( + coords_interp, method=method, kwargs={"fill_value": "extrapolate"} + ) + result = scalar_field.integrate(coord=coord) + return _make_base_result_data_array(result) + + def _get_field_along_path(self, scalar_field: EMScalarFieldType) -> EMScalarFieldType: + """Returns a selection of the input ``scalar_field`` ready for integration.""" + (axis1, axis2) = self.remaining_axes + (coord1, coord2) = self.remaining_dims + + if self.snap_path_to_grid: + # Coordinates that are not integrated + remaining_coords = { + coord1: self.center[axis1], + coord2: self.center[axis2], + } + # Select field nearest to center of integration line + scalar_field = scalar_field.sel( + remaining_coords, + method="nearest", + drop=False, + ) + else: + # Try to interpolate unless there is only a single coordinate + coord1dict = {coord1: self.center[axis1]} + if scalar_field.sizes[coord1] == 1: + scalar_field = scalar_field.sel(coord1dict, method="nearest") + else: + scalar_field = scalar_field.interp( + coord1dict, method="linear", kwargs={"bounds_error": True} + ) + coord2dict = {coord2: self.center[axis2]} + if scalar_field.sizes[coord2] == 1: + scalar_field = scalar_field.sel(coord2dict, method="nearest") + else: + scalar_field = scalar_field.interp( + coord2dict, method="linear", kwargs={"bounds_error": True} + ) + # Remove unneeded coordinates + scalar_field = scalar_field.reset_coords(drop=True) + return scalar_field + + @staticmethod + def _check_monitor_data_supported(em_field: IntegrableMonitorDataType): + """Helper for validating that monitor data is supported.""" + if not isinstance(em_field, (FieldData, FieldTimeData, ModeData, ModeSolverData)): + supported_types = list(IntegrableMonitorDataType.__args__) + raise DataError( + f"'em_field' type {type(em_field)} not supported. Supported types are " + f"{supported_types}" + ) + + +class Custom2DPathIntegral(Custom2DPathIntegralSpec): + """Class for defining a custom path integral defined as a curve on an axis-aligned plane. + + Notes + ----- + + Given a set of vertices :math:`\\vec{r}_i`, this class approximates path integrals over + vector fields of the form :math:`\\int{\\vec{F} \\cdot \\vec{dl}}` + as :math:`\\sum_i{\\vec{F}(\\vec{r}_i) \\cdot \\vec{dl}_i}`, + where the differential length :math:`\\vec{dl}` is approximated using central differences + :math:`\\vec{dl}_i = \\frac{\\vec{r}_{i+1} - \\vec{r}_{i-1}}{2}`. + If the path is not closed, forward and backward differences are used at the endpoints. + + Example + ------- + >>> import numpy as np + >>> vertices = np.array([[0, 0], [1, 0], [1, 1], [0, 1]]) + >>> path = Custom2DPathIntegral( + ... axis=2, + ... position=0.5, + ... vertices=vertices, + ... ) + """ + + def compute_integral( + self, field: FieldParameter, em_field: IntegrableMonitorDataType + ) -> IntegralResultType: + """Computes the path integral defined by ``vertices`` given the input ``em_field``. + + Parameters + ---------- + field : :class:`.FieldParameter` + Can take the value of ``"E"`` or ``"H"``. Determines whether to perform the integral + over electric or magnetic field. + em_field : :class:`.IntegrableMonitorDataType` + The electromagnetic field data that will be used for integrating. + + Returns + ------- + :class:`.IntegralResultType` + Result of integral over remaining dimensions (frequency, time, mode indices). + """ + + (dim1, dim2, dim3) = self.local_dims + + h_field_name = f"{field}{dim1}" + v_field_name = f"{field}{dim2}" + + # Validate that fields are present + em_field._check_fields_stored([h_field_name, v_field_name]) + + # Select fields lying on the plane + plane_indexer = {dim3: self.position} + field1 = em_field.field_components[h_field_name].sel(plane_indexer, method="nearest") + field2 = em_field.field_components[v_field_name].sel(plane_indexer, method="nearest") + + # Although for users we use the convention that an axis is simply `popped` + # internally we prefer a right-handed coordinate system where dimensions + # keep a proper order. The only change is to swap 'x' and 'z' when the + # normal axis is along `y` + # Dim 's' represents the parameterization of the line + # 't' is likely used for time + if self.main_axis == 1: + x_path = xr.DataArray(self.vertices[:, 1], dims="s") + y_path = xr.DataArray(self.vertices[:, 0], dims="s") + else: + x_path = xr.DataArray(self.vertices[:, 0], dims="s") + y_path = xr.DataArray(self.vertices[:, 1], dims="s") + + path_indexer = {dim1: x_path, dim2: y_path} + field1_interp = field1.interp(path_indexer, method="linear") + field2_interp = field2.interp(path_indexer, method="linear") + + # Determine the differential length elements along the path + dl_x = Custom2DPathIntegralSpec._compute_dl_component(x_path, self.is_closed_contour) + dl_y = Custom2DPathIntegralSpec._compute_dl_component(y_path, self.is_closed_contour) + dl_x = xr.DataArray(dl_x, dims="s") + dl_y = xr.DataArray(dl_y, dims="s") + + # Compute the dot product between differential length element and vector field + integrand = field1_interp * dl_x + field2_interp * dl_y + # Integrate along the path + result = integrand.integrate(coord="s") + result = result.reset_coords(drop=True) + return _make_base_result_data_array(result) diff --git a/tidy3d/components/microwave/path_integrals/integrals/current.py b/tidy3d/components/microwave/path_integrals/integrals/current.py new file mode 100644 index 0000000000..d6ffddd332 --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/integrals/current.py @@ -0,0 +1,300 @@ +"""Current integral classes""" + +from __future__ import annotations + +from typing import Union + +import numpy as np +import xarray as xr + +from tidy3d.components.base import cached_property +from tidy3d.components.data.data_array import ( + CurrentIntegralResultType, + FreqDataArray, + FreqModeDataArray, + IntegralResultType, + _make_current_data_array, +) +from tidy3d.components.data.monitor_data import FieldTimeData +from tidy3d.components.microwave.path_integrals.integrals.base import ( + AxisAlignedPathIntegral, + Custom2DPathIntegral, + IntegrableMonitorDataType, +) +from tidy3d.components.microwave.path_integrals.specs.current import ( + AxisAlignedCurrentIntegralSpec, + CompositeCurrentIntegralSpec, + Custom2DCurrentIntegralSpec, +) +from tidy3d.exceptions import DataError +from tidy3d.log import log + + +class AxisAlignedCurrentIntegral(AxisAlignedCurrentIntegralSpec): + """Class for computing conduction current via Ampère's circuital law on an axis-aligned loop. + + Example + ------- + >>> current = AxisAlignedCurrentIntegral( + ... center=(0, 0, 0), + ... size=(1, 1, 0), + ... sign="+", + ... extrapolate_to_endpoints=True, + ... snap_contour_to_grid=True, + ... ) + """ + + def compute_current(self, em_field: IntegrableMonitorDataType) -> CurrentIntegralResultType: + """Compute current flowing in loop defined by the outer edge of a rectangle.""" + + AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) + ax1 = self.remaining_axes[0] + ax2 = self.remaining_axes[1] + h_component = "xyz"[ax1] + v_component = "xyz"[ax2] + h_field_name = f"H{h_component}" + v_field_name = f"H{v_component}" + # Validate that fields are present + em_field._check_fields_stored([h_field_name, v_field_name]) + h_horizontal = em_field.field_components[h_field_name] + h_vertical = em_field.field_components[v_field_name] + + # Decompose contour into path integrals + (bottom, right, top, left) = self._to_path_integrals(h_horizontal, h_vertical) + + current = 0 + # Compute and add contributions from each part of the contour + current += bottom.compute_integral(h_horizontal) + current += right.compute_integral(h_vertical) + current -= top.compute_integral(h_horizontal) + current -= left.compute_integral(h_vertical) + + if self.sign == "-": + current *= -1 + return _make_current_data_array(current) + + def _to_path_integrals( + self, h_horizontal=None, h_vertical=None + ) -> tuple[AxisAlignedPathIntegral, ...]: + """Returns four ``AxisAlignedPathIntegral`` instances, which represent a contour + integral around the surface defined by ``self.size``.""" + path_specs = self._to_path_integral_specs(h_horizontal=h_horizontal, h_vertical=h_vertical) + path_integrals = tuple( + AxisAlignedPathIntegral(**path_spec.dict(exclude={"type"})) for path_spec in path_specs + ) + return path_integrals + + +class Custom2DCurrentIntegral(Custom2DPathIntegral, Custom2DCurrentIntegralSpec): + """Class for computing conduction current via Ampère's circuital law on a custom path. + To compute the current flowing in the positive ``axis`` direction, the vertices should be + ordered in a counterclockwise direction. + + Example + ------- + >>> import numpy as np + >>> vertices = np.array([[0, 0], [2, 0], [2, 1], [0, 1], [0, 0]]) + >>> current = Custom2DCurrentIntegral( + ... axis=2, + ... position=0.0, + ... vertices=vertices, + ... ) + """ + + def compute_current(self, em_field: IntegrableMonitorDataType) -> CurrentIntegralResultType: + """Compute current flowing in a custom loop. + + Parameters + ---------- + em_field : :class:`.IntegrableMonitorDataType` + The electromagnetic field data that will be used for integrating. + + Returns + ------- + :class:`.CurrentIntegralResultType` + Result of current computation over remaining dimensions (frequency, time, mode indices). + """ + + AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) + current = self.compute_integral(field="H", em_field=em_field) + return _make_current_data_array(current) + + +class CompositeCurrentIntegral(CompositeCurrentIntegralSpec): + """Current integral comprising one or more disjoint paths + + Example + ------- + >>> spec1 = AxisAlignedCurrentIntegralSpec(center=(0, 0, 0), size=(1, 1, 0), sign="+") + >>> spec2 = AxisAlignedCurrentIntegralSpec(center=(2, 0, 0), size=(1, 1, 0), sign="+") + >>> composite = CompositeCurrentIntegral(path_specs=(spec1, spec2), sum_spec="sum") + """ + + @cached_property + def current_integrals( + self, + ) -> tuple[Union[AxisAlignedCurrentIntegral, Custom2DCurrentIntegral], ...]: + """ "Collection of closed current path integrals.""" + from tidy3d.components.microwave.path_integrals.factory import ( + make_current_integral, + ) + + current_integrals = [make_current_integral(path_spec) for path_spec in self.path_specs] + return current_integrals + + def compute_current(self, em_field: IntegrableMonitorDataType) -> IntegralResultType: + """Compute current flowing in loop defined by the outer edge of a rectangle.""" + if isinstance(em_field, FieldTimeData) and self.sum_spec == "split": + raise DataError( + "Only frequency domain field data is supported when using the 'split' sum_spec. " + "Either switch the sum_spec to 'sum' or supply frequency domain data." + ) + + current_integrals = self.current_integrals + + # Calculate currents from each path integral and store in dataarray with path index dimension + path_currents = [] + for path in current_integrals: + term = path.compute_current(em_field) + path_currents.append(term) + + # Stack all path currents along a new 'path_index' dimension + path_currents_array = xr.concat(path_currents, dim="path_index") + path_currents_array = path_currents_array.assign_coords( + path_index=range(len(path_currents)) + ) + + # Initialize output arrays with zeros + first_term = path_currents[0] + current_in_phase = xr.zeros_like(first_term) + current_out_phase = xr.zeros_like(first_term) + + # Choose phase reference for each frequency using phase from current with largest magnitude + path_magnitudes = np.abs(path_currents_array) + max_magnitude_indices = path_magnitudes.argmax(dim="path_index") + + # Get the phase reference for each frequency from the path resulting in the largest magnitude current + phase_reference = xr.zeros_like(first_term.angle) + for freq_idx in range(len(first_term.f.values)): + if hasattr(first_term, "mode_index"): + max_path_indices = max_magnitude_indices.isel(f=freq_idx).values + for mode_idx in range(len(first_term.mode_index.values)): + max_path_idx = max_path_indices[mode_idx] + phase_reference[freq_idx, mode_idx] = path_currents_array.isel( + path_index=max_path_idx, f=freq_idx, mode_index=mode_idx + ).angle.values + else: + max_path_idx = max_magnitude_indices.isel(f=freq_idx).values + phase_reference[freq_idx] = path_currents_array.isel( + path_index=max_path_idx, f=freq_idx + ).angle.values + + # Perform phase splitting into in and out of phase for each frequency separately + for term in path_currents: + if np.all(term.abs == 0): + continue + + # Compare phase to reference for each frequency + phase_diff = term.angle - phase_reference + # Wrap phase difference to [-pi, pi] + phase_diff.values = np.mod(phase_diff.values + np.pi, 2 * np.pi) - np.pi + + # Add to in-phase or out-of-phase current based on phase difference + is_in_phase = np.abs(phase_diff) <= np.pi / 2 + current_in_phase += xr.where(is_in_phase, term, 0) + current_out_phase += xr.where(~is_in_phase, term, 0) + + current_in_phase = _make_current_data_array(current_in_phase) + current_out_phase = _make_current_data_array(current_out_phase) + + if self.sum_spec == "sum": + return current_in_phase + current_out_phase + + # For split mode, return the larger magnitude current + current = xr.where( + abs(current_in_phase) >= abs(current_out_phase), current_in_phase, current_out_phase + ) + return _make_current_data_array(current) + + def _check_phase_sign_consistency( + self, + phase_difference: Union[FreqDataArray, FreqModeDataArray], + ) -> bool: + """ + Check that the provided current data has a consistent phase with respect to the reference + phase. A consistent phase allows for the automatic identification of currents flowing in + opposite directions. However, when the provided data does not correspond with a transmission + line mode, this consistent phase condition will likely fail, so we emit a warning here to + notify the user. + """ + + # Check phase consistency across frequencies + freq_axis = phase_difference.get_axis_num("f") + all_in_phase = np.all(abs(phase_difference) <= np.pi / 2, axis=freq_axis) + all_out_of_phase = np.all(abs(phase_difference) > np.pi / 2, axis=freq_axis) + consistent_phase = np.logical_or(all_in_phase, all_out_of_phase) + + if not np.all(consistent_phase) and self.sum_spec == "split": + warning_msg = ( + "Phase alignment of computed current is not consistent across frequencies. " + "The provided fields are not suitable for the 'split' method of computing current. " + "Please provide the current path specifications manually." + ) + + if isinstance(phase_difference, FreqModeDataArray): + inconsistent_modes = [] + mode_indices = phase_difference.mode_index.values + for mode_idx in range(len(mode_indices)): + if not consistent_phase[mode_idx]: + inconsistent_modes.append(mode_idx) + + warning_msg += ( + f" Modes with indices {inconsistent_modes} violated the phase consistency " + "requirement." + ) + + log.warning(warning_msg) + + return False + return True + + def _check_phase_amplitude_consistency( + self, + current_in_phase: Union[FreqDataArray, FreqModeDataArray], + current_out_phase: Union[FreqDataArray, FreqModeDataArray], + ) -> bool: + """ + Check that the summed in phase and out of phase components of current have a consistent relative amplitude. + A consistent amplitude across frequencies allows for the automatic identification of the total conduction + current flowing in the transmission line. If the amplitudes are not consistent, we emit a warning. + """ + + # For split mode, return the larger magnitude current + freq_axis = current_in_phase.get_axis_num("f") + in_all_larger = np.all(abs(current_in_phase) >= abs(current_out_phase), axis=freq_axis) + in_all_smaller = np.all(abs(current_in_phase) < abs(current_out_phase), axis=freq_axis) + consistent_max_current = np.logical_or(in_all_larger, in_all_smaller) + if not np.all(consistent_max_current) and self.sum_spec == "split": + warning_msg = ( + "There is not a consistently larger current across frequencies between the in-phase " + "and out-of-phase components. The provided fields are not suitable for the " + "'split' method of computing current. Please provide the current path " + "specifications manually." + ) + + if isinstance(current_in_phase, FreqModeDataArray): + inconsistent_modes = [] + mode_indices = current_in_phase.mode_index.values + for mode_idx in range(len(mode_indices)): + if not consistent_max_current[mode_idx]: + inconsistent_modes.append(int(mode_indices[mode_idx])) + + warning_msg += ( + f" Modes with indices {inconsistent_modes} violated the amplitude consistency " + "requirement." + ) + + log.warning(warning_msg) + + return False + return True diff --git a/tidy3d/components/microwave/path_integrals/integrals/voltage.py b/tidy3d/components/microwave/path_integrals/integrals/voltage.py new file mode 100644 index 0000000000..3d803a5a49 --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/integrals/voltage.py @@ -0,0 +1,91 @@ +"""Voltage integral classes""" + +from __future__ import annotations + +from tidy3d.components.data.data_array import ( + VoltageIntegralResultType, + _make_voltage_data_array, +) +from tidy3d.components.microwave.path_integrals.integrals.base import ( + AxisAlignedPathIntegral, + Custom2DPathIntegral, + IntegrableMonitorDataType, +) +from tidy3d.components.microwave.path_integrals.specs.voltage import ( + AxisAlignedVoltageIntegralSpec, + Custom2DVoltageIntegralSpec, +) + + +class AxisAlignedVoltageIntegral(AxisAlignedPathIntegral, AxisAlignedVoltageIntegralSpec): + """Class for computing the voltage between two points defined by an axis-aligned line. + + Example + ------- + >>> voltage = AxisAlignedVoltageIntegral( + ... center=(0, 0, 0), + ... size=(0, 0, 2), + ... sign="+", + ... extrapolate_to_endpoints=True, + ... snap_path_to_grid=True, + ... ) + """ + + def compute_voltage(self, em_field: IntegrableMonitorDataType) -> VoltageIntegralResultType: + """Compute voltage along path defined by a line.""" + + self._check_monitor_data_supported(em_field=em_field) + e_component = "xyz"[self.main_axis] + field_name = f"E{e_component}" + # Validate that fields are present + em_field._check_fields_stored([field_name]) + e_field = em_field.field_components[field_name] + + voltage = self.compute_integral(e_field) + + if self.sign == "+": + voltage *= -1 + + return _make_voltage_data_array(voltage) + + +class Custom2DVoltageIntegral(Custom2DPathIntegral, Custom2DVoltageIntegralSpec): + """Class for computing the voltage between two points defined by a custom path. + Computed voltage is :math:`V=V_b-V_a`, where position b is the final vertex in the supplied path. + + Notes + ----- + + Use :class:`.AxisAlignedVoltageIntegral` if possible, since interpolation + near conductors will not be accurate. + + .. TODO Improve by including extrapolate_to_endpoints field, non-trivial extension. + + Example + ------- + >>> import numpy as np + >>> vertices = np.array([[0, 0], [0.5, 0.2], [1.0, 0.5]]) + >>> voltage = Custom2DVoltageIntegral( + ... axis=2, + ... position=0.0, + ... vertices=vertices, + ... ) + """ + + def compute_voltage(self, em_field: IntegrableMonitorDataType) -> VoltageIntegralResultType: + """Compute voltage along path defined by a line. + + Parameters + ---------- + em_field : :class:`.IntegrableMonitorDataType` + The electromagnetic field data that will be used for integrating. + + Returns + ------- + :class:`.VoltageIntegralResultType` + Result of voltage computation over remaining dimensions (frequency, time, mode indices). + """ + + AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) + voltage = -1.0 * self.compute_integral(field="E", em_field=em_field) + return _make_voltage_data_array(voltage) diff --git a/tidy3d/components/microwave/path_integrals/mode_plane_analyzer.py b/tidy3d/components/microwave/path_integrals/mode_plane_analyzer.py new file mode 100644 index 0000000000..292ec9084c --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/mode_plane_analyzer.py @@ -0,0 +1,365 @@ +"""Helper class for analyzing conductor geometry in a mode plane.""" + +from __future__ import annotations + +from itertools import chain +from math import isclose + +import pydantic.v1 as pd +import shapely +from shapely.geometry import LineString, Polygon + +from tidy3d.components.base import cached_property +from tidy3d.components.geometry.base import Box, Geometry +from tidy3d.components.geometry.utils import ( + SnapBehavior, + SnapLocation, + SnappingSpec, + flatten_shapely_geometries, + merging_geometries_on_plane, + snap_box_to_grid, +) +from tidy3d.components.grid.grid import Grid +from tidy3d.components.medium import LossyMetalMedium, Medium +from tidy3d.components.structure import Structure +from tidy3d.components.types import Axis, Bound, Coordinate, Shapely, Symmetry +from tidy3d.components.validators import assert_plane +from tidy3d.exceptions import SetupError + + +class ModePlaneAnalyzer(Box): + """Analyzes conductor geometry intersecting a mode plane. + + This class analyzes the geometry of conductors in a simulation cross-section and is for internal use. + """ + + _plane_validator = assert_plane() + + field_data_colocated: bool = pd.Field( + False, + title="Field Data Colocated", + description="Whether field data is colocated with grid points. When 'True', bounding boxes " + "are placed with additional margin to avoid interpolated field values near conductor surfaces.", + ) + + @cached_property + def _snap_spec(self) -> SnappingSpec: + """Creates snapping specification for bounding boxes.""" + behavior = [SnapBehavior.StrictExpand] * 3 + location = [SnapLocation.Center] * 3 + behavior[self._normal_axis] = SnapBehavior.Off + # To avoid interpolated H field near metal surface + margin = (2, 2, 2) if self.field_data_colocated else (0, 0, 0) + return SnappingSpec(location=location, behavior=behavior, margin=margin) + + def _get_mode_symmetry( + self, sim_box: Box, sym_symmetry: tuple[Symmetry, Symmetry, Symmetry] + ) -> tuple[Symmetry, Symmetry, Symmetry]: + """Get the mode symmetry, considering the simulation box and the simulation symmetry.""" + mode_symmetry = list(sym_symmetry) + for dim in range(3): + if sim_box.center[dim] != self.center[dim] or self.size[dim] == 0: + mode_symmetry[dim] = 0 + return mode_symmetry + + def _get_mode_limits( + self, sim_grid: Grid, mode_symmetry: tuple[Symmetry, Symmetry, Symmetry] + ) -> Bound: + """Restrict mode plane bounds to the final grid positions taking into account symmetry conditions. + + Mode profiles are calculated on a grid which is expanded from the monitor size to the closest grid boundaries. + """ + behavior = [SnapBehavior.StrictExpand] * 3 + location = [SnapLocation.Boundary] * 3 + behavior[self._normal_axis] = SnapBehavior.Off + margin = (1, 1, 1) + snap_spec = SnappingSpec(location=location, behavior=behavior, margin=margin) + mode_box = snap_box_to_grid(sim_grid, self.geometry, snap_spec=snap_spec) + min_b, max_b = mode_box.bounds + min_b_2d_list = list(min_b) + for dim in range(3): + if mode_symmetry[dim] != 0: + min_b_2d_list[dim] = self.center[dim] + + return (tuple(min_b_2d_list), max_b) + + def _get_isolated_conductors_as_shapely( + self, + plane: Box, + structures: list[Structure], + ) -> list[Shapely]: + """Find and merge all conductor structures that intersect the given plane. + + Parameters + ---------- + plane : Box + The plane to check for conductor intersections + structures : list[Structure] + List of all simulation structures to analyze + + Returns + ------- + list[Shapely] + List of merged conductor geometries as Shapely Polygons and LineStrings + that intersect with the given plane + """ + + def is_conductor(med: Medium) -> bool: + return med.is_pec or isinstance(med, LossyMetalMedium) + + geometry_list = [structure.geometry for structure in structures] + # For metal, we don't distinguish between LossyMetal and PEC, + # so they'll be merged to PEC. Other materials are considered as dielectric. + prop_list = [is_conductor(structure.medium) for structure in structures] + # merge geometries + geos = merging_geometries_on_plane(geometry_list, plane, prop_list) + conductor_geos = [item[1] for item in geos if item[0]] + shapely_list = flatten_shapely_geometries(conductor_geos, keep_types=(Polygon, LineString)) + return shapely_list + + def _filter_conductors_touching_sim_bounds( + self, + mode_limits: Bound, + mode_symmetry_3d: tuple[Symmetry, Symmetry, Symmetry], + conductor_polygons: list[Shapely], + ) -> list[Shapely]: + """Filters a list of Shapely geometries representing conductors in the mode plane. PEC-type boundary + conditions act like a short to ground, so any structures touching a PEC boundary can be ignored + from the current calculation. + + Parameters + ---------- + mode_limits : Bound + The locations of the boundary conditions. + mode_symmetry_3d : tuple[Symmetry, Symmetry, Symmetry] + Symmetry settings for the mode solver plane. + conductor_polygons : list[Shapely] + List of shapely geometries (polygons/lines) representing the exterior of conducting + structures in the mode plane. + + Returns + ------- + list[Shapely] + The filtered list of shapely geometries, where structures "shorted" to PEC boundaries have been removed. + """ + min_b_3d, max_b_3d = mode_limits[0], mode_limits[1] + _, mode_symmetry = Geometry.pop_axis(mode_symmetry_3d, self._normal_axis) + _, min_b = Geometry.pop_axis(min_b_3d, self._normal_axis) + _, max_b = Geometry.pop_axis(max_b_3d, self._normal_axis) + + # Add top, right, left, bottom + shapely_pec_bounds = [ + shapely.LineString([(min_b[0], max_b[1]), (max_b[0], max_b[1])]), + shapely.LineString([(max_b[0], min_b[1]), (max_b[0], max_b[1])]), + shapely.LineString([(min_b[0], min_b[1]), (min_b[0], max_b[1])]), + shapely.LineString([(min_b[0], min_b[1]), (max_b[0], min_b[1])]), + ] + + # If bottom bound is PMC remove + if mode_symmetry[1] == 1: + shapely_pec_bounds.pop(3) + + # If left bound is PMC remove + if mode_symmetry[0] == 1: + shapely_pec_bounds.pop(2) + + ml_pec_bounds = shapely.MultiLineString(shapely_pec_bounds) + return [shape for shape in conductor_polygons if not ml_pec_bounds.intersects(shape)] + + def get_conductor_bounding_boxes( + self, + structures: list[Structure], + grid: Grid, + symmetry: tuple[Symmetry, Symmetry, Symmetry], + sim_box: Box, + ) -> tuple[list[Box], list[Shapely]]: + """Returns bounding boxes that encompass each isolated conductor + in the mode plane. + + This method identifies isolated conductor geometries in the given plane. + The paths are snapped to the simulation grid + to ensure alignment with field data. + + Parameters + ---------- + structures : list + List of structures in the simulation. + grid : Grid + Simulation grid for snapping paths. + symmetry : tuple[Symmetry, Symmetry, Symmetry] + Symmetry conditions for the simulation in (x, y, z) directions. + sim_box : Box + Simulation domain box used for boundary conditions. + + Returns + ------- + tuple[list[Box], list[Shapely]] + Bounding boxes and list of merged conductor geometries. + """ + + def bounding_box_from_shapely(geom: Shapely) -> Box: + """Helper to convert the shapely geometry bounds to a Box.""" + bounds = geom.bounds + normal_center = self.center[self._normal_axis] + rmin = Geometry.unpop_axis(normal_center, (bounds[0], bounds[1]), self._normal_axis) + rmax = Geometry.unpop_axis(normal_center, (bounds[2], bounds[3]), self._normal_axis) + return Box.from_bounds(rmin, rmax) + + mode_symmetry_3d = self._get_mode_symmetry(sim_box, symmetry) + min_b_3d, max_b_3d = self._get_mode_limits(grid, mode_symmetry_3d) + + intersection_plane = Box.from_bounds(min_b_3d, max_b_3d) + conductor_shapely = self._get_isolated_conductors_as_shapely(intersection_plane, structures) + + conductor_shapely = self._filter_conductors_touching_sim_bounds( + (min_b_3d, max_b_3d), mode_symmetry_3d, conductor_shapely + ) + + if len(conductor_shapely) < 1: + raise SetupError( + "No valid isolated conductors were found in the mode plane. Please ensure that a 'Structure' " + "with a medium of type 'PEC' or 'LossyMetalMedium' intersects the mode plane and is not touching " + "the boundaries of the mode plane." + ) + + # Get desired snapping behavior of box enclosed conductors. + # Ideally, just large enough to coincide with the H field positions outside of the conductor. + # So a half grid cell, when the metal boundary is coincident with grid boundaries. + snap_spec = self._snap_spec + + bounding_boxes = [] + for shape in conductor_shapely: + box = bounding_box_from_shapely(shape) + boxes = self._apply_symmetries(symmetry, sim_box.center, box) + for box in boxes: + box_snapped = snap_box_to_grid(grid, box, snap_spec) + bounding_boxes.append(box_snapped) + + for bounding_box in bounding_boxes: + if self._check_box_intersects_with_conductors(conductor_shapely, bounding_box): + raise SetupError( + "Failed to automatically generate path specification because a generated path " + "specification was found to intersect with a conductor. There is currently limited " + "support for complex conductor geometries, so please provide an explicit current " + "path specification through a 'CustomImpedanceSpec'. Alternatively, enforce a " + "smaller grid around the conductors in the mode plane, which may resolve the issue." + ) + return bounding_boxes, conductor_shapely + + def _check_box_intersects_with_conductors( + self, shapely_list: list[Shapely], bounding_box: Box + ) -> bool: + """Makes sure that a box does not intersect with conductor shapes. + + Parameters + ---------- + shapely_list : list[Shapely] + Merged conductor geometries, expected to be polygons or lines for 2D structures. + bounding_box : Box + Box corresponding with a future path specification. + + Returns + ------- + bool: ``True`` if the bounding box intersects with any conductor geometry, ``False`` otherwise. + """ + min_b, max_b = bounding_box.bounds + _, min_b = Geometry.pop_axis(min_b, self._normal_axis) + _, max_b = Geometry.pop_axis(max_b, self._normal_axis) + path_shapely = shapely.box(min_b[0], min_b[1], max_b[0], max_b[1]) + for shapely_geo in shapely_list: + if path_shapely.intersects(shapely_geo) and not path_shapely.contains(shapely_geo): + return True + return False + + @staticmethod + def _reflect_box(box: Box, axis: Axis, position: float) -> Box: + """Reflects a box across a plane perpendicular to the given axis at the specified position. + + Parameters + ---------- + box : Box + The box to reflect. + axis : Axis + The axis perpendicular to the reflection plane (0,1,2) -> (x,y,z). + position : float + Position along the axis where the reflection plane is located. + + Returns + ------- + Box + The reflected box. + """ + new_center = list(box.center) + new_center[axis] = 2 * position - box.center[axis] + return box.updated_copy(center=new_center) + + @staticmethod + def _apply_symmetry_to_box(box: Box, axis: Axis, position: float) -> list[Box]: + """Applies a single symmetry condition to a box along a specified axis. + + If the box touches the symmetry plane, merges the box with its reflection. + Otherwise returns both the original and reflected box. + + Parameters + ---------- + box : Box + The box that will be reflected. + axis : Axis + The axis along which to apply symmetry (0,1,2) -> (x,y,z). + position : float + Position of the symmetry plane along the axis. + + Returns + ------- + list[Box] + List containing either merged box or original and reflected boxes + """ + new_box = ModePlaneAnalyzer._reflect_box(box, axis, position) + if isclose(new_box.bounds[0][axis], box.bounds[1][axis]) or isclose( + new_box.bounds[1][axis], box.bounds[0][axis] + ): + new_size = list(box.size) + new_size[axis] = 2 * box.size[axis] + new_center = list(box.center) + new_center[axis] = position + new_box = Box(size=new_size, center=new_center) + return [new_box] + return [box, new_box] + + def _apply_symmetries( + self, + symmetry: tuple[Symmetry, Symmetry, Symmetry], + sim_center: Coordinate, + box: Box, + ) -> list[Box]: + """Applies symmetry conditions to the location of a box. When a symmetry condition is present, + the box will be reflected. If the reflection is touching the original box, they will be merged. + + Parameters + ---------- + symmetry : tuple[Symmetry, Symmetry, Symmetry] + Symmetry conditions for each axis. + sim_center : Coordinate + Center coordinates where symmetry planes intersect. + box : Box + The box that will be reflected. + + Returns + ------- + list[Box] + List of boxes after applying all symmetry operations, so a list with either 1, 2, + or 4 Box elements + """ + symmetry = list(symmetry) + dims = [0, 1, 2] + symmetry.pop(self._normal_axis) + dims.pop(self._normal_axis) + result = [box] + for dim, sym in zip(dims, symmetry): + if sym != 0: + tmp_list = [ + ModePlaneAnalyzer._apply_symmetry_to_box(box, dim, sim_center[dim]) + for box in result + ] + result = list(chain.from_iterable(tmp_list)) + return result diff --git a/tidy3d/components/microwave/path_integrals/specs/__init__.py b/tidy3d/components/microwave/path_integrals/specs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tidy3d/components/microwave/path_integrals/specs/base.py b/tidy3d/components/microwave/path_integrals/specs/base.py new file mode 100644 index 0000000000..d012360b23 --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/specs/base.py @@ -0,0 +1,259 @@ +"""Module containing specifications for path integrals.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod + +import numpy as np +import pydantic.v1 as pd +import shapely +import xarray as xr +from typing_extensions import Self + +from tidy3d.components.base import cached_property +from tidy3d.components.geometry.base import Box, Geometry +from tidy3d.components.microwave.base import MicrowaveBaseModel +from tidy3d.components.types import ArrayFloat2D, Bound, Coordinate, Coordinate2D +from tidy3d.components.types.base import Axis, Direction +from tidy3d.components.validators import assert_line +from tidy3d.constants import MICROMETER, fp_eps +from tidy3d.exceptions import SetupError + + +class AbstractAxesRH(MicrowaveBaseModel, ABC): + """Represents an axis-aligned right-handed coordinate system with one axis preferred. + Typically `main_axis` would refer to the normal axis of a plane. + """ + + @cached_property + @abstractmethod + def main_axis(self) -> Axis: + """Get the preferred axis.""" + + @cached_property + def remaining_axes(self) -> tuple[Axis, Axis]: + """Get in-plane axes, ordered to maintain a right-handed coordinate system.""" + axes: list[Axis] = [0, 1, 2] + axes.pop(self.main_axis) + if self.main_axis == 1: + return (axes[1], axes[0]) + else: + return (axes[0], axes[1]) + + @cached_property + def remaining_dims(self) -> tuple[str, str]: + """Get in-plane dimensions, ordered to maintain a right-handed coordinate system.""" + dim1 = "xyz"[self.remaining_axes[0]] + dim2 = "xyz"[self.remaining_axes[1]] + return (dim1, dim2) + + @cached_property + def local_dims(self) -> tuple[str, str, str]: + """Get in-plane dimensions with in-plane dims first, followed by the `main_axis` dimension.""" + dim3 = "xyz"[self.main_axis] + return self.remaining_dims + tuple(dim3) + + +class AxisAlignedPathIntegralSpec(AbstractAxesRH, Box): + """Class for defining the simplest type of path integral, which is aligned with Cartesian axes. + + Example + ------- + >>> path_spec = AxisAlignedPathIntegralSpec( + ... center=(0, 0, 1), + ... size=(0, 0, 2), + ... extrapolate_to_endpoints=True + ... ) + """ + + _line_validator = assert_line() + + extrapolate_to_endpoints: bool = pd.Field( + False, + title="Extrapolate to Endpoints", + description="If the endpoints of the path integral terminate at or near a material interface, " + "the field is likely discontinuous. When this field is ``True``, fields that are outside and on the bounds " + "of the integral are ignored. Should be enabled when computing voltage between two conductors.", + ) + + snap_path_to_grid: bool = pd.Field( + False, + title="Snap Path to Grid", + description="It might be desirable to integrate exactly along the Yee grid associated with " + "a field. When this field is ``True``, the integration path will be snapped to the grid.", + ) + + @cached_property + def main_axis(self) -> Axis: + """Axis for performing integration.""" + for index, value in enumerate(self.size): + if value != 0: + return index + + def _vertices_2D(self, axis: Axis) -> tuple[Coordinate2D, Coordinate2D]: + """Returns the two vertices of this path in the plane defined by ``axis``.""" + min = self.bounds[0] + max = self.bounds[1] + _, min = Box.pop_axis(min, axis) + _, max = Box.pop_axis(max, axis) + + u = [min[0], max[0]] + v = [min[1], max[1]] + return (u, v) + + +class Custom2DPathIntegralSpec(AbstractAxesRH): + """Class for specifying a custom path integral defined as a curve on an axis-aligned plane. + + Example + ------- + >>> import numpy as np + >>> vertices = np.array([[0, 0], [1, 0], [1, 1], [0, 1]]) + >>> path_spec = Custom2DPathIntegralSpec( + ... axis=2, + ... position=0.5, + ... vertices=vertices + ... ) + + Notes + ----- + + Given a set of vertices :math:`\\vec{r}_i`, this class approximates path integrals over + vector fields of the form :math:`\\int{\\vec{F} \\cdot \\vec{dl}}` + as :math:`\\sum_i{\\vec{F}(\\vec{r}_i) \\cdot \\vec{dl}_i}`, + where the differential length :math:`\\vec{dl}` is approximated using central differences + :math:`\\vec{dl}_i = \\frac{\\vec{r}_{i+1} - \\vec{r}_{i-1}}{2}`. + If the path is not closed, forward and backward differences are used at the endpoints. + """ + + axis: Axis = pd.Field( + ..., title="Axis", description="Specifies dimension of the planar axis (0,1,2) -> (x,y,z)." + ) + + position: float = pd.Field( + ..., + title="Position", + description="Position of the plane along the ``axis``.", + ) + + vertices: ArrayFloat2D = pd.Field( + ..., + title="Vertices", + description="List of (d1, d2) defining the 2 dimensional positions of the path. " + "The index of dimension should be in the ascending order, which means " + "if the axis corresponds with ``y``, the coordinates of the vertices should be (x, z). " + "If you wish to indicate a closed contour, the final vertex should be made " + "equal to the first vertex, i.e., ``vertices[-1] == vertices[0]``", + units=MICROMETER, + ) + + @staticmethod + def _compute_dl_component(coord_array: xr.DataArray, closed_contour=False) -> np.ndarray: + """Computes the differential length element along the integration path.""" + dl = np.gradient(coord_array) + if closed_contour: + # If the contour is closed, we can use central difference on the starting/end point + # which will be more accurate than the default forward/backward choice in np.gradient + grad_end = np.gradient([coord_array[-2], coord_array[0], coord_array[1]]) + dl[0] = dl[-1] = grad_end[1] + return dl + + @classmethod + def from_circular_path( + cls, center: Coordinate, radius: float, num_points: int, normal_axis: Axis, clockwise: bool + ) -> Self: + """Creates a ``Custom2DPathIntegralSpec`` from a circular path given a desired number of points + along the perimeter. + + Parameters + ---------- + center : Coordinate + The center of the circle. + radius : float + The radius of the circle. + num_points : int + The number of equidistant points to use along the perimeter of the circle. + normal_axis : Axis + The axis normal to the defined circle. + clockwise : bool + When ``True``, the points will be ordered clockwise with respect to the positive + direction of the ``normal_axis``. + + Returns + ------- + :class:`.Custom2DPathIntegralSpec` + A path integral defined on a circular path. + """ + + def generate_circle_coordinates(radius: float, num_points: int, clockwise: bool): + """Helper for generating x,y vertices around a circle in the local coordinate frame.""" + sign = 1.0 + if clockwise: + sign = -1.0 + angles = np.linspace(0, sign * 2 * np.pi, num_points, endpoint=True) + xt = radius * np.cos(angles) + yt = radius * np.sin(angles) + return (xt, yt) + + # Get transverse axes + normal_center, trans_center = Geometry.pop_axis(center, normal_axis) + + # These x,y coordinates in the local coordinate frame + if normal_axis == 1: + # Handle special case when y is the axis that is popped + clockwise = not clockwise + xt, yt = generate_circle_coordinates(radius, num_points, clockwise) + xt += trans_center[0] + yt += trans_center[1] + circle_vertices = np.column_stack((xt, yt)) + # Close the contour exactly + circle_vertices[-1, :] = circle_vertices[0, :] + return cls(axis=normal_axis, position=normal_center, vertices=circle_vertices) + + @cached_property + def is_closed_contour(self) -> bool: + """Returns ``true`` when the first vertex equals the last vertex.""" + return np.isclose( + self.vertices[0, :], + self.vertices[-1, :], + rtol=fp_eps, + atol=np.finfo(np.float32).smallest_normal, + ).all() + + @cached_property + def main_axis(self) -> Axis: + """Axis for performing integration.""" + return self.axis + + @pd.validator("vertices", always=True) + def _correct_shape(cls, val): + """Makes sure vertices size is correct.""" + # overall shape of vertices + if val.shape[1] != 2: + raise SetupError( + "'Custom2DPathIntegralSpec.vertices' must be a 2 dimensional array shaped (N, 2). " + f"Given array with shape of '{val.shape}'." + ) + return val + + @cached_property + def bounds(self) -> Bound: + """Helper to get the geometric bounding box of the path integral.""" + path_min = np.amin(self.vertices, axis=0) + path_max = np.amax(self.vertices, axis=0) + min_bound = Geometry.unpop_axis(self.position, path_min, self.axis) + max_bound = Geometry.unpop_axis(self.position, path_max, self.axis) + return (min_bound, max_bound) + + @cached_property + def sign(self) -> Direction: + """Uses the ordering of the vertices to determine the direction of the current flow.""" + linestr = shapely.LineString(coordinates=self.vertices) + is_ccw = shapely.is_ccw(linestr) + # Invert statement when the vertices are given as (x, z) + if self.axis == 1: + is_ccw = not is_ccw + if is_ccw: + return "+" + else: + return "-" diff --git a/tidy3d/components/microwave/path_integrals/specs/current.py b/tidy3d/components/microwave/path_integrals/specs/current.py new file mode 100644 index 0000000000..8447902e47 --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/specs/current.py @@ -0,0 +1,374 @@ +"""Module containing specifications for current path integrals.""" + +from __future__ import annotations + +from typing import Literal, Optional, Union + +import numpy as np +import pydantic.v1 as pd + +from tidy3d.components.base import cached_property +from tidy3d.components.geometry.base import Box, Geometry +from tidy3d.components.microwave.base import MicrowaveBaseModel +from tidy3d.components.microwave.path_integrals.specs.base import ( + AbstractAxesRH, + AxisAlignedPathIntegralSpec, + Custom2DPathIntegralSpec, +) +from tidy3d.components.microwave.path_integrals.viz import ARROW_CURRENT, plot_params_current_path +from tidy3d.components.types import Ax +from tidy3d.components.types.base import Axis, Direction +from tidy3d.components.validators import assert_plane +from tidy3d.components.viz import add_ax_if_none +from tidy3d.constants import fp_eps +from tidy3d.exceptions import SetupError + + +class AxisAlignedCurrentIntegralSpec(AbstractAxesRH, Box): + """Class for specifying the computation of conduction current via Ampère's circuital law on an axis-aligned loop. + + Example + ------- + >>> current_spec = AxisAlignedCurrentIntegralSpec( + ... center=(0, 0, 0), + ... size=(1, 1, 0), + ... sign="+", + ... snap_contour_to_grid=True + ... ) + """ + + _plane_validator = assert_plane() + + sign: Direction = pd.Field( + ..., + title="Direction of Contour Integral", + description="Positive indicates current flowing in the positive normal axis direction.", + ) + + extrapolate_to_endpoints: bool = pd.Field( + False, + title="Extrapolate to Endpoints", + description="This parameter is passed to :class:`AxisAlignedPathIntegral` objects when computing the contour integral.", + ) + + snap_contour_to_grid: bool = pd.Field( + False, + title="Snap Contour to Grid", + description="This parameter is passed to :class:`AxisAlignedPathIntegral` objects when computing the contour integral.", + ) + + @cached_property + def main_axis(self) -> Axis: + """Axis normal to loop""" + for index, value in enumerate(self.size): + if value == 0: + return index + + def _to_path_integral_specs( + self, h_horizontal=None, h_vertical=None + ) -> tuple[AxisAlignedPathIntegralSpec, ...]: + """Returns four ``AxisAlignedPathIntegralSpec`` instances, which represent a contour + integral around the surface defined by ``self.size``.""" + ax1 = self.remaining_axes[0] + ax2 = self.remaining_axes[1] + + horizontal_passed = h_horizontal is not None + vertical_passed = h_vertical is not None + if self.snap_contour_to_grid and horizontal_passed and vertical_passed: + (coord1, coord2) = self.remaining_dims + + # Locations where horizontal paths will be snapped + v_bounds = [ + self.center[ax2] - self.size[ax2] / 2, + self.center[ax2] + self.size[ax2] / 2, + ] + h_snaps = h_horizontal.sel({coord2: v_bounds}, method="nearest").coords[coord2].values + # Locations where vertical paths will be snapped + h_bounds = [ + self.center[ax1] - self.size[ax1] / 2, + self.center[ax1] + self.size[ax1] / 2, + ] + v_snaps = h_vertical.sel({coord1: h_bounds}, method="nearest").coords[coord1].values + + bottom_bound = h_snaps[0] + top_bound = h_snaps[1] + left_bound = v_snaps[0] + right_bound = v_snaps[1] + else: + bottom_bound = self.bounds[0][ax2] + top_bound = self.bounds[1][ax2] + left_bound = self.bounds[0][ax1] + right_bound = self.bounds[1][ax1] + + # Horizontal paths + path_size = list(self.size) + path_size[ax1] = right_bound - left_bound + path_size[ax2] = 0 + path_center = list(self.center) + path_center[ax2] = bottom_bound + + bottom = AxisAlignedPathIntegralSpec( + center=path_center, + size=path_size, + extrapolate_to_endpoints=self.extrapolate_to_endpoints, + snap_path_to_grid=self.snap_contour_to_grid, + ) + path_center[ax2] = top_bound + top = AxisAlignedPathIntegralSpec( + center=path_center, + size=path_size, + extrapolate_to_endpoints=self.extrapolate_to_endpoints, + snap_path_to_grid=self.snap_contour_to_grid, + ) + + # Vertical paths + path_size = list(self.size) + path_size[ax1] = 0 + path_size[ax2] = top_bound - bottom_bound + path_center = list(self.center) + + path_center[ax1] = left_bound + left = AxisAlignedPathIntegralSpec( + center=path_center, + size=path_size, + extrapolate_to_endpoints=self.extrapolate_to_endpoints, + snap_path_to_grid=self.snap_contour_to_grid, + ) + path_center[ax1] = right_bound + right = AxisAlignedPathIntegralSpec( + center=path_center, + size=path_size, + extrapolate_to_endpoints=self.extrapolate_to_endpoints, + snap_path_to_grid=self.snap_contour_to_grid, + ) + + return (bottom, right, top, left) + + @add_ax_if_none + def plot( + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, + **path_kwargs, + ) -> Ax: + """Plot path integral at single (x,y,z) coordinate. + + Parameters + ---------- + x : float = None + Position of plane in x direction, only one of x,y,z can be specified to define plane. + y : float = None + Position of plane in y direction, only one of x,y,z can be specified to define plane. + z : float = None + Position of plane in z direction, only one of x,y,z can be specified to define plane. + ax : matplotlib.axes._subplots.Axes = None + Matplotlib axes to plot on, if not specified, one is created. + **path_kwargs + Optional keyword arguments passed to the matplotlib plotting of the line. + For details on accepted values, refer to + `Matplotlib's documentation `_. + + Returns + ------- + matplotlib.axes._subplots.Axes + The supplied or created matplotlib axes. + """ + axis, position = self.parse_xyz_kwargs(x=x, y=y, z=z) + if axis != self.main_axis or not np.isclose(position, self.center[axis], rtol=fp_eps): + return ax + + plot_params = plot_params_current_path.include_kwargs(**path_kwargs) + plot_kwargs = plot_params.to_kwargs() + path_integrals = self._to_path_integral_specs() + # Plot the path + for path in path_integrals: + (xs, ys) = path._vertices_2D(axis) + ax.plot(xs, ys, **plot_kwargs) + + (ax1, ax2) = self.remaining_axes + + # Add arrow to bottom path, unless right path is longer + arrow_path = path_integrals[0] + if self.size[ax2] > self.size[ax1]: + arrow_path = path_integrals[1] + + (xs, ys) = arrow_path._vertices_2D(axis) + X = (xs[0] + xs[1]) / 2 + Y = (ys[0] + ys[1]) / 2 + center = np.array([X, Y]) + dx = xs[1] - xs[0] + dy = ys[1] - ys[0] + direction = np.array([dx, dy]) + segment_length = np.linalg.norm(direction) + unit_dir = direction / segment_length + + # Change direction of arrow depending on sign of current definition + if self.sign == "-": + unit_dir *= -1.0 + # Change direction of arrow when the "y" axis is dropped, + # since the plotted coordinate system will be left-handed (x, z) + if self.main_axis == 1: + unit_dir *= -1.0 + + start = center - unit_dir * segment_length + end = center + ax.annotate( + "", + xytext=(start[0], start[1]), + xy=(end[0], end[1]), + arrowprops=ARROW_CURRENT, + ) + return ax + + +class Custom2DCurrentIntegralSpec(Custom2DPathIntegralSpec): + """Class for specifying the computation of conduction current via Ampère's circuital law on a custom path. + To compute the current flowing in the positive ``axis`` direction, the vertices should be + ordered in a counterclockwise direction. + + Example + ------- + >>> import numpy as np + >>> vertices = np.array([[0, 0], [2, 0], [2, 1], [0, 1], [0, 0]]) + >>> current_spec = Custom2DCurrentIntegralSpec( + ... axis=2, + ... position=0, + ... vertices=vertices + ... ) + """ + + @add_ax_if_none + def plot( + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, + **path_kwargs, + ) -> Ax: + """Plot path integral at single (x,y,z) coordinate. + + Parameters + ---------- + x : float = None + Position of plane in x direction, only one of x,y,z can be specified to define plane. + y : float = None + Position of plane in y direction, only one of x,y,z can be specified to define plane. + z : float = None + Position of plane in z direction, only one of x,y,z can be specified to define plane. + ax : matplotlib.axes._subplots.Axes = None + Matplotlib axes to plot on, if not specified, one is created. + **path_kwargs + Optional keyword arguments passed to the matplotlib plotting of the line. + For details on accepted values, refer to + `Matplotlib's documentation `_. + + Returns + ------- + matplotlib.axes._subplots.Axes + The supplied or created matplotlib axes. + """ + axis, position = Geometry.parse_xyz_kwargs(x=x, y=y, z=z) + if axis != self.main_axis or not np.isclose(position, self.position, rtol=fp_eps): + return ax + + plot_params = plot_params_current_path.include_kwargs(**path_kwargs) + plot_kwargs = plot_params.to_kwargs() + xs = self.vertices[:, 0] + ys = self.vertices[:, 1] + ax.plot(xs, ys, **plot_kwargs) + + # Add arrow at start of contour + ax.annotate( + "", + xytext=(xs[0], ys[0]), + xy=(xs[1], ys[1]), + arrowprops=ARROW_CURRENT, + ) + return ax + + +class CompositeCurrentIntegralSpec(MicrowaveBaseModel): + """Specification for a composite current integral. + + This class is used to set up a ``CompositeCurrentIntegral``, which combines + multiple current integrals. It does not perform any integration itself. + + Example + ------- + >>> spec1 = AxisAlignedCurrentIntegralSpec( + ... center=(0, 0, 0), size=(1, 1, 0), sign="+" + ... ) + >>> spec2 = AxisAlignedCurrentIntegralSpec( + ... center=(2, 0, 0), size=(1, 1, 0), sign="+" + ... ) + >>> composite_spec = CompositeCurrentIntegralSpec( + ... path_specs=(spec1, spec2), + ... sum_spec="sum" + ... ) + """ + + path_specs: tuple[Union[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( + ..., + title="Sum Specification", + description="Determines the method used to combine the currents calculated by the different " + "current integrals defined by ``path_specs``. ``sum`` simply adds all currents, while ``split`` " + "keeps contributions with opposite phase separate, which allows for isolating the current " + "flowing in opposite directions. In ``split`` version, the current returned is the maximum " + "of the two contributions.", + ) + + @add_ax_if_none + def plot( + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, + **path_kwargs, + ) -> Ax: + """Plot path integral at single (x,y,z) coordinate. + + Parameters + ---------- + x : float = None + Position of plane in x direction, only one of x,y,z can be specified to define plane. + y : float = None + Position of plane in y direction, only one of x,y,z can be specified to define plane. + z : float = None + Position of plane in z direction, only one of x,y,z can be specified to define plane. + ax : matplotlib.axes._subplots.Axes = None + Matplotlib axes to plot on, if not specified, one is created. + **path_kwargs + Optional keyword arguments passed to the matplotlib plotting of the line. + For details on accepted values, refer to + `Matplotlib's documentation `_. + + Returns + ------- + matplotlib.axes._subplots.Axes + The supplied or created matplotlib axes. + """ + for path_spec in self.path_specs: + ax = path_spec.plot(x=x, y=y, z=z, ax=ax, **path_kwargs) + return ax + + @pd.validator("path_specs", always=True) + def _path_specs_not_empty(cls, val): + """Makes sure at least one path spec has been supplied""" + # overall shape of vertices + if len(val) < 1: + raise SetupError( + "'CompositeCurrentIntegralSpec.path_specs' must be a list of one or more current integrals. " + ) + return val diff --git a/tidy3d/components/microwave/path_integrals/specs/impedance.py b/tidy3d/components/microwave/path_integrals/specs/impedance.py new file mode 100644 index 0000000000..cdffbdffba --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/specs/impedance.py @@ -0,0 +1,82 @@ +"""Specification for impedance computation in transmission lines and waveguides.""" + +from __future__ import annotations + +from typing import Optional, Union + +import pydantic.v1 as pd + +from tidy3d.components.base import skip_if_fields_missing +from tidy3d.components.microwave.base import MicrowaveBaseModel +from tidy3d.components.microwave.path_integrals.types import ( + CurrentPathSpecType, + VoltagePathSpecType, +) +from tidy3d.exceptions import SetupError + + +class AutoImpedanceSpec(MicrowaveBaseModel): + """Specification for fully automatic transmission line impedance computation. + + This specification automatically calculates impedance by current + paths based on the simulation geometry and conductors that intersect the mode plane. + No user-defined path specifications are required. + """ + + +class CustomImpedanceSpec(MicrowaveBaseModel): + """Specification for custom transmission line voltages and currents in mode solvers. + + The :class:`.CustomImpedanceSpec` class specifies how quantities related to transmission line + modes are computed. It defines the paths for line integrals, which are used to + compute voltage, current, and characteristic impedance of the transmission line. + + Users must supply at least one of voltage or current path specifications to control where these integrals + are evaluated. Both voltage_spec and current_spec cannot be ``None`` simultaneously. + + Example + ------- + >>> from tidy3d.components.microwave.path_integrals.specs.voltage import AxisAlignedVoltageIntegralSpec + >>> from tidy3d.components.microwave.path_integrals.specs.current import AxisAlignedCurrentIntegralSpec + >>> voltage_spec = AxisAlignedVoltageIntegralSpec( + ... center=(0, 0, 0), size=(0, 0, 1), sign="+" + ... ) + >>> current_spec = AxisAlignedCurrentIntegralSpec( + ... center=(0, 0, 0), size=(2, 1, 0), sign="+" + ... ) + >>> impedance_spec = CustomImpedanceSpec( + ... voltage_spec=voltage_spec, + ... current_spec=current_spec + ... ) + """ + + voltage_spec: Optional[VoltagePathSpecType] = 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( + None, + title="Current Integration Path", + description="Path specification for computing the current associated with a mode profile.", + ) + + @pd.validator("current_spec", always=True) + @skip_if_fields_missing(["voltage_spec"]) + def check_path_spec_combinations(cls, val, values): + """Validate that at least one of voltage_spec or current_spec is provided. + + In order to define voltage/current/impedance, either a voltage or current path specification + must be provided. Both cannot be ``None`` simultaneously. + """ + + voltage_spec = values["voltage_spec"] + if val is None and voltage_spec is None: + raise SetupError( + "Not a valid 'CustomImpedanceSpec', the 'voltage_spec' and 'current_spec' cannot both be 'None'." + ) + return val + + +ImpedanceSpecType = Union[AutoImpedanceSpec, CustomImpedanceSpec] diff --git a/tidy3d/components/microwave/path_integrals/specs/voltage.py b/tidy3d/components/microwave/path_integrals/specs/voltage.py new file mode 100644 index 0000000000..340940f20b --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/specs/voltage.py @@ -0,0 +1,206 @@ +"""Module containing specifications for voltage path integrals.""" + +from __future__ import annotations + +from typing import Optional + +import numpy as np +import pydantic.v1 as pd +from typing_extensions import Self + +from tidy3d.components.geometry.base import Geometry +from tidy3d.components.microwave.path_integrals.specs.base import ( + AxisAlignedPathIntegralSpec, + Custom2DPathIntegralSpec, +) +from tidy3d.components.microwave.path_integrals.viz import ( + plot_params_voltage_minus, + plot_params_voltage_path, + plot_params_voltage_plus, +) +from tidy3d.components.types import Ax +from tidy3d.components.types.base import Direction +from tidy3d.components.viz import add_ax_if_none +from tidy3d.constants import fp_eps + + +class AxisAlignedVoltageIntegralSpec(AxisAlignedPathIntegralSpec): + """Class for specifying the voltage calculation between two points defined by an axis-aligned line.""" + + sign: Direction = pd.Field( + ..., + title="Direction of Path Integral", + description="Positive indicates V=Vb-Va where position b has a larger coordinate along the axis of integration.", + ) + + @classmethod + def from_terminal_positions( + cls, + plus_terminal: float, + minus_terminal: float, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + extrapolate_to_endpoints: bool = True, + snap_path_to_grid: bool = True, + ) -> Self: + """Helper to create a :class:`AxisAlignedVoltageIntegralSpec` from two coordinates that + define a line and two positions indicating the endpoints of the path integral. + + Parameters + ---------- + plus_terminal : float + Position along the voltage axis of the positive terminal. + minus_terminal : float + Position along the voltage axis of the negative terminal. + x : float = None + Position in x direction, only two of x,y,z can be specified to define line. + y : float = None + Position in y direction, only two of x,y,z can be specified to define line. + z : float = None + Position in z direction, only two of x,y,z can be specified to define line. + extrapolate_to_endpoints: bool = True + Passed directly to :class:`AxisAlignedVoltageIntegralSpec` + snap_path_to_grid: bool = True + Passed directly to :class:`AxisAlignedVoltageIntegralSpec` + + Returns + ------- + AxisAlignedVoltageIntegralSpec + The created path integral for computing voltage between the two terminals. + """ + axis_positions = Geometry.parse_two_xyz_kwargs(x=x, y=y, z=z) + # Calculate center and size of the future box + midpoint = (plus_terminal + minus_terminal) / 2 + length = np.abs(plus_terminal - minus_terminal) + center = [midpoint, midpoint, midpoint] + size = [length, length, length] + for axis, position in axis_positions: + size[axis] = 0 + center[axis] = position + + direction = "+" + if plus_terminal < minus_terminal: + direction = "-" + + return cls( + center=center, + size=size, + extrapolate_to_endpoints=extrapolate_to_endpoints, + snap_path_to_grid=snap_path_to_grid, + sign=direction, + ) + + @add_ax_if_none + def plot( + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, + **path_kwargs, + ) -> Ax: + """Plot path integral at single (x,y,z) coordinate. + + Parameters + ---------- + x : float = None + Position of plane in x direction, only one of x,y,z can be specified to define plane. + y : float = None + Position of plane in y direction, only one of x,y,z can be specified to define plane. + z : float = None + Position of plane in z direction, only one of x,y,z can be specified to define plane. + ax : matplotlib.axes._subplots.Axes = None + Matplotlib axes to plot on, if not specified, one is created. + **path_kwargs + Optional keyword arguments passed to the matplotlib plotting of the line. + For details on accepted values, refer to + `Matplotlib's documentation `_. + + Returns + ------- + matplotlib.axes._subplots.Axes + The supplied or created matplotlib axes. + """ + axis, position = self.parse_xyz_kwargs(x=x, y=y, z=z) + if axis == self.main_axis or not np.isclose(position, self.center[axis], rtol=fp_eps): + return ax + + (xs, ys) = self._vertices_2D(axis) + # Plot the path + plot_params = plot_params_voltage_path.include_kwargs(**path_kwargs) + plot_kwargs = plot_params.to_kwargs() + ax.plot(xs, ys, markevery=[0, -1], **plot_kwargs) + + # Plot special end points + end_kwargs = plot_params_voltage_plus.include_kwargs(**path_kwargs).to_kwargs() + start_kwargs = plot_params_voltage_minus.include_kwargs(**path_kwargs).to_kwargs() + + if self.sign == "-": + start_kwargs, end_kwargs = end_kwargs, start_kwargs + + ax.plot(xs[0], ys[0], **start_kwargs) + ax.plot(xs[1], ys[1], **end_kwargs) + return ax + + +class Custom2DVoltageIntegralSpec(Custom2DPathIntegralSpec): + """Class for specifying the computation of voltage between two points defined by a custom path. + Computed voltage is :math:`V=V_b-V_a`, where position b is the final vertex in the supplied path. + + Notes + ----- + + Use :class:`.AxisAlignedVoltageIntegralSpec` if possible, since interpolation + near conductors will not be accurate. + + .. TODO Improve by including extrapolate_to_endpoints field, non-trivial extension.""" + + @add_ax_if_none + def plot( + self, + x: Optional[float] = None, + y: Optional[float] = None, + z: Optional[float] = None, + ax: Ax = None, + **path_kwargs, + ) -> Ax: + """Plot path integral at single (x,y,z) coordinate. + + Parameters + ---------- + x : float = None + Position of plane in x direction, only one of x,y,z can be specified to define plane. + y : float = None + Position of plane in y direction, only one of x,y,z can be specified to define plane. + z : float = None + Position of plane in z direction, only one of x,y,z can be specified to define plane. + ax : matplotlib.axes._subplots.Axes = None + Matplotlib axes to plot on, if not specified, one is created. + **path_kwargs + Optional keyword arguments passed to the matplotlib plotting of the line. + For details on accepted values, refer to + `Matplotlib's documentation `_. + + Returns + ------- + matplotlib.axes._subplots.Axes + The supplied or created matplotlib axes. + """ + axis, position = Geometry.parse_xyz_kwargs(x=x, y=y, z=z) + if axis != self.main_axis or not np.isclose(position, self.position, rtol=fp_eps): + return ax + + plot_params = plot_params_voltage_path.include_kwargs(**path_kwargs) + plot_kwargs = plot_params.to_kwargs() + xs = self.vertices[:, 0] + ys = self.vertices[:, 1] + ax.plot(xs, ys, markevery=[0, -1], **plot_kwargs) + + # Plot special end points + end_kwargs = plot_params_voltage_plus.include_kwargs(**path_kwargs).to_kwargs() + start_kwargs = plot_params_voltage_minus.include_kwargs(**path_kwargs).to_kwargs() + ax.plot(xs[0], ys[0], **start_kwargs) + ax.plot(xs[-1], ys[-1], **end_kwargs) + + return ax diff --git a/tidy3d/components/microwave/path_integrals/types.py b/tidy3d/components/microwave/path_integrals/types.py new file mode 100644 index 0000000000..0bfda5285b --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/types.py @@ -0,0 +1,20 @@ +"""Common type definitions for path integral specifications.""" + +from __future__ import annotations + +from typing import Union + +from tidy3d.components.microwave.path_integrals.specs.current import ( + AxisAlignedCurrentIntegralSpec, + CompositeCurrentIntegralSpec, + Custom2DCurrentIntegralSpec, +) +from tidy3d.components.microwave.path_integrals.specs.voltage import ( + AxisAlignedVoltageIntegralSpec, + Custom2DVoltageIntegralSpec, +) + +VoltagePathSpecType = Union[AxisAlignedVoltageIntegralSpec, Custom2DVoltageIntegralSpec] +CurrentPathSpecType = Union[ + AxisAlignedCurrentIntegralSpec, Custom2DCurrentIntegralSpec, CompositeCurrentIntegralSpec +] diff --git a/tidy3d/components/microwave/path_integrals/viz.py b/tidy3d/components/microwave/path_integrals/viz.py new file mode 100644 index 0000000000..3f24174506 --- /dev/null +++ b/tidy3d/components/microwave/path_integrals/viz.py @@ -0,0 +1,56 @@ +"""Utilities for plotting microwave components""" + +from __future__ import annotations + +from numpy import inf + +from tidy3d.components.viz import PathPlotParams + +""" Constants """ +VOLTAGE_COLOR = "red" +CURRENT_COLOR = "blue" +PATH_LINEWIDTH = 2 +ARROW_CURRENT = { + "arrowstyle": "-|>", + "mutation_scale": 32, + "linestyle": "", + "lw": PATH_LINEWIDTH, + "color": CURRENT_COLOR, +} + +plot_params_voltage_path = PathPlotParams( + alpha=1.0, + zorder=inf, + color=VOLTAGE_COLOR, + linestyle="--", + linewidth=PATH_LINEWIDTH, + marker="o", + markersize=10, + markeredgecolor=VOLTAGE_COLOR, + markerfacecolor="white", +) + +plot_params_voltage_plus = PathPlotParams( + alpha=1.0, + zorder=inf, + color=VOLTAGE_COLOR, + marker="+", + markersize=6, +) + +plot_params_voltage_minus = PathPlotParams( + alpha=1.0, + zorder=inf, + color=VOLTAGE_COLOR, + marker="_", + markersize=6, +) + +plot_params_current_path = PathPlotParams( + alpha=1.0, + zorder=inf, + color=CURRENT_COLOR, + linestyle="--", + linewidth=PATH_LINEWIDTH, + marker="", +) diff --git a/tidy3d/components/mode/data/sim_data.py b/tidy3d/components/mode/data/sim_data.py index 6f996cfff7..1dc6684dbc 100644 --- a/tidy3d/components/mode/data/sim_data.py +++ b/tidy3d/components/mode/data/sim_data.py @@ -7,10 +7,11 @@ import pydantic.v1 as pd from tidy3d.components.base import cached_property -from tidy3d.components.data.monitor_data import MediumData, ModeSolverData, PermittivityData +from tidy3d.components.data.monitor_data import MediumData, PermittivityData from tidy3d.components.data.sim_data import AbstractYeeGridSimulationData from tidy3d.components.mode.simulation import ModeSimulation -from tidy3d.components.types import Ax, PlotScale +from tidy3d.components.types import TYPE_TAG_STR, Ax, PlotScale +from tidy3d.components.types.monitor_data import ModeSolverDataType ModeSimulationMonitorDataType = Union[PermittivityData, MediumData] @@ -22,10 +23,11 @@ class ModeSimulationData(AbstractYeeGridSimulationData): ..., title="Mode simulation", description="Mode simulation associated with this data." ) - modes_raw: ModeSolverData = pd.Field( + modes_raw: ModeSolverDataType = pd.Field( ..., title="Raw Modes", - description=":class:`.ModeSolverData` containing the field and effective index on unexpanded grid.", + description=":class:`.ModeSolverDataType` containing the field and effective index on unexpanded grid.", + discriminator=TYPE_TAG_STR, ) data: tuple[ModeSimulationMonitorDataType, ...] = pd.Field( @@ -36,7 +38,7 @@ class ModeSimulationData(AbstractYeeGridSimulationData): ) @cached_property - def modes(self) -> ModeSolverData: + def modes(self) -> ModeSolverDataType: """:class:`.ModeSolverData` containing the field and effective index data.""" return self.modes_raw.symmetry_expanded_copy diff --git a/tidy3d/components/mode/mode_solver.py b/tidy3d/components/mode/mode_solver.py index 12f511383f..eb94ff1f51 100644 --- a/tidy3d/components/mode/mode_solver.py +++ b/tidy3d/components/mode/mode_solver.py @@ -19,6 +19,9 @@ ModeIndexDataArray, ScalarModeFieldCylindricalDataArray, ScalarModeFieldDataArray, + _make_current_data_array, + _make_impedance_data_array, + _make_voltage_data_array, ) from tidy3d.components.data.monitor_data import ModeSolverData from tidy3d.components.data.sim_data import SimulationData @@ -31,6 +34,16 @@ IsotropicUniformMediumType, LossyMetalMedium, ) +from tidy3d.components.microwave.data.dataset import TransmissionLineDataset +from tidy3d.components.microwave.data.monitor_data import MicrowaveModeSolverData +from tidy3d.components.microwave.impedance_calculator import ( + CurrentIntegralType, + ImpedanceCalculator, + VoltageIntegralType, +) +from tidy3d.components.microwave.mode_spec import MicrowaveModeSpec +from tidy3d.components.microwave.monitor import MicrowaveModeMonitor, MicrowaveModeSolverMonitor +from tidy3d.components.microwave.path_integrals.factory import make_path_integrals from tidy3d.components.mode_spec import ModeSpec from tidy3d.components.monitor import ModeMonitor, ModeSolverMonitor from tidy3d.components.scene import Scene @@ -55,6 +68,8 @@ PlotScale, Symmetry, ) +from tidy3d.components.types.mode_spec import ModeSpecType +from tidy3d.components.types.monitor_data import ModeSolverDataType from tidy3d.components.validators import ( validate_freqs_min, validate_freqs_not_empty, @@ -146,10 +161,11 @@ class ModeSolver(Tidy3dBaseModel): discriminator=TYPE_TAG_STR, ) - mode_spec: ModeSpec = pydantic.Field( + mode_spec: ModeSpecType = pydantic.Field( ..., title="Mode specification", description="Container with specifications about the modes to be solved for.", + discriminator=TYPE_TAG_STR, ) freqs: FreqArray = pydantic.Field( @@ -427,6 +443,12 @@ def _num_cells_freqs_modes(self) -> tuple[int, int, int]: num_freqs = len(self.freqs) return num_cells, num_freqs, num_modes + @property + def _has_microwave_mode_spec(self) -> bool: + """Check if the mode solver is using a :class:`.MicrowaveModeSpec`., + and will thus be creating :class:`.MicrowaveModeSolverData`.""" + return isinstance(self.mode_spec, MicrowaveModeSpec) + def solve(self) -> ModeSolverData: """:class:`.ModeSolverData` containing the field and effective index data. @@ -497,13 +519,13 @@ def _grid_snapped(cls, simulation: Simulation, plane: Box) -> Grid: return simulation._snap_zero_dim(grid_snapped, skip_axis=normal_axis) @cached_property - def data_raw(self) -> ModeSolverData: + def data_raw(self) -> ModeSolverDataType: """:class:`.ModeSolverData` containing the field and effective index on unexpanded grid. Returns ------- - ModeSolverData - :class:`.ModeSolverData` object containing the effective index and mode fields. + ModeSolverDataType + A mode solver data type object containing the effective index and mode fields. """ if self.mode_spec.group_index_step > 0: @@ -514,6 +536,8 @@ def data_raw(self) -> ModeSolverData: # Compute data on the Yee grid mode_solver_data = self._data_on_yee_grid() + if self._has_microwave_mode_spec: + mode_solver_data = MicrowaveModeSolverData(**mode_solver_data.dict(exclude={"type"})) # Colocate to grid boundaries if requested if self.colocate: @@ -533,6 +557,9 @@ def data_raw(self) -> ModeSolverData: self._field_decay_warning(mode_solver_data.symmetry_expanded) mode_solver_data = self._filter_components(mode_solver_data) + # Calculate and add the characteristic impedance + if self._has_microwave_mode_spec: + mode_solver_data = self._add_microwave_data(mode_solver_data) return mode_solver_data @cached_property @@ -1353,14 +1380,65 @@ def _filter_polarization(self, mode_solver_data: ModeSolverData): ]: data.values[..., ifreq, :] = data.values[..., ifreq, sort_inds] + def _make_path_integrals( + self, + ) -> tuple[tuple[Optional[VoltageIntegralType]], tuple[Optional[CurrentIntegralType]]]: + """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: + raise ValueError( + "Cannot make path integrals for when 'mode_spec' is not a 'MicrowaveModeSpec'." + ) + return make_path_integrals(self.mode_spec) + + def _add_microwave_data( + self, mode_solver_data: MicrowaveModeSolverData + ) -> MicrowaveModeSolverData: + """Calculate and add microwave data to ``mode_solver_data`` which uses the path specifications.""" + voltage_integrals, current_integrals = self._make_path_integrals() + # Need to operate on the full symmetry expanded fields + mode_solver_data_expanded = mode_solver_data.symmetry_expanded_copy + Z0_list = [] + V_list = [] + I_list = [] + if len(voltage_integrals) == 1 and self.mode_spec.num_modes > 1: + voltage_integrals = voltage_integrals * self.mode_spec.num_modes + current_integrals = current_integrals * self.mode_spec.num_modes + for mode_index in range(self.mode_spec.num_modes): + vi = voltage_integrals[mode_index] + ci = current_integrals[mode_index] + if vi is None and ci is None: + continue + impedance_calc = ImpedanceCalculator( + voltage_integral=voltage_integrals[mode_index], + current_integral=current_integrals[mode_index], + ) + single_mode_data = mode_solver_data_expanded._isel(mode_index=[mode_index]) + Z0, voltage, current = impedance_calc.compute_impedance( + single_mode_data, return_voltage_and_current=True + ) + Z0_list.append(Z0) + V_list.append(voltage) + I_list.append(current) + all_mode_Z0 = xr.concat(Z0_list, dim="mode_index") + all_mode_Z0 = _make_impedance_data_array(all_mode_Z0) + all_mode_V = xr.concat(V_list, dim="mode_index") + all_mode_V = _make_voltage_data_array(all_mode_V) + all_mode_I = xr.concat(I_list, dim="mode_index") + all_mode_I = _make_current_data_array(all_mode_I) + mw_data = TransmissionLineDataset( + Z0=all_mode_Z0, voltage_coeffs=all_mode_V, current_coeffs=all_mode_I + ) + return mode_solver_data.updated_copy(transmission_line_data=mw_data) + @cached_property - def data(self) -> ModeSolverData: + def data(self) -> ModeSolverDataType: """:class:`.ModeSolverData` containing the field and effective index data. Returns ------- - ModeSolverData - :class:`.ModeSolverData` object containing the effective index and mode fields. + ModeSolverDataType + A mode solver data type object containing the effective index and mode fields. """ mode_solver_data = self.data_raw return mode_solver_data.symmetry_expanded_copy @@ -1946,11 +2024,16 @@ def to_monitor( "The default value of 'None' is for backwards compatibility and is not accepted." ) - return ModeMonitor( + mode_solver_monitor_type = ModeMonitor + if self._has_microwave_mode_spec: + mode_solver_monitor_type = MicrowaveModeMonitor + + return mode_solver_monitor_type( center=self.plane.center, size=self.plane.size, freqs=freqs, mode_spec=self.mode_spec, + colocate=self.colocate, conjugated_dot_product=self.conjugated_dot_product, name=name, ) @@ -1977,7 +2060,11 @@ def to_mode_solver_monitor( if colocate is None: colocate = self.colocate - return ModeSolverMonitor( + mode_solver_monitor_type = ModeSolverMonitor + if self._has_microwave_mode_spec: + mode_solver_monitor_type = MicrowaveModeSolverMonitor + + return mode_solver_monitor_type( size=self.plane.size, center=self.plane.center, mode_spec=self.mode_spec, diff --git a/tidy3d/components/mode/simulation.py b/tidy3d/components/mode/simulation.py index 305f5e9b35..d0190747af 100644 --- a/tidy3d/components/mode/simulation.py +++ b/tidy3d/components/mode/simulation.py @@ -12,7 +12,6 @@ from tidy3d.components.geometry.base import Box from tidy3d.components.grid.grid import Grid from tidy3d.components.grid.grid_spec import GridSpec -from tidy3d.components.mode_spec import ModeSpec from tidy3d.components.monitor import ( MediumMonitor, ModeMonitor, @@ -26,6 +25,7 @@ ) from tidy3d.components.source.field import ModeSource from tidy3d.components.types import TYPE_TAG_STR, Ax, Direction, EMField, FreqArray +from tidy3d.components.types.mode_spec import ModeSpecType from tidy3d.constants import C_0 from tidy3d.exceptions import SetupError, ValidationError from tidy3d.log import log @@ -119,10 +119,11 @@ class ModeSimulation(AbstractYeeGridSimulation): * `Prelude to Integrated Photonics Simulation: Mode Injection `_ """ - mode_spec: ModeSpec = pd.Field( + mode_spec: ModeSpecType = pd.Field( ..., title="Mode specification", description="Container with specifications about the modes to be solved for.", + discriminator=TYPE_TAG_STR, ) freqs: FreqArray = pd.Field( diff --git a/tidy3d/components/mode_spec.py b/tidy3d/components/mode_spec.py index 0f85bb47c0..a730166b41 100644 --- a/tidy3d/components/mode_spec.py +++ b/tidy3d/components/mode_spec.py @@ -2,6 +2,7 @@ from __future__ import annotations +from abc import ABC from math import isclose from typing import Literal, Union @@ -18,46 +19,9 @@ GROUP_INDEX_STEP = 0.005 -class ModeSpec(Tidy3dBaseModel): +class AbstractModeSpec(Tidy3dBaseModel, ABC): """ - Stores specifications for the mode solver to find an electromagntic mode. - - Notes - ----- - - The :attr:`angle_theta` and :attr:`angle_phi` parameters define the injection axis as illustrated in the figure - below, with respect to the axis normal to the mode plane (``x`` in the figure). Note that :attr:`angle_theta` - must be smaller than :math:`\\frac{pi}{2}`. To inject in the backward direction, we can still use the - ``direction`` parameter as also shown in the figure. Similarly, the mode amplitudes computed in mode monitors - are defined w.r.t. the ``forward`` and ``backward`` directions as illustrated. Note, the planar axes are - found by popping the injection axis from ``{x,y,z}``. For example, if injection axis is ``y``, the planar - axes are ordered ``{x,z}``. - - .. image:: ../../notebooks/img/ring_modes.png - - The :attr:`bend_axis` is the axis normal to the plane in which the bend lies, (``z`` in the diagram below). In - the mode specification, it is defined locally for the mode plane as one of the two axes tangential to the - plane. In the case of bends that lie in the ``xy``-plane, the mode plane would be either in ``xz`` or in - ``yz``, so in both cases the correct setting is ``bend_axis=1``, selecting the global ``z``. The - ``bend_radius`` is counted from the center of the mode plane to the center of the curvature, - along the tangential axis perpendicular to the bend axis. This radius can also be negative, if the center of - the mode plane is smaller than the center of the bend. - - .. image:: ../../notebooks/img/mode_angled.png - - Example - ------- - >>> mode_spec = ModeSpec(num_modes=3, target_neff=1.5) - - See Also - -------- - - **Notebooks**: - * `Introduction on tidy3d working principles <../../notebooks/Primer.html#Modes>`_ - * `Defining mode sources and monitors <../../notebooks/ModalSourcesMonitors.html>`_ - * `Injecting modes in bent and angled waveguides <../../notebooks/ModesBentAngled.html>`_ - * `Waveguide to ring coupling <../../notebooks/WaveguideToRingCoupling.html>`_ - + Abstract base for mode specification data. """ num_modes: pd.PositiveInt = pd.Field( @@ -235,3 +199,46 @@ def angle_rotation_with_phi(cls, val, values): "enabled." ) return val + + +class ModeSpec(AbstractModeSpec): + """ + Stores specifications for the mode solver to find an electromagnetic mode. + + Notes + ----- + + The :attr:`angle_theta` and :attr:`angle_phi` parameters define the injection axis as illustrated in the figure + below, with respect to the axis normal to the mode plane (``x`` in the figure). Note that :attr:`angle_theta` + must be smaller than :math:`\\frac{pi}{2}`. To inject in the backward direction, we can still use the + ``direction`` parameter as also shown in the figure. Similarly, the mode amplitudes computed in mode monitors + are defined w.r.t. the ``forward`` and ``backward`` directions as illustrated. Note, the planar axes are + found by popping the injection axis from ``{x,y,z}``. For example, if injection axis is ``y``, the planar + axes are ordered ``{x,z}``. + + .. image:: ../../notebooks/img/ring_modes.png + + The :attr:`bend_axis` is the axis normal to the plane in which the bend lies, (``z`` in the diagram below). In + the mode specification, it is defined locally for the mode plane as one of the two axes tangential to the + plane. In the case of bends that lie in the ``xy``-plane, the mode plane would be either in ``xz`` or in + ``yz``, so in both cases the correct setting is ``bend_axis=1``, selecting the global ``z``. The + ``bend_radius`` is counted from the center of the mode plane to the center of the curvature, + along the tangential axis perpendicular to the bend axis. This radius can also be negative, if the center of + the mode plane is smaller than the center of the bend. + + .. image:: ../../notebooks/img/mode_angled.png + + Example + ------- + >>> mode_spec = ModeSpec(num_modes=3, target_neff=1.5) + + See Also + -------- + + **Notebooks**: + * `Introduction on tidy3d working principles <../../notebooks/Primer.html#Modes>`_ + * `Defining mode sources and monitors <../../notebooks/ModalSourcesMonitors.html>`_ + * `Injecting modes in bent and angled waveguides <../../notebooks/ModesBentAngled.html>`_ + * `Waveguide to ring coupling <../../notebooks/WaveguideToRingCoupling.html>`_ + + """ diff --git a/tidy3d/components/monitor.py b/tidy3d/components/monitor.py index 693c22623e..9d72c65f57 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, Union +from typing import Literal, Optional import numpy as np import pydantic.v1 as pydantic @@ -16,6 +16,7 @@ from .base import Tidy3dBaseModel, cached_property, skip_if_fields_missing from .base_sim.monitor import AbstractMonitor from .medium import MediumType +from .microwave.base import MicrowaveBaseModel from .mode_spec import ModeSpec from .types import ( ArrayFloat1D, @@ -1218,7 +1219,7 @@ def storage_size(self, num_cells: int, tmesh: ArrayFloat1D) -> int: return BYTES_COMPLEX * len(self.theta) * len(self.phi) * len(self.freqs) * 6 -class DirectivityMonitor(FieldProjectionAngleMonitor, FluxMonitor): +class DirectivityMonitor(MicrowaveBaseModel, FieldProjectionAngleMonitor, FluxMonitor): """ :class:`Monitor` that records the radiation characteristics of antennas in the frequency domain at specified observation angles. @@ -1247,14 +1248,6 @@ def storage_size(self, num_cells: int, tmesh: ArrayFloat1D) -> int: self.freqs ) * 6 + BYTES_REAL * len(self.freqs) - @pydantic.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values - class FieldProjectionCartesianMonitor(AbstractFieldProjectionMonitor): """:class:`Monitor` that samples electromagnetic near fields in the frequency domain @@ -1574,22 +1567,3 @@ def storage_size(self, num_cells: int, tmesh: ArrayFloat1D) -> int: def _storage_size_solver(self, num_cells: int, tmesh: ArrayFloat1D) -> int: """Size of intermediate data recorded by the monitor during a solver run.""" return BYTES_COMPLEX * num_cells * len(self.freqs) * 6 - - -# types of monitors that are accepted by simulation -MonitorType = Union[ - FieldMonitor, - FieldTimeMonitor, - AuxFieldTimeMonitor, - MediumMonitor, - PermittivityMonitor, - FluxMonitor, - FluxTimeMonitor, - ModeMonitor, - ModeSolverMonitor, - FieldProjectionAngleMonitor, - FieldProjectionCartesianMonitor, - FieldProjectionKSpaceMonitor, - DiffractionMonitor, - DirectivityMonitor, -] diff --git a/tidy3d/components/simulation.py b/tidy3d/components/simulation.py index 4dbde67f38..21769c74c2 100644 --- a/tidy3d/components/simulation.py +++ b/tidy3d/components/simulation.py @@ -10,6 +10,8 @@ import autograd.numpy as np +from .types.monitor import MonitorType + try: import matplotlib as mpl except ImportError: @@ -70,6 +72,8 @@ MediumType3D, PECMedium, ) +from .microwave.mode_spec import MicrowaveModeSpec +from .microwave.path_integrals.mode_plane_analyzer import ModePlaneAnalyzer from .monitor import ( AbstractFieldProjectionMonitor, AbstractModeMonitor, @@ -86,7 +90,6 @@ MediumMonitor, ModeMonitor, Monitor, - MonitorType, PermittivityMonitor, SurfaceIntegrationMonitor, TimeMonitor, @@ -4447,6 +4450,7 @@ def validate_pre_upload(self, source_required: bool = True) -> None: self._warn_time_monitors_outside_run_time() self._validate_time_monitors_num_steps() self._validate_freq_monitors_freq_range() + self._validate_microwave_mode_specs() log.end_capture(self) if source_required and len(self.sources) == 0: raise SetupError("No sources in simulation.") @@ -4635,6 +4639,33 @@ def _validate_freq_monitors_freq_range(self) -> None: "(Hz) as defined by the sources." ) + def _validate_microwave_mode_specs(self) -> None: + """Raise error if any microwave mode specifications with ``AutoImpedanceSpec`` will + fail to instantiate. + """ + for monitor in self.monitors: + if not isinstance(monitor, AbstractModeMonitor): + continue + + if ( + isinstance(monitor.mode_spec, MicrowaveModeSpec) + and monitor.mode_spec._using_auto_current_spec + ): + mode_plane_analyzer = ModePlaneAnalyzer( + center=monitor.center, size=monitor.size, field_data_colocated=monitor.colocate + ) + try: + _ = mode_plane_analyzer.get_conductor_bounding_boxes( + self.volumetric_structures, + self.grid, + self.symmetry, + self.simulation_geometry, + ) + except SetupError as e: + raise SetupError( + f"Failed to setup auto impedance specification for monitor '{monitor.name}'" + ) from e + @cached_property def monitors_data_size(self) -> dict[str, float]: """Dictionary mapping monitor names to their estimated storage size in bytes.""" diff --git a/tidy3d/components/source/field.py b/tidy3d/components/source/field.py index 7f46614a51..1632edcaa8 100644 --- a/tidy3d/components/source/field.py +++ b/tidy3d/components/source/field.py @@ -14,6 +14,7 @@ from tidy3d.components.mode_spec import ModeSpec from tidy3d.components.source.frame import PECFrame from tidy3d.components.types import TYPE_TAG_STR, Ax, Axis, Coordinate, Direction +from tidy3d.components.types.mode_spec import ModeSpecType from tidy3d.components.validators import ( assert_plane, assert_single_freq_in_range, @@ -391,10 +392,11 @@ class ModeSource(DirectionalSource, PlanarSource, BroadbandSource): * `Prelude to Integrated Photonics Simulation: Mode Injection `_ """ - mode_spec: ModeSpec = pydantic.Field( + mode_spec: ModeSpecType = pydantic.Field( ModeSpec(), title="Mode Specification", description="Parameters to feed to mode solver which determine modes measured by monitor.", + discriminator=TYPE_TAG_STR, ) mode_index: pydantic.NonNegativeInt = pydantic.Field( diff --git a/tidy3d/components/types/mode_spec.py b/tidy3d/components/types/mode_spec.py new file mode 100644 index 0000000000..1e11a69122 --- /dev/null +++ b/tidy3d/components/types/mode_spec.py @@ -0,0 +1,11 @@ +"""Type definitions for mode specifications.""" + +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] diff --git a/tidy3d/components/types/monitor.py b/tidy3d/components/types/monitor.py new file mode 100644 index 0000000000..d8585cab51 --- /dev/null +++ b/tidy3d/components/types/monitor.py @@ -0,0 +1,43 @@ +"""Type definitions for monitors.""" + +from __future__ import annotations + +from typing import Union + +from tidy3d.components.microwave.monitor import MicrowaveModeMonitor, MicrowaveModeSolverMonitor +from tidy3d.components.monitor import ( + AuxFieldTimeMonitor, + DiffractionMonitor, + DirectivityMonitor, + FieldMonitor, + FieldProjectionAngleMonitor, + FieldProjectionCartesianMonitor, + FieldProjectionKSpaceMonitor, + FieldTimeMonitor, + FluxMonitor, + FluxTimeMonitor, + MediumMonitor, + ModeMonitor, + ModeSolverMonitor, + PermittivityMonitor, +) + +# 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, +] diff --git a/tidy3d/components/types/monitor_data.py b/tidy3d/components/types/monitor_data.py new file mode 100644 index 0000000000..50f5188865 --- /dev/null +++ b/tidy3d/components/types/monitor_data.py @@ -0,0 +1,46 @@ +"""Type definitions for monitor data.""" + +from __future__ import annotations + +from typing import Union + +from tidy3d.components.data.monitor_data import ( + AuxFieldTimeData, + DiffractionData, + DirectivityData, + FieldData, + FieldProjectionAngleData, + FieldProjectionCartesianData, + FieldProjectionKSpaceData, + FieldTimeData, + FluxData, + FluxTimeData, + MediumData, + ModeData, + ModeSolverData, + PermittivityData, +) +from tidy3d.components.microwave.data.monitor_data import MicrowaveModeData, MicrowaveModeSolverData + +# Type aliases +ModeDataType = Union[ModeData, MicrowaveModeData] +ModeSolverDataType = Union[ModeSolverData, MicrowaveModeSolverData] +MonitorDataTypes = ( + FieldData, + FieldTimeData, + PermittivityData, + MediumData, + ModeSolverData, + ModeData, + FluxData, + FluxTimeData, + AuxFieldTimeData, + FieldProjectionKSpaceData, + FieldProjectionCartesianData, + FieldProjectionAngleData, + DiffractionData, + DirectivityData, + MicrowaveModeData, + MicrowaveModeSolverData, +) +MonitorDataType = Union[MonitorDataTypes] diff --git a/tidy3d/components/types/simulation.py b/tidy3d/components/types/simulation.py index d894098b24..1194b5cfbd 100644 --- a/tidy3d/components/types/simulation.py +++ b/tidy3d/components/types/simulation.py @@ -6,6 +6,7 @@ from tidy3d.components.data.sim_data import SimulationData from tidy3d.components.eme.data.sim_data import EMESimulationData from tidy3d.components.eme.simulation import EMESimulation +from tidy3d.components.microwave.data.monitor_data import MicrowaveModeSolverData from tidy3d.components.mode.data.sim_data import ModeSimulationData from tidy3d.components.mode.simulation import ModeSimulation from tidy3d.components.simulation import Simulation @@ -33,6 +34,7 @@ HeatChargeSimulationData, HeatSimulationData, EMESimulationData, + MicrowaveModeSolverData, ModeSolverData, ModeSimulationData, VolumeMesherData, diff --git a/tidy3d/config.py b/tidy3d/config.py index 9dfc7b702c..d4ddb59cf2 100644 --- a/tidy3d/config.py +++ b/tidy3d/config.py @@ -37,6 +37,13 @@ class Config: "for several elements.", ) + suppress_rf_license_warning: bool = pd.Field( + False, + title="Suppress RF License Warning", + description="Enable or disable the RF/microwave license warning message when " + "instantiating microwave components.", + ) + use_local_subpixel: Optional[bool] = pd.Field( None, title="Whether to use local subpixel averaging. If 'None', local subpixel " diff --git a/tidy3d/plugins/microwave/__init__.py b/tidy3d/plugins/microwave/__init__.py index 25c0c40fd9..8431c18fd3 100644 --- a/tidy3d/plugins/microwave/__init__.py +++ b/tidy3d/plugins/microwave/__init__.py @@ -2,6 +2,28 @@ from __future__ import annotations +from tidy3d.components.microwave.impedance_calculator import ( + CurrentIntegralType, + ImpedanceCalculator, + VoltageIntegralType, +) +from tidy3d.components.microwave.path_integrals.integrals.auto import ( + path_integrals_from_lumped_element, +) +from tidy3d.components.microwave.path_integrals.integrals.base import ( + AxisAlignedPathIntegral, + Custom2DPathIntegral, +) +from tidy3d.components.microwave.path_integrals.integrals.current import ( + AxisAlignedCurrentIntegral, + CompositeCurrentIntegral, + Custom2DCurrentIntegral, +) +from tidy3d.components.microwave.path_integrals.integrals.voltage import ( + AxisAlignedVoltageIntegral, + Custom2DVoltageIntegral, +) + from . import models from .array_factor import ( BlackmanHarrisWindow, @@ -15,31 +37,25 @@ RectangularTaper, TaylorWindow, ) -from .auto_path_integrals import path_integrals_from_lumped_element -from .custom_path_integrals import ( - CustomCurrentIntegral2D, - CustomPathIntegral2D, - CustomVoltageIntegral2D, -) -from .impedance_calculator import CurrentIntegralTypes, ImpedanceCalculator, VoltageIntegralTypes from .lobe_measurer import LobeMeasurer -from .path_integrals import ( - AxisAlignedPathIntegral, - CurrentIntegralAxisAligned, - VoltageIntegralAxisAligned, -) from .rf_material_library import rf_material_library +# Backwards compatibility +CurrentIntegralTypes = CurrentIntegralType +VoltageIntegralTypes = VoltageIntegralType + __all__ = [ + "AxisAlignedCurrentIntegral", "AxisAlignedPathIntegral", + "AxisAlignedVoltageIntegral", "BlackmanHarrisWindow", "BlackmanWindow", "ChebWindow", - "CurrentIntegralAxisAligned", + "CompositeCurrentIntegral", "CurrentIntegralTypes", - "CustomCurrentIntegral2D", - "CustomPathIntegral2D", - "CustomVoltageIntegral2D", + "Custom2DCurrentIntegral", + "Custom2DPathIntegral", + "Custom2DVoltageIntegral", "HammingWindow", "HannWindow", "ImpedanceCalculator", @@ -49,7 +65,6 @@ "RectangularAntennaArrayCalculator", "RectangularTaper", "TaylorWindow", - "VoltageIntegralAxisAligned", "VoltageIntegralTypes", "models", "path_integrals_from_lumped_element", diff --git a/tidy3d/plugins/microwave/array_factor.py b/tidy3d/plugins/microwave/array_factor.py index 16cb2d0f0b..b312649bb8 100644 --- a/tidy3d/plugins/microwave/array_factor.py +++ b/tidy3d/plugins/microwave/array_factor.py @@ -11,24 +11,26 @@ from scipy.signal.windows import blackman, blackmanharris, chebwin, hamming, hann, kaiser, taylor from scipy.special import j0, jn_zeros -from tidy3d.components.base import Tidy3dBaseModel, skip_if_fields_missing +from tidy3d.components.base import skip_if_fields_missing from tidy3d.components.data.monitor_data import AbstractFieldProjectionData, DirectivityData from tidy3d.components.data.sim_data import SimulationData from tidy3d.components.geometry.base import Box, Geometry from tidy3d.components.grid.grid_spec import GridSpec, LayerRefinementSpec from tidy3d.components.lumped_element import LumpedElement from tidy3d.components.medium import Medium, MediumType3D -from tidy3d.components.monitor import AbstractFieldProjectionMonitor, MonitorType +from tidy3d.components.microwave.base import MicrowaveBaseModel +from tidy3d.components.monitor import AbstractFieldProjectionMonitor from tidy3d.components.simulation import Simulation from tidy3d.components.source.utils import SourceType from tidy3d.components.structure import MeshOverrideStructure, Structure from tidy3d.components.types import TYPE_TAG_STR, ArrayLike, Axis, Bound, Undefined +from tidy3d.components.types.monitor import MonitorType from tidy3d.constants import C_0, inf from tidy3d.exceptions import Tidy3dNotImplementedError from tidy3d.log import log -class AbstractAntennaArrayCalculator(Tidy3dBaseModel, ABC): +class AbstractAntennaArrayCalculator(MicrowaveBaseModel, ABC): """Abstract base for phased array calculators.""" taper: Union[RectangularTaper, RadialTaper] = pd.Field( @@ -595,14 +597,6 @@ def simulation_data_from_array_factor( simulation=sim_array.updated_copy(monitors=good_monitors), data=data_array ) - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values - def _rect_taper_array_factor( self, exp_x: ArrayLike, exp_y: ArrayLike, exp_z: ArrayLike ) -> ArrayLike: @@ -734,7 +728,7 @@ class RectangularAntennaArrayCalculator(AbstractAntennaArrayCalculator): ... array_size=(3, 4, 5), ... spacings=(0.5, 0.5, 0.5), ... phase_shifts=(0, 0, 0), - ... ) # doctest: +SKIP + ... ) """ array_size: tuple[PositiveInt, PositiveInt, PositiveInt] = pd.Field( @@ -906,7 +900,7 @@ def array_factor( return self._general_taper_array_factor(exp_x, exp_y, exp_z) -class AbstractWindow(Tidy3dBaseModel, ABC): +class AbstractWindow(MicrowaveBaseModel, ABC): """This class provides interface for window selection.""" def _get_weights_discrete(self, N: int) -> ArrayLike: @@ -1168,7 +1162,7 @@ def _get_weights_continuous(self, p_vec: ArrayLike) -> ArrayLike: ] -class AbstractTaper(Tidy3dBaseModel, ABC): +class AbstractTaper(MicrowaveBaseModel, ABC): """Abstract taper class provides an interface for taper of Array antennas.""" @abstractmethod diff --git a/tidy3d/plugins/microwave/auto_path_integrals.py b/tidy3d/plugins/microwave/auto_path_integrals.py index b93e611f77..e4461b7848 100644 --- a/tidy3d/plugins/microwave/auto_path_integrals.py +++ b/tidy3d/plugins/microwave/auto_path_integrals.py @@ -1,85 +1,11 @@ -"""Helpers for automatic setup of path integrals.""" +"""Backwards compatibility - import from tidy3d.components.microwave.path_integrals.integrals instead.""" from __future__ import annotations -from tidy3d.components.geometry.base import Box -from tidy3d.components.geometry.utils import ( - SnapBehavior, - SnapLocation, - SnappingSpec, - snap_box_to_grid, +from tidy3d.components.microwave.path_integrals.integrals.auto import ( + path_integrals_from_lumped_element, ) -from tidy3d.components.grid.grid import Grid -from tidy3d.components.lumped_element import LinearLumpedElement -from tidy3d.components.types import Direction -from .path_integrals import ( - CurrentIntegralAxisAligned, - VoltageIntegralAxisAligned, -) - - -def path_integrals_from_lumped_element( - lumped_element: LinearLumpedElement, grid: Grid, polarity: Direction = "+" -) -> tuple[VoltageIntegralAxisAligned, CurrentIntegralAxisAligned]: - """Helper to create a :class:`.VoltageIntegralAxisAligned` and :class:`.CurrentIntegralAxisAligned` - from a supplied :class:`.LinearLumpedElement`. Takes into account any snapping the lumped element - undergoes using the supplied :class:`.Grid`. - - Parameters - ---------- - lumped_element : :class:`.LinearLumpedElement` - Position along the voltage axis of the positive terminal. - grid : :class:`.Grid` - Position along the voltage axis of the negative terminal. - polarity : Direction - Choice for defining voltage. When positive, the terminal of the lumped element with - the greatest coordinate is considered the positive terminal. - Returns - ------- - VoltageIntegralAxisAligned - The created path integral for computing voltage between the two terminals of the :class:`.LinearLumpedElement`. - CurrentIntegralAxisAligned - The created path integral for computing current flowing through the :class:`.LinearLumpedElement`. - """ - - # Quick access to voltage and the primary current axis - V_axis = lumped_element.voltage_axis - I_axis = lumped_element.lateral_axis - - # The exact position of the lumped element after any possible snapping - lumped_element_box = lumped_element._create_box_for_network(grid=grid) - - V_size = [0, 0, 0] - V_size[V_axis] = lumped_element_box.size[V_axis] - voltage_integral = VoltageIntegralAxisAligned( - center=lumped_element_box.center, - size=V_size, - sign=polarity, - extrapolate_to_endpoints=True, - snap_path_to_grid=True, - ) - - # Snap the current integral to a box that encloses the element along the lateral and normal axes - # using the closest positions of the magnetic field - snap_location = [SnapLocation.Center] * 3 - snap_behavior = [SnapBehavior.Expand] * 3 - # Don't need to snap along voltage axis, since it will already be snapped from the lumped element's box - snap_behavior[V_axis] = SnapBehavior.Off - snap_spec = SnappingSpec(location=snap_location, behavior=snap_behavior) - - I_size = [0, 0, 0] - I_size[I_axis] = lumped_element_box.size[I_axis] - current_box = Box(center=lumped_element_box.center, size=I_size) - current_box = snap_box_to_grid(grid, current_box, snap_spec) - # Convention is current flows from plus to minus terminals - current_sign = "-" if polarity == "+" else "+" - current_integral = CurrentIntegralAxisAligned( - center=current_box.center, - size=current_box.size, - sign=current_sign, - snap_contour_to_grid=True, - extrapolate_to_endpoints=True, - ) - - return (voltage_integral, current_integral) +__all__ = [ + "path_integrals_from_lumped_element", +] diff --git a/tidy3d/plugins/microwave/custom_path_integrals.py b/tidy3d/plugins/microwave/custom_path_integrals.py index 37e292aab8..f71f9f5b01 100644 --- a/tidy3d/plugins/microwave/custom_path_integrals.py +++ b/tidy3d/plugins/microwave/custom_path_integrals.py @@ -1,400 +1,35 @@ -"""Helper classes for performing custom path integrals with fields on the Yee grid""" +"""Backwards compatibility - import from tidy3d.components.microwave.path_integrals.integrals instead.""" from __future__ import annotations -from typing import Literal, Optional - -import numpy as np -import pydantic.v1 as pd -import shapely -import xarray as xr - -from tidy3d.components.base import cached_property -from tidy3d.components.geometry.base import Geometry -from tidy3d.components.types import ArrayFloat2D, Ax, Axis, Bound, Coordinate, Direction -from tidy3d.components.viz import add_ax_if_none -from tidy3d.constants import MICROMETER, fp_eps -from tidy3d.exceptions import SetupError - -from .path_integrals import ( - AbstractAxesRH, - AxisAlignedPathIntegral, - CurrentIntegralResultTypes, - IntegralResultTypes, - MonitorDataTypes, - VoltageIntegralResultTypes, +from tidy3d.components.data.data_array import ( + CurrentIntegralResultType, + IntegralResultType, + VoltageIntegralResultType, _make_base_result_data_array, _make_current_data_array, _make_voltage_data_array, ) -from .viz import ( - ARROW_CURRENT, - plot_params_current_path, - plot_params_voltage_minus, - plot_params_voltage_path, - plot_params_voltage_plus, +from tidy3d.components.microwave.path_integrals.integrals.base import ( + Custom2DPathIntegral, +) +from tidy3d.components.microwave.path_integrals.integrals.current import ( + CompositeCurrentIntegral, + Custom2DCurrentIntegral, +) +from tidy3d.components.microwave.path_integrals.integrals.voltage import ( + Custom2DVoltageIntegral, ) -FieldParameter = Literal["E", "H"] - - -class CustomPathIntegral2D(AbstractAxesRH): - """Class for defining a custom path integral defined as a curve on an axis-aligned plane. - - Notes - ----- - - Given a set of vertices :math:`\\vec{r}_i`, this class approximates path integrals over - vector fields of the form :math:`\\int{\\vec{F} \\cdot \\vec{dl}}` - as :math:`\\sum_i{\\vec{F}(\\vec{r}_i) \\cdot \\vec{dl}_i}`, - where the differential length :math:`\\vec{dl}` is approximated using central differences - :math:`\\vec{dl}_i = \\frac{\\vec{r}_{i+1} - \\vec{r}_{i-1}}{2}`. - If the path is not closed, forward and backward differences are used at the endpoints. - """ - - axis: Axis = pd.Field( - 2, title="Axis", description="Specifies dimension of the planar axis (0,1,2) -> (x,y,z)." - ) - - position: float = pd.Field( - ..., - title="Position", - description="Position of the plane along the ``axis``.", - ) - - vertices: ArrayFloat2D = pd.Field( - ..., - title="Vertices", - description="List of (d1, d2) defining the 2 dimensional positions of the path. " - "The index of dimension should be in the ascending order, which means " - "if the axis corresponds with ``y``, the coordinates of the vertices should be (x, z). " - "If you wish to indicate a closed contour, the final vertex should be made " - "equal to the first vertex, i.e., ``vertices[-1] == vertices[0]``", - units=MICROMETER, - ) - - def compute_integral( - self, field: FieldParameter, em_field: MonitorDataTypes - ) -> IntegralResultTypes: - """Computes the path integral defined by ``vertices`` given the input ``em_field``. - - Parameters - ---------- - field : :class:`.FieldParameter` - Can take the value of ``"E"`` or ``"H"``. Determines whether to perform the integral - over electric or magnetic field. - em_field : :class:`.MonitorDataTypes` - The electromagnetic field data that will be used for integrating. - - Returns - ------- - :class:`.IntegralResultTypes` - Result of integral over remaining dimensions (frequency, time, mode indices). - """ - - (dim1, dim2, dim3) = self.local_dims - - h_field_name = f"{field}{dim1}" - v_field_name = f"{field}{dim2}" - - # Validate that fields are present - em_field._check_fields_stored([h_field_name, v_field_name]) - - # Select fields lying on the plane - plane_indexer = {dim3: self.position} - field1 = em_field.field_components[h_field_name].sel(plane_indexer, method="nearest") - field2 = em_field.field_components[v_field_name].sel(plane_indexer, method="nearest") - - # Although for users we use the convention that an axis is simply `popped` - # internally we prefer a right-handed coordinate system where dimensions - # keep a proper order. The only change is to swap 'x' and 'z' when the - # normal axis is along `y` - # Dim 's' represents the parameterization of the line - # 't' is likely used for time - if self.main_axis == 1: - x_path = xr.DataArray(self.vertices[:, 1], dims="s") - y_path = xr.DataArray(self.vertices[:, 0], dims="s") - else: - x_path = xr.DataArray(self.vertices[:, 0], dims="s") - y_path = xr.DataArray(self.vertices[:, 1], dims="s") - - path_indexer = {dim1: x_path, dim2: y_path} - field1_interp = field1.interp(path_indexer, method="linear") - field2_interp = field2.interp(path_indexer, method="linear") - - # Determine the differential length elements along the path - dl_x = self._compute_dl_component(x_path, self.is_closed_contour) - dl_y = self._compute_dl_component(y_path, self.is_closed_contour) - dl_x = xr.DataArray(dl_x, dims="s") - dl_y = xr.DataArray(dl_y, dims="s") - - # Compute the dot product between differential length element and vector field - integrand = field1_interp * dl_x + field2_interp * dl_y - # Integrate along the path - result = integrand.integrate(coord="s") - result = result.reset_coords(drop=True) - return _make_base_result_data_array(result) - - @staticmethod - def _compute_dl_component(coord_array: xr.DataArray, closed_contour=False) -> np.array: - """Computes the differential length element along the integration path.""" - dl = np.gradient(coord_array) - if closed_contour: - # If the contour is closed, we can use central difference on the starting/end point - # which will be more accurate than the default forward/backward choice in np.gradient - grad_end = np.gradient([coord_array[-2], coord_array[0], coord_array[1]]) - dl[0] = dl[-1] = grad_end[1] - return dl - - @classmethod - def from_circular_path( - cls, center: Coordinate, radius: float, num_points: int, normal_axis: Axis, clockwise: bool - ) -> CustomPathIntegral2D: - """Creates a ``CustomPathIntegral2D`` from a circular path given a desired number of points - along the perimeter. - - Parameters - ---------- - center : Coordinate - The center of the circle. - radius : float - The radius of the circle. - num_points : int - THe number of equidistant points to use along the perimeter of the circle. - normal_axis : Axis - The axis normal to the defined circle. - clockwise : bool - When ``True``, the points will be ordered clockwise with respect to the positive - direction of the ``normal_axis``. - - Returns - ------- - :class:`.CustomPathIntegral2D` - A path integral defined on a circular path. - """ - - def generate_circle_coordinates(radius: float, num_points: int, clockwise: bool): - """Helper for generating x,y vertices around a circle in the local coordinate frame.""" - sign = 1.0 - if clockwise: - sign = -1.0 - angles = np.linspace(0, sign * 2 * np.pi, num_points, endpoint=True) - xt = radius * np.cos(angles) - yt = radius * np.sin(angles) - return (xt, yt) - - # Get transverse axes - normal_center, trans_center = Geometry.pop_axis(center, normal_axis) - - # These x,y coordinates in the local coordinate frame - if normal_axis == 1: - # Handle special case when y is the axis that is popped - clockwise = not clockwise - xt, yt = generate_circle_coordinates(radius, num_points, clockwise) - xt += trans_center[0] - yt += trans_center[1] - circle_vertices = np.column_stack((xt, yt)) - # Close the contour exactly - circle_vertices[-1, :] = circle_vertices[0, :] - return cls(axis=normal_axis, position=normal_center, vertices=circle_vertices) - - @cached_property - def is_closed_contour(self) -> bool: - """Returns ``true`` when the first vertex equals the last vertex.""" - return np.isclose( - self.vertices[0, :], - self.vertices[-1, :], - rtol=fp_eps, - atol=np.finfo(np.float32).smallest_normal, - ).all() - - @cached_property - def main_axis(self) -> Axis: - """Axis for performing integration.""" - return self.axis - - @pd.validator("vertices", always=True) - def _correct_shape(cls, val): - """Makes sure vertices size is correct.""" - # overall shape of vertices - if val.shape[1] != 2: - raise SetupError( - "'CustomPathIntegral2D.vertices' must be a 2 dimensional array shaped (N, 2). " - f"Given array with shape of '{val.shape}'." - ) - return val - - @cached_property - def bounds(self) -> Bound: - """Helper to get the geometric bounding box of the path integral.""" - path_min = np.amin(self.vertices, axis=0) - path_max = np.amax(self.vertices, axis=0) - min_bound = Geometry.unpop_axis(self.position, path_min, self.axis) - max_bound = Geometry.unpop_axis(self.position, path_max, self.axis) - return (min_bound, max_bound) - - -class CustomVoltageIntegral2D(CustomPathIntegral2D): - """Class for computing the voltage between two points defined by a custom path. - Computed voltage is :math:`V=V_b-V_a`, where position b is the final vertex in the supplied path. - - Notes - ----- - - Use :class:`.VoltageIntegralAxisAligned` if possible, since interpolation - near conductors will not be accurate. - - .. TODO Improve by including extrapolate_to_endpoints field, non-trivial extension.""" - - def compute_voltage(self, em_field: MonitorDataTypes) -> VoltageIntegralResultTypes: - """Compute voltage along path defined by a line. - - Parameters - ---------- - em_field : :class:`.MonitorDataTypes` - The electromagnetic field data that will be used for integrating. - - Returns - ------- - :class:`.VoltageIntegralResultTypes` - Result of voltage computation over remaining dimensions (frequency, time, mode indices). - """ - - AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) - voltage = -1.0 * self.compute_integral(field="E", em_field=em_field) - return _make_voltage_data_array(voltage) - - @add_ax_if_none - def plot( - self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - ax: Ax = None, - **path_kwargs, - ) -> Ax: - """Plot path integral at single (x,y,z) coordinate. - - Parameters - ---------- - x : float = None - Position of plane in x direction, only one of x,y,z can be specified to define plane. - y : float = None - Position of plane in y direction, only one of x,y,z can be specified to define plane. - z : float = None - Position of plane in z direction, only one of x,y,z can be specified to define plane. - ax : matplotlib.axes._subplots.Axes = None - Matplotlib axes to plot on, if not specified, one is created. - **path_kwargs - Optional keyword arguments passed to the matplotlib plotting of the line. - For details on accepted values, refer to - `Matplotlib's documentation `_. - - Returns - ------- - matplotlib.axes._subplots.Axes - The supplied or created matplotlib axes. - """ - axis, position = Geometry.parse_xyz_kwargs(x=x, y=y, z=z) - if axis != self.main_axis or not np.isclose(position, self.position, rtol=fp_eps): - return ax - - plot_params = plot_params_voltage_path.include_kwargs(**path_kwargs) - plot_kwargs = plot_params.to_kwargs() - xs = self.vertices[:, 0] - ys = self.vertices[:, 1] - ax.plot(xs, ys, markevery=[0, -1], **plot_kwargs) - - # Plot special end points - end_kwargs = plot_params_voltage_plus.include_kwargs(**path_kwargs).to_kwargs() - start_kwargs = plot_params_voltage_minus.include_kwargs(**path_kwargs).to_kwargs() - ax.plot(xs[0], ys[0], **start_kwargs) - ax.plot(xs[-1], ys[-1], **end_kwargs) - - return ax - - -class CustomCurrentIntegral2D(CustomPathIntegral2D): - """Class for computing conduction current via Ampère's circuital law on a custom path. - To compute the current flowing in the positive ``axis`` direction, the vertices should be - ordered in a counterclockwise direction.""" - - def compute_current(self, em_field: MonitorDataTypes) -> CurrentIntegralResultTypes: - """Compute current flowing in a custom loop. - - Parameters - ---------- - em_field : :class:`.MonitorDataTypes` - The electromagnetic field data that will be used for integrating. - - Returns - ------- - :class:`.CurrentIntegralResultTypes` - Result of current computation over remaining dimensions (frequency, time, mode indices). - """ - - AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) - current = self.compute_integral(field="H", em_field=em_field) - return _make_current_data_array(current) - - @add_ax_if_none - def plot( - self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - ax: Ax = None, - **path_kwargs, - ) -> Ax: - """Plot path integral at single (x,y,z) coordinate. - - Parameters - ---------- - x : float = None - Position of plane in x direction, only one of x,y,z can be specified to define plane. - y : float = None - Position of plane in y direction, only one of x,y,z can be specified to define plane. - z : float = None - Position of plane in z direction, only one of x,y,z can be specified to define plane. - ax : matplotlib.axes._subplots.Axes = None - Matplotlib axes to plot on, if not specified, one is created. - **path_kwargs - Optional keyword arguments passed to the matplotlib plotting of the line. - For details on accepted values, refer to - `Matplotlib's documentation `_. - - Returns - ------- - matplotlib.axes._subplots.Axes - The supplied or created matplotlib axes. - """ - axis, position = Geometry.parse_xyz_kwargs(x=x, y=y, z=z) - if axis != self.main_axis or not np.isclose(position, self.position, rtol=fp_eps): - return ax - - plot_params = plot_params_current_path.include_kwargs(**path_kwargs) - plot_kwargs = plot_params.to_kwargs() - xs = self.vertices[:, 0] - ys = self.vertices[:, 1] - ax.plot(xs, ys, **plot_kwargs) - - # Add arrow at start of contour - ax.annotate( - "", - xytext=(xs[0], ys[0]), - xy=(xs[1], ys[1]), - arrowprops=ARROW_CURRENT, - ) - return ax - - @cached_property - def sign(self) -> Direction: - """Uses the ordering of the vertices to determine the direction of the current flow.""" - linestr = shapely.LineString(coordinates=self.vertices) - is_ccw = shapely.is_ccw(linestr) - # Invert statement when the vertices are given as (x, z) - if self.axis == 1: - is_ccw = not is_ccw - if is_ccw: - return "+" - return "-" +__all__ = [ + "CompositeCurrentIntegral", + "CurrentIntegralResultType", + "Custom2DCurrentIntegral", + "Custom2DPathIntegral", + "Custom2DVoltageIntegral", + "IntegralResultType", + "VoltageIntegralResultType", + "_make_base_result_data_array", + "_make_current_data_array", + "_make_voltage_data_array", +] diff --git a/tidy3d/plugins/microwave/impedance_calculator.py b/tidy3d/plugins/microwave/impedance_calculator.py index 32c1cbf370..e5ed60b021 100644 --- a/tidy3d/plugins/microwave/impedance_calculator.py +++ b/tidy3d/plugins/microwave/impedance_calculator.py @@ -1,119 +1,15 @@ -"""Class for computing characteristic impedance of transmission lines.""" +"""Backwards compatibility - import from tidy3d.components.microwave instead.""" from __future__ import annotations -from typing import Optional, Union - -import numpy as np -import pydantic.v1 as pd - -from tidy3d.components.base import Tidy3dBaseModel -from tidy3d.components.data.data_array import ImpedanceResultTypes, _make_impedance_data_array -from tidy3d.components.data.monitor_data import FieldTimeData -from tidy3d.components.monitor import ModeMonitor, ModeSolverMonitor -from tidy3d.exceptions import ValidationError -from tidy3d.log import log - -from .custom_path_integrals import CustomCurrentIntegral2D, CustomVoltageIntegral2D -from .path_integrals import ( - AxisAlignedPathIntegral, - CurrentIntegralAxisAligned, - MonitorDataTypes, - VoltageIntegralAxisAligned, +from tidy3d.components.microwave.impedance_calculator import ( + CurrentIntegralType, + ImpedanceCalculator, + VoltageIntegralType, ) -VoltageIntegralTypes = Union[VoltageIntegralAxisAligned, CustomVoltageIntegral2D] -CurrentIntegralTypes = Union[CurrentIntegralAxisAligned, CustomCurrentIntegral2D] - - -class ImpedanceCalculator(Tidy3dBaseModel): - """Tool for computing the characteristic impedance of a transmission line.""" - - voltage_integral: Optional[VoltageIntegralTypes] = pd.Field( - None, - title="Voltage Integral", - description="Definition of path integral for computing voltage.", - ) - - current_integral: Optional[CurrentIntegralTypes] = pd.Field( - None, - title="Current Integral", - description="Definition of contour integral for computing current.", - ) - - def compute_impedance(self, em_field: MonitorDataTypes) -> ImpedanceResultTypes: - """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``. - - Parameters - ---------- - em_field : :class:`.MonitorDataTypes` - The electromagnetic field data that will be used for computing the characteristic - impedance. - - Returns - ------- - :class:`.ImpedanceResultTypes` - Result of impedance computation over remaining dimensions (frequency, time, mode indices). - """ - - AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) - - # If both voltage and current integrals have been defined then impedance is computed directly - if self.voltage_integral is not None: - voltage = self.voltage_integral.compute_voltage(em_field) - if self.current_integral is not None: - current = self.current_integral.compute_current(em_field) - - # If only one of the integrals has been provided, then the computation falls back to using - # total power (flux) with Ohm's law to compute the missing quantity. The input field should - # cover an area large enough to render the flux computation accurate. If the input field is - # a time signal, then it is real and flux corresponds to the instantaneous power. Otherwise - # the input field is in frequency domain, where flux indicates the time-averaged power - # 0.5*Re(V*conj(I)). - # We explicitly take the real part, in case Bloch BCs were used in the simulation. - flux_sign = 1.0 - # Determine flux sign - if isinstance(em_field.monitor, ModeSolverMonitor): - flux_sign = 1 if em_field.monitor.direction == "+" else -1 - if isinstance(em_field.monitor, ModeMonitor): - flux_sign = 1 if em_field.monitor.store_fields_direction == "+" else -1 - - if self.voltage_integral is None: - flux = flux_sign * em_field.complex_flux - if isinstance(em_field, FieldTimeData): - impedance = flux / np.real(current) ** 2 - else: - impedance = 2 * flux / (current * np.conj(current)) - elif self.current_integral is None: - flux = flux_sign * em_field.complex_flux - if isinstance(em_field, FieldTimeData): - impedance = np.real(voltage) ** 2 / flux - else: - impedance = (voltage * np.conj(voltage)) / (2 * np.conj(flux)) - else: - if isinstance(em_field, FieldTimeData): - impedance = np.real(voltage) / np.real(current) - else: - impedance = voltage / current - impedance = _make_impedance_data_array(impedance) - return impedance - - @pd.validator("current_integral", always=True) - def check_voltage_or_current(cls, val, values): - """Raise validation error if both ``voltage_integral`` and ``current_integral`` - are not provided.""" - if not values.get("voltage_integral") and not val: - raise ValidationError( - "At least one of 'voltage_integral' or 'current_integral' must be provided." - ) - return val - - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values +__all__ = [ + "CurrentIntegralType", + "ImpedanceCalculator", + "VoltageIntegralType", +] diff --git a/tidy3d/plugins/microwave/lobe_measurer.py b/tidy3d/plugins/microwave/lobe_measurer.py index c843314a8f..18985ac7c9 100644 --- a/tidy3d/plugins/microwave/lobe_measurer.py +++ b/tidy3d/plugins/microwave/lobe_measurer.py @@ -9,11 +9,11 @@ import pydantic.v1 as pd from pandas import DataFrame -from tidy3d.components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing +from tidy3d.components.base import cached_property, skip_if_fields_missing +from tidy3d.components.microwave.base import MicrowaveBaseModel from tidy3d.components.types import ArrayFloat1D, ArrayLike, Ax from tidy3d.constants import fp_eps from tidy3d.exceptions import ValidationError -from tidy3d.log import log from .viz import plot_params_lobe_FNBW, plot_params_lobe_peak, plot_params_lobe_width @@ -24,7 +24,7 @@ DEFAULT_NULL_THRESHOLD = 1e-3 -class LobeMeasurer(Tidy3dBaseModel): +class LobeMeasurer(MicrowaveBaseModel): """ Tool for detecting and analyzing lobes in antenna radiation patterns, along with their characteristics such as direction and beamwidth. @@ -35,8 +35,8 @@ class LobeMeasurer(Tidy3dBaseModel): >>> Urad = np.cos(theta) ** 2 * np.cos(3 * theta) ** 2 >>> lobe_measurer = LobeMeasurer( ... angle=theta, - ... radiation_pattern=Urad) # doctest: +SKIP - >>> lobe_measures = lobe_measurer.lobe_measures # doctest: +SKIP + ... radiation_pattern=Urad) + >>> lobe_measures = lobe_measurer.lobe_measures """ angle: ArrayFloat1D = pd.Field( @@ -349,11 +349,3 @@ def plot( ax.axvline(FNBW_bounds[1], **plot_params_lobe_FNBW.to_kwargs()) return ax - - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values diff --git a/tidy3d/plugins/microwave/path_integrals.py b/tidy3d/plugins/microwave/path_integrals.py index 2ef62aed5e..48e20397f6 100644 --- a/tidy3d/plugins/microwave/path_integrals.py +++ b/tidy3d/plugins/microwave/path_integrals.py @@ -1,562 +1,23 @@ -"""Helper classes for performing path integrals with fields on the Yee grid""" +"""Backwards compatibility - import from tidy3d.components.microwave.path_integrals.integrals instead.""" from __future__ import annotations -from abc import ABC, abstractmethod -from typing import Optional, Union - -import numpy as np -import pydantic.v1 as pd - -from tidy3d.components.base import Tidy3dBaseModel, cached_property -from tidy3d.components.data.data_array import ( - CurrentIntegralResultTypes, - IntegralResultTypes, - ScalarFieldDataArray, - ScalarFieldTimeDataArray, - ScalarModeFieldDataArray, - VoltageIntegralResultTypes, - _make_base_result_data_array, - _make_current_data_array, - _make_voltage_data_array, +from tidy3d.components.microwave.path_integrals.integrals.base import ( + AxisAlignedPathIntegral, + EMScalarFieldType, + IntegrableMonitorDataType, ) -from tidy3d.components.data.monitor_data import FieldData, FieldTimeData, ModeData, ModeSolverData -from tidy3d.components.geometry.base import Box, Geometry -from tidy3d.components.types import Ax, Axis, Coordinate2D, Direction -from tidy3d.components.validators import assert_line, assert_plane -from tidy3d.components.viz import add_ax_if_none -from tidy3d.constants import fp_eps -from tidy3d.exceptions import DataError, Tidy3dError -from tidy3d.log import log - -from .viz import ( - ARROW_CURRENT, - plot_params_current_path, - plot_params_voltage_minus, - plot_params_voltage_path, - plot_params_voltage_plus, +from tidy3d.components.microwave.path_integrals.integrals.current import ( + AxisAlignedCurrentIntegral, +) +from tidy3d.components.microwave.path_integrals.integrals.voltage import ( + AxisAlignedVoltageIntegral, ) -MonitorDataTypes = Union[FieldData, FieldTimeData, ModeData, ModeSolverData] -EMScalarFieldType = Union[ScalarFieldDataArray, ScalarFieldTimeDataArray, ScalarModeFieldDataArray] - - -class AbstractAxesRH(Tidy3dBaseModel, ABC): - """Represents an axis-aligned right-handed coordinate system with one axis preferred. - Typically `main_axis` would refer to the normal axis of a plane. - """ - - @cached_property - @abstractmethod - def main_axis(self) -> Axis: - """Get the preferred axis.""" - - @cached_property - def remaining_axes(self) -> tuple[Axis, Axis]: - """Get in-plane axes, ordered to maintain a right-handed coordinate system.""" - axes: list[Axis] = [0, 1, 2] - axes.pop(self.main_axis) - if self.main_axis == 1: - return (axes[1], axes[0]) - return (axes[0], axes[1]) - - @cached_property - def remaining_dims(self) -> tuple[str, str]: - """Get in-plane dimensions, ordered to maintain a right-handed coordinate system.""" - dim1 = "xyz"[self.remaining_axes[0]] - dim2 = "xyz"[self.remaining_axes[1]] - return (dim1, dim2) - - @cached_property - def local_dims(self) -> tuple[str, str, str]: - """Get in-plane dimensions with in-plane dims first, followed by the `main_axis` dimension.""" - dim3 = "xyz"[self.main_axis] - return self.remaining_dims + tuple(dim3) - - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values - - -class AxisAlignedPathIntegral(AbstractAxesRH, Box): - """Class for defining the simplest type of path integral, which is aligned with Cartesian axes.""" - - _line_validator = assert_line() - - extrapolate_to_endpoints: bool = pd.Field( - False, - title="Extrapolate to Endpoints", - description="If the endpoints of the path integral terminate at or near a material interface, " - "the field is likely discontinuous. When this field is ``True``, fields that are outside and on the bounds " - "of the integral are ignored. Should be enabled when computing voltage between two conductors.", - ) - - snap_path_to_grid: bool = pd.Field( - False, - title="Snap Path to Grid", - description="It might be desireable to integrate exactly along the Yee grid associated with " - "a field. When this field is ``True``, the integration path will be snapped to the grid.", - ) - - def compute_integral(self, scalar_field: EMScalarFieldType) -> IntegralResultTypes: - """Computes the defined integral given the input ``scalar_field``.""" - - if not scalar_field.does_cover(self.bounds, fp_eps, np.finfo(np.float32).smallest_normal): - raise DataError("Scalar field does not cover the integration domain.") - coord = "xyz"[self.main_axis] - - scalar_field = self._get_field_along_path(scalar_field) - # Get the boundaries - min_bound = self.bounds[0][self.main_axis] - max_bound = self.bounds[1][self.main_axis] - - if self.extrapolate_to_endpoints: - # Remove field outside the boundaries - scalar_field = scalar_field.sel({coord: slice(min_bound, max_bound)}) - # Ignore values on the boundary (sel is inclusive) - scalar_field = scalar_field.drop_sel({coord: (min_bound, max_bound)}, errors="ignore") - coordinates = scalar_field.coords[coord].values - else: - coordinates = scalar_field.coords[coord].sel({coord: slice(min_bound, max_bound)}) - - # Integration is along the original coordinates plus ensure that - # endpoints corresponding to the precise bounds of the port are included - coords_interp = np.array([min_bound]) - coords_interp = np.concatenate((coords_interp, coordinates)) - coords_interp = np.concatenate((coords_interp, [max_bound])) - coords_interp = {coord: coords_interp} - - # Use extrapolation for the 2 additional endpoints, unless there is only a single sample point - method = "linear" - if len(coordinates) == 1 and self.extrapolate_to_endpoints: - method = "nearest" - scalar_field = scalar_field.interp( - coords_interp, method=method, kwargs={"fill_value": "extrapolate"} - ) - result = scalar_field.integrate(coord=coord) - return _make_base_result_data_array(result) - - def _get_field_along_path(self, scalar_field: EMScalarFieldType) -> EMScalarFieldType: - """Returns a selection of the input ``scalar_field`` ready for integration.""" - (axis1, axis2) = self.remaining_axes - (coord1, coord2) = self.remaining_dims - - if self.snap_path_to_grid: - # Coordinates that are not integrated - remaining_coords = { - coord1: self.center[axis1], - coord2: self.center[axis2], - } - # Select field nearest to center of integration line - scalar_field = scalar_field.sel( - remaining_coords, - method="nearest", - drop=False, - ) - else: - # Try to interpolate unless there is only a single coordinate - coord1dict = {coord1: self.center[axis1]} - if scalar_field.sizes[coord1] == 1: - scalar_field = scalar_field.sel(coord1dict, method="nearest") - else: - scalar_field = scalar_field.interp( - coord1dict, method="linear", kwargs={"bounds_error": True} - ) - coord2dict = {coord2: self.center[axis2]} - if scalar_field.sizes[coord2] == 1: - scalar_field = scalar_field.sel(coord2dict, method="nearest") - else: - scalar_field = scalar_field.interp( - coord2dict, method="linear", kwargs={"bounds_error": True} - ) - # Remove unneeded coordinates - scalar_field = scalar_field.reset_coords(drop=True) - return scalar_field - - @cached_property - def main_axis(self) -> Axis: - """Axis for performing integration.""" - for index, value in enumerate(self.size): - if value != 0: - return index - raise Tidy3dError("Failed to identify axis.") - - def _vertices_2D(self, axis: Axis) -> tuple[Coordinate2D, Coordinate2D]: - """Returns the two vertices of this path in the plane defined by ``axis``.""" - min = self.bounds[0] - max = self.bounds[1] - _, min = Box.pop_axis(min, axis) - _, max = Box.pop_axis(max, axis) - - u = [min[0], max[0]] - v = [min[1], max[1]] - return (u, v) - - @staticmethod - def _check_monitor_data_supported(em_field: MonitorDataTypes): - """Helper for validating that monitor data is supported.""" - if not isinstance(em_field, (FieldData, FieldTimeData, ModeData, ModeSolverData)): - supported_types = list(MonitorDataTypes.__args__) - raise DataError( - f"'em_field' type {type(em_field)} not supported. Supported types are " - f"{supported_types}" - ) - - -class VoltageIntegralAxisAligned(AxisAlignedPathIntegral): - """Class for computing the voltage between two points defined by an axis-aligned line.""" - - sign: Direction = pd.Field( - ..., - title="Direction of Path Integral", - description="Positive indicates V=Vb-Va where position b has a larger coordinate along the axis of integration.", - ) - - def compute_voltage(self, em_field: MonitorDataTypes) -> VoltageIntegralResultTypes: - """Compute voltage along path defined by a line.""" - - self._check_monitor_data_supported(em_field=em_field) - e_component = "xyz"[self.main_axis] - field_name = f"E{e_component}" - # Validate that fields are present - em_field._check_fields_stored([field_name]) - e_field = em_field.field_components[field_name] - - voltage = self.compute_integral(e_field) - - if self.sign == "+": - voltage *= -1 - - return _make_voltage_data_array(voltage) - - @staticmethod - def from_terminal_positions( - plus_terminal: float, - minus_terminal: float, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - extrapolate_to_endpoints: bool = True, - snap_path_to_grid: bool = True, - ) -> VoltageIntegralAxisAligned: - """Helper to create a :class:`VoltageIntegralAxisAligned` from two coordinates that - define a line and two positions indicating the endpoints of the path integral. - - Parameters - ---------- - plus_terminal : float - Position along the voltage axis of the positive terminal. - minus_terminal : float - Position along the voltage axis of the negative terminal. - x : float = None - Position in x direction, only two of x,y,z can be specified to define line. - y : float = None - Position in y direction, only two of x,y,z can be specified to define line. - z : float = None - Position in z direction, only two of x,y,z can be specified to define line. - extrapolate_to_endpoints: bool = True - Passed directly to :class:`VoltageIntegralAxisAligned` - snap_path_to_grid: bool = True - Passed directly to :class:`VoltageIntegralAxisAligned` - - Returns - ------- - VoltageIntegralAxisAligned - The created path integral for computing voltage between the two terminals. - """ - axis_positions = Geometry.parse_two_xyz_kwargs(x=x, y=y, z=z) - # Calculate center and size of the future box - midpoint = (plus_terminal + minus_terminal) / 2 - length = np.abs(plus_terminal - minus_terminal) - center = [midpoint, midpoint, midpoint] - size = [length, length, length] - for axis, position in axis_positions: - size[axis] = 0 - center[axis] = position - - direction = "+" - if plus_terminal < minus_terminal: - direction = "-" - - return VoltageIntegralAxisAligned( - center=center, - size=size, - extrapolate_to_endpoints=extrapolate_to_endpoints, - snap_path_to_grid=snap_path_to_grid, - sign=direction, - ) - - @add_ax_if_none - def plot( - self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - ax: Ax = None, - **path_kwargs, - ) -> Ax: - """Plot path integral at single (x,y,z) coordinate. - - Parameters - ---------- - x : float = None - Position of plane in x direction, only one of x,y,z can be specified to define plane. - y : float = None - Position of plane in y direction, only one of x,y,z can be specified to define plane. - z : float = None - Position of plane in z direction, only one of x,y,z can be specified to define plane. - ax : matplotlib.axes._subplots.Axes = None - Matplotlib axes to plot on, if not specified, one is created. - **path_kwargs - Optional keyword arguments passed to the matplotlib plotting of the line. - For details on accepted values, refer to - `Matplotlib's documentation `_. - - Returns - ------- - matplotlib.axes._subplots.Axes - The supplied or created matplotlib axes. - """ - axis, position = self.parse_xyz_kwargs(x=x, y=y, z=z) - if axis == self.main_axis or not np.isclose(position, self.center[axis], rtol=fp_eps): - return ax - - (xs, ys) = self._vertices_2D(axis) - # Plot the path - plot_params = plot_params_voltage_path.include_kwargs(**path_kwargs) - plot_kwargs = plot_params.to_kwargs() - ax.plot(xs, ys, markevery=[0, -1], **plot_kwargs) - - # Plot special end points - end_kwargs = plot_params_voltage_plus.include_kwargs(**path_kwargs).to_kwargs() - start_kwargs = plot_params_voltage_minus.include_kwargs(**path_kwargs).to_kwargs() - - if self.sign == "-": - start_kwargs, end_kwargs = end_kwargs, start_kwargs - - ax.plot(xs[0], ys[0], **start_kwargs) - ax.plot(xs[1], ys[1], **end_kwargs) - return ax - - -class CurrentIntegralAxisAligned(AbstractAxesRH, Box): - """Class for computing conduction current via Ampère's circuital law on an axis-aligned loop.""" - - _plane_validator = assert_plane() - - sign: Direction = pd.Field( - ..., - title="Direction of Contour Integral", - description="Positive indicates current flowing in the positive normal axis direction.", - ) - - extrapolate_to_endpoints: bool = pd.Field( - False, - title="Extrapolate to Endpoints", - description="This parameter is passed to :class:`AxisAlignedPathIntegral` objects when computing the contour integral.", - ) - - snap_contour_to_grid: bool = pd.Field( - False, - title="Snap Contour to Grid", - description="This parameter is passed to :class:`AxisAlignedPathIntegral` objects when computing the contour integral.", - ) - - def compute_current(self, em_field: MonitorDataTypes) -> CurrentIntegralResultTypes: - """Compute current flowing in loop defined by the outer edge of a rectangle.""" - - AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) - ax1 = self.remaining_axes[0] - ax2 = self.remaining_axes[1] - h_component = "xyz"[ax1] - v_component = "xyz"[ax2] - h_field_name = f"H{h_component}" - v_field_name = f"H{v_component}" - # Validate that fields are present - em_field._check_fields_stored([h_field_name, v_field_name]) - h_horizontal = em_field.field_components[h_field_name] - h_vertical = em_field.field_components[v_field_name] - - # Decompose contour into path integrals - (bottom, right, top, left) = self._to_path_integrals(h_horizontal, h_vertical) - - current = 0 - # Compute and add contributions from each part of the contour - current += bottom.compute_integral(h_horizontal) - current += right.compute_integral(h_vertical) - current -= top.compute_integral(h_horizontal) - current -= left.compute_integral(h_vertical) - - if self.sign == "-": - current *= -1 - return _make_current_data_array(current) - - @cached_property - def main_axis(self) -> Axis: - """Axis normal to loop""" - for index, value in enumerate(self.size): - if value == 0: - return index - raise Tidy3dError("Failed to identify axis.") - - def _to_path_integrals( - self, h_horizontal=None, h_vertical=None - ) -> tuple[AxisAlignedPathIntegral, ...]: - """Returns four ``AxisAlignedPathIntegral`` instances, which represent a contour - integral around the surface defined by ``self.size``.""" - ax1 = self.remaining_axes[0] - ax2 = self.remaining_axes[1] - - horizontal_passed = h_horizontal is not None - vertical_passed = h_vertical is not None - if self.snap_contour_to_grid and horizontal_passed and vertical_passed: - (coord1, coord2) = self.remaining_dims - - # Locations where horizontal paths will be snapped - v_bounds = [ - self.center[ax2] - self.size[ax2] / 2, - self.center[ax2] + self.size[ax2] / 2, - ] - h_snaps = h_horizontal.sel({coord2: v_bounds}, method="nearest").coords[coord2].values - # Locations where vertical paths will be snapped - h_bounds = [ - self.center[ax1] - self.size[ax1] / 2, - self.center[ax1] + self.size[ax1] / 2, - ] - v_snaps = h_vertical.sel({coord1: h_bounds}, method="nearest").coords[coord1].values - - bottom_bound = h_snaps[0] - top_bound = h_snaps[1] - left_bound = v_snaps[0] - right_bound = v_snaps[1] - else: - bottom_bound = self.bounds[0][ax2] - top_bound = self.bounds[1][ax2] - left_bound = self.bounds[0][ax1] - right_bound = self.bounds[1][ax1] - - # Horizontal paths - path_size = list(self.size) - path_size[ax1] = right_bound - left_bound - path_size[ax2] = 0 - path_center = list(self.center) - path_center[ax2] = bottom_bound - - bottom = AxisAlignedPathIntegral( - center=path_center, - size=path_size, - extrapolate_to_endpoints=self.extrapolate_to_endpoints, - snap_path_to_grid=self.snap_contour_to_grid, - ) - path_center[ax2] = top_bound - top = AxisAlignedPathIntegral( - center=path_center, - size=path_size, - extrapolate_to_endpoints=self.extrapolate_to_endpoints, - snap_path_to_grid=self.snap_contour_to_grid, - ) - - # Vertical paths - path_size = list(self.size) - path_size[ax1] = 0 - path_size[ax2] = top_bound - bottom_bound - path_center = list(self.center) - - path_center[ax1] = left_bound - left = AxisAlignedPathIntegral( - center=path_center, - size=path_size, - extrapolate_to_endpoints=self.extrapolate_to_endpoints, - snap_path_to_grid=self.snap_contour_to_grid, - ) - path_center[ax1] = right_bound - right = AxisAlignedPathIntegral( - center=path_center, - size=path_size, - extrapolate_to_endpoints=self.extrapolate_to_endpoints, - snap_path_to_grid=self.snap_contour_to_grid, - ) - - return (bottom, right, top, left) - - @add_ax_if_none - def plot( - self, - x: Optional[float] = None, - y: Optional[float] = None, - z: Optional[float] = None, - ax: Ax = None, - **path_kwargs, - ) -> Ax: - """Plot path integral at single (x,y,z) coordinate. - - Parameters - ---------- - x : float = None - Position of plane in x direction, only one of x,y,z can be specified to define plane. - y : float = None - Position of plane in y direction, only one of x,y,z can be specified to define plane. - z : float = None - Position of plane in z direction, only one of x,y,z can be specified to define plane. - ax : matplotlib.axes._subplots.Axes = None - Matplotlib axes to plot on, if not specified, one is created. - **path_kwargs - Optional keyword arguments passed to the matplotlib plotting of the line. - For details on accepted values, refer to - `Matplotlib's documentation `_. - - Returns - ------- - matplotlib.axes._subplots.Axes - The supplied or created matplotlib axes. - """ - axis, position = self.parse_xyz_kwargs(x=x, y=y, z=z) - if axis != self.main_axis or not np.isclose(position, self.center[axis], rtol=fp_eps): - return ax - - plot_params = plot_params_current_path.include_kwargs(**path_kwargs) - plot_kwargs = plot_params.to_kwargs() - path_integrals = self._to_path_integrals() - # Plot the path - for path in path_integrals: - (xs, ys) = path._vertices_2D(axis) - ax.plot(xs, ys, **plot_kwargs) - - (ax1, ax2) = self.remaining_axes - - # Add arrow to bottom path, unless right path is longer - arrow_path = path_integrals[0] - if self.size[ax2] > self.size[ax1]: - arrow_path = path_integrals[1] - - (xs, ys) = arrow_path._vertices_2D(axis) - X = (xs[0] + xs[1]) / 2 - Y = (ys[0] + ys[1]) / 2 - center = np.array([X, Y]) - dx = xs[1] - xs[0] - dy = ys[1] - ys[0] - direction = np.array([dx, dy]) - segment_length = np.linalg.norm(direction) - unit_dir = direction / segment_length - - # Change direction of arrow depending on sign of current definition - if self.sign == "-": - unit_dir *= -1.0 - # Change direction of arrow when the "y" axis is dropped, - # since the plotted coordinate system will be left-handed (x, z) - if self.main_axis == 1: - unit_dir *= -1.0 - - start = center - unit_dir * segment_length - end = center - ax.annotate( - "", - xytext=(start[0], start[1]), - xy=(end[0], end[1]), - arrowprops=ARROW_CURRENT, - ) - return ax +__all__ = [ + "AxisAlignedCurrentIntegral", + "AxisAlignedPathIntegral", + "AxisAlignedVoltageIntegral", + "EMScalarFieldType", + "IntegrableMonitorDataType", +] diff --git a/tidy3d/plugins/microwave/rf_material_library.py b/tidy3d/plugins/microwave/rf_material_library.py index 7cb784dd87..3a1ed2ae7a 100644 --- a/tidy3d/plugins/microwave/rf_material_library.py +++ b/tidy3d/plugins/microwave/rf_material_library.py @@ -1,6 +1,5 @@ """Holds dispersive models for several commonly used RF materials.""" -# from ...components.base import Tidy3dBaseModel from __future__ import annotations from tidy3d.components.medium import PoleResidue diff --git a/tidy3d/plugins/microwave/viz.py b/tidy3d/plugins/microwave/viz.py index 72f4570c86..8bdc86b3ef 100644 --- a/tidy3d/plugins/microwave/viz.py +++ b/tidy3d/plugins/microwave/viz.py @@ -7,56 +7,9 @@ from tidy3d.components.viz import PathPlotParams """ Constants """ -VOLTAGE_COLOR = "red" -CURRENT_COLOR = "blue" LOBE_PEAK_COLOR = "tab:red" LOBE_WIDTH_COLOR = "tab:orange" LOBE_FNBW_COLOR = "tab:blue" -PATH_LINEWIDTH = 2 -ARROW_CURRENT = { - "arrowstyle": "-|>", - "mutation_scale": 32, - "linestyle": "", - "lw": PATH_LINEWIDTH, - "color": CURRENT_COLOR, -} - -plot_params_voltage_path = PathPlotParams( - alpha=1.0, - zorder=inf, - color=VOLTAGE_COLOR, - linestyle="--", - linewidth=PATH_LINEWIDTH, - marker="o", - markersize=10, - markeredgecolor=VOLTAGE_COLOR, - markerfacecolor="white", -) - -plot_params_voltage_plus = PathPlotParams( - alpha=1.0, - zorder=inf, - color=VOLTAGE_COLOR, - marker="+", - markersize=6, -) - -plot_params_voltage_minus = PathPlotParams( - alpha=1.0, - zorder=inf, - color=VOLTAGE_COLOR, - marker="_", - markersize=6, -) - -plot_params_current_path = PathPlotParams( - alpha=1.0, - zorder=inf, - color=CURRENT_COLOR, - linestyle="--", - linewidth=PATH_LINEWIDTH, - marker="", -) plot_params_lobe_peak = PathPlotParams( alpha=1.0, diff --git a/tidy3d/plugins/smatrix/component_modelers/base.py b/tidy3d/plugins/smatrix/component_modelers/base.py index 9b3e8d47e8..d42c2a99f7 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, get_args +from typing import TYPE_CHECKING, Literal, Optional, Union import pydantic.v1 as pd @@ -102,22 +102,6 @@ def _sim_has_no_sources(cls, val): raise SetupError(f"'{cls.__name__}.simulation' must not have any sources.") return val - @pd.validator("ports", always=True) - def _warn_rf_license(cls, val): - """Warn about new licensing requirements for RF ports.""" - rf_port = False - TerminalPortTypeTuple = get_args(TerminalPortType) - for port in val: - if type(port) in TerminalPortTypeTuple: - rf_port = True - break - if rf_port: - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return val - @pd.validator("element_mappings", always=True) def _validate_element_mappings(cls, element_mappings, values): """ diff --git a/tidy3d/plugins/smatrix/component_modelers/terminal.py b/tidy3d/plugins/smatrix/component_modelers/terminal.py index 82348a43f1..41c2f968e7 100644 --- a/tidy3d/plugins/smatrix/component_modelers/terminal.py +++ b/tidy3d/plugins/smatrix/component_modelers/terminal.py @@ -11,6 +11,7 @@ from tidy3d.components.boundary import BroadbandModeABCSpec from tidy3d.components.geometry.utils_2d import snap_coordinate_to_grid from tidy3d.components.index import SimulationMap +from tidy3d.components.microwave.base import MicrowaveBaseModel from tidy3d.components.monitor import DirectivityMonitor from tidy3d.components.simulation import Simulation from tidy3d.components.source.time import GaussianPulse @@ -32,7 +33,7 @@ from tidy3d.plugins.smatrix.types import NetworkElement, NetworkIndex, SParamDef -class TerminalComponentModeler(AbstractComponentModeler): +class TerminalComponentModeler(AbstractComponentModeler, MicrowaveBaseModel): """ Tool for modeling two-terminal multiport devices and computing port parameters with lumped and wave ports. @@ -99,14 +100,6 @@ class TerminalComponentModeler(AbstractComponentModeler): description="Whether to compute scattering parameters using the 'pseudo' or 'power' wave definitions.", ) - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values - @pd.root_validator(pre=False) def _warn_refactor_2_10(cls, values): log.warning( diff --git a/tidy3d/plugins/smatrix/data/data_array.py b/tidy3d/plugins/smatrix/data/data_array.py index 358b89fc68..a715a43a76 100644 --- a/tidy3d/plugins/smatrix/data/data_array.py +++ b/tidy3d/plugins/smatrix/data/data_array.py @@ -2,10 +2,7 @@ from __future__ import annotations -import pydantic.v1 as pd - from tidy3d.components.data.data_array import DataArray -from tidy3d.log import log class PortDataArray(DataArray): @@ -24,14 +21,6 @@ class PortDataArray(DataArray): __slots__ = () _dims = ("f", "port") - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values - class ModalPortDataArray(DataArray): """Port parameter matrix elements for modal ports. @@ -76,11 +65,3 @@ class TerminalPortDataArray(DataArray): __slots__ = () _dims = ("f", "port_out", "port_in") _data_attrs = {"long_name": "terminal-based port matrix element"} - - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values diff --git a/tidy3d/plugins/smatrix/data/terminal.py b/tidy3d/plugins/smatrix/data/terminal.py index 5599db383b..f1d0d24a05 100644 --- a/tidy3d/plugins/smatrix/data/terminal.py +++ b/tidy3d/plugins/smatrix/data/terminal.py @@ -7,12 +7,12 @@ import numpy as np import pydantic.v1 as pd -from tidy3d.components.base import Tidy3dBaseModel, cached_property +from tidy3d.components.base import cached_property from tidy3d.components.data.data_array import FreqDataArray from tidy3d.components.data.monitor_data import MonitorData from tidy3d.components.data.sim_data import SimulationData +from tidy3d.components.microwave.base import MicrowaveBaseModel from tidy3d.components.microwave.data.monitor_data import AntennaMetricsData -from tidy3d.log import log from tidy3d.plugins.smatrix.component_modelers.terminal import TerminalComponentModeler from tidy3d.plugins.smatrix.data.base import AbstractComponentModelerData from tidy3d.plugins.smatrix.data.data_array import PortDataArray, TerminalPortDataArray @@ -29,7 +29,7 @@ ) -class MicrowaveSMatrixData(Tidy3dBaseModel): +class MicrowaveSMatrixData(MicrowaveBaseModel): """Stores the computed S-matrix and reference impedances for the terminal ports.""" port_reference_impedances: Optional[PortDataArray] = pd.Field( @@ -51,7 +51,7 @@ class MicrowaveSMatrixData(Tidy3dBaseModel): ) -class TerminalComponentModelerData(AbstractComponentModelerData): +class TerminalComponentModelerData(AbstractComponentModelerData, MicrowaveBaseModel): """ Data associated with a :class:`.TerminalComponentModeler` simulation run. @@ -117,14 +117,6 @@ def smatrix( ) return smatrix_data - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values - def _monitor_data_at_port_amplitude( self, port: TerminalPortType, diff --git a/tidy3d/plugins/smatrix/ports/base_terminal.py b/tidy3d/plugins/smatrix/ports/base_terminal.py index 24594e76b1..086ac36b91 100644 --- a/tidy3d/plugins/smatrix/ports/base_terminal.py +++ b/tidy3d/plugins/smatrix/ports/base_terminal.py @@ -7,10 +7,11 @@ import pydantic.v1 as pd -from tidy3d.components.base import Tidy3dBaseModel, cached_property +from tidy3d.components.base import cached_property from tidy3d.components.data.data_array import FreqDataArray from tidy3d.components.data.sim_data import SimulationData from tidy3d.components.grid.grid import Grid +from tidy3d.components.microwave.base import MicrowaveBaseModel from tidy3d.components.monitor import FieldMonitor, ModeMonitor from tidy3d.components.source.base import Source from tidy3d.components.source.time import GaussianPulse @@ -18,7 +19,7 @@ from tidy3d.log import log -class AbstractTerminalPort(Tidy3dBaseModel, ABC): +class AbstractTerminalPort(MicrowaveBaseModel, ABC): """Class representing a single terminal-based port. All terminal ports must provide methods for computing voltage and current. These quantities represent the voltage between the terminals, and the current flowing from one terminal into the other. @@ -65,11 +66,3 @@ def compute_voltage(self, sim_data: SimulationData) -> FreqDataArray: @abstractmethod def compute_current(self, sim_data: SimulationData) -> FreqDataArray: """Helper to compute current flowing into the port.""" - - @pd.root_validator(pre=False) - def _warn_rf_license(cls, values): - log.warning( - "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.", - log_once=True, - ) - return values diff --git a/tidy3d/plugins/smatrix/ports/coaxial_lumped.py b/tidy3d/plugins/smatrix/ports/coaxial_lumped.py index c1def80bbd..ed93899fe3 100644 --- a/tidy3d/plugins/smatrix/ports/coaxial_lumped.py +++ b/tidy3d/plugins/smatrix/ports/coaxial_lumped.py @@ -15,6 +15,9 @@ from tidy3d.components.geometry.utils_2d import increment_float from tidy3d.components.grid.grid import Grid, YeeGrid from tidy3d.components.lumped_element import CoaxialLumpedResistor +from tidy3d.components.microwave.path_integrals.integrals.current import Custom2DCurrentIntegral +from tidy3d.components.microwave.path_integrals.integrals.voltage import AxisAlignedVoltageIntegral +from tidy3d.components.microwave.path_integrals.specs.base import AbstractAxesRH from tidy3d.components.monitor import FieldMonitor from tidy3d.components.source.current import CustomCurrentSource from tidy3d.components.source.time import GaussianPulse @@ -22,8 +25,6 @@ from tidy3d.components.validators import skip_if_fields_missing from tidy3d.constants import MICROMETER from tidy3d.exceptions import SetupError, ValidationError -from tidy3d.plugins.microwave import CustomCurrentIntegral2D, VoltageIntegralAxisAligned -from tidy3d.plugins.microwave.path_integrals import AbstractAxesRH from .base_lumped import AbstractLumpedPort @@ -42,7 +43,7 @@ class CoaxialLumpedPort(AbstractLumpedPort, AbstractAxesRH): ... direction="+", ... name="coax_port_1", ... impedance=50 - ... ) # doctest: +SKIP + ... ) """ center: Coordinate = pd.Field( @@ -282,7 +283,7 @@ def compute_voltage(self, sim_data: SimulationData) -> FreqDataArray: exact_port_center = self.snapped_center(sim_data.simulation.grid) field_data = sim_data[self._voltage_monitor_name] - voltage_integral = VoltageIntegralAxisAligned( + voltage_integral = AxisAlignedVoltageIntegral( center=self._voltage_path_center(exact_port_center), size=self._voltage_path_size, extrapolate_to_endpoints=True, @@ -332,7 +333,7 @@ def compute_current(self, sim_data: SimulationData) -> FreqDataArray: # Setup the path integral and integrate the H field path_center = list(exact_port_center) path_center[self.injection_axis] = path_pos - path_integral = CustomCurrentIntegral2D.from_circular_path( + path_integral = Custom2DCurrentIntegral.from_circular_path( path_center, radius, num_path_coords, self.injection_axis, False ) current = path_integral.compute_current(field_data) diff --git a/tidy3d/plugins/smatrix/ports/rectangular_lumped.py b/tidy3d/plugins/smatrix/ports/rectangular_lumped.py index 68aeb979f9..b81974dbd8 100644 --- a/tidy3d/plugins/smatrix/ports/rectangular_lumped.py +++ b/tidy3d/plugins/smatrix/ports/rectangular_lumped.py @@ -20,13 +20,14 @@ from tidy3d.components.geometry.utils_2d import increment_float from tidy3d.components.grid.grid import Grid, YeeGrid from tidy3d.components.lumped_element import LinearLumpedElement, LumpedResistor, RLCNetwork +from tidy3d.components.microwave.path_integrals.integrals.current import AxisAlignedCurrentIntegral +from tidy3d.components.microwave.path_integrals.integrals.voltage import AxisAlignedVoltageIntegral from tidy3d.components.monitor import FieldMonitor from tidy3d.components.source.current import UniformCurrentSource from tidy3d.components.source.time import GaussianPulse from tidy3d.components.types import Axis, FreqArray, LumpDistType from tidy3d.components.validators import assert_line_or_plane from tidy3d.exceptions import SetupError, ValidationError -from tidy3d.plugins.microwave import CurrentIntegralAxisAligned, VoltageIntegralAxisAligned from .base_lumped import AbstractLumpedPort @@ -41,7 +42,7 @@ class LumpedPort(AbstractLumpedPort, Box): ... voltage_axis=2, ... name="port_1", ... impedance=50 - ... ) # doctest: +SKIP + ... ) See Also -------- @@ -211,7 +212,7 @@ def compute_voltage(self, sim_data: SimulationData) -> FreqDataArray: """Helper to compute voltage across the port.""" voltage_box = self._to_voltage_box(sim_data.simulation.grid) field_data = sim_data[self._voltage_monitor_name] - voltage_integral = VoltageIntegralAxisAligned( + voltage_integral = AxisAlignedVoltageIntegral( center=voltage_box.center, size=voltage_box.size, extrapolate_to_endpoints=True, @@ -238,7 +239,7 @@ def compute_current(self, sim_data: SimulationData) -> FreqDataArray: current_box = self._to_current_box(sim_data.simulation.grid) # H field is continuous at integral bounds, so extrapolation is turned off - I_integral = CurrentIntegralAxisAligned( + I_integral = AxisAlignedCurrentIntegral( center=current_box.center, size=current_box.size, sign="+", diff --git a/tidy3d/plugins/smatrix/ports/wave.py b/tidy3d/plugins/smatrix/ports/wave.py index 896029fcf5..881420b604 100644 --- a/tidy3d/plugins/smatrix/ports/wave.py +++ b/tidy3d/plugins/smatrix/ports/wave.py @@ -15,6 +15,11 @@ from tidy3d.components.geometry.base import Box from tidy3d.components.geometry.bound_ops import bounds_contains from tidy3d.components.grid.grid import Grid +from tidy3d.components.microwave.impedance_calculator import ( + CurrentIntegralType, + ImpedanceCalculator, + VoltageIntegralType, +) from tidy3d.components.monitor import ModeMonitor from tidy3d.components.simulation import Simulation from tidy3d.components.source.field import ModeSource, ModeSpec @@ -24,7 +29,6 @@ from tidy3d.components.types import Axis, Direction, FreqArray from tidy3d.constants import fp_eps from tidy3d.exceptions import ValidationError -from tidy3d.plugins.microwave import CurrentIntegralTypes, ImpedanceCalculator, VoltageIntegralTypes from tidy3d.plugins.mode import ModeSolver from .base_terminal import AbstractTerminalPort @@ -58,13 +62,13 @@ class WavePort(AbstractTerminalPort, Box): "``num_modes`` in the solver will be set to ``mode_index + 1``.", ) - voltage_integral: Optional[VoltageIntegralTypes] = pd.Field( + voltage_integral: Optional[VoltageIntegralType] = pd.Field( None, title="Voltage Integral", description="Definition of voltage integral used to compute voltage and the characteristic impedance.", ) - current_integral: Optional[CurrentIntegralTypes] = pd.Field( + current_integral: Optional[CurrentIntegralType] = pd.Field( None, title="Current Integral", description="Definition of current integral used to compute current and the characteristic impedance.", diff --git a/tidy3d/utils.py b/tidy3d/utils.py new file mode 100644 index 0000000000..279a904b14 --- /dev/null +++ b/tidy3d/utils.py @@ -0,0 +1,10 @@ +"""General utility functions.""" + +from __future__ import annotations + +import sys + + +def is_running_pytest() -> bool: + """Return True if the code is currently running under pytest.""" + return "pytest" in sys.modules diff --git a/tidy3d/web/api/tidy3d_stub.py b/tidy3d/web/api/tidy3d_stub.py index b1b3edb300..749df3a09e 100644 --- a/tidy3d/web/api/tidy3d_stub.py +++ b/tidy3d/web/api/tidy3d_stub.py @@ -15,6 +15,7 @@ from tidy3d.components.data.sim_data import SimulationData from tidy3d.components.eme.data.sim_data import EMESimulationData from tidy3d.components.eme.simulation import EMESimulation +from tidy3d.components.microwave.data.monitor_data import MicrowaveModeSolverData from tidy3d.components.mode.data.sim_data import ModeSimulationData from tidy3d.components.mode.simulation import ModeSimulation from tidy3d.components.simulation import Simulation @@ -245,6 +246,7 @@ def from_file( supported_data_classes = [ SimulationData, ModeSolverData, + MicrowaveModeSolverData, HeatSimulationData, HeatChargeSimulationData, EMESimulationData, @@ -334,6 +336,7 @@ def _check_convergence_and_warnings(stub_data: WorkflowDataType) -> None: stub_data, ( ModeSolverData, + MicrowaveModeSolverData, ModeSimulationData, TerminalComponentModelerData, ModalComponentModelerData,