Skip to content

Commit

Permalink
fixes: inf eval includes interior coords, arrows check intersections,…
Browse files Browse the repository at this point in the history
… plot_structures doesnt set global alpha
  • Loading branch information
tylerflex committed Mar 31, 2022
1 parent 0ae7694 commit 395a317
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 41 deletions.
109 changes: 71 additions & 38 deletions tidy3d/components/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""Defines spatial extent of objects."""

from abc import ABC, abstractmethod
from typing import List, Tuple, Union, Any
from typing import List, Tuple, Union, Any, Callable

import pydantic
import numpy as np
Expand Down Expand Up @@ -247,35 +247,68 @@ def plot_shape(self, shape: Shapely, plot_params: PlotParams, ax: Ax) -> Ax:
ax.add_artist(patch)
return ax

@staticmethod
def _get_shape_coords(shape: Shapely) -> Tuple[float, float]:
"""Return xs, ys for given shapely shape."""

def strip_xy(shape):
"""get lists of xs and ys coordinates for a single polygon."""
xs, ys = shape.exterior.coords.xy
xs = xs.tolist()
ys = ys.tolist()
xs = [-LARGE_NUMBER / 1e5 if np.isneginf(x) else x for x in xs]
xs = [LARGE_NUMBER / 1e5 if np.isposinf(x) else x for x in xs]
ys = [-LARGE_NUMBER / 1e5 if np.isneginf(y) else y for y in ys]
ys = [LARGE_NUMBER / 1e5 if np.isposinf(y) else y for y in ys]
return xs, ys

if isinstance(shape, MultiPolygon):
shapes = list(shape.geoms)
else:
shapes = [shape]
@classmethod
def strip_coords(
cls, shape: Shapely
) -> Tuple[List[float], List[float], Tuple[List[float], List[float]]]:
"""Get the exterior and list of interior xy coords for a shape.
Parameters
----------
shape: shapely.geometry.base.BaseGeometry
The shape that you want to strip coordinates from.
xs = []
ys = []
Returns
-------
Tuple[List[float], List[float], Tuple[List[float], List[float]]]
List of exterior xy coordinates
and a list of lists of the interior xy coordinates of the "holes" in the shape.
"""

for _shape in shapes:
_xs, _ys = strip_xy(_shape)
xs += _xs
ys += _ys
if isinstance(shape, Polygon):
ext_coords = shape.exterior.coords[:]
list_int_coords = [interior.coords[:] for interior in shape.interiors]
elif isinstance(shape, MultiPolygon):
all_ext_coords = []
list_all_int_coords = []
for _shape in shape.geoms:
all_ext_coords.append(_shape.exterior.coords[:])
all_int_coords = [_interior.coords[:] for _interior in _shape.interiors]
list_all_int_coords.append(all_int_coords)
ext_coords = np.concatenate(all_ext_coords, axis=0)
list_int_coords = [
np.concatenate(all_int_coords, axis=0) for all_int_coords in list_all_int_coords
]
return ext_coords, list_int_coords

return xs, ys
@classmethod
def map_to_coords(cls, func: Callable[[float], float], shape: Shapely) -> Shapely:
"""Maps a function to each coordinate in shape.
Parameters
----------
func : Callable[[float], float]
Takes old coordinate and returns new coordinate.
shape: shapely.geometry.base.BaseGeometry
The shape to map this function to.
Returns
-------
shapely.geometry.base.BaseGeometry
A new copy of the input shape with the mapping applied to the coordinates.
"""

if not isinstance(shape, (Polygon, MultiPolygon)):
return shape

def apply_func(coords):
return [(func(coord_x), func(coord_y)) for (coord_x, coord_y) in coords]

ext_coords, list_int_coords = cls.strip_coords(shape)
new_ext_coords = apply_func(ext_coords)
list_new_int_coords = [apply_func(int_coords) for int_coords in list_int_coords]

return Polygon(new_ext_coords, holes=list_new_int_coords)

def _get_plot_labels(self, axis: Axis) -> Tuple[str, str]:
"""Returns planar coordinate x and y axis labels for cross section plots.
Expand Down Expand Up @@ -334,7 +367,7 @@ def add_ax_labels_lims(self, axis: Axis, ax: Ax, buffer: float = PLOT_BUFFER) ->
(xmin, xmax), (ymin, ymax) = self._get_plot_limits(axis=axis, buffer=buffer)

