Skip to content

Commit

Permalink
Slanted PolySlab implementation
Browse files Browse the repository at this point in the history
More Validators for PolySlab to make sure the base polygon is valid
Validator to make sure no vertices crossing during extrusion with sidewall
Change "inside" function in geometry to include boundaries
Implement itersection_side for slanted PolySlab
  • Loading branch information
weiliangjin2021 authored and tylerflex committed Mar 24, 2022
1 parent 6f71eda commit 3bcfa27
Show file tree
Hide file tree
Showing 4 changed files with 969 additions and 85 deletions.
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[MASTER]
extension-pkg-allow-list=pydantic
ignore=material_library.py, plugins
good-names=ax, im, Lx, Ly, Lz, x0, y0, z0, x, y, z, u, v, w, f, t, y1, y2, x1, x2, xs, ys, zs, Ax, Nx, Ny, Nz, N, dl, rr, E, H, xx, yy, zz, dx, dy, Jx, Jy, Jz, Ex, Ey, Ez, Mx, My, Mz, Hx, Hy, Hz, dz, e, fp, dt, a, c, kx, ky, kz, k0, Jx_k, Jy_k, My_k, Mx_k, N_theta, N_phi, L_theta, L_phi, ux, uy, uz
good-names=ax, im, Lx, Ly, Lz, x0, y0, z0, x, y, z, u, v, w, f, t, y1, y2, x1, x2, x3, x4, y3, y4, xs, ys, zs, Ax, Nx, Ny, Nz, N, dl, rr, E, H, xx, yy, zz, dx, dy, Jx, Jy, Jz, Ex, Ey, Ez, Mx, My, Mz, Hx, Hy, Hz, dz, e, fp, dt, a, c, kx, ky, kz, k0, Jx_k, Jy_k, My_k, Mx_k, N_theta, N_phi, L_theta, L_phi, ux, uy, uz

[BASIC]

Expand Down
1 change: 1 addition & 0 deletions test_local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ pytest -ra tests/test_grid.py
pytest -ra tests/test_IO.py
pytest -ra tests/test_material_library.py
pytest -ra tests/test_plugins.py
pytest -ra tests/test_sidewall.py

pytest --doctest-modules tidy3d/components --ignore=tidy3d/components/base.py
264 changes: 264 additions & 0 deletions tests/test_sidewall.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
from typing import Dict
import pytest
import numpy as np
import pydantic
from shapely.geometry import Polygon, Point

import tidy3d as td
from tidy3d.log import ValidationError, SetupError


def setup_polyslab(vertices, dilation, angle, bounds):
s = td.PolySlab(
vertices=vertices,
slab_bounds=bounds,
axis=2,
dilation=dilation,
sidewall_angle=angle,
)
return s


def minimal_edge_length(vertices):
"""compute the minimal edge length in a polygon"""
vs = vertices.T.copy()
vsp = np.roll(vs.copy(), axis=-1, shift=-1)
edge_length = np.linalg.norm(vsp - vs, axis=0)
return np.min(edge_length)


def convert_valid_polygon(vertices):
"""Given vertices that might have intersecting eduges, generate
vertices of a valid polygon
"""
poly = Polygon(vertices).buffer(1e-3) # make sure no intersecting edges
if type(poly) is not Polygon:
poly = poly[0]

vertices_n = np.array(poly.exterior.coords[:])
return vertices_n


# default values
bounds = (0, 0.5)
dilation = 0.0
angle = 0


def test_remove_duplicate():
"""
Make sure redundant neighboring vertices are removed
"""
vertices = np.random.random((10, 2))
vertices[0, :] = vertices[9, :]
vertices[1, :] = vertices[0, :]
vertices[5, :] = vertices[6, :]

vertices = td.PolySlab._remove_duplicate_vertices(vertices)
assert vertices.shape[0] == 7


def test_valid_polygon():
"""No intersecting edges"""

# area = 0
vertices = ((0, 0), (1, 0), (2, 0))
with pytest.raises(SetupError) as e_info:
s = setup_polyslab(vertices, dilation, angle, bounds)

# only two points
vertices = ((0, 0), (1, 0), (1, 0))
with pytest.raises(SetupError) as e_info:
s = setup_polyslab(vertices, dilation, angle, bounds)

# intersecting edges
vertices = ((0, 0), (1, 0), (1, 1), (0, 1), (0.5, -1))

with pytest.raises(SetupError) as e_info:
s = setup_polyslab(vertices, dilation, angle, bounds)


def test_crossing_square():
"""
Vertices crossing detection for a simple square
"""
vertices = ((0, 0), (1, 0), (1, -1), (0, -1))
dilation = 0.0
angle = np.pi / 4
s = setup_polyslab(vertices, dilation, angle, bounds)

# dilation too significant
dilation = -1.1
angle = 0
with pytest.raises(SetupError) as e_info:
s = setup_polyslab(vertices, dilation, angle, bounds)

# angle too large
dilation = 0
angle = np.pi / 3
with pytest.raises(SetupError) as e_info:
s = setup_polyslab(vertices, dilation, angle, bounds)

