Skip to content
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

Robust path length extraction #453

Merged
merged 6 commits into from
Aug 2, 2024
Merged

Conversation

mdecea
Copy link
Contributor

@mdecea mdecea commented Aug 1, 2024

I put quite a lot of work on making the path length extraction logic more robust.

There are some important differences with respect to the previous implementation:

  1. The code now requires a component to have ports, and the path lengths are computed between ports. I think this makes sense because the path length of a component is not well defined without knowing which ports we are talking about.

  2. Instead of return a gf.Path, now the function extract_paths returns a dictionary with {port1},{port2} as the keys and the gf.Path as the value.

  3. The code supports evanescent coupling between structures that are not connected. This is important for directional couplers.

I have tested the code with straights, s bends, euler bends, circular bends and directional couplers and it works.

Some examples:

S bend:
image

MMI:
image

Directional coupler:
image

Copy link
Contributor

sourcery-ai bot commented Aug 1, 2024

🧙 Sourcery has finished reviewing your pull request!


Tips
  • Trigger a new Sourcery review by commenting @sourcery-ai review on the pull request.
  • Continue your discussion with Sourcery by replying directly to review comments.
  • You can change your review settings at any time by accessing your dashboard:
    • Enable or disable the Sourcery-generated pull request summary or reviewer's guide;
    • Change the review language;
  • You can always contact us if you have any questions or feedback.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

We've reviewed this pull request using the Sourcery rules engine. If you would also like our AI-powered code review then let us know.

@mdecea
Copy link
Contributor Author

mdecea commented Aug 1, 2024

@nikosavola @joamatab

@nikosavola nikosavola self-requested a review August 2, 2024 05:51
@nikosavola nikosavola added enhancement New feature or request Verification DRC and LVS python Pull requests that update Python code labels Aug 2, 2024
Copy link
Member

@nikosavola nikosavola left a comment

Choose a reason for hiding this comment

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

Amazing job! Everything is well commented as well, very commendable.

Comment on lines 366 to 470
# Same port - skip
continue
if (
f"{port1};{port2}" in paths
or f"{port2};{port1}" in paths
or f"{port1};{port2}" in ev_paths
or f"{port2};{port1}" in ev_paths
):
# The path has already been computed
continue
else:
# We need to calculate the (evanescent) path

# First gather a connected path that contains each port
for key in paths.keys():
if port1 in key:
path1 = paths[key]
key1 = key
if port2 in key:
path2 = paths[key]
key2 = key

# Now calculate the closest point between the two paths
# We do so by iterating over the x points of path1
# and finding the point closest in x of path2, then calculating distance
distances = distance.cdist(path1.points, path2.points)
# ind = np.unravel_index(np.argmin(distances), distances.shape)

# What do we do if there are multiple points with the same minimum distance?
# Choose the one that's closer to the center of the component
ind_min_dist_pts = np.where(distances == distances.min())
min_dist_points1 = path1.points[
np.unique(ind_min_dist_pts[0]), :
]
min_dist_points2 = path2.points[
np.unique(ind_min_dist_pts[1]), :
]

# Find the point closest to the center of the polygon
center = np.array(
[
simplified_component.dcenter.x,
simplified_component.dcenter.y,
]
)

distances2 = distance.cdist(
center.reshape(1, -1), min_dist_points1
)
ind = np.argmin(distances2)

# Now that we have the closest point we just start by one path and
# transition to the other at the closest point
ind_1 = np.where(
np.all(path1.points == min_dist_points1[ind, :], axis=1)
)[0][0]
ind_2 = np.where(
np.all(path2.points == min_dist_points2[ind, :], axis=1)
)[0][0]

# We can decide which part of the path we choose depending on the position
# of the port in the key
if f"{port1};" in key1:
part1 = path1.points[:ind_1, :]
else:
part1 = path1.points[ind_1:, :]
inds = np.argsort(part1[:, 0])
part1 = part1[inds, :]

if f"{port2};" in key2:
part2 = path2.points[:ind_2, :]
else:
part2 = path2.points[ind_2:, :]

