-
Notifications
You must be signed in to change notification settings - Fork 35
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
Conversation
🧙 Sourcery has finished reviewing your pull request! Tips
|
There was a problem hiding this 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.
There was a problem hiding this 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.
# 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) |
There was a problem hiding this comment.
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:
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) | |
Co-authored-by: Niko Savola <nikomsavola@gmail.com>
for more information, see https://pre-commit.ci
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:
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.
Instead of return a
gf.Path
, now the functionextract_paths
returns a dictionary with{port1},{port2}
as the keys and thegf.Path
as the value.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:

MMI:

Directional coupler:
