Skip to content

Conversation

@dmarek-flex
Copy link
Contributor

@dmarek-flex dmarek-flex commented Oct 31, 2025

backend: https://github.com/flexcompute/compute/pull/2764

Greptile Overview

Greptile Summary

This PR adds voltage-based mode selection capability to the ModeSolver through a new TerminalSpec class that allows users to specify which conductors should be at positive or negative voltage for transmission line modes.

Key changes:

  • Added TerminalSpec class in mode_spec.py to specify voltage patterns across conductors using coordinate points, structure names, or geometric regions (arrays)
  • Added terminal_specs field to MicrowaveModeSpec for mode selection and ordering
  • Implemented validation methods in ModePlaneAnalyzer: _convert_terminal_specifications_to_candidate_geometry(), _identify_conductor_voltage_sets(), and _validate_conductor_voltage_configurations()
  • Added early validation in ModeSolver._validate_mode_plane_analysis() to catch configuration errors before submission
  • Refactored Simulation._validate_microwave_mode_specs() to _validate_microwave_mode_plane_analysis() for consistency
  • Added _post_process_modes_with_terminal_specs() placeholder that raises SetupError for local mode solver (backend implementation pending)

Implementation notes:

  • The null check for structure name lookup is correctly implemented (lines 325-329 in mode_plane_analyzer.py)
  • Previous comments about assert statements and ValueError/SetupError mismatches were referring to outdated code - the current implementation uses proper error handling throughout
  • Tests comprehensively cover validation, error cases, and various terminal specification types
  • The terminal specification conversion logic correctly handles tuples (points), strings (structure names), and arrays (points/lines/polygons based on shape)

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk - the implementation is well-tested and adds new functionality without breaking existing features
  • Score reflects solid implementation with comprehensive test coverage. The code properly handles error cases, has appropriate validation, and follows the codebase's error handling patterns. Previous review comments appear to be outdated and don't reflect the current state of the code.
  • No files require special attention - all implementations follow best practices and have good test coverage

Important Files Changed

File Analysis

Filename Score Overview
tidy3d/components/microwave/mode_spec.py 4/5 Added TerminalSpec class and terminal_specs field to MicrowaveModeSpec for voltage-based mode selection. Clean implementation with good validation logic.
tidy3d/components/microwave/path_integrals/mode_plane_analyzer.py 2/5 Added terminal specification conversion and validation methods. Contains assert statements (lines 319, 325) that should be proper error handling, and null check for structure name lookup is present (lines 325-329).
tidy3d/components/mode/mode_solver.py 4/5 Added _validate_mode_plane_analysis() and _post_process_modes_with_terminal_specs() methods for early validation. Also fixed return type annotation from list to tuple.

Sequence Diagram

sequenceDiagram
    participant User
    participant ModeSolver
    participant MicrowaveModeSpec
    participant ModePlaneAnalyzer
    participant TerminalSpec
    
    User->>ModeSolver: Create with terminal_specs
    ModeSolver->>ModeSolver: _post_init_validators()
    ModeSolver->>ModeSolver: _validate_mode_plane_analysis()
    
    alt terminal_specs is not None
        ModeSolver->>ModePlaneAnalyzer: Create analyzer instance
        ModePlaneAnalyzer->>ModePlaneAnalyzer: conductor_bounding_boxes
        ModePlaneAnalyzer->>ModePlaneAnalyzer: conductor_shapes (cached)
        ModePlaneAnalyzer-->>ModeSolver: conductor info
        
        ModeSolver->>ModePlaneAnalyzer: _identify_conductor_voltage_sets()
        ModePlaneAnalyzer->>ModePlaneAnalyzer: _convert_terminal_specifications_to_candidate_geometry()
        ModePlaneAnalyzer->>TerminalSpec: Read plus/minus terminals
        TerminalSpec-->>ModePlaneAnalyzer: terminal identifiers
        
        alt terminal is tuple
            ModePlaneAnalyzer->>ModePlaneAnalyzer: Create Point
        else terminal is string
            ModePlaneAnalyzer->>ModePlaneAnalyzer: Find structure by name
            ModePlaneAnalyzer->>ModePlaneAnalyzer: Get intersections with geometry
        else terminal is array
            ModePlaneAnalyzer->>ModePlaneAnalyzer: Create Point/LineString/Polygon
        end
        
        ModePlaneAnalyzer->>ModePlaneAnalyzer: _find_conductor_terminals()
        ModePlaneAnalyzer->>ModePlaneAnalyzer: Match terminals to conductors
        ModePlaneAnalyzer-->>ModeSolver: voltage_sets
        
        ModeSolver->>ModePlaneAnalyzer: _validate_conductor_voltage_configurations()
        ModePlaneAnalyzer->>ModePlaneAnalyzer: Check for conflicts/duplicates
        
        alt validation fails
            ModePlaneAnalyzer-->>ModeSolver: Raise SetupError
            ModeSolver-->>User: "TerminalSpec was not setup correctly"
        end
    end
    
    User->>ModeSolver: solve() / access .data
    ModeSolver->>ModeSolver: _add_microwave_data()
    
    alt terminal_specs is not None
        ModeSolver->>ModeSolver: _post_process_modes_with_terminal_specs()
        Note over ModeSolver: Raises SetupError<br/>(not available locally)
    end
    
    ModeSolver->>ModeSolver: _make_path_integrals()
    ModeSolver-->>User: MicrowaveModeSolverData
