Skip to content

XY transposable sim.plot() plots (issue1072) VERSION 2 #2544

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 56 commits into
base: develop
Choose a base branch
from

Conversation

jewettaijfc
Copy link
Contributor

@jewettaijfc jewettaijfc commented Jun 5, 2025

This PR addresses he feature request from issue #1072 (adding the ability to swap the horizontal and vertical axes of a simulation plot).

(Note: The plot_field() and TriangularGridDataset.plot() functions are beyond the scope of this PR.)

How to test this PR

  • Download and unzip this file: test_tidy3d_pr2544.ipynb.zip
  • Open it with jupyter.
  • Evaluate the cells to verify that the plots using transpose=True are flipped with respect to the plots that omit the transpose argument.

(I confess I was a little bit sloppy creating this .ipynb file, so I might have forgotten to use transpose=True consistently in all of the plots. In that case, feel free to add transpose=True to any of the .plot functions you see in this file.)


Motivation

I can think of 2 different strategies to transpose XY axes:

  1. Wait until the very end, after the matplotlib axes object has been fully defined. Then swap the horizontal and vertical axes at display time.
  2. Swap the geometry, limits, and axes-label information before sending them to matplotlib.

This PR uses the second strategy.

Specifically:

  • I systematically added a transpose argument to all plot-related functions that previously accepted axis or x, y, z arguments. (Conceptually, these arguments all go together: they specify how to display a plane from a 3D space on the screen.)
  • This includes pop_axis() and unpop_axis(), and the (plot-related) functions that invoke them ...all the way up to the top-level plot() functions.
  • By default transpose=False everywhere. So unless someone overrides this by setting transpose=True, the code should behave as it did before.

It seems like I made a lot of complicated changes in this PR, but I really didn't. The only non-trivial changes to the code happen inside pop_axis() and unpop_axis() (see below). All of the higher-level functions merely propagate the transpose argument downwards until it is finally processed by either pop_axis() or unpop_axis(). This reduces the cognitive burden on the programmer: In each function, I no longer have to think hard about how to deal with the new transpose argument. I just forward the transpose argument downwards to any functions that ultimately invoke either pop_axis() or unpop_axis(). If we do this consistently, we hopefully shouldn't see surprising behavior later.

(UPDATE: These functions are now called pop_axis_and_swap() and unpop_axis_and_swap().)


For completeness, here are the new definitions of pop_axis_and_swap() and unpop_axis_and_swap():

    def pop_axis_and_swap(
        coord: tuple[Any, Any, Any],
        axis: int,
        transpose: bool = False,                         # <-- newly added
    ) -> tuple[Any, tuple[Any, Any]]:
        plane_vals = list(coord)
        axis_val = plane_vals.pop(axis)
        if transpose:                                    # <-- newly added
            plane_vals = [plane_vals[1], plane_vals[0]]  # <-- newly added
        return axis_val, tuple(plane_vals)

    def unpop_axis_and_swap(
        ax_coord: Any,
        plane_coords: tuple[Any, Any],
        axis: int,
        transpose: bool = False,              # <-- newly added
    ) -> tuple[Any, Any, Any]:
	coords = list(plane_coords)
        if transpose:                         # <-- newly added
            coords = [coords[1], coords[0]]   # <-- newly added
        coords.insert(axis, ax_coord)
        return tuple(coords)

That's all this PR does.

@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 5, 2025