# There is a chance that we need to flip the parts
d1 = np.sum(np.power(part1[-1, :] - part2[0, :], 2))
d2 = np.sum(np.power(part1[-1, :] - part2[-1, :], 2))
d3 = np.sum(np.power(part1[0, :] - part2[-1, :], 2))
d4 = np.sum(np.power(part1[0, :] - part2[0, :], 2))

if d1 == np.min([d1, d2, d3, d4]):
evan_path = np.vstack((part1, part2))
elif d2 == np.min([d1, d2, d3, d4]):
evan_path = np.vstack((part1, np.flipud(part2)))
elif d3 == np.min([d1, d2, d3, d4]):
evan_path = np.vstack((np.flipud(part1), np.flipud(part2)))
else:
evan_path = np.vstack((np.flipud(part1), part2))

# ==== This is for debugging, keep it until this is table ===
# plt.figure()
# plt.plot(path1.points[:, 0], path1.points[:, 1], "--")
# plt.plot(path2.points[:, 0], path2.points[:, 1], "--")
# plt.plot(evan_path[:, 0], evan_path[:, 1], "-o")
# plt.show()
# ========

if filter_function is not None:
evan_path = filter_function(evan_path)
ev_paths[f"{port1};{port2}"] = gf.Path(evan_path)
Copy link
Member

Choose a reason for hiding this comment

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

We can simplify the nesting a bit with itertools.combinations and removing the else:

