Skip to content

Commit

Permalink
Merge pull request #410 from gdsfactory/path_analysis_from_gds_niko
Browse files Browse the repository at this point in the history
Path analysis from gds
  • Loading branch information
joamatab authored May 30, 2024
2 parents a69b19d + 74a30ac commit 95135ba
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 23 deletions.
29 changes: 16 additions & 13 deletions gplugins/klayout/netlist_spice_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,25 @@ def parse_element(self, s: str, element: str) -> kdb.ParseElementData:
x_value, y_value = (int(e) / 1000 for e in location_matches.group(1, 2))

# Use default KLayout parser for rest of the SPICE
s, *_ = s.split("$")
s, *_ = s.split(" $")

parsed = super().parse_element(s, element)
parsed.parameters |= {"x": x_value, "y": y_value}

# ensure uniqueness
parsed.model_name = parsed.model_name
self.n_nodes += 1
return parsed

@staticmethod
def hash_str_to_int(s: str) -> int:
return int(hashlib.shake_128(s.encode()).hexdigest(4), 16)

def write_str_property_as_int(self, value: str) -> int:
"""Store string property in hash map and return integer hash value."""
hashed_value = CalibreSpiceReader.hash_str_to_int(value)
self.integer_to_string_map[hashed_value] = value
return hashed_value