Loading

@dmarek-flex dmarek-flex self-assigned this Oct 31, 2025
@dmarek-flex dmarek-flex added RF rc3 3rd pre-release labels Oct 31, 2025
@dmarek-flex dmarek-flex force-pushed the dmarek/FXC-3725-voltage-based-mode-selection branch 2 times, most recently from c6b771b to fac5487 Compare November 4, 2025 21:15
@dmarek-flex dmarek-flex marked this pull request as ready for review November 4, 2025 21:20
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.

7 files reviewed, 8 comments

Edit Code Review Agent Settings | Greptile

@dmarek-flex dmarek-flex force-pushed the dmarek/FXC-3725-voltage-based-mode-selection branch from fac5487 to 90a1ac0 Compare November 4, 2025 21:25
@github-actions
Copy link
Contributor

github-actions bot commented Nov 4, 2025

Diff Coverage

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

  • tidy3d/init.py (100%)
  • tidy3d/components/microwave/data/monitor_data.py (100%)
  • tidy3d/components/microwave/mode_spec.py (100%)
  • tidy3d/components/microwave/path_integrals/mode_plane_analyzer.py (100%)
  • tidy3d/components/mode/mode_solver.py (100%)
  • tidy3d/components/simulation.py (100%)

Summary

  • Total: 168 lines
  • Missing: 0 lines
  • Coverage: 100%

@dmarek-flex dmarek-flex force-pushed the dmarek/FXC-3725-voltage-based-mode-selection branch 3 times, most recently from 70ed291 to 0349e69 Compare November 10, 2025 21:37
@dmarek-flex dmarek-flex force-pushed the dmarek/FXC-3725-voltage-based-mode-selection branch from 0349e69 to a26413b Compare November 11, 2025 03:16
@dmarek-flex
Copy link
Contributor Author

another review @greptile

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.

15 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

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.

_get_isolated_conductors_as_shapely in ModePlaneAnalyzer can be slow as it seems to take all structures, and interior_disjoint_geometries defaults to False. Could you filter structures similar to

structures_intersect = [s for s in structure_list if self.intersects(s.geometry)]
, and pass interior_disjoint_geometries variable that defaults toTrue`?

"been used to set up the monitor or mode solver.",
)

def _is_transmission_line_mode(self, mode_index: int) -> bool:
Copy link
Collaborator

Choose a reason for hiding this comment

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

should we name it _is_quasi_tem_mode to be more specific?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, and since I need to add this mode_type categorization anyways in the other PR. I will try to quickly generalize to determine TEM/quasiTEM,TE,TM.

)

def _is_transmission_line_mode(self, mode_index: int) -> bool:
"""Check if a mode qualifies as a quasi-TEM transmission line mode.
Copy link
Collaborator

Choose a reason for hiding this comment

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

add here that it's checked at lowest frequency

"""Validate conductor identification inputs."""

# If it's a string or tuple, pass through
if isinstance(val, (str, tuple)):
Copy link
Collaborator

Choose a reason for hiding this comment

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

could you elaborate on why tuple is always valid?

Copy link
Collaborator

Choose a reason for hiding this comment

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

i see, it's supposed to be a 2d point. that needs validation as well.

description="Optional tuple of terminal specifications for mode selection and ordering in "
"transmission line systems. Each 'TerminalSpec' defines the desired voltage pattern (which conductors "
"should be positive vs. negative) for a mode. When provided, the mode solver automatically reorders "
"computed modes to match the terminal specification order and applies phase corrections to ensure "
Copy link
Collaborator

Choose a reason for hiding this comment

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

sometimes it's called phase alignment

raise SetupError(
f"No conductor found intersecting with the {terminal_type}_terminal. "
"Please ensure that your terminal specification (coordinate, line, or polygon) "
"intersects with at least one conductive structure in the mode plane. "
Copy link
Collaborator

Choose a reason for hiding this comment

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

"intersects with exactly one conductive structure"

plus_indices = [
i for i, geom in enumerate(conductor_shapely) if geom.intersects(plus_terminal)
]
validate_conductor_intersection(plus_indices, "plus")
Copy link
Collaborator

Choose a reason for hiding this comment

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

what about multiple terminal_specs intersect the same conductor?

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.

Looks very good to me, some minor comments

"of vertices defining a geometric region (point for N=1, line for N=2, polygon for N>2). "
"The coordinate or region should lie within the desired conductor in the mode plane.",
)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it is checked at some point, but would it make sense to add a cheap validator here that checks that when plus and minus terminals are converted to sets their intersection is empty? I think that might require to convert numpy array into list or tuples. Just to invalidate grossly incorrectly setups like TerminalSpec(plus_terminals=["ground"], minus_terminals=["ground"])

... )
"""

plus_terminals: tuple[ConductorIdentifierType, ...] = pd.Field(
Copy link
Contributor

Choose a reason for hiding this comment

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

Definitely optional, but I think plus and minus would also be acceptable here instead of plus_terminals and minus_terminals

"ignored for the associated mode.",
)

terminal_specs: Optional[tuple[TerminalSpec, ...]] = pd.Field(
Copy link
Contributor

Choose a reason for hiding this comment

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

is there any restrictions on the length of this tuple, like < num_modes? It would also be good to clarify in description whether each terminal_spec is for a separate mode

@dmarek-flex
Copy link
Contributor Author

and pass interior_disjoint_geometries variable that defaults toTrue`?