Suggested change
for port1p in all_ports:
port1 = port1p.name
for port2p in all_ports:
port2 = port2p.name
if port1 == port2:
# Same port - skip
continue
if (
f"{port1};{port2}" in paths
or f"{port2};{port1}" in paths
or f"{port1};{port2}" in ev_paths
or f"{port2};{port1}" in ev_paths
):
# The path has already been computed
continue
else:
# We need to calculate the (evanescent) path
# First gather a connected path that contains each port
for key in paths.keys():
if port1 in key:
path1 = paths[key]
key1 = key
if port2 in key:
path2 = paths[key]
key2 = key
# Now calculate the closest point between the two paths
# We do so by iterating over the x points of path1
# and finding the point closest in x of path2, then calculating distance
distances = distance.cdist(path1.points, path2.points)
# ind = np.unravel_index(np.argmin(distances), distances.shape)
# What do we do if there are multiple points with the same minimum distance?
# Choose the one that's closer to the center of the component
ind_min_dist_pts = np.where(distances == distances.min())
min_dist_points1 = path1.points[
np.unique(ind_min_dist_pts[0]), :
]
min_dist_points2 = path2.points[
np.unique(ind_min_dist_pts[1]), :
]
# Find the point closest to the center of the polygon
center = np.array(
[
simplified_component.dcenter.x,
simplified_component.dcenter.y,
]
)
distances2 = distance.cdist(
center.reshape(1, -1), min_dist_points1
)
ind = np.argmin(distances2)
# Now that we have the closest point we just start by one path and
# transition to the other at the closest point
ind_1 = np.where(
np.all(path1.points == min_dist_points1[ind, :], axis=1)
)[0][0]
ind_2 = np.where(
np.all(path2.points == min_dist_points2[ind, :], axis=1)
)[0][0]
# We can decide which part of the path we choose depending on the position
# of the port in the key
if f"{port1};" in key1:
part1 = path1.points[:ind_1, :]
else:
part1 = path1.points[ind_1:, :]
inds = np.argsort(part1[:, 0])
part1 = part1[inds, :]
if f"{port2};" in key2:
part2 = path2.points[:ind_2, :]
else:
part2 = path2.points[ind_2:, :]
# There is a chance that we need to flip the parts
d1 = np.sum(np.power(part1[-1, :] - part2[0, :], 2))
d2 = np.sum(np.power(part1[-1, :] - part2[-1, :], 2))
d3 = np.sum(np.power(part1[0, :] - part2[-1, :], 2))
d4 = np.sum(np.power(part1[0, :] - part2[0, :], 2))
if d1 == np.min([d1, d2, d3, d4]):
evan_path = np.vstack((part1, part2))
elif d2 == np.min([d1, d2, d3, d4]):
evan_path = np.vstack((part1, np.flipud(part2)))
elif d3 == np.min([d1, d2, d3, d4]):
evan_path = np.vstack((np.flipud(part1), np.flipud(part2)))
else:
evan_path = np.vstack((np.flipud(part1), part2))
# ==== This is for debugging, keep it until this is table ===
# plt.figure()
# plt.plot(path1.points[:, 0], path1.points[:, 1], "--")
# plt.plot(path2.points[:, 0], path2.points[:, 1], "--")
# plt.plot(evan_path[:, 0], evan_path[:, 1], "-o")
# plt.show()
# ========
if filter_function is not None:
evan_path = filter_function(evan_path)
ev_paths[f"{port1};{port2}"] = gf.Path(evan_path)
import itertools
for port1p, port2p in itertools.combinations(all_ports, 2):
port1 = port1p.name
port2 = port2p.name
if (
f"{port1};{port2}" in paths
or f"{port2};{port1}" in paths
or f"{port1};{port2}" in ev_paths
or f"{port2};{port1}" in ev_paths
):
# The path has already been computed
continue
# We need to calculate the (evanescent) path
# First gather a connected path that contains each port
for key in paths.keys():
if port1 in key:
path1 = paths[key]
key1 = key
if port2 in key:
path2 = paths[key]
key2 = key
# Now calculate the closest point between the two paths
# We do so by iterating over the x points of path1
# and finding the point closest in x of path2, then calculating distance
distances = distance.cdist(path1.points, path2.points)
# ind = np.unravel_index(np.argmin(distances), distances.shape)
# What do we do if there are multiple points with the same minimum distance?
# Choose the one that's closer to the center of the component
ind_min_dist_pts = np.where(distances == distances.min())
min_dist_points1 = path1.points[
np.unique(ind_min_dist_pts[0]), :
]
min_dist_points2 = path2.points[
np.unique(ind_min_dist_pts[1]), :
]
# Find the point closest to the center of the polygon
center = np.array(
[
simplified_component.dcenter.x,
simplified_component.dcenter.y,
]
)
distances2 = distance.cdist(
center.reshape(1, -1), min_dist_points1
)
ind = np.argmin(distances2)
# Now that we have the closest point we just start by one path and
# transition to the other at the closest point
ind_1 = np.where(
np.all(path1.points == min_dist_points1[ind, :], axis=1)
)[0][0]
ind_2 = np.where(
np.all(path2.points == min_dist_points2[ind, :], axis=1)
)[0][0]
# We can decide which part of the path we choose depending on the position
# of the port in the key
if f"{port1};" in key1:
part1 = path1.points[:ind_1, :]
else:
part1 = path1.points[ind_1:, :]
inds = np.argsort(part1[:, 0])
part1 = part1[inds, :]
if f"{port2};" in key2:
part2 = path2.points[:ind_2, :]
else:
part2 = path2.points[ind_2:, :]
# There is a chance that we need to flip the parts
d1 = np.sum(np.power(part1[-1, :] - part2[0, :], 2))
d2 = np.sum(np.power(part1[-1, :] - part2[-1, :], 2))
d3 = np.sum(np.power(part1[0, :] - part2[-1, :], 2))
d4 = np.sum(np.power(part1[0, :] - part2[0, :], 2))
if d1 == np.min([d1, d2, d3, d4]):
evan_path = np.vstack((part1, part2))
elif d2 == np.min([d1, d2, d3, d4]):
evan_path = np.vstack((part1, np.flipud(part2)))
elif d3 == np.min([d1, d2, d3, d4]):
evan_path = np.vstack((np.flipud(part1), np.flipud(part2)))
else:
evan_path = np.vstack((np.flipud(part1), part2))
# ==== This is for debugging, keep it until this is table ===
# plt.figure()
# plt.plot(path1.points[:, 0], path1.points[:, 1], "--")
# plt.plot(path2.points[:, 0], path2.points[:, 1], "--")
# plt.plot(evan_path[:, 0], evan_path[:, 1], "-o")
# plt.show()
# ========
if filter_function is not None:
evan_path = filter_function(evan_path)
ev_paths[f"{port1};{port2}"] = gf.Path(evan_path)

@joamatab joamatab merged commit 0d751b1 into gdsfactory:main Aug 2, 2024
8 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request python Pull requests that update Python code Verification DRC and LVS
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants