From 71149e1ba65048d5b93b2ba62f08eb9e77d908ec Mon Sep 17 00:00:00 2001 From: yoshi74ls181 Date: Mon, 11 Apr 2022 17:36:00 +0900 Subject: [PATCH 01/35] Fix a bug in geometry.outline() for distance < 0 Boolean operation within geometry.outline() should be "B-A" for distance < 0. --- phidl/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index 79a53554..94b89c2b 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -728,7 +728,7 @@ def outline( Outline = boolean( A=D_bloated, B=[D, Trim], - operation="A-B", + operation="A-B" if distance > 0 else "B-A", num_divisions=num_divisions, max_points=max_points, precision=precision, From 4f95527eec997eccc4913785b5e01032343b5267 Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Sat, 23 Apr 2022 08:44:58 -0700 Subject: [PATCH 02/35] remove layers works also with paths --- phidl/device_layout.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/phidl/device_layout.py b/phidl/device_layout.py index 5ed7af7e..f346ef3f 100644 --- a/phidl/device_layout.py +++ b/phidl/device_layout.py @@ -1516,6 +1516,14 @@ def remove_layers(self, layers=(), include_labels=True, invert_selection=False): p for p, keep in zip(polygonset.datatypes, polygons_to_keep) if keep ] + paths = [] + for path in D.paths: + for layer in zip(path.layers, path.datatypes): + if layer not in layers: + paths.append(path) + + D.paths = paths + if include_labels: new_labels = [] for l in D.labels: From 4b746cfbd810bc95a97aa3f09b4ea16331417c1c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Wed, 25 May 2022 16:25:55 -0700 Subject: [PATCH 03/35] check whether a number is used --- phidl/device_layout.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phidl/device_layout.py b/phidl/device_layout.py index 5ed7af7e..8140e6ec 100644 --- a/phidl/device_layout.py +++ b/phidl/device_layout.py @@ -36,6 +36,7 @@ import hashlib +import numbers import warnings from copy import deepcopy as _deepcopy @@ -540,7 +541,7 @@ def _parse_layer(layer): gds_layer, gds_datatype = layer[0], 0 elif layer is None: gds_layer, gds_datatype = 0, 0 - elif isinstance(layer, (int, float)): + elif isinstance(layer, numbers.Number): gds_layer, gds_datatype = layer, 0 else: raise ValueError( From a9d92ce8be14ee145d31a8481e99ccf16fe64607 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Fri, 27 May 2022 17:29:45 -0700 Subject: [PATCH 04/35] Copy over properties --- phidl/device_layout.py | 10 +++++++--- phidl/geometry.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/phidl/device_layout.py b/phidl/device_layout.py index 5ed7af7e..f391ef23 100644 --- a/phidl/device_layout.py +++ b/phidl/device_layout.py @@ -1206,9 +1206,13 @@ def add_polygon(self, points, layer=np.nan): layers = zip(points.layers, points.datatypes) else: layers = [layer] * len(points.polygons) - return [ - self.add_polygon(p, layer) for p, layer in zip(points.polygons, layers) - ] + + polygons = [] + for p, layer in zip(points.polygons, layers): + new_polygon = self.add_polygon(p, layer) + new_polygon.properties = points.properties + polygons.append(new_polygon) + return polygons if layer is np.nan: layer = 0 diff --git a/phidl/geometry.py b/phidl/geometry.py index 79a53554..c54e3915 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -1814,7 +1814,7 @@ def import_gds(filename, cellname=None, flatten=False): layer=(label.layer, label.texttype), ) l.anchor = label.anchor - c2dmap.update({cell: D}) + c2dmap[cell] = D D_list.append(D) for D in D_list: From 10d17c68ac26d0ab3f5260ec18d44bd525da2b41 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Fri, 27 May 2022 17:34:34 -0700 Subject: [PATCH 05/35] preserve properties in DeviceReference --- phidl/geometry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/phidl/geometry.py b/phidl/geometry.py index c54e3915..c9fe3851 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -1830,6 +1830,7 @@ def import_gds(filename, cellname=None, flatten=False): magnification=e.magnification, x_reflection=e.x_reflection, ) + dr.properties = e.properties dr.owner = D converted_references.append(dr) elif isinstance(e, gdspy.CellArray): From 1e368bb1f05967820126b4319cf6cb6828f36ad7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Fri, 27 May 2022 17:39:20 -0700 Subject: [PATCH 06/35] add test --- tests/test_device.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/test_device.py b/tests/test_device.py index 4d67c1e7..3bcdb1e2 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -1,7 +1,10 @@ +import os +import tempfile + import numpy as np import phidl.geometry as pg -from phidl import Device, Group # , Layer, LayerSet, make_device, Port +from phidl import Device, Group # import phidl.routing as pr # import phidl.utilities as pu @@ -328,3 +331,15 @@ def test_polygon_simplify(): h = D.hash_geometry(precision=1e-4) assert h == "7d9ebcb231fb0107cbbf618353adeb583782ca11" # qp(D) + + +def test_preserve_properties(): + fname = os.path.join(tempfile.mkdtemp(), "properties.gds") + d = pg.bbox() + d.polygons[0].properties[1] = "yolo" + r = d << pg.bbox() + r.properties[1] = "foo" + d.write_gds(fname) + d2 = pg.import_gds(fname) + assert d2.polygons[0].properties == d.polygons[0].properties + assert d2.references[0].properties == r.properties From dff6100841ee939000889b20865344b4743282e9 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 2 Jun 2022 19:40:35 -0700 Subject: [PATCH 07/35] Copy over paths in import_gds Currently CellArrays with paths are missing from the Device. --- phidl/geometry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/phidl/geometry.py b/phidl/geometry.py index 79a53554..0536dfc7 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -1802,6 +1802,7 @@ def import_gds(filename, cellname=None, flatten=False): D.polygons = cell.polygons D.references = cell.references D.name = cell.name + D.paths = cell.paths for label in cell.labels: rotation = label.rotation if rotation is None: From 36f1ffdc7ae2acb42bda51ce09d89ff6ea98d462 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Fri, 3 Jun 2022 09:24:11 -0700 Subject: [PATCH 08/35] revert if-else logic --- phidl/geometry.py | 125 +++++++++++++++++++++++----------------------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index 0536dfc7..b2c9c59d 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -1794,69 +1794,7 @@ def import_gds(filename, cellname=None, flatten=False): "one of them" ) - if not flatten: - D_list = [] - c2dmap = {} - for cell in gdsii_lib.cells.values(): - D = Device(name=cell.name) - D.polygons = cell.polygons - D.references = cell.references - D.name = cell.name - D.paths = cell.paths - for label in cell.labels: - rotation = label.rotation - if rotation is None: - rotation = 0 - l = D.add_label( - text=label.text, - position=np.asfarray(label.position), - magnification=label.magnification, - rotation=rotation * 180 / np.pi, - layer=(label.layer, label.texttype), - ) - l.anchor = label.anchor - c2dmap.update({cell: D}) - D_list.append(D) - - for D in D_list: - # First convert each reference so it points to the right Device - converted_references = [] - for e in D.references: - ref_device = c2dmap[e.ref_cell] - if isinstance(e, gdspy.CellReference): - dr = DeviceReference( - device=ref_device, - origin=e.origin, - rotation=e.rotation, - magnification=e.magnification, - x_reflection=e.x_reflection, - ) - dr.owner = D - converted_references.append(dr) - elif isinstance(e, gdspy.CellArray): - dr = CellArray( - device=ref_device, - columns=e.columns, - rows=e.rows, - spacing=e.spacing, - origin=e.origin, - rotation=e.rotation, - magnification=e.magnification, - x_reflection=e.x_reflection, - ) - dr.owner = D - converted_references.append(dr) - D.references = converted_references - # Next convert each Polygon - temp_polygons = list(D.polygons) - D.polygons = [] - for p in temp_polygons: - D.add_polygon(p) - - topdevice = c2dmap[topcell] - return topdevice - - elif flatten: + if flatten: D = Device("import_gds") polygons = topcell.get_polygons(by_spec=True) @@ -1864,6 +1802,67 @@ def import_gds(filename, cellname=None, flatten=False): D.add_polygon(polys, layer=layer_in_gds) return D + D_list = [] + c2dmap = {} + for cell in gdsii_lib.cells.values(): + D = Device(name=cell.name) + D.polygons = cell.polygons + D.references = cell.references + D.name = cell.name + D.paths = cell.paths + for label in cell.labels: + rotation = label.rotation + if rotation is None: + rotation = 0 + l = D.add_label( + text=label.text, + position=np.asfarray(label.position), + magnification=label.magnification, + rotation=rotation * 180 / np.pi, + layer=(label.layer, label.texttype), + ) + l.anchor = label.anchor + c2dmap.update({cell: D}) + D_list.append(D) + + for D in D_list: + # First convert each reference so it points to the right Device + converted_references = [] + for e in D.references: + ref_device = c2dmap[e.ref_cell] + if isinstance(e, gdspy.CellReference): + dr = DeviceReference( + device=ref_device, + origin=e.origin, + rotation=e.rotation, + magnification=e.magnification, + x_reflection=e.x_reflection, + ) + dr.owner = D + converted_references.append(dr) + elif isinstance(e, gdspy.CellArray): + dr = CellArray( + device=ref_device, + columns=e.columns, + rows=e.rows, + spacing=e.spacing, + origin=e.origin, + rotation=e.rotation, + magnification=e.magnification, + x_reflection=e.x_reflection, + ) + dr.owner = D + converted_references.append(dr) + D.references = converted_references + # Next convert each Polygon + temp_polygons = list(D.polygons) + D.polygons = [] + for p in temp_polygons: + D.add_polygon(p) + + topdevice = c2dmap[topcell] + return topdevice + def _translate_cell(c): D = Device(name=c.name) From 63cc01d16d3af0ae26bf9e368096a7f8d9810193 Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sun, 12 Jun 2022 17:40:55 -0600 Subject: [PATCH 09/35] Revert "revert if-else logic" This reverts commit 36f1ffdc7ae2acb42bda51ce09d89ff6ea98d462. --- phidl/geometry.py | 125 +++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index b2c9c59d..0536dfc7 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -1794,7 +1794,69 @@ def import_gds(filename, cellname=None, flatten=False): "one of them" ) - if flatten: + if not flatten: + D_list = [] + c2dmap = {} + for cell in gdsii_lib.cells.values(): + D = Device(name=cell.name) + D.polygons = cell.polygons + D.references = cell.references + D.name = cell.name + D.paths = cell.paths + for label in cell.labels: + rotation = label.rotation + if rotation is None: + rotation = 0 + l = D.add_label( + text=label.text, + position=np.asfarray(label.position), + magnification=label.magnification, + rotation=rotation * 180 / np.pi, + layer=(label.layer, label.texttype), + ) + l.anchor = label.anchor + c2dmap.update({cell: D}) + D_list.append(D) + + for D in D_list: + # First convert each reference so it points to the right Device + converted_references = [] + for e in D.references: + ref_device = c2dmap[e.ref_cell] + if isinstance(e, gdspy.CellReference): + dr = DeviceReference( + device=ref_device, + origin=e.origin, + rotation=e.rotation, + magnification=e.magnification, + x_reflection=e.x_reflection, + ) + dr.owner = D + converted_references.append(dr) + elif isinstance(e, gdspy.CellArray): + dr = CellArray( + device=ref_device, + columns=e.columns, + rows=e.rows, + spacing=e.spacing, + origin=e.origin, + rotation=e.rotation, + magnification=e.magnification, + x_reflection=e.x_reflection, + ) + dr.owner = D + converted_references.append(dr) + D.references = converted_references + # Next convert each Polygon + temp_polygons = list(D.polygons) + D.polygons = [] + for p in temp_polygons: + D.add_polygon(p) + + topdevice = c2dmap[topcell] + return topdevice + + elif flatten: D = Device("import_gds") polygons = topcell.get_polygons(by_spec=True) @@ -1802,67 +1864,6 @@ def import_gds(filename, cellname=None, flatten=False): D.add_polygon(polys, layer=layer_in_gds) return D - D_list = [] - c2dmap = {} - for cell in gdsii_lib.cells.values(): - D = Device(name=cell.name) - D.polygons = cell.polygons - D.references = cell.references - D.name = cell.name - D.paths = cell.paths - for label in cell.labels: - rotation = label.rotation - if rotation is None: - rotation = 0 - l = D.add_label( - text=label.text, - position=np.asfarray(label.position), - magnification=label.magnification, - rotation=rotation * 180 / np.pi, - layer=(label.layer, label.texttype), - ) - l.anchor = label.anchor - c2dmap.update({cell: D}) - D_list.append(D) - - for D in D_list: - # First convert each reference so it points to the right Device - converted_references = [] - for e in D.references: - ref_device = c2dmap[e.ref_cell] - if isinstance(e, gdspy.CellReference): - dr = DeviceReference( - device=ref_device, - origin=e.origin, - rotation=e.rotation, - magnification=e.magnification, - x_reflection=e.x_reflection, - ) - dr.owner = D - converted_references.append(dr) - elif isinstance(e, gdspy.CellArray): - dr = CellArray( - device=ref_device, - columns=e.columns, - rows=e.rows, - spacing=e.spacing, - origin=e.origin, - rotation=e.rotation, - magnification=e.magnification, - x_reflection=e.x_reflection, - ) - dr.owner = D - converted_references.append(dr) - D.references = converted_references - # Next convert each Polygon - temp_polygons = list(D.polygons) - D.polygons = [] - for p in temp_polygons: - D.add_polygon(p) - - topdevice = c2dmap[topcell] - return topdevice - def _translate_cell(c): D = Device(name=c.name) From 8a8d54f9949716cb6e6a7c17ae26b21855650d1b Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Tue, 14 Jun 2022 09:22:35 -0600 Subject: [PATCH 10/35] Fix typos in distribute() docs --- docs/geometry_reference.ipynb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/geometry_reference.ipynb b/docs/geometry_reference.ipynb index 4a26d8e2..9b503ca0 100644 --- a/docs/geometry_reference.ipynb +++ b/docs/geometry_reference.ipynb @@ -996,7 +996,10 @@ "D = Device()\n", "[D.add_ref(pg.rectangle(size = [n*15+20,n*15+20]).move((n,n*4))) for n in [0,2,3,1,2]]\n", "D.distribute(elements = 'all', direction = 'x', spacing = 100, separation = False,\n", - " edge = 'xmin') # edge must be either 'xmin' (left), 'xmax' (right), or 'x' (center)\n", + " edge = 'xmin')\n", + "# edge must be either 'xmin' (left), 'xmax' (right), or 'x' (x-center)\n", + "# or if direction = 'y' then\n", + "# edge must be either 'ymin' (bottom), 'ymax' (top), or 'y' (y-center)\n", "\n", "qp(D) # quickplot the geometry" ] @@ -1005,7 +1008,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The alignment can be done along the right edge as well by setting ``edge = 'max'``, or along the center by setting ``edge = 'center'`` like in the following:" + "The alignment can be done along the right edge as well by setting ``edge = 'xmax'``, or along the x-center by setting ``edge = 'x'`` like in the following:" ] }, { @@ -1034,7 +1037,10 @@ "D = Device()\n", "[D.add_ref(pg.rectangle(size = [n*15+20,n*15+20]).move((n-10,n*4))) for n in [0,2,3,1,2]]\n", "D.distribute(elements = 'all', direction = 'x', spacing = 100, separation = False,\n", - " edge = 'x') # edge must be either 'xmin' (left), 'xmax' (right), or 'x' (center)\n", + " edge = 'x')\n", + "# edge must be either 'xmin' (left), 'xmax' (right), or 'x' (x-center)\n", + "# or if direction = 'y' then\n", + "# edge must be either 'ymin' (bottom), 'ymax' (top), or 'y' (y-center)\n", "\n", "qp(D) # quickplot the geometry" ] From 46ef91d3402dc3fddfe1f8f1888eea9951931061 Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Tue, 14 Jun 2022 09:23:24 -0600 Subject: [PATCH 11/35] Update geometry_reference.ipynb --- docs/geometry_reference.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/geometry_reference.ipynb b/docs/geometry_reference.ipynb index 9b503ca0..b45ef17b 100644 --- a/docs/geometry_reference.ipynb +++ b/docs/geometry_reference.ipynb @@ -967,7 +967,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Alternatively, we can spread them out on a fixed grid by setting ``separation = False``. Here we align the left edge (``edge = 'min'``) of each object along a grid spacing of 100:" + "Alternatively, we can spread them out on a fixed grid by setting ``separation = False``. Here we align the left edge (``edge = 'ymin'``) of each object along a grid spacing of 100:" ] }, { From 3d94b80fc4e1d8a5256be7d46bc47ac7aab4a0dd Mon Sep 17 00:00:00 2001 From: Stijn Balk Date: Wed, 15 Jun 2022 17:29:50 +0200 Subject: [PATCH 12/35] Fix a bug in boolean 'or' to always merge polys Suggestion to make boolean `'or'` merge all shapes within one Device, even if the second Device is `None` (or other way around). Similar behavior to a `gdspy.boolean(multi_path, None, "or", max_points=0)` where 'multi_path' is a `gdspy.Path` with multiple elements. For the trivial solutions to remain complete, a new `elif` case needs to be added for `(len(A_polys) == 0) and (len(B_polys) == 0) and (operation == 'or')` as `operation == 'or'` was moved out of the first case. --- phidl/geometry.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index 79a53554..b81c0f48 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -591,7 +591,7 @@ def boolean( # noqa: C901 ) # Check for trivial solutions - if (len(A_polys) == 0) or (len(B_polys) == 0): + if ((len(A_polys) == 0) or (len(B_polys) == 0)) and (operation != 'or'): if operation == "not": if len(A_polys) == 0: p = None @@ -599,13 +599,15 @@ def boolean( # noqa: C901 p = A_polys elif operation == "and": p = None - elif (operation == "or") or (operation == "xor"): + elif operation == "xor": if (len(A_polys) == 0) and (len(B_polys) == 0): p = None elif len(A_polys) == 0: p = B_polys elif len(B_polys) == 0: p = A_polys + elif (len(A_polys) == 0) and (len(B_polys) == 0) and (operation == 'or'): + p = None else: # If no trivial solutions, run boolean operation either in parallel or # straight From c78643ca49332a46369737fac2e38901bc225cc0 Mon Sep 17 00:00:00 2001 From: amccaugh Date: Wed, 15 Jun 2022 11:47:01 -0600 Subject: [PATCH 13/35] Fix pre-commit errors --- phidl/geometry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index 049319ac..95d5c9fd 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -591,7 +591,7 @@ def boolean( # noqa: C901 ) # Check for trivial solutions - if ((len(A_polys) == 0) or (len(B_polys) == 0)) and (operation != 'or'): + if ((len(A_polys) == 0) or (len(B_polys) == 0)) and (operation != "or"): if operation == "not": if len(A_polys) == 0: p = None @@ -606,7 +606,7 @@ def boolean( # noqa: C901 p = B_polys elif len(B_polys) == 0: p = A_polys - elif (len(A_polys) == 0) and (len(B_polys) == 0) and (operation == 'or'): + elif (len(A_polys) == 0) and (len(B_polys) == 0) and (operation == "or"): p = None else: # If no trivial solutions, run boolean operation either in parallel or From 4628fd4a78d72fe660e79419af05253cb513a656 Mon Sep 17 00:00:00 2001 From: Stijn Date: Thu, 16 Jun 2022 01:45:17 +0200 Subject: [PATCH 14/35] update Geomatric library part of docs with examples of all options --- docs/geometry_reference.ipynb | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/docs/geometry_reference.ipynb b/docs/geometry_reference.ipynb index 4a26d8e2..337d2826 100644 --- a/docs/geometry_reference.ipynb +++ b/docs/geometry_reference.ipynb @@ -1202,19 +1202,21 @@ "\n", "The ``pg.boolean()`` function can perform AND/OR/NOT/XOR operations, and will return a new geometry with the result of that operation.\n", "\n", + "All shapes in a single device can be merged with `pg.boolean(Device, None, opertion = 'or')`.\n", + "\n", "Speedup note: The ``num_divisions`` argument can be used to divide up the geometry into multiple rectangular regions and process each region sequentially (which is more computationally efficient). If you have a large geometry that takes a long time to process, try using ``num_divisions = [10,10]`` to optimize the operation." ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 1, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "
" + "
" ] }, "metadata": { @@ -1228,17 +1230,27 @@ "from phidl import quickplot as qp\n", "from phidl import Device\n", "\n", - "E = pg.ellipse(radii = (10,5), layer = 1)\n", - "R = pg.rectangle(size = [15,5], layer = 2).movey(-1.5)\n", - "C = pg.boolean(A = E, B = R, operation = 'not', precision = 1e-6,\n", + "E = pg.rectangle(size = [10, 10], layer = 1).move([-2.5,-2.5])\n", + "R = pg.rectangle(size = [10, 10], layer = 2).move([-7.5,-7.5])\n", + "NOT = pg.boolean(A = E, B = R, operation = 'not', precision = 1e-6,\n", + " num_divisions = [1,1], layer = 0)\n", + "AND = pg.boolean(A = E, B = R, operation = 'and', precision = 1e-6,\n", + " num_divisions = [1,1], layer = 0)\n", + "OR = pg.boolean(A = E, B = R, operation = 'or', precision = 1e-6,\n", + " num_divisions = [1,1], layer = 0)\n", + "XOR = pg.boolean(A = E, B = R, operation = 'xor', precision = 1e-6,\n", " num_divisions = [1,1], layer = 0)\n", - "# Other operations include 'and', 'or', 'xor', or equivalently 'A-B', 'B-A', 'A+B'\n", + "# ‘A+B’ is equivalent to ‘or’. ‘A-B’ is equivalent to ‘not’.\n", + "# ‘B-A’ is equivalent to ‘not’ with the operands switched.\n", "\n", "# Plot the originals and the result\n", "D = Device()\n", "D.add_ref(E)\n", "D.add_ref(R)\n", - "D.add_ref(C).movex(30)\n", + "D.add_ref(NOT).move([25, 10]) # top left\n", + "D.add_ref(AND).move([45, 10]) # top right\n", + "D.add_ref(OR).move([25, -10]) # bottom left\n", + "D.add_ref(XOR).move([45, -10]) # bottom righ\n", "qp(D) # quickplot the geometry" ] }, @@ -3043,7 +3055,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -3057,7 +3069,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.10.4" } }, "nbformat": 4, From 612619c8ab95d97f6fee9e8a0bb0fdc0204b046e Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Mon, 20 Jun 2022 08:57:14 -0700 Subject: [PATCH 15/35] add fstrings --- docs/gen_API.py | 8 ++++---- phidl/font.py | 2 +- phidl/geometry.py | 6 +++--- phidl/routing.py | 2 +- phidl/utilities.py | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/gen_API.py b/docs/gen_API.py index 1b080ec4..5e6e94f4 100644 --- a/docs/gen_API.py +++ b/docs/gen_API.py @@ -62,7 +62,7 @@ class B(*): if os.stat(fwrite).st_size == 0: fw.write( "#" * len(main_header) - + "\n{}\n".format(main_header) + + f"\n{main_header}\n" + "#" * len(main_header) + "\n\n\n" ) @@ -81,7 +81,7 @@ class B(*): fread_header = fread_header.capitalize() fw.write( "*" * (len(fread_header) + len(sub_header)) - + "\n{}{}\n".format(fread_header, sub_header) + + f"\n{fread_header}{sub_header}\n" + "*" * (len(fread_header) + len(sub_header)) + "\n\n" ) @@ -116,14 +116,14 @@ class B(*): fw.write(name[0] + "\n") fw.write(("=" * len(name[0])) + "\n\n") if name[1] == "C": - fw.write(".. autoclass:: phidl.{}.{}\n".format(fread_name, name[0])) + fw.write(f".. autoclass:: phidl.{fread_name}.{name[0]}\n") fw.write( " :members:\n" " :inherited-members:\n" " :show-inheritance:\n\n\n" ) else: - fw.write(".. autofunction:: phidl.{}.{}\n\n\n".format(fread_name, name[0])) + fw.write(f".. autofunction:: phidl.{fread_name}.{name[0]}\n\n\n") fw.close() diff --git a/phidl/font.py b/phidl/font.py index f1b81a6e..5705f0da 100644 --- a/phidl/font.py +++ b/phidl/font.py @@ -93,7 +93,7 @@ def _get_glyph(font, letter): # noqa: C901 # If there is no postscript name, use the family name font_name = font.family_name.replace(" ", "_") - block_name = "*char_{}_0x{:2X}".format(font_name, ord(letter)) + block_name = f"*char_{font_name}_0x{ord(letter):2X}" # Load control points from font file font.load_char(letter, freetype.FT_LOAD_FLAGS["FT_LOAD_NO_BITMAP"]) diff --git a/phidl/geometry.py b/phidl/geometry.py index 79a53554..cc98507b 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -1915,7 +1915,7 @@ def preview_layerset(ls, size=100, spacing=100): for n, layer in enumerate(sorted_layers): R = rectangle(size=(100 * scale, 100 * scale), layer=layer) T = text( - text="{}\n{} / {}".format(layer.name, layer.gds_layer, layer.gds_datatype), + text=f"{layer.name}\n{layer.gds_layer} / {layer.gds_datatype}", size=20 * scale, justify="center", layer=layer, @@ -1982,7 +1982,7 @@ def _convert_port_to_geometry(port, layer=0): """ if port.parent is None: raise ValueError( - "Port {}: Port needs a parent in which to draw".format(port.name) + f"Port {port.name}: Port needs a parent in which to draw" ) if isinstance(port.parent, DeviceReference): device = port.parent.parent @@ -3452,7 +3452,7 @@ def _gen_param_variations( D_new = make_device(function, config=param_defaults, **new_params) label_text = "" for name, value in params.items(): - label_text += ("{}={}".format(name, value)) + "\n" + label_text += (f"{name}={value}") + "\n" if label_layer is not None: D_new.add_label(text=label_text, position=D_new.center, layer=label_layer) diff --git a/phidl/routing.py b/phidl/routing.py index 93508dc9..6fd304cd 100644 --- a/phidl/routing.py +++ b/phidl/routing.py @@ -953,7 +953,7 @@ def route_manhattan( # noqa: C901 valid_bend_types = ["circular", "gradual"] if bendType not in valid_bend_types: - raise ValueError("bendType{}= not in {}".format(bendType, valid_bend_types)) + raise ValueError(f"bendType{bendType}= not in {valid_bend_types}") if bendType == "gradual": b = _gradual_bend(radius=radius) diff --git a/phidl/utilities.py b/phidl/utilities.py index a5f36791..d464a679 100644 --- a/phidl/utilities.py +++ b/phidl/utilities.py @@ -32,7 +32,7 @@ def write_lyp(filename, layerset): gds_datatype = layer.gds_datatype color = layer.color - name = "{}/{} - ".format(str(gds_layer), str(gds_datatype)) + layer.name + name = f"{str(gds_layer)}/{str(gds_datatype)} - " + layer.name if layer.description is not None: name = name + " - (" + layer.description + ")" @@ -84,7 +84,7 @@ def write_lyp(filename, layerset): f.write(" %s\n" % name) # Writing line to specify source f.write( - " {}/{}@1\n".format(str(gds_layer), str(gds_datatype)) + f" {str(gds_layer)}/{str(gds_datatype)}@1\n" ) # Writing properties closer for specific layer f.write(" \n") From 2c3a2f652ba1611f35c95143d936f6717ecfade2 Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Thu, 23 Jun 2022 10:08:48 -0700 Subject: [PATCH 16/35] fix changing parent issue --- phidl/device_layout.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/phidl/device_layout.py b/phidl/device_layout.py index 5ed7af7e..cd7b4335 100644 --- a/phidl/device_layout.py +++ b/phidl/device_layout.py @@ -1907,7 +1907,6 @@ def __init__( x_reflection=x_reflection, ignore_missing=False, ) - self.parent = device self.owner = None # The ports of a DeviceReference have their own unique id (uid), # since two DeviceReferences of the same parent Device can be @@ -1916,6 +1915,14 @@ def __init__( name: port._copy(new_uid=True) for name, port in device.ports.items() } + @property + def parent(self): + return self.ref_cell + + @parent.setter + def parent(self, value): + self.ref_cell = value + def __repr__(self): """Prints a description of the DeviceReference, including parent Device, ports, origin, rotation, and x_reflection. From e8fd51bc95245c3c629e70d5f226e84482fdf6c4 Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sat, 25 Jun 2022 09:30:38 -0600 Subject: [PATCH 17/35] Update geometry_reference.ipynb --- docs/geometry_reference.ipynb | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/geometry_reference.ipynb b/docs/geometry_reference.ipynb index 337d2826..ef1f9ce1 100644 --- a/docs/geometry_reference.ipynb +++ b/docs/geometry_reference.ipynb @@ -1209,14 +1209,14 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "
" + "
" ] }, "metadata": { @@ -1230,27 +1230,27 @@ "from phidl import quickplot as qp\n", "from phidl import Device\n", "\n", - "E = pg.rectangle(size = [10, 10], layer = 1).move([-2.5,-2.5])\n", - "R = pg.rectangle(size = [10, 10], layer = 2).move([-7.5,-7.5])\n", - "NOT = pg.boolean(A = E, B = R, operation = 'not', precision = 1e-6,\n", + "D1 = pg.circle(radius = 5, layer = 1).move([5,5])\n", + "D2 = pg.rectangle(size = [10, 10], layer = 2).move([-5,-5])\n", + "NOT = pg.boolean(A = D1, B = D2, operation = 'not', precision = 1e-6,\n", " num_divisions = [1,1], layer = 0)\n", - "AND = pg.boolean(A = E, B = R, operation = 'and', precision = 1e-6,\n", + "AND = pg.boolean(A = D1, B = D2, operation = 'and', precision = 1e-6,\n", " num_divisions = [1,1], layer = 0)\n", - "OR = pg.boolean(A = E, B = R, operation = 'or', precision = 1e-6,\n", + "OR = pg.boolean(A = D1, B = D2, operation = 'or', precision = 1e-6,\n", " num_divisions = [1,1], layer = 0)\n", - "XOR = pg.boolean(A = E, B = R, operation = 'xor', precision = 1e-6,\n", + "XOR = pg.boolean(A = D1, B = D2, operation = 'xor', precision = 1e-6,\n", " num_divisions = [1,1], layer = 0)\n", "# ‘A+B’ is equivalent to ‘or’. ‘A-B’ is equivalent to ‘not’.\n", "# ‘B-A’ is equivalent to ‘not’ with the operands switched.\n", "\n", "# Plot the originals and the result\n", "D = Device()\n", - "D.add_ref(E)\n", - "D.add_ref(R)\n", - "D.add_ref(NOT).move([25, 10]) # top left\n", - "D.add_ref(AND).move([45, 10]) # top right\n", - "D.add_ref(OR).move([25, -10]) # bottom left\n", - "D.add_ref(XOR).move([45, -10]) # bottom righ\n", + "D.add_ref(D1)\n", + "D.add_ref(D2)\n", + "D.add_ref(NOT).move([25, 10]) # top left\n", + "D.add_ref(AND).move([45, 10]) # top right\n", + "D.add_ref(OR).move([25, -10]) # bottom left\n", + "D.add_ref(XOR).move([45, -10]) # bottom right\n", "qp(D) # quickplot the geometry" ] }, @@ -3055,7 +3055,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -3069,7 +3069,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.4" + "version": "3.8.3" } }, "nbformat": 4, From 083d0b076a555f3a4cfac069d2ca6e7ea4965bb5 Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sat, 25 Jun 2022 09:34:52 -0600 Subject: [PATCH 18/35] formatting fix --- phidl/geometry.py | 4 +--- phidl/routing.py | 4 ++-- phidl/utilities.py | 4 +--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index cc98507b..bfe2914c 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -1981,9 +1981,7 @@ def _convert_port_to_geometry(port, layer=0): The Port must start with a parent. """ if port.parent is None: - raise ValueError( - f"Port {port.name}: Port needs a parent in which to draw" - ) + raise ValueError(f"Port {port.name}: Port needs a parent in which to draw") if isinstance(port.parent, DeviceReference): device = port.parent.parent else: diff --git a/phidl/routing.py b/phidl/routing.py index 6fd304cd..29df319d 100644 --- a/phidl/routing.py +++ b/phidl/routing.py @@ -297,7 +297,7 @@ def route_smooth( manual_path=None, smooth_options={"corner_fun": pp.euler, "use_eff": True}, layer=np.nan, - **kwargs + **kwargs, ): """Convenience function that routes a path between ports using pp.smooth(), @@ -416,7 +416,7 @@ def route_sharp( path_type="manhattan", manual_path=None, layer=np.nan, - **kwargs + **kwargs, ): """Convenience function that routes a path between ports and immediately diff --git a/phidl/utilities.py b/phidl/utilities.py index d464a679..7f0b150d 100644 --- a/phidl/utilities.py +++ b/phidl/utilities.py @@ -83,9 +83,7 @@ def write_lyp(filename, layerset): # Writing line to specify layer name f.write(" %s\n" % name) # Writing line to specify source - f.write( - f" {str(gds_layer)}/{str(gds_datatype)}@1\n" - ) + f.write(f" {str(gds_layer)}/{str(gds_datatype)}@1\n") # Writing properties closer for specific layer f.write(" \n") From 5ccdf928c875dd713614fa029bc4739cf9fa4d1a Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sat, 25 Jun 2022 09:59:10 -0600 Subject: [PATCH 19/35] Deprecate python 3.5 --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index bda59a47..d8c8bf83 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -9,7 +9,7 @@ jobs: pytest: strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] fail-fast: false runs-on: ubuntu-latest steps: From a58c8caa7f7edc24b8d41d06c70e62d8b8b511de Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sat, 25 Jun 2022 12:19:39 -0600 Subject: [PATCH 20/35] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ca103374..d097b4ec 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ If you found PHIDL useful, please consider citing it in (just one!) of your publ # Installation / requirements - Install or upgrade with `pip install -U phidl` -- Python version >=3.5 +- Python version >=3.6 - If you are on Windows or Mac and don't already have `gdspy` installed, you will need a C++ compiler - For Windows + Python 3, install ["Build Tools for Visual Studio"](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019) (make sure to check the "C++ build tools" checkbox when installing) - For Mac, install "Xcode" from the App Store, then run the command `xcode-select --install` in the terminal From 3a2da30f209430c18da5b3afe0739593a05831b3 Mon Sep 17 00:00:00 2001 From: "Dileep V. Reddy" Date: Sun, 26 Jun 2022 17:41:56 -0600 Subject: [PATCH 21/35] Added candelabra meander method to geometry.py --- phidl/geometry.py | 230 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/phidl/geometry.py b/phidl/geometry.py index 79a53554..d23849e5 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -5396,6 +5396,236 @@ def snspd_expanded( # quickplot(s) +def candelabra_meander(wire_width=0.52, wire_pitch=0.56, haxis=90, vaxis=50, + equalize_path_lengths=False, xwing=False, layer=0): + ''' Returns phidl meander with low current crowding for any fill factor.''' + + def optimal_stepL(start_width=10, end_width=22, length=100, + tolerance=1e-3, symmetric=True, layer=0): + ''' Returns phidl device optimal step of requested length. ''' + afac = 1.2 + D = optimal_step(start_width=start_width, end_width=end_width, + anticrowding_factor=afac, num_pts=256, + symmetric=symmetric, layer=layer) + while(abs(D.xsize - length) > tolerance): + afac = afac * length / D.xsize + D = optimal_step(start_width=start_width, end_width=end_width, + anticrowding_factor=afac, num_pts=256, + symmetric=symmetric, layer=layer) + return D + + def off_axis_uturn(wire_width=0.52, wire_pitch=0.56, pfact=10.0 / 3, + sharp=False, pad_length=0, layer=0): + ''' Returns phidl device low-crowding u-turn for candelabra meander.''' + barc = optimal_90deg(width=wire_width, layer=layer) + if(not sharp): + # For non-rounded outer radii + # Not fully implemented + port1mp = [barc.ports[1].x, barc.ports[1].y] + port1or = barc.ports[1].orientation + port2mp = [barc.ports[2].x, barc.ports[2].y] + port2or = barc.ports[2].orientation + barc = boolean(A=barc, B=copy(barc).move( + [-wire_width, -wire_width]), operation='not', layer=layer) + barc.add_port(name=1, midpoint=port1mp, width=wire_width, + orientation=port1or) + barc.add_port(name=2, midpoint=port2mp, width=wire_width, + orientation=port2or) + pin = optimal_hairpin(width=wire_width, pitch=pfact * wire_width, + length=8 * wire_width, layer=layer) + pas = compass(size=(wire_width, wire_pitch), layer=layer) + D = Device() + arc1 = D.add_ref(barc) + arc1.rotate(90) + pin1 = D.add_ref(pin) + pin1.connect(1, arc1.ports[2]) + pas1 = D.add_ref(pas) + pas1.connect(pas1.ports['N'], pin1.ports[2]) + arc2 = D.add_ref(barc) + arc2.connect(2, pas1.ports['S']) + if(pad_length > 0): + pin1.movey(pad_length * 0.5) + tempc = D.add_ref( + compass(size=(pin1.ports[1].width, + pin1.ports[1].y - arc1.ports[2].y), + layer=layer)) + tempc.connect('N', pin1.ports[1]) + tempc = D.add_ref( + compass(size=(pin1.ports[2].width, + pin1.ports[2].y - pas1.ports['N'].y), + layer=layer)) + tempc.connect('N', pin1.ports[2]) + D.flatten() + D.add_port(name=1, midpoint=arc1.ports[1].midpoint, + width=wire_width, + orientation=arc1.ports[1].orientation) + D.add_port(name=2, midpoint=arc2.ports[1].midpoint, + width=wire_width, + orientation=arc2.ports[1].orientation) + return D + + def xwing_uturn(wire_width=0.52, wire_pitch=0.56, pfact=10.0 / 3, + pad_length=0, layer=0): + ''' Returns phidl device low-crowding u-turn for X-wing meander. ''' + barc = arc(radius=wire_width * 3, width=wire_width, layer=layer, + theta=45).rotate(180) + + pin = optimal_hairpin(width=wire_width, pitch=pfact * wire_width, + length=15 * wire_width, layer=layer) + + paslen = pfact * wire_width - np.sqrt(2) * wire_pitch + pas = compass(size=(wire_width, abs(paslen)), layer=layer) + Dtemp = Device() + arc1 = Dtemp.add_ref(barc) + arc1.rotate(90) + pin1 = Dtemp.add_ref(pin) + pas1 = Dtemp.add_ref(pas) + arc2 = Dtemp.add_ref(barc) + if(paslen > 0): + pas1.connect(pas1.ports['S'], arc1.ports[2]) + pin1.connect(1, pas1.ports['N']) + arc2.connect(2, pin1.ports[2]) + else: + pin1.connect(1, arc1.ports[2]) + pas1.connect('N', pin1.ports[2]) + arc2.connect(2, pas1.ports['S']) + if(pad_length > 0): + pin1.move([pad_length * 0.5 / np.sqrt(2), + pad_length * 0.5 / np.sqrt(2)]) + if(paslen > 0): + indx1 = 2 + indx2 = 1 + myarc = arc2 + else: + indx1 = 1 + indx2 = 2 + myarc = arc1 + compdist = np.sqrt( + np.sum( + np.square( + pin1.ports[indx1].midpoint - myarc.ports[2].midpoint))) + tempc = Dtemp.add_ref( + compass(size=(pin1.ports[indx1].width, + compdist), + layer=layer)) + tempc.connect('N', pin1.ports[indx1]) + compdist = np.sqrt( + np.sum( + np.square( + pin1.ports[indx2].midpoint - pas1.ports['N'].midpoint))) + tempc = Dtemp.add_ref( + compass(size=(pin1.ports[indx2].width, + compdist), + layer=layer)) + tempc.connect('N', pin1.ports[indx2]) + + Dtemp.flatten() + Dtemp.add_port(name=1, midpoint=arc1.ports[1].midpoint, + width=wire_width, orientation=arc1.ports[1].orientation) + Dtemp.add_port(name=2, midpoint=arc2.ports[1].midpoint, + width=wire_width, orientation=arc2.ports[1].orientation) + + return Dtemp + + D = Device(name='snspd_candelabra_meander') + if xwing: + Dtemp = xwing_uturn(wire_width=wire_width, wire_pitch=wire_pitch) + else: + Dtemp = off_axis_uturn(wire_width=wire_width, wire_pitch=wire_pitch) + padding = Dtemp.xsize + maxll = haxis - 2 * padding + dll = abs(Dtemp.ports[1].x - Dtemp.ports[2].x) + wire_pitch + half_num_meanders = int(np.ceil(0.5 * vaxis / wire_pitch)) + 2 + + if xwing: + bend = D.add_ref(arc(radius=wire_width * 3, width=wire_width, + theta=90)).rotate(180) + else: + bend = D.add_ref(optimal_90deg(width=wire_width)) + if (maxll - dll * half_num_meanders) <= 0.0: + print('Horizontal axis too small! Shrinking vertical axis.') + while((maxll - dll * half_num_meanders) <= 0.0): + half_num_meanders = half_num_meanders - 1 + fpas = D.add_ref(compass(size=(0.5 * (maxll - dll * half_num_meanders), + wire_width))) + D.movex(-bend.ports[1].x) + fpas.connect(fpas.ports['W'], bend.ports[2]) + ll = D.xsize * 2 - wire_width + if xwing: + Dtemp = xwing_uturn( + wire_width=wire_width, wire_pitch=wire_pitch, + pad_length=(maxll - ll - dll) * equalize_path_lengths) + else: + Dtemp = off_axis_uturn( + wire_width=wire_width, wire_pitch=wire_pitch, + pad_length=(maxll - ll - dll) * equalize_path_lengths) + uturn = D.add_ref(Dtemp) + uturn.connect(1, fpas.ports['E']) + dir_left = True + + turn_padding = maxll - ll - 2 * dll + + while(ll < maxll - dll): + ll = ll + dll + if xwing: + Dtemp = xwing_uturn( + wire_width=wire_width, wire_pitch=wire_pitch, + pad_length=turn_padding * equalize_path_lengths) + else: + Dtemp = off_axis_uturn( + wire_width=wire_width, wire_pitch=wire_pitch, + pad_length=turn_padding * equalize_path_lengths) + turn_padding = turn_padding - dll + newpas = D.add_ref(compass(size=(ll, wire_width))) + if(dir_left): + newpas.connect(newpas.ports['E'], uturn.ports[2]) + uturn = D.add_ref(Dtemp.mirror([0, 0], [0, 1])) + uturn.connect(1, newpas.ports['W']) + dir_left = False + else: + newpas.connect(newpas.ports['W'], uturn.ports[2]) + uturn = D.add_ref(Dtemp) + uturn.connect(1, newpas.ports['E']) + dir_left = True + + newpas = D.add_ref(compass(size=(ll / 2, wire_width))) + if(dir_left): + newpas.connect(newpas.ports['E'], uturn.ports[2]) + dir_left = False + else: + newpas.connect(newpas.ports['W'], uturn.ports[2]) + dir_left = True + + D.movex(-D.x) + if not xwing: + bend.movex(-bend.ports[1].x) + if (fpas.ports['W'].x - bend.ports[2].x) > 0: + tempc = D.add_ref(compass(size=(fpas.ports['W'].x - bend.ports[2].x, + bend.ports[2].width))) + tempc.connect('E', fpas.ports['W']) + D.move([-D.x, -D.ymin - wire_width * 0.5]) + D.add_port(name=1, port=bend.ports[1]) + if(dir_left): + D.add_port(name=2, port=newpas.ports['E']) + else: + D.add_port(name=2, port=newpas.ports['W']) + + Dout = Device() + D1 = Dout.add_ref(D) + D2 = Dout.add_ref(copy(D).rotate(180)) + tempc = Dout.add_ref(compass(size=(abs(D1.ports[2].x - D2.ports[2].x), + D1.ports[2].width))) + if D1.ports[2].x > D2.ports[2].x: + tempc.connect('E', D1.ports[2]) + else: + tempc.connect('W', D1.ports[2]) + Dout = copy_layer(Dout, layer=0, new_layer=layer) + Dout.add_port(name=1, port=D1.ports[1]) + Dout.add_port(name=2, port=D2.ports[1]) + Dout.flatten() + return Dout + + def ytron_round( rho=1, arm_lengths=(500, 300), From c3dc461b191ba8d41ed666679f77d2da1be212dd Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Tue, 12 Jul 2022 18:15:16 +0200 Subject: [PATCH 22/35] lazy load matplotlib --- phidl/quickplotter.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/phidl/quickplotter.py b/phidl/quickplotter.py index 2cf7939a..840d1781 100644 --- a/phidl/quickplotter.py +++ b/phidl/quickplotter.py @@ -6,7 +6,6 @@ import gdspy import numpy as np -from matplotlib.lines import Line2D import phidl from phidl.device_layout import ( @@ -22,15 +21,6 @@ _SUBPORT_RGB = (0, 120, 120) _PORT_RGB = (190, 0, 0) -try: - import matplotlib - from matplotlib import pyplot as plt - from matplotlib.collections import PolyCollection - from matplotlib.widgets import RectangleSelector - - matplotlib_imported = True -except ImportError: - matplotlib_imported = False try: from PyQt5 import QtCore, QtGui @@ -108,6 +98,8 @@ def zoom_fun(event, ax, scale): def _rectangle_selector_factory(fig, ax): + from matplotlib.widgets import RectangleSelector + def line_select_callback(eclick, erelease): x1, y1 = eclick.xdata, eclick.ydata x2, y2 = erelease.xdata, erelease.ydata @@ -202,6 +194,14 @@ def quickplot(items): # noqa: C901 >>> quickplot([R, E]) """ + + try: + from matplotlib import pyplot as plt + + matplotlib_imported = True + except ImportError: + matplotlib_imported = False + # Override default options with _quickplot_options show_ports = _quickplot_options["show_ports"] show_subports = _quickplot_options["show_subports"] @@ -330,6 +330,8 @@ def quickplot(items): # noqa: C901 def _use_interactive_zoom(): """Checks whether the current matplotlib backend is compatible with interactive zoom""" + import matplotlib + if _quickplot_options["interactive_zoom"] is not None: return _quickplot_options["interactive_zoom"] forbidden_backends = ["nbagg"] @@ -382,6 +384,8 @@ def _get_layerprop(layer, datatype): def _draw_polygons(polygons, ax, **kwargs): + from matplotlib.collections import PolyCollection + coll = PolyCollection(polygons, **kwargs) ax.add_collection(coll) stacked_polygons = np.vstack(polygons) @@ -392,6 +396,8 @@ def _draw_polygons(polygons, ax, **kwargs): def _draw_line(x, y, ax, **kwargs): + from matplotlib.lines import Line2D + line = Line2D(x, y, **kwargs) ax.add_line(line) xmin, ymin = np.min(x), np.min(y) @@ -447,6 +453,8 @@ def _draw_port(ax, port, is_subport, color): def _draw_port_as_point(ax, port, **kwargs): + from matplotlib import pyplot as plt + x = port.midpoint[0] y = port.midpoint[1] plt.plot(x, y, "r+", alpha=0.5, markersize=15, markeredgewidth=2) # Draw port edge From 0ceeb6000546ff5596df4294523f844d1ae520dc Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Fri, 22 Jul 2022 12:41:11 +0200 Subject: [PATCH 23/35] pre-commit passing --- phidl/quickplotter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/phidl/quickplotter.py b/phidl/quickplotter.py index 840d1781..d527559b 100644 --- a/phidl/quickplotter.py +++ b/phidl/quickplotter.py @@ -194,7 +194,6 @@ def quickplot(items): # noqa: C901 >>> quickplot([R, E]) """ - try: from matplotlib import pyplot as plt From 1c1ebdfb16d9562b73e9044509bb70ebdf25c83c Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sun, 24 Jul 2022 14:51:58 -0600 Subject: [PATCH 24/35] Pre commit fixes and remove unused optimal_stepL() --- phidl/geometry.py | 278 ++++++++++++++++++++++++++-------------------- 1 file changed, 159 insertions(+), 119 deletions(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index d23849e5..be93ee17 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -5396,43 +5396,52 @@ def snspd_expanded( # quickplot(s) -def candelabra_meander(wire_width=0.52, wire_pitch=0.56, haxis=90, vaxis=50, - equalize_path_lengths=False, xwing=False, layer=0): - ''' Returns phidl meander with low current crowding for any fill factor.''' - - def optimal_stepL(start_width=10, end_width=22, length=100, - tolerance=1e-3, symmetric=True, layer=0): - ''' Returns phidl device optimal step of requested length. ''' - afac = 1.2 - D = optimal_step(start_width=start_width, end_width=end_width, - anticrowding_factor=afac, num_pts=256, - symmetric=symmetric, layer=layer) - while(abs(D.xsize - length) > tolerance): - afac = afac * length / D.xsize - D = optimal_step(start_width=start_width, end_width=end_width, - anticrowding_factor=afac, num_pts=256, - symmetric=symmetric, layer=layer) - return D - - def off_axis_uturn(wire_width=0.52, wire_pitch=0.56, pfact=10.0 / 3, - sharp=False, pad_length=0, layer=0): - ''' Returns phidl device low-crowding u-turn for candelabra meander.''' +def candelabra_meander( # noqa: C901 + wire_width=0.52, + wire_pitch=0.56, + haxis=90, + vaxis=50, + equalize_path_lengths=False, + xwing=False, + layer=0, +): + """Returns phidl meander with low current crowding for any fill factor.""" + + def off_axis_uturn( + wire_width=0.52, + wire_pitch=0.56, + pfact=10.0 / 3, + sharp=False, + pad_length=0, + layer=0, + ): + """Returns phidl device low-crowding u-turn for candelabra meander.""" barc = optimal_90deg(width=wire_width, layer=layer) - if(not sharp): + if not sharp: # For non-rounded outer radii # Not fully implemented port1mp = [barc.ports[1].x, barc.ports[1].y] port1or = barc.ports[1].orientation port2mp = [barc.ports[2].x, barc.ports[2].y] port2or = barc.ports[2].orientation - barc = boolean(A=barc, B=copy(barc).move( - [-wire_width, -wire_width]), operation='not', layer=layer) - barc.add_port(name=1, midpoint=port1mp, width=wire_width, - orientation=port1or) - barc.add_port(name=2, midpoint=port2mp, width=wire_width, - orientation=port2or) - pin = optimal_hairpin(width=wire_width, pitch=pfact * wire_width, - length=8 * wire_width, layer=layer) + barc = boolean( + A=barc, + B=copy(barc).move([-wire_width, -wire_width]), + operation="not", + layer=layer, + ) + barc.add_port( + name=1, midpoint=port1mp, width=wire_width, orientation=port1or + ) + barc.add_port( + name=2, midpoint=port2mp, width=wire_width, orientation=port2or + ) + pin = optimal_hairpin( + width=wire_width, + pitch=pfact * wire_width, + length=8 * wire_width, + layer=layer, + ) pas = compass(size=(wire_width, wire_pitch), layer=layer) D = Device() arc1 = D.add_ref(barc) @@ -5440,38 +5449,54 @@ def off_axis_uturn(wire_width=0.52, wire_pitch=0.56, pfact=10.0 / 3, pin1 = D.add_ref(pin) pin1.connect(1, arc1.ports[2]) pas1 = D.add_ref(pas) - pas1.connect(pas1.ports['N'], pin1.ports[2]) + pas1.connect(pas1.ports["N"], pin1.ports[2]) arc2 = D.add_ref(barc) - arc2.connect(2, pas1.ports['S']) - if(pad_length > 0): + arc2.connect(2, pas1.ports["S"]) + if pad_length > 0: pin1.movey(pad_length * 0.5) tempc = D.add_ref( - compass(size=(pin1.ports[1].width, - pin1.ports[1].y - arc1.ports[2].y), - layer=layer)) - tempc.connect('N', pin1.ports[1]) + compass( + size=(pin1.ports[1].width, pin1.ports[1].y - arc1.ports[2].y), + layer=layer, + ) + ) + tempc.connect("N", pin1.ports[1]) tempc = D.add_ref( - compass(size=(pin1.ports[2].width, - pin1.ports[2].y - pas1.ports['N'].y), - layer=layer)) - tempc.connect('N', pin1.ports[2]) + compass( + size=(pin1.ports[2].width, pin1.ports[2].y - pas1.ports["N"].y), + layer=layer, + ) + ) + tempc.connect("N", pin1.ports[2]) D.flatten() - D.add_port(name=1, midpoint=arc1.ports[1].midpoint, - width=wire_width, - orientation=arc1.ports[1].orientation) - D.add_port(name=2, midpoint=arc2.ports[1].midpoint, - width=wire_width, - orientation=arc2.ports[1].orientation) + D.add_port( + name=1, + midpoint=arc1.ports[1].midpoint, + width=wire_width, + orientation=arc1.ports[1].orientation, + ) + D.add_port( + name=2, + midpoint=arc2.ports[1].midpoint, + width=wire_width, + orientation=arc2.ports[1].orientation, + ) return D - def xwing_uturn(wire_width=0.52, wire_pitch=0.56, pfact=10.0 / 3, - pad_length=0, layer=0): - ''' Returns phidl device low-crowding u-turn for X-wing meander. ''' - barc = arc(radius=wire_width * 3, width=wire_width, layer=layer, - theta=45).rotate(180) - - pin = optimal_hairpin(width=wire_width, pitch=pfact * wire_width, - length=15 * wire_width, layer=layer) + def xwing_uturn( + wire_width=0.52, wire_pitch=0.56, pfact=10.0 / 3, pad_length=0, layer=0 + ): + """Returns phidl device low-crowding u-turn for X-wing meander.""" + barc = arc( + radius=wire_width * 3, width=wire_width, layer=layer, theta=45 + ).rotate(180) + + pin = optimal_hairpin( + width=wire_width, + pitch=pfact * wire_width, + length=15 * wire_width, + layer=layer, + ) paslen = pfact * wire_width - np.sqrt(2) * wire_pitch pas = compass(size=(wire_width, abs(paslen)), layer=layer) @@ -5481,18 +5506,17 @@ def xwing_uturn(wire_width=0.52, wire_pitch=0.56, pfact=10.0 / 3, pin1 = Dtemp.add_ref(pin) pas1 = Dtemp.add_ref(pas) arc2 = Dtemp.add_ref(barc) - if(paslen > 0): - pas1.connect(pas1.ports['S'], arc1.ports[2]) - pin1.connect(1, pas1.ports['N']) + if paslen > 0: + pas1.connect(pas1.ports["S"], arc1.ports[2]) + pin1.connect(1, pas1.ports["N"]) arc2.connect(2, pin1.ports[2]) else: pin1.connect(1, arc1.ports[2]) - pas1.connect('N', pin1.ports[2]) - arc2.connect(2, pas1.ports['S']) - if(pad_length > 0): - pin1.move([pad_length * 0.5 / np.sqrt(2), - pad_length * 0.5 / np.sqrt(2)]) - if(paslen > 0): + pas1.connect("N", pin1.ports[2]) + arc2.connect(2, pas1.ports["S"]) + if pad_length > 0: + pin1.move([pad_length * 0.5 / np.sqrt(2), pad_length * 0.5 / np.sqrt(2)]) + if paslen > 0: indx1 = 2 indx2 = 1 myarc = arc2 @@ -5501,33 +5525,37 @@ def xwing_uturn(wire_width=0.52, wire_pitch=0.56, pfact=10.0 / 3, indx2 = 2 myarc = arc1 compdist = np.sqrt( - np.sum( - np.square( - pin1.ports[indx1].midpoint - myarc.ports[2].midpoint))) + np.sum(np.square(pin1.ports[indx1].midpoint - myarc.ports[2].midpoint)) + ) tempc = Dtemp.add_ref( - compass(size=(pin1.ports[indx1].width, - compdist), - layer=layer)) - tempc.connect('N', pin1.ports[indx1]) + compass(size=(pin1.ports[indx1].width, compdist), layer=layer) + ) + tempc.connect("N", pin1.ports[indx1]) compdist = np.sqrt( - np.sum( - np.square( - pin1.ports[indx2].midpoint - pas1.ports['N'].midpoint))) + np.sum(np.square(pin1.ports[indx2].midpoint - pas1.ports["N"].midpoint)) + ) tempc = Dtemp.add_ref( - compass(size=(pin1.ports[indx2].width, - compdist), - layer=layer)) - tempc.connect('N', pin1.ports[indx2]) + compass(size=(pin1.ports[indx2].width, compdist), layer=layer) + ) + tempc.connect("N", pin1.ports[indx2]) Dtemp.flatten() - Dtemp.add_port(name=1, midpoint=arc1.ports[1].midpoint, - width=wire_width, orientation=arc1.ports[1].orientation) - Dtemp.add_port(name=2, midpoint=arc2.ports[1].midpoint, - width=wire_width, orientation=arc2.ports[1].orientation) + Dtemp.add_port( + name=1, + midpoint=arc1.ports[1].midpoint, + width=wire_width, + orientation=arc1.ports[1].orientation, + ) + Dtemp.add_port( + name=2, + midpoint=arc2.ports[1].midpoint, + width=wire_width, + orientation=arc2.ports[1].orientation, + ) return Dtemp - D = Device(name='snspd_candelabra_meander') + D = Device(name="snspd_candelabra_meander") if xwing: Dtemp = xwing_uturn(wire_width=wire_width, wire_pitch=wire_pitch) else: @@ -5538,87 +5566,99 @@ def xwing_uturn(wire_width=0.52, wire_pitch=0.56, pfact=10.0 / 3, half_num_meanders = int(np.ceil(0.5 * vaxis / wire_pitch)) + 2 if xwing: - bend = D.add_ref(arc(radius=wire_width * 3, width=wire_width, - theta=90)).rotate(180) + bend = D.add_ref(arc(radius=wire_width * 3, width=wire_width, theta=90)).rotate( + 180 + ) else: bend = D.add_ref(optimal_90deg(width=wire_width)) if (maxll - dll * half_num_meanders) <= 0.0: - print('Horizontal axis too small! Shrinking vertical axis.') - while((maxll - dll * half_num_meanders) <= 0.0): + print("Horizontal axis too small! Shrinking vertical axis.") + while (maxll - dll * half_num_meanders) <= 0.0: half_num_meanders = half_num_meanders - 1 - fpas = D.add_ref(compass(size=(0.5 * (maxll - dll * half_num_meanders), - wire_width))) + fpas = D.add_ref( + compass(size=(0.5 * (maxll - dll * half_num_meanders), wire_width)) + ) D.movex(-bend.ports[1].x) - fpas.connect(fpas.ports['W'], bend.ports[2]) + fpas.connect(fpas.ports["W"], bend.ports[2]) ll = D.xsize * 2 - wire_width if xwing: Dtemp = xwing_uturn( - wire_width=wire_width, wire_pitch=wire_pitch, - pad_length=(maxll - ll - dll) * equalize_path_lengths) + wire_width=wire_width, + wire_pitch=wire_pitch, + pad_length=(maxll - ll - dll) * equalize_path_lengths, + ) else: Dtemp = off_axis_uturn( - wire_width=wire_width, wire_pitch=wire_pitch, - pad_length=(maxll - ll - dll) * equalize_path_lengths) + wire_width=wire_width, + wire_pitch=wire_pitch, + pad_length=(maxll - ll - dll) * equalize_path_lengths, + ) uturn = D.add_ref(Dtemp) - uturn.connect(1, fpas.ports['E']) + uturn.connect(1, fpas.ports["E"]) dir_left = True turn_padding = maxll - ll - 2 * dll - while(ll < maxll - dll): + while ll < maxll - dll: ll = ll + dll if xwing: Dtemp = xwing_uturn( - wire_width=wire_width, wire_pitch=wire_pitch, - pad_length=turn_padding * equalize_path_lengths) + wire_width=wire_width, + wire_pitch=wire_pitch, + pad_length=turn_padding * equalize_path_lengths, + ) else: Dtemp = off_axis_uturn( - wire_width=wire_width, wire_pitch=wire_pitch, - pad_length=turn_padding * equalize_path_lengths) + wire_width=wire_width, + wire_pitch=wire_pitch, + pad_length=turn_padding * equalize_path_lengths, + ) turn_padding = turn_padding - dll newpas = D.add_ref(compass(size=(ll, wire_width))) - if(dir_left): - newpas.connect(newpas.ports['E'], uturn.ports[2]) + if dir_left: + newpas.connect(newpas.ports["E"], uturn.ports[2]) uturn = D.add_ref(Dtemp.mirror([0, 0], [0, 1])) - uturn.connect(1, newpas.ports['W']) + uturn.connect(1, newpas.ports["W"]) dir_left = False else: - newpas.connect(newpas.ports['W'], uturn.ports[2]) + newpas.connect(newpas.ports["W"], uturn.ports[2]) uturn = D.add_ref(Dtemp) - uturn.connect(1, newpas.ports['E']) + uturn.connect(1, newpas.ports["E"]) dir_left = True newpas = D.add_ref(compass(size=(ll / 2, wire_width))) - if(dir_left): - newpas.connect(newpas.ports['E'], uturn.ports[2]) + if dir_left: + newpas.connect(newpas.ports["E"], uturn.ports[2]) dir_left = False else: - newpas.connect(newpas.ports['W'], uturn.ports[2]) + newpas.connect(newpas.ports["W"], uturn.ports[2]) dir_left = True D.movex(-D.x) if not xwing: bend.movex(-bend.ports[1].x) - if (fpas.ports['W'].x - bend.ports[2].x) > 0: - tempc = D.add_ref(compass(size=(fpas.ports['W'].x - bend.ports[2].x, - bend.ports[2].width))) - tempc.connect('E', fpas.ports['W']) + if (fpas.ports["W"].x - bend.ports[2].x) > 0: + tempc = D.add_ref( + compass(size=(fpas.ports["W"].x - bend.ports[2].x, bend.ports[2].width)) + ) + tempc.connect("E", fpas.ports["W"]) D.move([-D.x, -D.ymin - wire_width * 0.5]) D.add_port(name=1, port=bend.ports[1]) - if(dir_left): - D.add_port(name=2, port=newpas.ports['E']) + if dir_left: + D.add_port(name=2, port=newpas.ports["E"]) else: - D.add_port(name=2, port=newpas.ports['W']) + D.add_port(name=2, port=newpas.ports["W"]) Dout = Device() D1 = Dout.add_ref(D) D2 = Dout.add_ref(copy(D).rotate(180)) - tempc = Dout.add_ref(compass(size=(abs(D1.ports[2].x - D2.ports[2].x), - D1.ports[2].width))) + tempc = Dout.add_ref( + compass(size=(abs(D1.ports[2].x - D2.ports[2].x), D1.ports[2].width)) + ) if D1.ports[2].x > D2.ports[2].x: - tempc.connect('E', D1.ports[2]) + tempc.connect("E", D1.ports[2]) else: - tempc.connect('W', D1.ports[2]) + tempc.connect("W", D1.ports[2]) Dout = copy_layer(Dout, layer=0, new_layer=layer) Dout.add_port(name=1, port=D1.ports[1]) Dout.add_port(name=2, port=D2.ports[1]) From bfa6b81e71c1389a46af61b9f66abb76fea6bd13 Mon Sep 17 00:00:00 2001 From: "Dileep V. Reddy" Date: Sun, 24 Jul 2022 15:38:02 -0600 Subject: [PATCH 25/35] Added parameter definitions in hit text for candelabra_meander() method. --- phidl/geometry.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index be93ee17..7c1c73e6 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -5405,7 +5405,34 @@ def candelabra_meander( # noqa: C901 xwing=False, layer=0, ): - """Returns phidl meander with low current crowding for any fill factor.""" + """ Creates an optimally-rounded SNSPD with low current crowding and + arbtitrarily-high fill factor as described by Reddy et. al., + APL Photonics 7, 051302 (2022) https://doi.org/10.1063/5.0088007 + + Parameters + ---------- + width : int or float + Width of the wire. + pitch : int or float + Distance between two adjacent wires. Must be greater than `width`. + haxis : int or float + Length of horizontal diagonal of the rhomboidal active area. + vaxis : int or float + Length of vertical diagonal of the rhomboidal active area. + equalize_path_lengths : bool + If True, adds wire segments to hairpin bends to equalize path lengths + from center to center for all parallel wires in active area. + xwing : bool + If True, replaces 90-degree bends with 135-degree bends. + layer : int + Specific layer to put polygon geometry on. + + Returns + ------- + D : Device + A Device containing an optimally-rounded SNSPD with minimized current + crowding for any fill factor. + """ def off_axis_uturn( wire_width=0.52, From 3cf28d04bc609ba435cd0ac8dd2d88b244bf6e57 Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sun, 24 Jul 2022 15:48:29 -0600 Subject: [PATCH 26/35] Remove deprecated Device.inset() --- phidl/geometry.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index 15a2502b..0c8b02f2 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -742,10 +742,6 @@ def outline( return Outline -def inset(elements, distance=0.1, join_first=True, precision=1e-4, layer=0): - raise ValueError("[PHIDL] pg.inset() is deprecated, " "please use pg.offset()") - - def invert( elements, border=10, precision=1e-4, num_divisions=[1, 1], max_points=4000, layer=0 ): From a056b9a20937673a397467a7a2214ce03370921f Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sun, 24 Jul 2022 15:54:37 -0600 Subject: [PATCH 27/35] Update parameters --- phidl/geometry.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index 7c1c73e6..5ac2c483 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -5396,7 +5396,7 @@ def snspd_expanded( # quickplot(s) -def candelabra_meander( # noqa: C901 +def snspd_candelabra( # noqa: C901 wire_width=0.52, wire_pitch=0.56, haxis=90, @@ -5405,15 +5405,15 @@ def candelabra_meander( # noqa: C901 xwing=False, layer=0, ): - """ Creates an optimally-rounded SNSPD with low current crowding and + """Creates an optimally-rounded SNSPD with low current crowding and arbtitrarily-high fill factor as described by Reddy et. al., APL Photonics 7, 051302 (2022) https://doi.org/10.1063/5.0088007 Parameters ---------- - width : int or float + wire_width : int or float Width of the wire. - pitch : int or float + wire_pitch : int or float Distance between two adjacent wires. Must be greater than `width`. haxis : int or float Length of horizontal diagonal of the rhomboidal active area. From 155b9ab91806a3f0fcce6d64066a7b930af084bd Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sun, 24 Jul 2022 16:08:24 -0600 Subject: [PATCH 28/35] Cleanup of snspd_candelabra() --- phidl/geometry.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index 5ac2c483..f577d9e0 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -5495,7 +5495,6 @@ def off_axis_uturn( ) ) tempc.connect("N", pin1.ports[2]) - D.flatten() D.add_port( name=1, midpoint=arc1.ports[1].midpoint, @@ -5566,7 +5565,6 @@ def xwing_uturn( ) tempc.connect("N", pin1.ports[indx2]) - Dtemp.flatten() Dtemp.add_port( name=1, midpoint=arc1.ports[1].midpoint, @@ -5582,7 +5580,7 @@ def xwing_uturn( return Dtemp - D = Device(name="snspd_candelabra_meander") + D = Device(name="snspd_candelabra") if xwing: Dtemp = xwing_uturn(wire_width=wire_width, wire_pitch=wire_pitch) else: @@ -5599,7 +5597,6 @@ def xwing_uturn( else: bend = D.add_ref(optimal_90deg(width=wire_width)) if (maxll - dll * half_num_meanders) <= 0.0: - print("Horizontal axis too small! Shrinking vertical axis.") while (maxll - dll * half_num_meanders) <= 0.0: half_num_meanders = half_num_meanders - 1 fpas = D.add_ref( @@ -5689,7 +5686,6 @@ def xwing_uturn( Dout = copy_layer(Dout, layer=0, new_layer=layer) Dout.add_port(name=1, port=D1.ports[1]) Dout.add_port(name=2, port=D2.ports[1]) - Dout.flatten() return Dout From d5efaa6e65517fe98b0938e7a7b18a6ffc39e7d3 Mon Sep 17 00:00:00 2001 From: "Dileep V. Reddy" Date: Sun, 24 Jul 2022 16:49:01 -0600 Subject: [PATCH 29/35] Reduced size of candelabra_meander() output by using references and avoiding flattening. --- phidl/geometry.py | 85 ++++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index 7c1c73e6..5b1fef2f 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -5417,6 +5417,7 @@ def candelabra_meander( # noqa: C901 Distance between two adjacent wires. Must be greater than `width`. haxis : int or float Length of horizontal diagonal of the rhomboidal active area. + The parameter `haxis` is prioritized over `vaxis`. vaxis : int or float Length of vertical diagonal of the rhomboidal active area. equalize_path_lengths : bool @@ -5584,67 +5585,81 @@ def xwing_uturn( D = Device(name="snspd_candelabra_meander") if xwing: - Dtemp = xwing_uturn(wire_width=wire_width, wire_pitch=wire_pitch) + Dtemp = xwing_uturn(wire_width=wire_width, wire_pitch=wire_pitch, + layer=layer) else: - Dtemp = off_axis_uturn(wire_width=wire_width, wire_pitch=wire_pitch) + Dtemp = off_axis_uturn(wire_width=wire_width, wire_pitch=wire_pitch, + layer=layer) + Dtemp_mirrored = deepcopy(Dtemp).mirror([0, 0], [0, 1]) padding = Dtemp.xsize maxll = haxis - 2 * padding dll = abs(Dtemp.ports[1].x - Dtemp.ports[2].x) + wire_pitch half_num_meanders = int(np.ceil(0.5 * vaxis / wire_pitch)) + 2 if xwing: - bend = D.add_ref(arc(radius=wire_width * 3, width=wire_width, theta=90)).rotate( + bend = D.add_ref(arc(radius=wire_width * 3, width=wire_width, theta=90, + layer=layer)).rotate( 180 ) else: - bend = D.add_ref(optimal_90deg(width=wire_width)) + bend = D.add_ref(optimal_90deg(width=wire_width, layer=layer)) if (maxll - dll * half_num_meanders) <= 0.0: - print("Horizontal axis too small! Shrinking vertical axis.") + # Horizontal axis too small! Shrinking vertical axis. while (maxll - dll * half_num_meanders) <= 0.0: half_num_meanders = half_num_meanders - 1 fpas = D.add_ref( - compass(size=(0.5 * (maxll - dll * half_num_meanders), wire_width)) + compass(size=(0.5 * (maxll - dll * half_num_meanders), wire_width), + layer=layer) ) D.movex(-bend.ports[1].x) fpas.connect(fpas.ports["W"], bend.ports[2]) ll = D.xsize * 2 - wire_width - if xwing: - Dtemp = xwing_uturn( - wire_width=wire_width, - wire_pitch=wire_pitch, - pad_length=(maxll - ll - dll) * equalize_path_lengths, - ) - else: - Dtemp = off_axis_uturn( - wire_width=wire_width, - wire_pitch=wire_pitch, - pad_length=(maxll - ll - dll) * equalize_path_lengths, - ) - uturn = D.add_ref(Dtemp) - uturn.connect(1, fpas.ports["E"]) - dir_left = True - - turn_padding = maxll - ll - 2 * dll - - while ll < maxll - dll: - ll = ll + dll + if equalize_path_lengths: if xwing: Dtemp = xwing_uturn( wire_width=wire_width, wire_pitch=wire_pitch, - pad_length=turn_padding * equalize_path_lengths, + pad_length=(maxll - ll - dll) * equalize_path_lengths, + layer=layer ) else: Dtemp = off_axis_uturn( wire_width=wire_width, wire_pitch=wire_pitch, - pad_length=turn_padding * equalize_path_lengths, + pad_length=(maxll - ll - dll) * equalize_path_lengths, + layer=layer ) + uturn = D.add_ref(Dtemp) + uturn.connect(1, fpas.ports["E"]) + dir_left = True + + turn_padding = maxll - ll - 2 * dll + + while ll < maxll - dll: + ll = ll + dll + if equalize_path_lengths: + if xwing: + Dtemp = xwing_uturn( + wire_width=wire_width, + wire_pitch=wire_pitch, + pad_length=turn_padding * equalize_path_lengths, + layer=layer, + ) + else: + Dtemp = off_axis_uturn( + wire_width=wire_width, + wire_pitch=wire_pitch, + pad_length=turn_padding * equalize_path_lengths, + layer=layer, + ) turn_padding = turn_padding - dll - newpas = D.add_ref(compass(size=(ll, wire_width))) + newpas = D.add_ref(compass(size=(ll, wire_width), layer=layer)) if dir_left: newpas.connect(newpas.ports["E"], uturn.ports[2]) - uturn = D.add_ref(Dtemp.mirror([0, 0], [0, 1])) + if equalize_path_lengths: + uturn = D.add_ref(Dtemp.mirror([0, 0], [0, 1])) + else: + uturn = D.add_ref(Dtemp_mirrored) uturn.connect(1, newpas.ports["W"]) dir_left = False else: @@ -5653,7 +5668,7 @@ def xwing_uturn( uturn.connect(1, newpas.ports["E"]) dir_left = True - newpas = D.add_ref(compass(size=(ll / 2, wire_width))) + newpas = D.add_ref(compass(size=(ll / 2, wire_width), layer=layer)) if dir_left: newpas.connect(newpas.ports["E"], uturn.ports[2]) dir_left = False @@ -5666,7 +5681,8 @@ def xwing_uturn( bend.movex(-bend.ports[1].x) if (fpas.ports["W"].x - bend.ports[2].x) > 0: tempc = D.add_ref( - compass(size=(fpas.ports["W"].x - bend.ports[2].x, bend.ports[2].width)) + compass(size=(fpas.ports["W"].x - bend.ports[2].x, bend.ports[2].width), + layer=layer) ) tempc.connect("E", fpas.ports["W"]) D.move([-D.x, -D.ymin - wire_width * 0.5]) @@ -5680,16 +5696,15 @@ def xwing_uturn( D1 = Dout.add_ref(D) D2 = Dout.add_ref(copy(D).rotate(180)) tempc = Dout.add_ref( - compass(size=(abs(D1.ports[2].x - D2.ports[2].x), D1.ports[2].width)) + compass(size=(abs(D1.ports[2].x - D2.ports[2].x), D1.ports[2].width), + layer=layer) ) if D1.ports[2].x > D2.ports[2].x: tempc.connect("E", D1.ports[2]) else: tempc.connect("W", D1.ports[2]) - Dout = copy_layer(Dout, layer=0, new_layer=layer) Dout.add_port(name=1, port=D1.ports[1]) Dout.add_port(name=2, port=D2.ports[1]) - Dout.flatten() return Dout From c2e798bab6983fdc5987be21ad0bb57b3b438166 Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sun, 24 Jul 2022 20:42:20 -0600 Subject: [PATCH 30/35] Pre-commit fixes --- phidl/geometry.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/phidl/geometry.py b/phidl/geometry.py index 908f5c50..e2662101 100644 --- a/phidl/geometry.py +++ b/phidl/geometry.py @@ -5583,11 +5583,11 @@ def xwing_uturn( D = Device(name="snspd_candelabra") if xwing: - Dtemp = xwing_uturn(wire_width=wire_width, wire_pitch=wire_pitch, - layer=layer) + Dtemp = xwing_uturn(wire_width=wire_width, wire_pitch=wire_pitch, layer=layer) else: - Dtemp = off_axis_uturn(wire_width=wire_width, wire_pitch=wire_pitch, - layer=layer) + Dtemp = off_axis_uturn( + wire_width=wire_width, wire_pitch=wire_pitch, layer=layer + ) Dtemp_mirrored = deepcopy(Dtemp).mirror([0, 0], [0, 1]) padding = Dtemp.xsize maxll = haxis - 2 * padding @@ -5595,18 +5595,16 @@ def xwing_uturn( half_num_meanders = int(np.ceil(0.5 * vaxis / wire_pitch)) + 2 if xwing: - bend = D.add_ref(arc(radius=wire_width * 3, width=wire_width, theta=90, - layer=layer)).rotate( - 180 - ) + bend = D.add_ref( + arc(radius=wire_width * 3, width=wire_width, theta=90, layer=layer) + ).rotate(180) else: bend = D.add_ref(optimal_90deg(width=wire_width, layer=layer)) if (maxll - dll * half_num_meanders) <= 0.0: while (maxll - dll * half_num_meanders) <= 0.0: half_num_meanders = half_num_meanders - 1 fpas = D.add_ref( - compass(size=(0.5 * (maxll - dll * half_num_meanders), wire_width), - layer=layer) + compass(size=(0.5 * (maxll - dll * half_num_meanders), wire_width), layer=layer) ) D.movex(-bend.ports[1].x) fpas.connect(fpas.ports["W"], bend.ports[2]) @@ -5617,14 +5615,14 @@ def xwing_uturn( wire_width=wire_width, wire_pitch=wire_pitch, pad_length=(maxll - ll - dll) * equalize_path_lengths, - layer=layer + layer=layer, ) else: Dtemp = off_axis_uturn( wire_width=wire_width, wire_pitch=wire_pitch, pad_length=(maxll - ll - dll) * equalize_path_lengths, - layer=layer + layer=layer, ) uturn = D.add_ref(Dtemp) uturn.connect(1, fpas.ports["E"]) @@ -5678,8 +5676,10 @@ def xwing_uturn( bend.movex(-bend.ports[1].x) if (fpas.ports["W"].x - bend.ports[2].x) > 0: tempc = D.add_ref( - compass(size=(fpas.ports["W"].x - bend.ports[2].x, bend.ports[2].width), - layer=layer) + compass( + size=(fpas.ports["W"].x - bend.ports[2].x, bend.ports[2].width), + layer=layer, + ) ) tempc.connect("E", fpas.ports["W"]) D.move([-D.x, -D.ymin - wire_width * 0.5]) @@ -5693,8 +5693,9 @@ def xwing_uturn( D1 = Dout.add_ref(D) D2 = Dout.add_ref(copy(D).rotate(180)) tempc = Dout.add_ref( - compass(size=(abs(D1.ports[2].x - D2.ports[2].x), D1.ports[2].width), - layer=layer) + compass( + size=(abs(D1.ports[2].x - D2.ports[2].x), D1.ports[2].width), layer=layer + ) ) if D1.ports[2].x > D2.ports[2].x: tempc.connect("E", D1.ports[2]) From a3900bad09965fccf4783568bd8fab827039fa29 Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sun, 24 Jul 2022 20:57:47 -0600 Subject: [PATCH 31/35] Update version --- README.md | 2 +- phidl/device_layout.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d097b4ec..eb23e48a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ GDS scripting for Python that's intuitive, fast, and powerful. - [**Installation / requirements**](#installation--requirements) - [**Tutorial + examples**](https://phidl.readthedocs.io/en/latest/tutorials.html) (or [try an interactive notebook](https://mybinder.org/v2/gh/amccaugh/phidl/master?filepath=phidl_tutorial_example.ipynb)) - [**Geometry library + function documentation**](https://phidl.readthedocs.io/en/latest/geometry_reference.html) -- [Changelog](https://github.com/amccaugh/phidl/blob/master/CHANGELOG.md) (latest update 1.6.1 on April 7, 2022) +- [Changelog](https://github.com/amccaugh/phidl/blob/master/CHANGELOG.md) (latest update 1.6.2 on July 25, 2022) - Huge new routing rewrite for `phidl.routing`, including automatic manhattan routing with custom cross-sections! See [the routing documentation](https://phidl.readthedocs.io/en/latest/tutorials/routing.html) for details. Big thanks to Jeffrey Holzgrafe @jolzgrafe for this contribution - `Path`s can now be used to produce sharp angles, in addition to smooth bends. See [the Path documentation](https://phidl.readthedocs.io/en/latest/tutorials/waveguides.html#Sharp/angular-paths) diff --git a/phidl/device_layout.py b/phidl/device_layout.py index b1bb8f87..669708ec 100644 --- a/phidl/device_layout.py +++ b/phidl/device_layout.py @@ -52,7 +52,7 @@ gdspy.library.use_current_library = False -__version__ = "1.6.1" +__version__ = "1.6.2" # ============================================================================== diff --git a/setup.py b/setup.py index 1d5be498..03ef6cf3 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup( name="phidl", - version="1.6.1", + version="1.6.2", description="PHIDL", long_description=long_description, long_description_content_type="text/markdown", From 456411702468ac8d3fb9e41831f49dfaed1b1db5 Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sun, 24 Jul 2022 20:57:50 -0600 Subject: [PATCH 32/35] Update CHANGELOG.md --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c47d0c83..a1b3827e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 1.6.2 (July 25, 2022) + +### New features +- Addition of `pg.snspd_candelabra()` which creates an optimally-rounded SNSPD with low current crowding and arbtitrarily-high fill factor (thanks Dileep Reddy @dileepvr) +- Lazy loading of `matplotlib`, allowing loading the base phidl libraries much faster (thanks Joaquin Matres @joamatab) + + +### Changes +- Modification to `pg.boolean()` s othat `OR`/union will merge all shapes within one Device, even if the second Device is `None` (thanks +Stijn Balk @sbalk) + +### Bugfixes +- Modifying the `parent` of a `DeviceReference` now correctly updates the reference cell (thanks Joaquin Matres @joamatab) +- GDS path objects now copy over when using `pg.import_gds()` (thanks Bas Nijholt @basnijholt) +- Preserve Polygon.properties and DeviceReference.properties when saving and loading (thanks Bas Nijholt @basnijholt) +- `D.remove_layers()` works also with GDS path objects (thanks Joaquin Matres @joamatab) + + ## 1.6.1 (April 7, 2022) ### New features From f63f2f599e608a049107c9bc4c8687550b4b08a7 Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sun, 24 Jul 2022 21:15:08 -0600 Subject: [PATCH 33/35] Update API.rst --- docs/API.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/API.rst b/docs/API.rst index 396fba16..21928847 100644 --- a/docs/API.rst +++ b/docs/API.rst @@ -299,6 +299,12 @@ snspd_expanded .. autofunction:: phidl.geometry.snspd_expanded +snspd_candelabra +============== + +.. autofunction:: phidl.geometry.snspd_candelabra + + straight ======== From 8d48dd6b70d961d32aa39c2bdbc4802d736029b4 Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Sun, 24 Jul 2022 21:19:16 -0600 Subject: [PATCH 34/35] Update geometry_reference.ipynb --- docs/geometry_reference.ipynb | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/geometry_reference.ipynb b/docs/geometry_reference.ipynb index 80316bd5..2ace70d1 100644 --- a/docs/geometry_reference.ipynb +++ b/docs/geometry_reference.ipynb @@ -2938,6 +2938,40 @@ "qp(D) # quickplot the geometry" ] }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import phidl.geometry as pg\n", + "from phidl import quickplot as qp\n", + "\n", + "D = pg.snspd_candelabra(\n", + " wire_width=0.52,\n", + " wire_pitch=0.56,\n", + " haxis=40,\n", + " vaxis=20,\n", + " equalize_path_lengths=False,\n", + " xwing=False,\n", + " layer=0,\n", + ")\n", + "qp(D) # quickplot the geometry" + ] + }, { "cell_type": "markdown", "metadata": {}, From a3385d78d91e6df54e40b9dabc173e52c2f9d6d3 Mon Sep 17 00:00:00 2001 From: Adam McCaughan Date: Tue, 26 Jul 2022 10:40:30 -0600 Subject: [PATCH 35/35] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1b3827e..207953d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Stijn Balk @sbalk) ### Bugfixes - Modifying the `parent` of a `DeviceReference` now correctly updates the reference cell (thanks Joaquin Matres @joamatab) +- Fix bug in `pg.outline()` when `distance < 0` (thanks @yoshi74ls181) - GDS path objects now copy over when using `pg.import_gds()` (thanks Bas Nijholt @basnijholt) - Preserve Polygon.properties and DeviceReference.properties when saving and loading (thanks Bas Nijholt @basnijholt) - `D.remove_layers()` works also with GDS path objects (thanks Joaquin Matres @joamatab)