From understanding, I need that False. I need the shapes of conductors that will ultimately be simulated, users might place dielectrics after conductors to mask parts of them. In other words, geometries can overlap with different properties in the plane.

@weiliangjin2021
Copy link
Collaborator

and pass interior_disjoint_geometries variable that defaults toTrue`?

From understanding, I need that False. I need the shapes of conductors that will ultimately be simulated, users might place dielectrics after conductors to mask parts of them. In other words, geometries can overlap with different properties in the plane.

My understanding is that we'll follow standard conventions that metal structure always override dielectrics. For mask, users need to manually apply boolean operations to metal structures.

Comment on lines +366 to +375
mode_plane_analyzer = ModePlaneAnalyzer(
center=plane.center,
size=plane.size,
field_data_colocated=colocate,
structures=sim.volumetric_structures,
grid=sim.grid,
symmetry=sim.symmetry,
sim_box=sim.simulation_geometry,
)
_ = mode_plane_analyzer.conductor_bounding_boxes
Copy link
Collaborator

Choose a reason for hiding this comment

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

What benefit do we get by having ModePlaneAnalyzer being a class rather than a function? Just how you're envisioning this to grow

Comment on lines +384 to +387
voltage_sets = mode_plane_analyzer._identify_conductor_voltage_sets(
mode_spec.terminal_specs
)
mode_plane_analyzer._validate_conductor_voltage_configurations(voltage_sets)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The thing is that if it were a function it does not require schema changes really? And don't see the class itself bring much value?

# where the first set contains positive terminals and the second set contains negative terminals.
VoltageSets = tuple[set[int], set[int]]
# Conversion of user-supplied TerminalSpec into shapely geometries which are used to find intersecting conductors
TerminalSpecShapes = tuple[list[Shapely], list[Shapely]]
Copy link
Collaborator

@daquinteroflex daquinteroflex Nov 13, 2025

Choose a reason for hiding this comment

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

So this is what you want to extend to more inputs ultimately?


# Type for holding sets of indices associated with conductors,
# where the first set contains positive terminals and the second set contains negative terminals.
VoltageSets = tuple[set[int], set[int]]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we feel this is going to be extensible fully?

Comment on lines +87 to +98
def mode_symmetry(self) -> tuple[Symmetry, Symmetry, Symmetry]:
"""Mode symmetry considering simulation box and simulation symmetry."""
return self._get_mode_symmetry(self.sim_box, self.symmetry)

@cached_property
def mode_limits(self) -> Bound:
"""Mode plane bounds restricted to final grid positions.
Mode profiles are calculated on a grid which is expanded from the monitor size
to the closest grid boundaries, taking into account symmetry conditions.
"""
return self._get_mode_limits(self.grid, self.mode_symmetry)
Copy link
Collaborator

Choose a reason for hiding this comment

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

So this type of code structure gives me an impression of trying to store a state but then we have the initialization latency ultimately, and it depends on the applications

@dmarek-flex dmarek-flex removed the rc3 3rd pre-release label Nov 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants