Skip to content

Conversation

dmarek-flex
Copy link
Contributor

@dmarek-flex dmarek-flex commented Sep 23, 2025

add a ImpedanceSpec for controlling how characteristic impedance is calculated from modes

backend PR: https://github.com/flexcompute/compute/pull/2497

Greptile Overview

Updated On: 2025-09-23 19:17:19 UTC

Summary

This PR introduces a comprehensive impedance calculation system for microwave mode solvers. The implementation adds an ImpedanceSpec for controlling characteristic impedance calculations from modes, integrating seamlessly with the existing mode solver infrastructure.

Key Changes:

  • Added ImpedanceSpecTypes union with AutoImpedanceSpec and CustomImpedanceSpec classes
  • Created MicrowaveModeSpec to hold impedance specifications for each mode
  • Extended ModeSpec with optional microwave_mode_spec field with proper validation
  • Implemented ImpedanceCalculator with flexible voltage/current path integral support
  • Added comprehensive path integral specifications for voltage and current calculations
  • Extensive test coverage including analytical validation for stripline and coaxial geometries

The architecture properly separates concerns with automatic impedance calculation for simple cases and custom path integral specifications for advanced users. The implementation follows established patterns in the codebase and includes proper validation at multiple levels.

Confidence Score: 4/5

  • This PR is generally safe to merge with minor fixes needed
  • The implementation is well-structured with comprehensive tests and follows established patterns. Score reduced by 1 due to syntax error in validation message that needs fixing
  • Pay attention to tidy3d/components/mode_spec.py for the syntax error in the validation message

Important Files Changed

File Analysis

Filename        Score        Overview
tidy3d/components/microwave/path_integrals/impedance_spec.py 4/5 New file defining impedance calculation specifications with proper validation and Union types
tidy3d/components/microwave/microwave_mode_spec.py 5/5 Clean implementation of microwave mode specification with proper caching and validation
tidy3d/plugins/microwave/impedance_calculator.py 4/5 Comprehensive impedance calculator with proper validation, minor style improvement needed
tidy3d/components/mode_spec.py 4/5 Extended mode specification with microwave integration, contains syntax error in validation message

Sequence Diagram

sequenceDiagram
    participant User
    participant ModeSpec
    participant MicrowaveModeSpec
    participant ImpedanceSpec
    participant ModeSolver
    participant ImpedanceCalculator
    participant PathIntegrals
    
    User->>ModeSpec: Create mode spec with microwave_mode_spec
    ModeSpec->>MicrowaveModeSpec: Validate impedance_spec count vs num_modes
    MicrowaveModeSpec->>ImpedanceSpec: Create AutoImpedanceSpec or CustomImpedanceSpec
    
    alt CustomImpedanceSpec
        ImpedanceSpec->>ImpedanceSpec: Validate voltage_spec or current_spec provided
    end
    
    User->>ModeSolver: solve() with ModeSpec
    ModeSolver->>PathIntegrals: make_path_integrals() for AutoImpedanceSpec
    PathIntegrals-->>ModeSolver: Return voltage/current integrals
    ModeSolver->>ImpedanceCalculator: Create calculator with integrals
    ImpedanceCalculator->>ImpedanceCalculator: Validate at least one integral provided
    
    ModeSolver->>ImpedanceCalculator: compute_impedance(em_field)
    ImpedanceCalculator->>ImpedanceCalculator: Check monitor data supported
    
    alt Both voltage and current integrals provided
        ImpedanceCalculator->>ImpedanceCalculator: Direct impedance = voltage/current
    else Only voltage integral
        ImpedanceCalculator->>ImpedanceCalculator: Use flux: impedance = voltage²/(2*flux)
    else Only current integral
        ImpedanceCalculator->>ImpedanceCalculator: Use flux: impedance = 2*flux/current²
    end
    
    ImpedanceCalculator-->>ModeSolver: Return impedance data array
    ModeSolver-->>User: Return ModeSolverData with impedance results
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

28 files reviewed, 2 comments

Edit Code Review Bot Settings | Greptile

@dmarek-flex dmarek-flex force-pushed the dmarek/add_impedance_to_mode_data branch 2 times, most recently from ceccb9e to 18ced37 Compare September 23, 2025 19:21
@dmarek-flex dmarek-flex linked an issue Sep 23, 2025 that may be closed by this pull request
@dmarek-flex dmarek-flex self-assigned this Sep 23, 2025
@dmarek-flex dmarek-flex added the RF label Sep 23, 2025
Copy link
Contributor

github-actions bot commented Sep 24, 2025

Diff Coverage