@override
def element(
self,
Expand All @@ -95,15 +100,8 @@ def element(
if not clx:
clx = kdb.DeviceClass()
clx.name = model
for key, value in parameters.items():
for key in parameters:
clx.add_parameter(kdb.DeviceParameterDefinition(key))
# map string variables to integers
if (
isinstance(value, str)
and value not in self.integer_to_string_map.values()
):
hashed_value = CalibreSpiceReader.hash_str_to_int(value)
self.integer_to_string_map[hashed_value] = value

for i in range(len(nets)):
clx.add_terminal(kdb.DeviceTerminalDefinition(str(i)))
Expand All @@ -114,12 +112,17 @@ def element(
device.connect_terminal(i, net)

for key, value in parameters.items():
# map string variables to integers
possibly_hashed_value = (
self.write_str_property_as_int(value)
if isinstance(value, str)
else value
)

device.set_parameter(
key,
(
CalibreSpiceReader.hash_str_to_int(value)
if isinstance(value, str)
else value
possibly_hashed_value
or 0 # default to 0 for None in order to still have the parameter field
),
)
Expand Down
7 changes: 6 additions & 1 deletion gplugins/klayout/plot_nets.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ def plot_nets(
spice_reader: The KLayout Spice reader to use for parsing SPICE netlists.
"""

G_connectivity = networkx_from_spice(**locals())
G_connectivity = networkx_from_spice(
filepath=filepath,
include_labels=include_labels,
top_cell=top_cell,
spice_reader=spice_reader,
)

if nodes_to_reduce:

Expand Down
27 changes: 19 additions & 8 deletions gplugins/path_length_analysis/path_length_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from bokeh.palettes import Category10, Spectral4
from bokeh.plotting import figure, from_networkx
from gdsfactory.component import Component, ComponentReference
from gdsfactory.get_netlist import get_netlist, get_netlist_recursive

DEFAULT_CS_COLORS = {
"xs_rc": "red",
Expand Down Expand Up @@ -95,6 +96,7 @@ def report_pathlengths(
result_dir: str | Path,
visualize: bool = False,
component_connectivity=None,
netlist=None,
) -> None:
"""Reports pathlengths for a given PIC.
Expand All @@ -103,11 +105,15 @@ def report_pathlengths(
result_dir: the directory to write the pathlength table to.
visualize: whether to visualize the pathlength graph.
component_connectivity: a dictionary of component connectivity information.
netlist: a netlist dictionary. If None, will be generated from the pic.
"""

print(f"Reporting pathlengths for {pic.name}...")
pathlength_graph = get_edge_based_route_attr_graph(
pic, recursive=True, component_connectivity=component_connectivity
pic,
recursive=True,
component_connectivity=component_connectivity,
netlist=netlist,
)
route_records = get_paths(pathlength_graph)

Expand Down Expand Up @@ -332,7 +338,10 @@ def _get_edge_based_route_attr_graph(


def get_edge_based_route_attr_graph(
pic: Component, recursive=False, component_connectivity=None
pic: Component,
recursive=False,
component_connectivity=None,
netlist: dict[str, Any] | None = None,
) -> nx.Graph:
"""
Gets a connectivity graph for the circuit, with all path attributes on edges and ports as nodes.
Expand All @@ -342,17 +351,19 @@ def get_edge_based_route_attr_graph(
recursive: True to expand all hierarchy. False to only report top-level connectivity.
component_connectivity: a function to report connectivity for base components.\
None to treat as black boxes with no internal connectivity.
netlist: a netlist dictionary. If None, will be generated from the pic.
Returns:
A NetworkX Graph
"""
from gdsfactory.get_netlist import get_netlist, get_netlist_recursive

if recursive:
netlists = get_netlist_recursive(pic, component_suffix="")
netlist = netlists[pic.name]
if netlist is None:
if recursive:
netlists = get_netlist_recursive(pic, component_suffix="")
netlist = netlists[pic.name]
else:
netlist = get_netlist(pic)
netlists = None
else:
netlist = get_netlist(pic)
netlists = None

return _get_edge_based_route_attr_graph(
Expand Down
164 changes: 164 additions & 0 deletions gplugins/path_length_analysis/path_length_analysis_from_gds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
from collections.abc import Callable
from functools import partial

import gdsfactory as gf
import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import savgol_filter

filter_savgol_filter = partial(savgol_filter, window_length=11, polyorder=3, axis=0)


def extract_path(
component: gf.Component,
layer: gf.typings.LayerSpec = (1, 0),
plot: bool = False,
filter_function: Callable = None,
under_sampling: int = 1,
) -> gf.Path:
"""Extracts the centerline of a component from a GDS file.
Args:
component: GDS component.
layer: GDS layer to extract the centerline from.
plot: Plot the centerline.
filter_function: optional Function to filter the centerline.
under_sampling: under sampling factor.
"""
points = component.get_polygons(by_spec=layer)[0]

# Assume the points are ordered and the first half is the outer curve, the second half is the inner curve
# This assumption might need to be adjusted based on your specific geometry
mid_index = len(points) // 2
outer_points = points[:mid_index]
inner_points = points[mid_index:]
inner_points = inner_points[::-1]

inner_points = inner_points[::under_sampling]
outer_points = outer_points[::under_sampling]

centerline = np.mean([outer_points, inner_points], axis=0)

if filter_function is not None:
centerline = filter_function(centerline)

p = gf.Path(centerline)
if plot:
plt.figure()
plt.plot(outer_points[:, 0], outer_points[:, 1], "o", label="Outer Points")
plt.plot(inner_points[:, 0], inner_points[:, 1], "o", label="Inner Points")
plt.plot(centerline[:, 0], centerline[:, 1], "k--", label="Centerline")
plt.legend()
plt.title("Curve with Spline Interpolation for Inner and Outer Edges")
plt.xlabel("X-coordinate")
plt.ylabel("Y-coordinate")
plt.grid(True)
plt.show()
return p


def get_min_radius_and_length(path: gf.Path) -> tuple[float, float]:
"""Get the minimum radius of curvature and the length of a path."""
_, K = path.curvature()
radius = 1 / K
min_radius = np.min(np.abs(radius))
return min_radius, path.length()


def plot_curvature(path: gf.Path, rmax: int | float = 200) -> None:
"""Plot the curvature of a path.
Args:
path: Path object.
rmax: Maximum radius of curvature to plot.
"""
s, K = path.curvature()
radius = 1 / K
valid_indices = (radius > -rmax) & (radius < rmax)
radius2 = radius[valid_indices]
s2 = s[valid_indices]

plt.figure(figsize=(10, 5))
plt.plot(s2, radius2, ".-")
plt.xlabel("Position along curve (arc length)")
plt.ylabel("Radius of curvature")
plt.show()


def plot_radius(path: gf.Path, rmax: float = 200) -> plt.Figure:
"""Plot the radius of curvature of a path.
Args:
path: Path object.
rmax: Maximum radius of curvature to plot.
"""
s, K = path.curvature()
radius = 1 / K
valid_indices = (radius > -rmax) & (radius < rmax)
radius2 = radius[valid_indices]
s2 = s[valid_indices]

fig, ax = plt.subplots(1, 1, figsize=(15, 5))
ax.plot(s2, radius2, ".-")
ax.set_xlabel("Position along curve (arc length)")
ax.set_ylabel("Radius of curvature")
ax.grid(True)
return fig


def _demo_routes():
ys_right = [0, 10, 20, 40, 50, 80]
pitch = 127.0
N = len(ys_right)
ys_left = [(i - N / 2) * pitch for i in range(N)]
layer = (1, 0)

right_ports = [
gf.Port(
f"R_{i}", center=(0, ys_right[i]), width=0.5, orientation=180, layer=layer
)
for i in range(N)
]
left_ports = [
gf.Port(
f"L_{i}", center=(-200, ys_left[i]), width=0.5, orientation=0, layer=layer
)
for i in range(N)
]

# you can also mess up the port order and it will sort them by default
left_ports.reverse()

c = gf.Component(name="connect_bundle_v2")
routes = gf.routing.get_bundle(
left_ports,
right_ports,
sort_ports=True,
start_straight_length=100,
enforce_port_ordering=False,
)
for route in routes:
c.add(route.references)
return c


if __name__ == "__main__":
# c0 = gf.components.bend_euler(npoints=20)
# c0 = gf.components.bend_euler(cross_section="xs_sc", with_arc_floorplan=True)
# c0 = gf.components.bend_circular()
# c0 = gf.components.bend_s(npoints=7)
# c0 = gf.components.coupler()
c0 = _demo_routes()

gdspath = c0.write_gds()
n = c0.get_netlist()
c0.show()

c = gf.import_gds(gdspath)
# p = extract_path(c, plot=False, window_length=None, polyorder=None)
p = extract_path(c, plot=True, under_sampling=5)
min_radius, length = get_min_radius_and_length(p)
print(f"Minimum radius of curvature: {min_radius:.2f}")
print(f"Length: {length:.2f}")
print(c0.info)
plot_radius(p)
5 changes: 4 additions & 1 deletion notebooks/11_get_netlist_spice.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"\n",
"from gplugins.klayout.get_netlist import get_l2n, get_netlist\n",
"from gplugins.klayout.plot_nets import plot_nets\n",
"from gplugins.klayout.netlist_graph import GdsfactorySpiceReader"
"from gplugins.klayout.netlist_spice_reader import (\n",
" GdsfactorySpiceReader,\n",
")\n"
]
},
{
Expand All @@ -45,6 +47,7 @@
"source": [
"c = pads_correct()\n",
"gdspath = c.write_gds()\n",
"c.show()\n",
"c.plot()"
]
},
Expand Down
Loading

0 comments on commit 95135ba

Please sign in to comment.