# combines both
dilation = -0.1
angle = np.pi / 4
with pytest.raises(SetupError) as e_info:
s = setup_polyslab(vertices, dilation, angle, bounds)


def test_max_erosion_polygon():
"""
Maximal erosion distance validation
"""
N = 10 # number of vertices
for i in range(50):
vertices = convert_valid_polygon(np.random.random((N, 2)) * 10)

dilation = 0
angle = 0
bounds = (0, 0.5)
s = setup_polyslab(vertices, dilation, angle, bounds)

# compute maximal allowed erosion distance
_, max_dist = s._crossing_detection(s.base_polygon, -100)

# verify it is indeed maximal allowed
dilation = -max_dist + 1e-10
# avoid vertex-edge crossing case
try:
s = setup_polyslab(vertices, dilation, angle, bounds)
except:
continue
assert np.isclose(minimal_edge_length(s.base_polygon), 0, atol=1e-5)

# verify it is indeed maximal allowed
dilation = 0.0
bounds = (0, max_dist - 1e-10)
angle = np.pi / 4

# avoid vertex-edge crossing case
try:
s = setup_polyslab(vertices, dilation, angle, bounds)
except:
continue
assert np.isclose(minimal_edge_length(s.top_polygon), 0, atol=1e-5)


def test_shift_height():
"""Make sure a list of height where the plane will intersect with the vertices
works properly
"""
N = 10 # number of vertices
Lx = 10.0
for i in range(50):
vertices = convert_valid_polygon(np.random.random((N, 2)) * Lx)
dilation = 0
angle = 0
bounds = (0, 1)
s = setup_polyslab(vertices, dilation, angle, bounds)
# set up proper thickness
_, max_dist = s._crossing_detection(s.base_polygon, -100)
dilation = 0.0
bounds = (0, max_dist * 0.99)
angle = np.pi / 4
# avoid vertex-edge crossing case
try:
s = setup_polyslab(vertices, dilation, angle, bounds)
except:
continue

for axis in (0, 1):
position = np.random.random(1)[0] * Lx - Lx / 2
height = s._find_intersecting_height(position, axis)
for h in height:
bounds = (0, h)
s = setup_polyslab(vertices, dilation, angle, bounds)
diff = s.top_polygon[:, axis] - position
assert np.any(np.isclose(diff, 0)) == True


def test_intersection_with_inside():
"""Make sure intersection produces the same result as inside"""

N = 10 # number of vertices
Lx = 10 # maximal length in x,y direction
for i in range(50):
vertices = convert_valid_polygon(np.random.random((N, 2)) * Lx)
vertices = np.array(vertices).astype("float32")
dilation = 0
angle = 0
bounds = (0, 1)
s = setup_polyslab(vertices, dilation, angle, bounds)

# set up proper thickness
_, max_dist = s._crossing_detection(s.base_polygon, -100)
dilation = 0.0
bounds = (0, np.float32(max_dist * 0.95))
angle = np.pi / 4
# avoid vertex-edge crossing case
try:
s = setup_polyslab(vertices, dilation, angle, bounds)
except:
continue

### side x
xp = np.random.random(1)[0] * 2 * Lx - Lx
yp = np.random.random(10) * 2 * Lx - Lx
zp = np.random.random(10) * (bounds[1] - bounds[0]) + bounds[0]
xp = np.float32(xp)
yp = np.float32(yp)
zp = np.float32(zp)
shape_intersect = s.intersections(x=xp)

for i in range(len(yp)):
for j in range(len(zp)):
# inside
res_inside = s.inside(xp, yp[i], zp[j])
# intersect
res_inter = False
for shape in shape_intersect:
if shape.covers(Point(yp[i], zp[j])):
res_inter = True
# if res_inter != res_inside:
# print(repr(vertices))
# print(repr(s.base_polygon))
# print(bounds)
# print(xp, yp[i], zp[j])
assert res_inter == res_inside

### side y
xp = np.random.random(10) * 2 * Lx - Lx
yp = np.random.random(1)[0] * 2 * Lx - Lx
zp = np.random.random(10) * (bounds[1] - bounds[0]) + bounds[0]
xp = np.float32(xp)
yp = np.float32(yp)
zp = np.float32(zp)
shape_intersect = s.intersections(y=yp)

for i in range(len(xp)):
for j in range(len(zp)):
# inside
res_inside = s.inside(xp[i], yp, zp[j])
# intersect
res_inter = False
for shape in shape_intersect:
if shape.covers(Point(xp[i], zp[j])):
res_inter = True
assert res_inter == res_inside

### norm z
xp = np.random.random(10) * 2 * Lx - Lx
yp = np.random.random(10) * 2 * Lx - Lx
zp = np.random.random(1)[0] * (bounds[1] - bounds[0]) + bounds[0]
shape_intersect = s.intersections(z=zp)

for i in range(len(xp)):
for j in range(len(yp)):
# inside
res_inside = s.inside(xp[i], yp[j], zp)
# intersect
res_inter = False
for shape in shape_intersect:
if shape.covers(Point(xp[i], yp[j])):
res_inter = True
assert res_inter == res_inside
Loading

0 comments on commit 3bcfa27

Please sign in to comment.