Diff: origin/develop...HEAD, staged and unstaged changes

  • tidy3d/init.py (100%)
  • tidy3d/components/boundary.py (100%)
  • tidy3d/components/data/data_array.py (100%)
  • tidy3d/components/data/monitor_data.py (100%)
  • tidy3d/components/geometry/utils.py (97.8%): Missing lines 571
  • tidy3d/components/lumped_element.py (100%)
  • tidy3d/components/microwave/base.py (90.9%): Missing lines 19
  • tidy3d/components/microwave/data/dataset.py (100%)
  • tidy3d/components/microwave/data/monitor_data.py (100%)
  • tidy3d/components/microwave/microwave_mode_spec.py (100%)
  • tidy3d/components/microwave/path_integrals/base_spec.py (100%)
  • tidy3d/components/microwave/path_integrals/current_spec.py (100%)
  • tidy3d/components/microwave/path_integrals/impedance_spec.py (100%)
  • tidy3d/components/microwave/path_integrals/mode_plane_analyzer.py (99.2%): Missing lines 212
  • tidy3d/components/microwave/path_integrals/path_integral_factory.py (96.4%): Missing lines 127-128
  • tidy3d/components/microwave/path_integrals/types.py (100%)
  • tidy3d/components/microwave/path_integrals/viz.py (100%)
  • tidy3d/components/microwave/path_integrals/voltage_spec.py (100%)
  • tidy3d/components/mode/mode_solver.py (97.7%): Missing lines 1383
  • tidy3d/components/mode/simulation.py (100%)
  • tidy3d/components/mode/types.py (100%)
  • tidy3d/components/monitor.py (100%)
  • tidy3d/components/simulation.py (100%)
  • tidy3d/components/source/field.py (100%)
  • tidy3d/plugins/microwave/array_factor.py (100%)
  • tidy3d/plugins/microwave/auto_path_integrals.py (100%)
  • tidy3d/plugins/microwave/custom_path_integrals.py (98.9%): Missing lines 233
  • tidy3d/plugins/microwave/impedance_calculator.py (100%)
  • tidy3d/plugins/microwave/lobe_measurer.py (100%)
  • tidy3d/plugins/microwave/path_integrals.py (100%)
  • tidy3d/plugins/smatrix/component_modelers/base.py (100%)
  • tidy3d/plugins/smatrix/component_modelers/terminal.py (100%)
  • tidy3d/plugins/smatrix/data/terminal.py (100%)
  • tidy3d/plugins/smatrix/ports/base_terminal.py (100%)
  • tidy3d/plugins/smatrix/ports/coaxial_lumped.py (100%)
  • tidy3d/plugins/smatrix/ports/rectangular_lumped.py (100%)
  • tidy3d/utils.py (100%)

Summary

  • Total: 851 lines
  • Missing: 7 lines
  • Coverage: 99%

tidy3d/components/geometry/utils.py

Lines 567-575

  567         else:  # SnapType.Contract
  568             min_upper_bound_idx += snap_margin
  569             max_upper_bound_idx -= snap_margin
  570             if max_upper_bound_idx < min_upper_bound_idx:
! 571                 raise SetupError("The supplied 'snap_buffer' is too large for this contraction.")
  572             min_snap = get_upper_bound(
  573                 interval_min, coords, min_upper_bound_idx, rel_tol=rtol, strict_bounds=strict_bounds
  574             )
  575             max_snap = get_lower_bound(

tidy3d/components/microwave/base.py

Lines 15-23

  15     @pd.root_validator(pre=False)
  16     def _warn_rf_license(cls, values):
  17         # Skip warning during doctest runs
  18         if not is_running_pytest():
! 19             log.warning(
  20                 "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.",
  21                 log_once=True,
  22             )
  23         return values

tidy3d/components/microwave/path_integrals/mode_plane_analyzer.py

Lines 208-216

  208             (min_b_3d, max_b_3d), mode_symmetry_3d, conductor_shapely
  209         )
  210 
  211         if len(conductor_shapely) < 1:
! 212             raise SetupError(
  213                 "No valid isolated conductors were found in the mode plane. Please ensure that a 'Structure' "
  214                 "with a medium of type 'PEC' or 'LossyMetalMedium' intersects the mode plane and is not touching "
  215                 "the boundaries of the mode plane."
  216             )

tidy3d/components/microwave/path_integrals/path_integral_factory.py

Lines 123-132

  123             v_integrals.append(None)
  124             i_integrals.append(None)
  125             continue
  126         elif isinstance(impedance_spec, AutoImpedanceSpec):