# note: axes limits dont like inf values, so we need to evaluate them first if present
xmin, xmax, ymin, ymax = self._evaluate_infs(xmin, xmax, ymin, ymax)
xmin, xmax, ymin, ymax = (self._evaluate_inf(v) for v in (xmin, xmax, ymin, ymax))

ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
Expand All @@ -343,9 +376,9 @@ def add_ax_labels_lims(self, axis: Axis, ax: Ax, buffer: float = PLOT_BUFFER) ->
return ax

@staticmethod
def _evaluate_infs(*values):
def _evaluate_inf(v):
"""Processes values and evaluates any infs into large (signed) numbers."""
return map(lambda v: v if not np.isinf(v) else np.sign(v) * LARGE_NUMBER, values)
return v if not np.isinf(v) else np.sign(v) * LARGE_NUMBER / 2.0

@classmethod
def evaluate_inf_shape(cls, shape: Shapely) -> Shapely:
Expand All @@ -354,12 +387,7 @@ def evaluate_inf_shape(cls, shape: Shapely) -> Shapely:
if not isinstance(shape, Polygon):
return shape

coords = shape.exterior.coords[:]
new_coords = []
for (coord_x, coord_y) in coords:
new_coord = tuple(cls._evaluate_infs(coord_x, coord_y))
new_coords.append(new_coord)
return Polygon(new_coords)
return cls.map_to_coords(cls._evaluate_inf, shape)

@staticmethod
def pop_axis(coord: Tuple[Any, Any, Any], axis: int) -> Tuple[Any, Tuple[Any, Any]]:
Expand Down Expand Up @@ -833,8 +861,13 @@ def _plot_arrow( # pylint:disable=too-many-arguments, too-many-locals
arrow_axis = [component == 0 for component in direction]
arrow_length, arrow_width = self._arrow_dims(ax, length_factor, width_factor)

# only add arrow if the plotting plane is perpendicular to the source
if arrow_axis.count(0.0) > 1 or arrow_axis.index(0.0) != plot_axis:
# conditions to check to determine whether to plot arrow
arrow_intersecting_plane = len(self.intersections(x=x, y=y, z=z)) > 0
arrow_perp_to_screen = arrow_axis.index(0.0) != plot_axis
arrow_not_cartesian_axis = arrow_axis.count(0.0) > 1

# plot if arrow in plotting plane and some non-zero component can be displayed.
if arrow_intersecting_plane and (arrow_not_cartesian_axis or arrow_perp_to_screen):
_, (x0, y0) = self.pop_axis(self.center, axis=plot_axis)
_, (dx, dy) = self.pop_axis(direction, axis=plot_axis)

Expand Down
4 changes: 2 additions & 2 deletions tidy3d/components/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,7 @@ def _plot_shape_structure(self, medium: Medium, shape: Shapely, ax: Ax) -> Ax:
def _get_structure_plot_params(self, medium: Medium) -> PlotParams:
"""Constructs the plot parameters for a given medium in simulation.plot()."""

plot_params = plot_params_structure
plot_params = plot_params_structure.copy(deep=True)
plot_params.linewidth = 0

mat_index = self.medium_map[medium]
Expand Down Expand Up @@ -801,7 +801,7 @@ def _get_structure_eps_plot_params(
) -> PlotParams:
"""Constructs the plot parameters for a given medium in simulation.plot_eps()."""

plot_params = plot_params_structure
plot_params = plot_params_structure.copy(deep=True)
plot_params.linewidth = 0
if alpha is not None:
plot_params.alpha = alpha
Expand Down
2 changes: 1 addition & 1 deletion tidy3d/plugins/plotly/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def plotly_shape(
) -> PlotlyFig:
"""Plot a shape to a figure."""
_shape = Geometry.evaluate_inf_shape(shape)
xs, ys = Geometry._get_shape_coords(shape=shape)
(xs, ys), _ = Geometry.strip_coords(shape=_shape)
plotly_trace = go.Scatter(
x=xs,
y=ys,
Expand Down

0 comments on commit 395a317

Please sign in to comment.