I don't want to add complexity to your code where it's not needed, @tylerflex ! The pop_axis() and unpop_axis() functions appear in many places. Let me know if I made too many modifications. I tried to avoid changing functions which are clearly not plot-related, even if they invoke pop_axis() or unpop_axis(). Specifically:

  • I did not modify beam.py, boundary.py, field_projection.py, geometry/polyslab.py, geometry/utils2d.py, grid/*.py, lumped_element.py, medium.py, microwave/formulas/circuit_parameters.py, mode_solver.py, source/field.py, or any files outside of the components/ folder.
  • I did not modify any functions that compute derivatives.
  • I did not modify geometry/primitives.py, except for the various *intersections*() and *cross_section() functions (which are frequently needed for plotting).
  • I did not modify monitor.py, except for the plot related functions.

@jewettaijfc
Copy link
Contributor Author

This code has not yet been tested. It probably won't even run. I am beginning the (manual) testing process now.

@@ -1315,6 +1345,7 @@ def to_gdstk(
z: Optional[float] = None,
Copy link
Collaborator

Choose a reason for hiding this comment

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

we probably dont need to pass transpose to these gds functions, these are used to export the polygons to file.

@tylerflex
Copy link
Collaborator

hey @jewettaijfc , this is an interesting approach. I think it looks promising! I would have a few pieces of advice at this stage:

  1. invest in a good solid test case. Dig into the test_viz.py and write some plot tests with transpose=True. Learn how to run the unit tests (should be in the developer guide) and that will save you a lot of time as you test this.
  2. No need to pass transpose to any functions involving gds, eg to_gds. These are used when importing and exporting to GDSII files. https://www.flexcompute.com/tidy3d/examples/notebooks/GDSImport/
  3. Pretty minor comment but might as well start thinking about the argument naming. I am pretty confident that a bool is the right data type for this option, but the naming I'm not sure about.
    a. in the plotting functions, we could also consider like swap_axes : bool = False?
    b. in the pop_axis and unpop_axis, maybe we consider something similar? or swap_planar_coords?
  4. The only thing I'm still not sure about / hesitant about is whether we should be modifying these pop_axis and unpop_axis functions directly. I'm leaning towards thinking it's ok, but another way we could approach this to introduce new functions pop_axis_and_swap(coords, axis, swap_axes: bool) that simply calls pop_axis(coords, axis) and swaps the results if swap_axes is true.). Something to think about, but not sure if it improves anything or just complicates things.

Thanks for the effort on this! good luck on the testing.

@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 5, 2025

hey @jewettaijfc , this is an interesting approach. I think it looks promising! I would have a few pieces of advice at this stage:

  1. invest in a good solid test case. Dig into the test_viz.py and write some plot tests with transpose=True. Learn how to run the unit tests (should be in the developer guide) and that will save you a lot of time as you test this.

For sure. Regarding my work-flow, I don't put PRs up for review until I have some unit tests. It takes a little extra time to write them, but unit tests have saved my butt so many times.

  1. No need to pass transpose to any functions involving gds, eg to_gds. These are used when importing and exporting to GDSII files. https://www.flexcompute.com/tidy3d/examples/notebooks/GDSImport/

Thanks! I'll fix that now.

  1. Pretty minor comment but might as well start thinking about the argument naming. I am pretty confident that a bool is the right data type for this option, but the naming I'm not sure about.
    a. in the plotting functions, we could also consider like swap_axes : bool = False?
    b. in the pop_axis and unpop_axis, maybe we consider something similar? or swap_planar_coords?

I will let you decide. But I suggest we use the same name for all functions. (I like both swap_axes and transpose because the names are short compared to swap_panar_coords.)

  1. The only thing I'm still not sure about / hesitant about is whether we should be modifying these pop_axis and unpop_axis functions directly. I'm leaning towards thinking it's ok, but another way we could approach this to introduce new functions pop_axis_and_swap(coords, axis, swap_axes: bool) that simply calls pop_axis(coords, axis) and swaps the results if swap_axes is true.). Something to think about, but not sure if it improves anything or just complicates things.

I was wondering about this too. I will look into that.

Thanks for the effort on this! good luck on the testing.

Thank you!

@jewettaijfc
Copy link
Contributor Author

Currently:

  • import tidy3d works without crashing
  • Box.plot(), is working (with transpose=New)
  • Scene.plot(), is working (with transpose=New), except when the scene contains Polyslabs (See below)

Here are some things fixed in this PR (which weren't working before in PR2537):

  • Monitor.plot() is working (with transpose=True).
  • Source.plot() is working (with transpose=True).
  • plot_arrow() is working (with transpose=True).
  • When hlim and vlim are unspecified (=None), the boundaries are correctly swapped when transpose=True. (This is what we want to happen.)
  • Simulation.plot_grid() seems to be working (with transpose=True). This needs more testing...

Here are some things that are currently broken in this PR (which were working in PN2537):

  • Transposing Polyslabobjects does not work yet. But I know why: I haven't made any changes to thegeometry/polyslab.py` file yet. I'll work on that tomorrow.

@jewettaijfc jewettaijfc changed the title Jewettaijfc/issue1072 v2 XY transposable sim.plot() plots (issue1072) VERSION 2 Jun 6, 2025
@jewettaijfc
Copy link
Contributor Author

  • PolySlabs are working with transpose=True. (I had to add a transpose argument to PolySlab._intersect_normal() and PolySlab._intersect_side().)
  • I will test cylinders next.

NOTE: I left some print() statements in the code that I'm still using for (manual) debugging. I'll remove these soon.

@jewettaijfc
Copy link
Contributor Author

  • Cylinders are working with transpose=True

@jewettaijfc
Copy link
Contributor Author

I'm done with manual testing.

I started writing unit tests.

@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 7, 2025

Oops. I guess I wasn't done with manual testing.
I fixed a bug and (visually) verified that

  • Simulation.plot_eps() is working (with both transpose=True and False).
  • Now all of the functions in VisSimulation.ipynb are working (with both transpose=True and False).

@jewettaijfc jewettaijfc self-assigned this Jun 8, 2025
@jewettaijfc
Copy link
Contributor Author

  • HeatChargeSimulation.plot_sources() is now working when swap_axes=True and false
  • The HeatSolver.ipynb notebook is working now

@jewettaijfc
Copy link
Contributor Author

  • The HeatDissipation.ipynb notebook (from the tidy3d-notebooks examples) also seems to be working ...up until the point where I have to submit simulations. (I can't run the remaining portion of the .ipynb notebook because it looks like my free tidy3d account has expired. I'm working on fixing this.)

@@ -89,7 +89,6 @@ class PolySlab(base.Planar):
@staticmethod
def make_shapely_polygon(vertices: ArrayLike) -> shapely.Polygon:
"""Make a shapely polygon from some vertices, first ensures they are untraced."""
vertices = get_static(vertices)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is causing the error, this line should be put back

Copy link
Collaborator

Choose a reason for hiding this comment

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

yup! just verified that it works after adding it back

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wow. Thank you! This would have taken me hours to figure out.

return [self.make_shapely_polygon(vertices_z)]
vertices = vertices_z
if swap_axes:
vertices = vertices[:, (1, 0)] # swap column 0 (x coords) with column 1 (y coords)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm worried this code will not work with autograd (the package I mentioned that handles our automatic differentiation).

I would suggest replacing with

vertices = vertices[:, ::-1]   # reverse the second axis

Copy link
Contributor Author

@jewettaijfc jewettaijfc Jun 9, 2025

Choose a reason for hiding this comment

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

Thanks!
Other than pop_axis_and_swap() and unpop_axis_and_swap(), this is the only other place in the code (that I am aware of) containing non-trivial modifications. I need to write a unit-test for it.

max_bounds = list(field_data_bounds[1])
min_bounds.pop(axis)
max_bounds.pop(axis)
_, min_bounds = self.pop_axis_and_swap(field_data_bounds[0], axis, swap_axes=swap_axes)
Copy link
Collaborator

Choose a reason for hiding this comment

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

good catch, this is probably more robust than what we had before.

@jewettaijfc jewettaijfc force-pushed the jewettaijfc/issue1072_v2 branch from 2f38175 to de2d14d Compare June 9, 2025 15:29
@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 10, 2025

I think my previous two commits broke all of the plot_field() functions.

EDIT 2025-6-10: I JUST REMOVED THOSE COMMITS FROM THIS PR. THEY NO LONGER APPEAR IN THE HISTORY

In retrospect, I'm not sure I should be modifying these functions. Most of the plot_field() functions already have a **sel_kwargs argument which seems to accomplish the same goal. (For example, here.) I worry that both **sel_kwargs and swap_axes in the same function might be redundant. But perhaps I am misunderstanding what **sel_kwargs was intended for.

  • For consistency, I'd prefer to have only argument to specify planar axes (eg. either **sel_kwargs or swap_axes) that is shared by all the plots (eg. plot_sim() and plot_field()).
  • I'll ask for guidance in tomorrow's TidyGrad Office Hours.

@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 10, 2025

It sounds like plot_field() might be out of scope for this PR.
I'm starting a new PR that includes all of the changes to get plot_field() working.
I'll move the last two commits (no longer visible here) into that PR. (This will remove them from this PR.)

@jewettaijfc jewettaijfc force-pushed the jewettaijfc/issue1072_v2 branch from 2510f60 to f0995ca Compare June 10, 2025 18:32
@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 10, 2025

Minor issue:

A few days ago, commit de2d14d accidentally added a binary file to this branch (named results.prof_BRANCH=develop in the root directory). I removed it using git rm, but it's still in the git history and increasing the size of our user's downloads by 300kb.


Note to self: Later on, if we really want to purge that file from the history, we can use:

pip install git-filter-repo

cd ~/tidy3d

LARGE_BINARY_FILE_PATH="results.prof_BRANCH=develop"
git filter-repo --path "$LARGE_BINARY_FILE_PATH" --invert-paths

# (Note: The --path argument is interpreted relative to the repository 
#  root directory.  In this example, the file was in the root directory.)

Note: If you are not in the main branch, you probably have to use this command instead:

git filter-repo --path "$LARGE_BINARY_FILE" --invert-paths --force

Clean up:

git reflog expire --expire=now --all
git gc --prune=now

git push origin --force-with-lease --all
git push origin --force-with-lease --tags

I am not brave enough to try this yet.

@tylerflex
Copy link
Collaborator

what I would recommend is doing git rebase -i develop and squashing all of the commits to 1. we will do this before merging anyway. the file should be erased in this process.

@jewettaijfc jewettaijfc marked this pull request as ready for review June 11, 2025 04:10
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.

PR Summary

Implements axis transposition functionality across Tidy3D's visualization system by adding a transpose parameter to plot-related functions.

  • Core implementation centers on new pop_axis_and_swap() and unpop_axis_and_swap() functions in geometry/base.py that handle coordinate transformations
  • Systematically propagates transpose parameter through all plotting functions that accept axis arguments (x,y,z)
  • Notable update to intersections_tilted_plane() in geometry/base.py contains potential bug where X,Y axis order ignores normal direction
  • Comprehensive test coverage added across components including parameterized tests for both transpose=True/False cases
  • Clean implementation using strategy of swapping geometry/limits pre-matplotlib rather than post-rendering axis manipulation

25 files reviewed, 3 comments
Edit PR Review Bot Settings | Greptile

Copy link
Contributor

github-actions bot commented Jun 11, 2025

Diff Coverage

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

  • tidy3d/components/base_sim/simulation.py (100%)
  • tidy3d/components/eme/simulation.py (100%)
  • tidy3d/components/geometry/base.py (94.9%): Missing lines 2203,3131-3132
  • tidy3d/components/geometry/mesh.py (20.0%): Missing lines 558-560,638
  • tidy3d/components/geometry/polyslab.py (88.2%): Missing lines 635,667
  • tidy3d/components/geometry/primitives.py (72.7%): Missing lines 117,484-485
  • tidy3d/components/geometry/utils.py (100%)
  • tidy3d/components/mode/mode_solver.py (100%)
  • tidy3d/components/monitor.py (100%)
  • tidy3d/components/scene.py (68.3%): Missing lines 1235-1236,1238,1246,2023,2038,2044-2047,2050,2056,2068
  • tidy3d/components/simulation.py (100%)
  • tidy3d/components/source/base.py (100%)
  • tidy3d/components/source/field.py (100%)
  • tidy3d/components/structure.py (100%)
  • tidy3d/components/tcad/data/sim_data.py (100%)
  • tidy3d/components/tcad/simulation/heat_charge.py (100%)
  • tidy3d/plugins/waveguide/rectangular_dielectric.py (0.0%): Missing lines 1074-1075,1077,1121-1122

Summary

  • Total: 215 lines
  • Missing: 30 lines
  • Coverage: 86%

tidy3d/components/geometry/base.py

Lines 2199-2207

  2199         if section is None:
  2200             return []
  2201         path, _ = section.to_planar(to_2D=to_2D)
  2202         if transpose:
! 2203             path.vertices = path.vertices[:, ::-1]  # swap column 0 with column 1
  2204 
  2205         print(f"Box._do_intersections_tilted_plane({transpose=}), {path=}")  # DEBUG
  2206 
  2207         return path.polygons_full

Lines 3127-3136

  3127             List of 2D shapes that intersect plane.
  3128             For more details refer to
  3129             `Shapely's Documentation <https://shapely.readthedocs.io/en/stable/project.html>`_.
  3130         """
! 3131         a = self.geometry_a.intersections_tilted_plane(normal, origin, to_2D, transpose=transpose)
! 3132         b = self.geometry_b.intersections_tilted_plane(normal, origin, to_2D, transpose=transpose)
  3133         geom_a = shapely.unary_union([Geometry.evaluate_inf_shape(g) for g in a])
  3134         geom_b = shapely.unary_union([Geometry.evaluate_inf_shape(g) for g in b])
  3135         return ClipOperation.to_polygon_list(self._shapely_operation(geom_a, geom_b))

tidy3d/components/geometry/mesh.py

Lines 554-564

  554         section = self.trimesh.section(plane_origin=origin, plane_normal=normal)
  555         if section is None:
  556             return []
  557         path, _ = section.to_planar(to_2D=to_2D)
! 558         if transpose:
! 559             path.vertices = path.vertices[:, ::-1]  # swap column 0 (X) with column 1 (Y)
! 560         print(f"TriangleMesh.intersections_tilted_plane({transpose=}), {path=}")  # DEBUG
  561         return path.polygons_full
  562 
  563     def intersections_plane(
  564         self,

Lines 634-642

  634                     "Unable to compute 'TriangleMesh.intersections_plane'. "
  635                     "Using bounding box instead."
  636                 )
  637             log.warning(f"Error encountered: {e}")
! 638             return self.bounding_box.intersections_plane(x=x, y=y, z=z, transpose=transpose)
  639 
  640     def inside(
  641         self, x: np.ndarray[float], y: np.ndarray[float], z: np.ndarray[float]
  642     ) -> np.ndarray[bool]:

tidy3d/components/geometry/polyslab.py

Lines 631-639

  631         if section is None:
  632             return []
  633         path, _ = section.to_planar(to_2D=to_2D)
  634         if transpose:
! 635             path.vertices = path.vertices[:, ::-1]  # swap column 0 (X) with column 1 (Y)
  636         print(f"TriangleMesh.intersections_tilted_plane({transpose=}), {path=}")  # DEBUG
  637         return path.polygons_full
  638 
  639     def _intersections_normal(self, z: float, transpose: bool = False):

Lines 663-671

  663         dist = -z_local * self._tanq
  664         vertices_z = self._shift_vertices(self.middle_polygon, dist)[0]
  665         vertices = vertices_z
  666         if transpose:
! 667             vertices = vertices[:, ::-1]  # swap column 0 (x coords) with column 1 (y coords)
  668         return [self.make_shapely_polygon(vertices)]
  669 
  670     def _intersections_side(self, position, axis, transpose: bool = False) -> list:
  671         """Find shapely geometries intersecting planar geometry with axis orthogonal to slab.

tidy3d/components/geometry/primitives.py

Lines 113-121

  113         angles = np.linspace(0, 2 * np.pi, _N_SHAPELY_QUAD_SEGS * 4 + 1)[:-1]
  114         circ = center + np.outer(np.cos(angles), radius * u) + np.outer(np.sin(angles), radius * v)
  115         vertices = np.dot(np.hstack((circ, np.ones((angles.size, 1)))), to_2D.T)
  116         if transpose:
! 117             vertices = vertices[:, ::-1]  # swap column 0 (X) with column 1 (Y)
  118         return [shapely.Polygon(vertices[:, :2])]
  119 
  120     def intersections_plane(
  121         self,

Lines 480-489

  480         section = mesh.section(plane_origin=origin, plane_normal=normal)
  481         if section is None:
  482             return []
  483         path, _ = section.to_planar(to_2D=to_2D)
! 484         if transpose:
! 485             path.vertices = path.vertices[:, ::-1]  # swap column 0 (X) with column 1 (Y)
  486         return path.polygons_full
  487 
  488     def _intersections_normal(self, z: float, transpose: bool = False):
  489         """Find shapely geometries intersecting cylindrical geometry with axis normal to slab.

tidy3d/components/scene.py

Lines 1231-1242

  1231         rmin.insert(normal_axis_ind, normal_position)
  1232         rmax.insert(normal_axis_ind, normal_position)
  1233 
  1234         if grid is None:
! 1235             if transpose:
! 1236                 import warnings
  1237 
! 1238                 warnings.warn(
  1239                     "UNTESTED! The plotting function you are using has not\n"
  1240                     "yet been rigorously tested with `transpose=True`.\n"
  1241                     "To confirm that the plot you are seeing now is correct,\n"
  1242                     "try again with `transpose=False` and compare the two plots.",

Lines 1242-1250

  1242                     "try again with `transpose=False` and compare the two plots.",
  1243                     stacklevel=2,
  1244                 )
  1245 
! 1246             _, plane_axes_inds = Box.pop_axis_and_swap(
  1247                 [0, 1, 2], axis=normal_axis_ind, transpose=transpose
  1248             )
  1249 
  1250             eps_diag = medium.eps_dataarray_freq(frequency=freq)

Lines 2019-2027

  2019         plt_type accepts ["doping", "N_a", "N_d"]
  2020         """
  2021         coords = "xyz"
  2022         normal_axis_ind, normal_position = Box.parse_xyz_kwargs(x=x, y=y, z=z)
! 2023         normal_axis, plane_axes = Box.pop_axis_and_swap(
  2024             coords, normal_axis_ind, transpose=transpose
  2025         )
  2026 
  2027         # make grid for eps interpolation

Lines 2034-2042

  2034         rmin.insert(normal_axis_ind, normal_position)
  2035         rmax.insert(normal_axis_ind, normal_position)
  2036 
  2037         # for the time being let's assume we'll always need to generate a mesh
! 2038         _, plane_axes_inds = Box.pop_axis_and_swap(
  2039             [0, 1, 2], axis=normal_axis_ind, transpose=transpose
  2040         )
  2041 
  2042         # build grid

Lines 2040-2054

  2040         )
  2041 
  2042         # build grid
  2043         N = 100
! 2044         coords_2D_mesh = [np.linspace(rmin[d], rmax[d], N) for d in plane_axes_inds]
! 2045         X, Y = np.meshgrid(coords_2D_mesh[0], coords_2D_mesh[1], indexing="ij")
! 2046         coords_2D_lookup = coords_2D_mesh.copy()
! 2047         if transpose:
  2048             # `X`, `Y`` are the coordinates that will be displayed to the user.  We want to
  2049             # change them so they are displayed at new locations with the axes swapped.
! 2050             X, Y = Y, X
  2051             # The `coords_2D_lookup[]` arrays will be used to lookup the property
  2052             # we are plotting as a function of the real (physical) coordinates.
  2053             # This should not change.  However the `coords_2D_mesh[]` array's
  2054             # order was reversed when we previously invoked `pop_axis_and_swap()`

Lines 2052-2060

  2052             # we are plotting as a function of the real (physical) coordinates.
  2053             # This should not change.  However the `coords_2D_mesh[]` array's
  2054             # order was reversed when we previously invoked `pop_axis_and_swap()`
  2055             # with `transpose=True`.  We need to undo that now.
! 2056             coords_2D_lookup.reverse()  # = [coords_2D_mesh[1], coords_2D_mesh[0]]
  2057 
  2058         struct_doping = [
  2059             np.zeros(X.shape),  # let's use 0 for N_a
  2060             np.zeros(X.shape),  # and 1 for N_d

Lines 2064-2072

  2064         for n, doping in enumerate([electric_spec.N_a, electric_spec.N_d]):
  2065             if isinstance(doping, float):
  2066                 struct_doping[n] = struct_doping[n] + doping
  2067             if isinstance(doping, SpatialDataArray):
! 2068                 struct_coords = {
  2069                     "xyz"[d]: coords_2D_lookup[i] for i, d in enumerate(plane_axes_inds)
  2070                 }
  2071                 data_2D = doping
  2072                 # check whether the provided doping data is 2 or 3D

tidy3d/plugins/waveguide/rectangular_dielectric.py

Lines 1070-1081

  1070         -------
  1071         matplotlib.axes._subplots.Axes
  1072             The supplied or created matplotlib axes.
  1073         """
! 1074         if transpose:
! 1075             import warnings
  1076 
! 1077             warnings.warn(
  1078                 "UNTESTED! The plot_geometry_edges() function has not yet been tested\n"
  1079                 "with `transpose=True`.  To confirm that the plot you are seeing now\n"
  1080                 "is correct, try again with `transpose=False` and compare the two plots.",
  1081                 stacklevel=2,

Lines 1117-1126

  1117             ):
  1118                 p = self._translate(x, y, 0)
  1119                 plot_x.append(p[u])
  1120                 plot_y.append(p[v])
! 1121             if transpose:
! 1122                 plot_x, plot_y = plot_y, plot_x
  1123             ax.plot(plot_x, plot_y, linestyle="-", **kwargs)
  1124         ax.set_aspect("equal")
  1125         return ax

@jewettaijfc
Copy link
Contributor Author

Hi Tyler. I'm going to bed.
I'll respond to the greptile-apps bot tomorrow morning. If you want to wait until then before reviewing this diff, that's fine with me.

Copy link
Collaborator

@tylerflex tylerflex 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! Just minor comments for now. You can focus on clearing these up + those from the new automated review bot 😆 and then request me again for another review.

Let's have @yaugenst-flex also take a look when he's back.

One thing I think we should do also before merging is pick a handful of existing notebooks at random (maybe 5-10) and just throw a transpose=True in to test this a bit more in some real world situations.

Thanks @jewettaijfc !

b = td.Box(size=(1, 1, 1))
for axis in range(3):
coords = (1, 2, 3)
Lz, (Lx, Ly) = b.pop_axis_and_swap(coords, axis=axis, transpose=transpose)
Copy link
Collaborator

Choose a reason for hiding this comment

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

just FYI, since this is a static method, you can just call it without initializing the class, eg td.Box.pop_axis_and_swap

@@ -732,6 +763,7 @@ def pop_axis(coord: tuple[Any, Any, Any], axis: int) -> tuple[Any, tuple[Any, An
axis : int
Integer index into 'xyz' (0,1,2).


Copy link
Collaborator

Choose a reason for hiding this comment

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

might need to remove this extra line

transpose: bool = False,
) -> tuple[Any, tuple[Any, Any]]:
"""
pop_axis_and_swap() is identical to pop_axis(), except that accepts an
Copy link
Collaborator

Choose a reason for hiding this comment

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

for any code in docstrings, we need to follow the style guide, otherwise it will show up in plaintext in the docs, please read through this guide as it will come up all of the time as you work on more PRs: https://www.notion.so/flexcompute/Python-Style-Guide-afac5dbbf949463e89c18fe39b7453c9

axis : int
Integer index into 'xyz' (0,1,2).
transpose: bool = False
Optional: Swap the order of the data from the two remaining axes in the output tuple?
Copy link
Collaborator

Choose a reason for hiding this comment

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

check this, at least remove "?" :)

transpose: bool = False,
) -> tuple[Any, Any, Any]:
"""
unpop_axis_and_swap() is identical to unpop_axis(), except that accepts
Copy link
Collaborator

Choose a reason for hiding this comment

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

same comments as above docstring, check here too.

@@ -1570,7 +1679,7 @@ def intersections_tilted_plane(
axis = np.argmax(np.abs(normal)).item()
coord = "xyz"[axis]
kwargs = {coord: origin[axis]}
section = self.intersections_plane(**kwargs)
section = self.intersections_plane(**kwargs) # <-- BUG?: X,Y axis order ignores normal axis direction +/-
Copy link
Collaborator

Choose a reason for hiding this comment

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

hm, what is this? could you explain?

Copy link
Contributor Author

@jewettaijfc jewettaijfc Jun 11, 2025

Choose a reason for hiding this comment

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

Summary

This is a somewhat outdated comment. But perhaps it's worth looking at this code anyway.

Details (feel free to skip)

I added the comment because the first time I saw the intersections_tilted_plane() function, I attempted to add the transpose: bool = False argument to this function. But I eventually got the impression that this function is not used by the various plot functions, so I removed the transpose argument. But I forgot to remove that comment. But there is still an underlying issue I'm worried about.

Source of concern

  • The intersections_tilted_plane() figures out which objects intersect the plane determined by normal and origin.

  • I think it also generates 2D polygon silhouettes of these objects at the location where the plane slices through them. (Please correct me if I'm wrong.)

  • If so, then the coordinates of the polygons in the plane depends on the choice of coordinate system in the plane (i.e. the planar axes basis vectors).

  • I'm not sure how intersections_tilted_plane() chooses these vectors, but I'm guessing that it normally tries to use a right-handed coordinate system. That means, if $\vec{z'}$ denotes the direction normal to the plane, then intersections_tilted_plane() normally chooses other two axes in the plane ($\vec{x'}$ and $\vec{y'}$) to satisfy: $\vec{x'} \times \vec{y'} = \vec{z'}$.
    As a consequence of this, an object shaped like the letter N might get sliced in a way that rotates it to resemble a letter Z, but not to it's mirror-image (ᴎ).

  • However that's no longer guaranteed if intersections_tilted_plane() uses pop_axis(). The pop_axis("xyz", axis=1) function returns ("y", ("x", "z")), and $\vec{x} \times \vec{z} = -\vec{y}$. That could cause a mirror-image (ᴎ).

  • Is this inconsistency a problem? (Maybe not, but I wanted to run this by you anyway.)

  • If there's any doubt, the code should behave correctly if we remove that if statement. (if np.sum(np.isclose(normal, 0.0)) == 2:). (That if statement was only added to make the code run faster in some situations.)

Copy link
Collaborator

@tylerflex tylerflex Jun 11, 2025

Choose a reason for hiding this comment

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

I'm actually not sure about this.. if it's a problem we haven't encountered it yet. It would be @weiliangjin2021 who would know but he's on parental leave at the moment. for now maybe we can leave the comment in, but you can do it as

... # NOTE: message

we tend to use "NOTE" to mark something to come back to later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops. Sorry. I'll take it out.
(I'll put a notice on my google-calendar to remind me to ask weiliangjin2021 about this.)

@yaugenst-flex yaugenst-flex self-requested a review June 12, 2025 07:26
@jewettaijfc jewettaijfc force-pushed the jewettaijfc/issue1072_v2 branch from 8e2434a to f9b57ba Compare June 12, 2025 18:46
@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 12, 2025

(Incidentally, that long list of additional commits was inadvertently generated when I attempted to rebase on top of the latest version of the develop branch. I'll check the developer guide again to see if I'm using git the correct way.)

@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 12, 2025

Update: The code does not quite work yet. I am going through all of the notebooks in the tidy3d-notebooks repository, trying all the plot functions. Apparently, I missed a few. Not all of these plot functions have been updated to work with transpose=True yet. Specifically, right now I'm updating these functions

  • _pcolormesh_shape_doping_box() (from scene.py)
  • _pcolormesh_shape_custom_medium_structure_eps() (frome scene.py) <-- EDIT: This also needs to be changed
  • _get_contrib() (from doping.py) <-- EDIT: No need to modify any functions in doping.py
  • _get_indices_in_box() (from doping.py) <-- EDIT: No need to modify any functions in doping.py
  • and possibly other functions as well.

(NOTE: Even after I have fixed these problems, I still have many more notebook examples to test. I've only tested about 40% of the notebook examples from tidy3d-notebooks so far. It looks like this PR needs more work on my part)
:(

@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 13, 2025

I put about 6 hours into fixing those functions (mentioned in the previous comment). Those functions are more complicated than anything I have seen earlier.

I think I can get them working in a few more hours, but I'm going to bed first.

@tylerflex
Copy link
Collaborator

@jewettaijfc thanks for the efforts on this! If the changes are too complicated, I think it would be fine to discuss just disabling the transpose option for the HeatChargeSimulation plotting. We could reassign that to someone else as a followup for someone who knows that part of the code better. It might be better to limit this in scope and move on. Let me know what you think. If you can make it work, that would be great, but if it looks like a can of worms, we can just try to wrap it up.

@jewettaijfc
Copy link
Contributor Author

@jewettaijfc thanks for the efforts on this! If the changes are too complicated, I think it would be fine to discuss just disabling the transpose option for the HeatChargeSimulation plotting. We could reassign that to someone else as a followup for someone who knows that part of the code better. It might be better to limit this in scope and move on. Let me know what you think. If you can make it work, that would be great, but if it looks like a can of worms, we can just try to wrap it up.

Hi Tyler! I think I can get it working by the end of lunch time. I'd rather have these functions updated as consistently as possible

@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 13, 2025

I think I fixed the problem. (After I clean out all of the extra print() statements I added I'll commit the change.)

…ed all of the print() statements used for debugging yet
@jewettaijfc
Copy link
Contributor Author

I just realized that I also need to fix _pcolormesh_shape_custom_medium_structure_eps(). This function is similar to the function I just fixed. It should not take me long.

…messages when the new code is (visually) untested.
…or debugging. After I'm sure the code is working, I will remove the rest.
@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 14, 2025

  • I finally got _pcolormesh_shape_custom_medium_structure_eps() working to enable swapping (transpose=true) (Thanks to Yannick for showing me how to test this.)
  • I know it took a while, but I'm really happy it's working now. Almost half of the tidy3d-notebooks examples I've tested so far depend on these _pcolormesh_shape functions that I spent the last 1.5 days working on.
  • I've tested about 50 notebooks from tidy3d-notebooks.
  • So far, they are all working (except for the arrow scaling problem. see below)
  • I haven't removed all of the print() statements I added for debugging. I will do that after I test more notebooks.
  • This PR is getting close!

Arrow scaling

The only problem I've detected so far is that arrows are sometimes not scaled correctly when transpose=True. This only seems to happen when using ax.set_aspect() to override the default aspect ratio (1).

Here's an example. The plot on the right uses transpose=True. It looks correct however the orange arrows in the plot on the right are scaled so short that they appear to be pointing in the opposite direction!
PR2544_test_AdiabaticCouplerLN__transposed_arrow_scale

I will take a look at this issue, but I want to go through more notebook examples first.

@jewettaijfc
Copy link
Contributor Author

jewettaijfc commented Jun 15, 2025

Arrow scaling

The only problem I've detected so far is that arrows are sometimes not scaled correctly when transpose=True. This only seems to happen when using ax.set_aspect() to override the default aspect ratio (1).

This was really annoying me today.

It looks like this is a preexisting issue with tidy3d plots, and not specific to features I added in this PR.

I attempted a quick fix for that issue with a different PR.

Now I can continue testing this PR with peace of mind.

@jewettaijfc
Copy link
Contributor Author

FYI. This PR is in a broken state right now. I know why. I'm working on it.

…ed_plane() or _do_intersections_tilted_plane(). This fixed some plots and broke others. After this commit, rotated Box objects work with "transpose=True", and cylinders no longer work with "transpose=True".
…n to accept a argument. This seems to work! More importantly, it saves me from having to dig into the details of how to calculate plane intersections with each type of geometric primitive. I will continue testing. If all goes well, I will clean out the transpose argument from places where it is no longer needed before I send this PR back for review again.
@jewettaijfc
Copy link
Contributor Author

  • The GeometryTransformations.ipynb examples are finally working now.
  • This change may have broken the code elsewhere.
  • I should probably clean up (simplify) the code other places to undo some of the changes I made in this PR which I no longer need.
  • After that, more testing is still needed.

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.

Add option to transpose plot
2 participants