! 127             v_spec = None
! 128             i_spec = auto_spec.current_spec
  129         else:
  130             v_spec = impedance_spec.voltage_spec
  131             i_spec = impedance_spec.current_spec

tidy3d/components/mode/mode_solver.py

Lines 1379-1387

  1379     ) -> tuple[tuple[Optional[VoltageIntegralTypes]], tuple[Optional[CurrentIntegralTypes]]]:
  1380         """Wrapper for making path integrals from the MicrowaveModeSpec. Note: overriden in the backend to support
  1381         auto creation of path integrals."""
  1382         if not self._has_microwave_mode_spec:
! 1383             raise ValueError(
  1384                 "Cannot make path integrals for when 'mode_spec' is not a 'MicrowaveModeSpec'."
  1385             )
  1386         return make_path_integrals(self.mode_spec)

tidy3d/plugins/microwave/custom_path_integrals.py

Lines 229-237

  229 
  230         # Perform phase splitting into in and out of phase for each frequency separately
  231         for term in path_currents:
  232             if np.all(term.abs == 0):
! 233                 continue
  234 
  235             # Compare phase to reference for each frequency
  236             phase_diff = term.angle - phase_reference
  237             # Wrap phase difference to [-pi, pi]

Copy link
Collaborator

@weiliangjin2021 weiliangjin2021 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most went through code structures, and the structure looks in good shape. Some minor comments:

f"but the number of modes requested is {num_modes}. Please either ensure that the "
"number of impedance specifications is equal to the number of modes or leave the "
"'MicrowaveModeSpec' field as 'None', if impedances are not needed."
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it common to have the same or different MicrowaveModeSpec for each mode?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention here is to put any additional RF/Microwave specific settings which may control the overall results of the mode solver (like adding options for processing groups of degenerate modes). Right now it only has impedance_specs which is needed for each individual mode.

So the purpose of MicrowaveModeSpec is to mainly keep rf and photonics things a bit separate. Although we could add impedance_specs directly to ModeSpec, or MicrowaveModeSpec derive from ModeSpec. But then we might need like a MicrowaveModeMonitor. paging @daquinteroflex

Copy link
Collaborator

@daquinteroflex daquinteroflex Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MicrowaveModeSpec derive (inherits) from ModeSpec

would be much better if you can do this @dmarek-flex ? I believe we have a goal to split things more cleanly also for all core fdtd and mode components

ie

class MicrowaveModeSpec(ModeSpec):
   microwave_parameters1:  ...
   microwave_parameters2:  ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I should do the same with MicrowaveModeData?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah ideally, let me have a look across the PR iab then

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately, I had to revert back to a composition approach. Inheritance led to too many changes related to ModeData/ModeSolverData.

But I did manage to allow for broadcasting a single impedance spec to all modes

Copy link
Contributor

@dbochkov-flexcompute dbochkov-flexcompute left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very impressive work, finished the first pass and left some comments. Additionally, it seems like many new classes AxisAlignedPathIntegralSpec, CustomPathIntegral2DSpec, CurrentIntegralAxisAlignedSpec, CustomCurrentIntegral2DSpec, CompositeCurrentIntegralSpec, CustomImpedanceSpec, etc are missing examples in docstrings

@dmarek-flex dmarek-flex force-pushed the dmarek/add_impedance_to_mode_data branch from 7ad7a12 to 2727e2f Compare September 30, 2025 13:11
@dmarek-flex dmarek-flex added the rc2 2nd pre-release label Sep 30, 2025
@dmarek-flex dmarek-flex force-pushed the dmarek/add_impedance_to_mode_data branch from 917c3ad to d23f936 Compare September 30, 2025 17:52
@dmarek-flex
Copy link
Contributor Author

Thanks @dbochkov-flexcompute for finding those docstring mistakes. It was a little bit harder than expected to get doc tests running correctly because of the log warning we have for rf and microwave components. I got it working by introducing the MicrowaveBaseModel class. @daquinteroflex does this approach make sense for replacing the duplicated method for warning.

Copy link
Collaborator

@daquinteroflex daquinteroflex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good @dmarek-flex ! Just a few questions on very few API design considerations

tidy3d.plugins.microwave.Custom2DCurrentIntegral
tidy3d.plugins.microwave.AxisAlignedPathIntegral
tidy3d.plugins.microwave.CustomPathIntegral2D
tidy3d.plugins.microwave.Custom2DPathIntegral
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was only introduced in rc1 right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path integrals have been since ~2.7, but we are taking the opportunity here to get in some breaking changes before RF release.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm maybe we need a breaking changes section of 2.10

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need to update a bunch of notebooks too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a notebook branch incorporating these changes.

Hmm maybe we need a breaking changes section of 2.10
Like in the docs or changelog?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe in Changelog given it's a special one

"computed. Possible values are 'diagonal', 'tensorial_real', 'tensorial_complex'.",
)

microwave_data: Optional[MicrowaveModeDataset] = pd.Field(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry wasn't it meant to be a MicrowaveModeData instead of adding this parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment on ModeSpec

GROUP_INDEX_STEP = 0.005


class ModeSpec(Tidy3dBaseModel):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So yeah why simply not make a class MicrowaveModeSpec(ModeSpec) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try this approach and it worked ok for the ModeSpec/MicrowaveModeSpec side of things. But then I felt like I would need to have a MicrowaveModeData/MicrowaveModeSolverData and that would require modifications all over the code base (frontend and backend) to provide the correct type hints and Pydantic fields.

And then following that logic we would need a MicrowaveModeMonitor/MicrowaveModeSolverMonitor/MicrowaveModeSimulation and it felt like maintaining the proper type hints would be a lot of extra work. From trying this out, inheritance did not seem the easiest approach for maintenance, and I decided that composition would be a better approach.

Copy link
Collaborator

@daquinteroflex daquinteroflex Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yaugenst-flex @tylerflex it'd be good to get your input here in terms of how we do separation of scope rather than mixing RF into the general ModeSpec as currently implemented in this PR per the following lines in this file. It is concievable that MicrowaveModeSpec(ModeSpec) keeps getting extended as well as future classes that Damian mentioned in order to provide custom RF functionality eventually, but it is a larger effort in terms of the API restructure that we also have to consider. The benefit though is a stronger separation of API, which is related to previous conversations we've had privately about product scope.

microwave_spec: Optional[MicrowaveModeSpec] = pd.Field(
None,
title="Microwave Mode Specification",
description="Optional field with additional parameters for RF and microwave applications. "
"Includes impedance specifications that enable the calculation of characteristic impedances "
"for transmission line modes.",
)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I'd rather make a dedicated MicrowaveModeSpec(ModeSpec) in a separate place vs mixing it into the component as microwave_spec: Optional[MicrowaveModeSpec] = pd.Field

eventually I think all of the RF-specific components should be moved into their own separate namespace, like a tidy3d.rf, and that includes terminal component modeler, MicrowaveModeSpec, and probably a separate RFSimulation.

To me that seems like the long term direction (a separate rf python package that extends tidy3d, but tidy3d proper can be independent of it)

Copy link
Contributor Author

@dmarek-flex dmarek-flex Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MicrowaveModeSpec makes sense but then I need to create new classes that accept it, which will lead to MicrowaveModeData/MicrowaveModeSolverData/MicrowaveModeMonitor/MicrowaveModeSolverMonitor/MicrowaveModeSimulation versions?

Or do I allow a ModeMonitor to accept both mode_specs but produce either ModeData or MicrowaveModeData depending on the spec?

Copy link
Collaborator

@daquinteroflex daquinteroflex Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately, sounds like we should go this way in the long run @dmarek-flex though, and try to have a cleaner separation for RF. Happy to chat to scope this out if we want to do this for 2.10 too before getting to MicrowaveSimulation? This said it'd be good for the product council to discuss the extra effort this might take to start doing this separation in the RF product plans @tylerflex ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, it's a tough call, because we will start to have tons of classes, but I also feel it should happen eventually. Hopefully most of these could be very thin wrappers around the tidy3d objects that they inherit from?

Ideally we can get to a place where tidy3d just contains stuff for basic fdtd + photonics. and tidy3d.rf extends tidy3d and adds anything specific to microwave. I think we'll necessarily have to add more classes there but hopefully most things can be shared.

Comment on lines -38 to +45
"CurrentIntegralAxisAligned",
"CompositeCurrentIntegral",
"CurrentIntegralTypes",
"CustomCurrentIntegral2D",
"CustomPathIntegral2D",
"CustomVoltageIntegral2D",
"Custom2DCurrentIntegral",
"Custom2DPathIntegral",
"Custom2DVoltageIntegral",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with the standardisation just got to check it is not in 2.9

add a ImpedanceSpec for controlling how characteristic impedance is calculated from modes

refactor path integral names

add doctests

fix docstring
@dmarek-flex dmarek-flex force-pushed the dmarek/add_impedance_to_mode_data branch from a4c3c40 to 837a080 Compare October 2, 2025 15:55
@dmarek-flex dmarek-flex force-pushed the dmarek/add_impedance_to_mode_data branch from 837a080 to c685b6b Compare October 2, 2025 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rc2 2nd pre-release RF
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Automatic setup of path integrals
5 participants