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

Draft
wants to merge 26 commits into
base: develop
Choose a base branch
from

Conversation

jewettaijfc
Copy link

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

This PR is an alternative to PR#2537

I don't know which approach will work better in the long run. I will test both.


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.
  • The previous PR #2537 uses a combination of both strategies. It also tries to minimalize change to the existing code needed just to get Simulation.plot() to work.
  • This PR uses the second strategy, and it updates all of the plot-related functions, not just Simulation.plot().

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
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
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
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
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
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
Author

  • Cylinders are working with transpose=True

@jewettaijfc
Copy link
Author

I'm done with manual testing.

I started writing unit tests.

@jewettaijfc
Copy link
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
Author

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

@jewettaijfc
Copy link
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
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
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
…unit tests that Tyler suggested (and add a new unit test for PolySlab._intersections_normal())
…s it depends on. WARNING: This effort is not complete. The code is now in a completely broken state. But I had to backup my changes before I changed branches.
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.

2 participants