Skip to content

Conversation

@marcorudolphflex
Copy link
Contributor

@marcorudolphflex marcorudolphflex commented Dec 16, 2025

Previously, we just took the geometry bounds to construct adjoint monitors. This PR uses the plane intersection in 2D simulations.

Greptile Overview

Greptile Summary

This PR improves adjoint monitor sizing in 2D simulations by using planar geometry intersections instead of full bounding boxes. In 2D simulations (detected via self.size.count(0.0) == 1), the code now calls geometry.intersections_plane() to compute tight monitor bounds based on the actual geometry cross-section at the simulation plane.

Key changes:

  • simulation.py:4887 detects 2D simulations and passes plane info to structure monitor generation
  • structure.py:328-352 implements _box_from_plane_intersection() that computes monitor bounds from plane intersections with multi-level fallback (geometry → bounding box → full bounding box)
  • Comprehensive tests validate behavior for spheres, meshes, cylinders, boxes, and polyslabs in both 2D and 3D cases
  • Changelog entry added under 'Fixed' section

The implementation handles edge cases through a cascading fallback strategy and maintains backward compatibility for 3D simulations.

Confidence Score: 5/5

  • This PR is safe to merge with comprehensive test coverage and proper fallback handling
  • The changes are well-tested with multiple geometry types, include proper fallback logic for edge cases, maintain backward compatibility for 3D simulations, and fix an actual bug where adjoint monitors were oversized in 2D simulations. Code follows established patterns and includes appropriate documentation.
  • No files require special attention

Important Files Changed

File Analysis

Filename Score Overview
CHANGELOG.md 5/5 Added changelog entry under 'Fixed' section documenting adjoint monitor cropping in 2D simulations
tests/test_components/autograd/test_adjoint_monitors.py 5/5 New comprehensive test file covering 2D and 3D adjoint monitor sizing with various geometries
tidy3d/components/simulation.py 5/5 Detects 2D simulations and passes plane info to structure monitor generation
tidy3d/components/structure.py 4/5 Implements plane intersection logic for 2D adjoint monitors, with fallback to bounding box

Sequence Diagram

sequenceDiagram
    participant Sim as Simulation
    participant Struct as Structure
    participant Geom as Geometry
    participant BoxCls as Box

    Note over Sim: User calls _make_adjoint_monitors
    Sim->>Sim: Check if 2D via size.count(0.0) == 1
    alt Is 2D simulation
        Sim->>Sim: sim_plane = self
    else Is 3D simulation
        Sim->>Sim: sim_plane = None
    end
    
    loop For each structure
        Sim->>Struct: _make_adjoint_monitors(freqs, index, field_keys, plane)
        
        alt plane is not None (2D case)
            Struct->>Struct: Call _box_from_plane_intersection
            Struct->>Geom: geometry.intersections_plane
            Geom-->>Struct: Return intersection shapes
            
            alt Intersections found
                Struct->>Struct: Compute union of bounds
            else No intersections
                Struct->>Geom: geom_box.intersections_plane
                alt Box intersections found
                    Struct->>Struct: Compute union of bounds
                else Still no intersections
                    Struct->>Struct: Use geom_box as fallback
                end
            end
            
            Struct->>BoxCls: Box.from_bounds(rmin, rmax)
            BoxCls-->>Struct: Return planar box
        else plane is None (3D case)
            Struct->>Struct: box = geom_box
        end
        
        Struct->>Struct: Create FieldMonitor with box
        Struct->>Struct: Create PermittivityMonitor with box
        Struct-->>Sim: Return monitors
    end
    
    Sim-->>Sim: Collect all monitors
Loading

@marcorudolphflex
Copy link
Contributor Author

@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.

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@marcorudolphflex marcorudolphflex force-pushed the FXC-4563-use-2-d-intersections-for-adjoint-monitor-sizes-in-2-d-simulations branch from 4c5b691 to 2ecf314 Compare December 16, 2025 12:40
@marcorudolphflex
Copy link
Contributor Author

@greptile

@marcorudolphflex marcorudolphflex force-pushed the FXC-4563-use-2-d-intersections-for-adjoint-monitor-sizes-in-2-d-simulations branch from 2ecf314 to b1d5083 Compare December 16, 2025 12:43
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.

4 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@marcorudolphflex marcorudolphflex force-pushed the FXC-4563-use-2-d-intersections-for-adjoint-monitor-sizes-in-2-d-simulations branch 2 times, most recently from 1e0bc03 to 481b3fa Compare December 16, 2025 12:47
@marcorudolphflex marcorudolphflex marked this pull request as ready for review December 16, 2025 12:47
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.

4 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@marcorudolphflex marcorudolphflex force-pushed the FXC-4563-use-2-d-intersections-for-adjoint-monitor-sizes-in-2-d-simulations branch from 481b3fa to bd8e561 Compare December 16, 2025 14:27
"""make a random DataArray out of supplied coordinates and data_type."""
data_shape = [len(coords[k]) for k in data_array_type._dims]
np.random.seed(1)
np.random.seed(0)
Copy link
Contributor Author

@marcorudolphflex marcorudolphflex Dec 16, 2025

Choose a reason for hiding this comment

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

that's a funny one. It caused a gradient being 2e-10 by chance in test_multi_frequency_equivalence

index_to_keys[index].append(fields)

freqs = self._freqs_adjoint
sim_plane = self if self.size.count(0.0) == 1 else None
Copy link
Contributor

Choose a reason for hiding this comment

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

if we have adjoint monitors that are 3D, but the simulation is 2D, I believe the clipping will happen naturally in that the adjoint monitor will just have a 2D overlap with the simulation and so we will only get 2D data. I might be missing something though!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah it does and this is fine most of the times. I target cases like here where the geometry bounds do not reflect the bounds within the sim plane: The non-centered Sphere has smaller bounds in the simulation plane. Same would apply for non-prism-like meshes.
Basically it is more about performance/efficiency as we may only shrink the monitor potentially.

image

Copy link
Contributor

Choose a reason for hiding this comment

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

I see that makes sense!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants