From b3e252192c9317f5222349a1ed5193f29112cb6e Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Sun, 10 Sep 2023 21:04:07 -0700 Subject: [PATCH 1/3] add install colab --- Makefile | 3 + README.md | 4 + notebooks/10_layout.ipynb | 17 + notebooks/10_layout_full.ipynb | 9405 ++++++++++++++++++++++++++++ notebooks/11_drc.ipynb | 24 +- notebooks/20_modesolver_fem.ipynb | 17 + notebooks/21_modesolver_fdfd.ipynb | 17 + notebooks/22_heater_fem.ipynb | 17 + notebooks/30_mzi.ipynb | 17 + notebooks/31_ring.ipynb | 17 + 10 files changed, 9534 insertions(+), 4 deletions(-) create mode 100644 notebooks/10_layout_full.ipynb diff --git a/Makefile b/Makefile index 2c73025..b7c7ab0 100644 --- a/Makefile +++ b/Makefile @@ -15,4 +15,7 @@ git-rm-merged: docs: jb build docs +clean: + nbstripout --drop-empty-cells notebooks/*.ipynb + .PHONY: docs diff --git a/README.md b/README.md index f2dce1c..bfd1cf4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # PIC training +You can [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/gdsfactory/gdsfactory-photonics-training) + +Or Install gdsfactory locally on your computer. + ## 1. Install Python - [ ] Make sure you have python 3.10 or 3.11 installed on your computer. Here is the [anaconda](https://www.anaconda.com/download/) installer. diff --git a/notebooks/10_layout.ipynb b/notebooks/10_layout.ipynb index ec9f0e7..d241ccf 100644 --- a/notebooks/10_layout.ipynb +++ b/notebooks/10_layout.ipynb @@ -14,6 +14,22 @@ "In gdsfactory **all dimensions** are in **microns**" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "b066ee6c", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " import google.colab\n", + " is_running_on_colab = True\n", + " !pip install gdsfactory klayout > /dev/null\n", + " \n", + "except ImportError:\n", + " is_running_on_colab = False" + ] + }, { "cell_type": "markdown", "id": "2bc35b51", @@ -35,6 +51,7 @@ "from gdsfactory.generic_tech import get_generic_pdk\n", "\n", "gf.config.rich_output()\n", + "gf.CONF.display_type = 'klayout'\n", "\n", "PDK = get_generic_pdk()\n", "PDK.activate()\n", diff --git a/notebooks/10_layout_full.ipynb b/notebooks/10_layout_full.ipynb new file mode 100644 index 0000000..d4a7c57 --- /dev/null +++ b/notebooks/10_layout_full.ipynb @@ -0,0 +1,9405 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "o09UQOsnYpQO", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "executionInfo": { + "elapsed": 32041, + "status": "ok", + "timestamp": 1692924563677, + "user": { + "displayName": "Nate Palmquist", + "userId": "09827099203523851911" + }, + "user_tz": 420 + }, + "id": "o09UQOsnYpQO", + "outputId": "116839fa-0600-4d0b-80bc-231ee7c6b69c" + }, + "outputs": [], + "source": [ + "try:\n", + " import google.colab\n", + " is_running_on_colab = True\n", + " !pip install gdsfactory klayout > /dev/null\n", + " \n", + "except ImportError:\n", + " is_running_on_colab = False" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "k-iw2RqvkEAn", + "metadata": { + "executionInfo": { + "elapsed": 2313, + "status": "ok", + "timestamp": 1692924565985, + "user": { + "displayName": "Nate Palmquist", + "userId": "09827099203523851911" + }, + "user_tz": 420 + }, + "id": "k-iw2RqvkEAn" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "gf.CONF.display_type = 'klayout' # for plotting in Colab notebook" + ] + }, + { + "cell_type": "markdown", + "id": "6c4c0e4c", + "metadata": { + "id": "6c4c0e4c" + }, + "source": [ + "# Component\n", + "\n", + "A `Component` is like an empty canvas, where you can add polygons, references to other Components and ports (to connect to other components)\n", + "\n", + "![](https://i.imgur.com/oeuKGsc.png)\n", + "\n", + "In gdsfactory **all dimensions** are in **microns**\n", + "\n", + "based on [UCSB workshop](https://github.com/aisichenko/gdsfactory-workshop-ucsb) workshop" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34975127", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 722 + }, + "executionInfo": { + "elapsed": 1590, + "status": "ok", + "timestamp": 1692924567564, + "user": { + "displayName": "Nate Palmquist", + "userId": "09827099203523851911" + }, + "user_tz": 420 + }, + "id": "34975127", + "outputId": "9d185a08-4ba9-4545-c317-73fe536ce2fd" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "E = gf.components.ellipse(radii=(10, 5), layer=(1, 0))\n", + "R = gf.components.rectangle(size=[15, 5], layer=(2, 0))\n", + "C = gf.geometry.boolean(A=E, B=R, operation=\"not\", precision=1e-6, layer=(3, 0))\n", + "# Other operations include 'and', 'or', 'xor', or equivalently 'A-B', 'B-A', 'A+B'\n", + "\n", + "# Plot the originals and the result\n", + "D = gf.Component(\"bool\")\n", + "D.add_ref(E)\n", + "D.add_ref(R).movey(-1.5)\n", + "D.add_ref(C).movex(30)\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "ea481eac", + "metadata": { + "id": "ea481eac" + }, + "source": [ + "## Polygons\n", + "\n", + "You can add polygons to different layers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c58f78fe", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 688 + }, + "executionInfo": { + "elapsed": 920, + "status": "ok", + "timestamp": 1692924761614, + "user": { + "displayName": "Nate Palmquist", + "userId": "09827099203523851911" + }, + "user_tz": 420 + }, + "id": "c58f78fe", + "outputId": "f1816413-3e15-478b-9431-42fbcf1212f7" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "from gdsfactory.generic_tech import get_generic_pdk\n", + "\n", + "gf.config.rich_output()\n", + "\n", + "PDK = get_generic_pdk()\n", + "PDK.activate()\n", + "\n", + "\n", + "@gf.cell\n", + "def demo_polygons():\n", + " # Create a blank component (essentially an empty GDS cell with some special features)\n", + " c = gf.Component()\n", + "\n", + " # Create and add a polygon from separate lists of x points and y points\n", + " # (Can also be added like [(x1,y1), (x2,y2), (x3,y3), ... ]\n", + " poly1 = c.add_polygon(\n", + " [(-8, 6, 7, 9), (-6, 8, 17, 5)], layer=1\n", + " ) # GDS layers are tuples of ints (but if we use only one number it assumes the other number is 0)\n", + " return c\n", + "\n", + "\n", + "c = demo_polygons()\n", + "c.plot() # show it in KLayout" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7f80e84", + "metadata": { + "id": "b7f80e84", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "c = gf.Component(\"enclosure1\")\n", + "r = c << gf.components.ring_single()\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "dc692338", + "metadata": { + "id": "dc692338", + "lines_to_next_cell": 2 + }, + "source": [ + "## Connect **ports**\n", + "\n", + "Components can have a \"Port\" that allows you to connect ComponentReferences together like legos.\n", + "\n", + "You can write a simple function to make a rectangular straight, assign ports to the ends, and then connect those rectangles together.\n", + "\n", + "Notice that `connect` transform each reference but things won't remain connected if you move any of the references afterwards.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e3f6a43e", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 633 + }, + "executionInfo": { + "elapsed": 574, + "status": "ok", + "timestamp": 1692924932686, + "user": { + "displayName": "Nate Palmquist", + "userId": "09827099203523851911" + }, + "user_tz": 420 + }, + "id": "e3f6a43e", + "outputId": "e2c636ae-566b-4635-81f5-b6bd4c01f3df" + }, + "outputs": [], + "source": [ + "@gf.cell\n", + "def straight(length=10, width=1, layer=(1, 0)):\n", + " WG = gf.Component()\n", + " WG.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=layer)\n", + " WG.add_port(\n", + " name=\"o1\", center=[0, width / 2], width=width, orientation=180, layer=layer\n", + " )\n", + " WG.add_port(\n", + " name=\"o2\", center=[length, width / 2], width=width, orientation=0, layer=layer\n", + " )\n", + " return WG\n", + "\n", + "\n", + "c = gf.Component(\"straights_not_connected\")\n", + "\n", + "wg1 = c << straight(length=6, width=2.5, layer=1)\n", + "wg2 = c << straight(length=11, width=2.5, layer=2)\n", + "wg3 = c << straight(length=15, width=2.5, layer=3)\n", + "wg2.movey(10).rotate(10)\n", + "wg3.movey(20).rotate(15)\n", + "\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "86a940b7", + "metadata": { + "id": "86a940b7" + }, + "source": [ + "Now we can connect everything together using the ports:\n", + "\n", + "Each straight has two ports: 'o1' and 'o2', respectively on the East and West sides of the rectangular straight component. These are arbitrary\n", + "names defined in our straight() function above" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ac597c3", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 633 + }, + "executionInfo": { + "elapsed": 839, + "status": "ok", + "timestamp": 1692925146226, + "user": { + "displayName": "Nate Palmquist", + "userId": "09827099203523851911" + }, + "user_tz": 420 + }, + "id": "1ac597c3", + "outputId": "046ac7d7-49e4-451c-9d04-68858f47bdd3" + }, + "outputs": [], + "source": [ + "# Let's keep wg1 in place on the bottom, and connect the other straights to it.\n", + "# To do that, on wg2 we'll grab the \"o1\" port and connect it to the \"o2\" on wg1:\n", + "wg2.connect(\"o1\", wg1.ports[\"o2\"])\n", + "# Next, on wg3 let's grab the \"o1\" port and connect it to the \"o2\" on wg2:\n", + "wg3.connect(\"o1\", wg2.ports[\"o2\"])\n", + "\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "8ceab3a7", + "metadata": { + "id": "8ceab3a7" + }, + "source": [ + "Ports can be added by copying existing ports. In the example below, ports are added at the component-level on c from the existing ports of children wg1 and wg3\n", + "(i.e. eastmost and westmost ports)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80da9d4f", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 633 + }, + "executionInfo": { + "elapsed": 562, + "status": "ok", + "timestamp": 1692925211464, + "user": { + "displayName": "Nate Palmquist", + "userId": "09827099203523851911" + }, + "user_tz": 420 + }, + "id": "80da9d4f", + "outputId": "40ca9cee-eaaa-49a4-ad29-1d0848a0bdac" + }, + "outputs": [], + "source": [ + "c.add_port(\"o1\", port=wg1.ports[\"o1\"])\n", + "c.add_port(\"o2\", port=wg3.ports[\"o2\"])\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "aae942de", + "metadata": { + "id": "aae942de" + }, + "source": [ + "## Write\n", + "\n", + "You can write your Component to:\n", + "\n", + "- GDS file (Graphical Database System) or OASIS for chips.\n", + "- gerber for PCB.\n", + "- STL for 3d printing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfb2ef8e", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 650 + }, + "executionInfo": { + "elapsed": 798, + "status": "ok", + "timestamp": 1692925242786, + "user": { + "displayName": "Nate Palmquist", + "userId": "09827099203523851911" + }, + "user_tz": 420 + }, + "id": "bfb2ef8e", + "outputId": "f7238564-dc6c-4f07-826e-75a9b2702d03" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "c = gf.components.cross()\n", + "c.write_gds(\"demo.gds\")\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "eebb788e", + "metadata": { + "id": "eebb788e" + }, + "source": [ + "You can see the GDS file in Klayout viewer.\n", + "\n", + "Sometimes you also want to save the GDS together with metadata (settings, port names, widths, locations ...) in YAML" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "885df462", + "metadata": { + "id": "885df462" + }, + "outputs": [], + "source": [ + "c.write_gds(\"demo.gds\", with_metadata=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ece04c5f", + "metadata": { + "id": "ece04c5f" + }, + "outputs": [], + "source": [ + "c.write_oas(\"demo.oas\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "666a3a49", + "metadata": { + "id": "666a3a49" + }, + "outputs": [], + "source": [ + "c.write_stl(\"demo.stl\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b10a8eb", + "metadata": { + "id": "6b10a8eb" + }, + "outputs": [], + "source": [ + "scene = c.to_3d()\n", + "scene.show()" + ] + }, + { + "cell_type": "markdown", + "id": "31a27ff6", + "metadata": { + "id": "31a27ff6" + }, + "source": [ + "## Importing GDS files" + ] + }, + { + "cell_type": "markdown", + "id": "0f26e0a1", + "metadata": { + "id": "0f26e0a1" + }, + "source": [ + "`gf.import_gds()` allows you to easily import external GDSII files. It imports a single cell from the external GDS file and converts it into a gdsfactory component." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2137531", + "metadata": { + "id": "e2137531" + }, + "outputs": [], + "source": [ + "D = gf.components.ellipse()\n", + "D.write_gds(\"myoutput.gds\")\n", + "D2 = gf.import_gds(gdspath=\"myoutput.gds\", cellname=None, flatten=False)\n", + "D2.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "e2dec483", + "metadata": { + "id": "e2dec483" + }, + "source": [ + "# Generic_tech\n", + "\n", + "gdsfactory includes a generic Technology module in `gdsfactory.generic_tech` that you can use as an inspiration to create your own.\n", + "\n", + "## LayerMap\n", + "\n", + "A layer map maps layer names to a integer numbers pair (GDSlayer, GDSpurpose)\n", + "\n", + "Each foundry uses different GDS layer numbers for different process steps.\n", + "\n", + "We follow the generic layer numbers from the book \"Silicon Photonics Design: From Devices to Systems Lukas Chrostowski, Michael Hochberg\".\n", + "\n", + "| GDS (layer, purpose) | layer_name | Description |\n", + "| -------------------- | ---------- | ----------------------------------------------------------- |\n", + "| 1 , 0 | WG | 220 nm Silicon core |\n", + "| 2 , 0 | SLAB150 | 150nm Silicon slab (70nm shallow Etch for grating couplers) |\n", + "| 3 , 0 | SLAB90 | 90nm Silicon slab (for modulators) |\n", + "| 4, 0 | DEEPTRENCH | Deep trench |\n", + "| 47, 0 | MH | heater |\n", + "| 41, 0 | M1 | metal 1 |\n", + "| 45, 0 | M2 | metal 2 |\n", + "| 40, 0 | VIAC | VIAC to contact Ge, NPP or PPP |\n", + "| 44, 0 | VIA1 | VIA1 |\n", + "| 46, 0 | PADOPEN | Bond pad opening |\n", + "| 51, 0 | UNDERCUT | Undercut |\n", + "| 66, 0 | TEXT | Text markup |\n", + "| 64, 0 | FLOORPLAN | Mask floorplan |\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5a06078f", + "metadata": { + "id": "5a06078f" + }, + "outputs": [], + "source": [ + "\n", + "from pydantic import BaseModel\n", + "\n", + "import gdsfactory as gf\n", + "from gdsfactory.generic_tech import LAYER, LAYER_STACK\n", + "from gdsfactory.generic_tech.get_klayout_pyxs import get_klayout_pyxs\n", + "from gdsfactory.technology import LayerLevel, LayerStack, LayerViews\n", + "from gdsfactory.generic_tech import get_generic_pdk" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1086f0c", + "metadata": { + "id": "d1086f0c" + }, + "outputs": [], + "source": [ + "Layer = tuple[int, int]\n", + "\n", + "gf.config.rich_output()\n", + "PDK = get_generic_pdk()\n", + "PDK.activate()\n", + "\n", + "\n", + "class GenericLayerMap(BaseModel):\n", + " \"\"\"Generic layermap based on book.\n", + "\n", + " Lukas Chrostowski, Michael Hochberg, \"Silicon Photonics Design\",\n", + " Cambridge University Press 2015, page 353\n", + " You will need to create a new LayerMap with your specific foundry layers.\n", + " \"\"\"\n", + "\n", + " WAFER: Layer = (99999, 0)\n", + "\n", + " WG: Layer = (1, 0)\n", + " WGCLAD: Layer = (111, 0)\n", + " SLAB150: Layer = (2, 0)\n", + " SLAB90: Layer = (3, 0)\n", + " DEEPTRENCH: Layer = (4, 0)\n", + " GE: Layer = (5, 0)\n", + " UNDERCUT: Layer = (6, 0)\n", + " WGN: Layer = (34, 0)\n", + " WGN_CLAD: Layer = (36, 0)\n", + "\n", + " N: Layer = (20, 0)\n", + " NP: Layer = (22, 0)\n", + " NPP: Layer = (24, 0)\n", + " P: Layer = (21, 0)\n", + " PP: Layer = (23, 0)\n", + " PPP: Layer = (25, 0)\n", + " GEN: Layer = (26, 0)\n", + " GEP: Layer = (27, 0)\n", + "\n", + " HEATER: Layer = (47, 0)\n", + " M1: Layer = (41, 0)\n", + " M2: Layer = (45, 0)\n", + " M3: Layer = (49, 0)\n", + " VIAC: Layer = (40, 0)\n", + " VIA1: Layer = (44, 0)\n", + " VIA2: Layer = (43, 0)\n", + " PADOPEN: Layer = (46, 0)\n", + "\n", + " DICING: Layer = (100, 0)\n", + " NO_TILE_SI: Layer = (71, 0)\n", + " PADDING: Layer = (67, 0)\n", + " DEVREC: Layer = (68, 0)\n", + " FLOORPLAN: Layer = (64, 0)\n", + " TEXT: Layer = (66, 0)\n", + " PORT: Layer = (1, 10)\n", + " PORTE: Layer = (1, 11)\n", + " PORTH: Layer = (70, 0)\n", + " SHOW_PORTS: Layer = (1, 12)\n", + " LABEL: Layer = (201, 0)\n", + " LABEL_SETTINGS: Layer = (202, 0)\n", + " TE: Layer = (203, 0)\n", + " TM: Layer = (204, 0)\n", + " DRC_MARKER: Layer = (205, 0)\n", + " LABEL_INSTANCE: Layer = (206, 0)\n", + " ERROR_MARKER: Layer = (207, 0)\n", + " ERROR_PATH: Layer = (208, 0)\n", + "\n", + " SOURCE: Layer = (110, 0)\n", + " MONITOR: Layer = (101, 0)\n", + "\n", + " class Config:\n", + " \"\"\"pydantic config.\"\"\"\n", + "\n", + " frozen = True\n", + " extra = \"forbid\"\n", + "\n", + "\n", + "LAYER = GenericLayerMap()\n", + "LAYER" + ] + }, + { + "cell_type": "markdown", + "id": "c60cbd2f", + "metadata": { + "id": "c60cbd2f" + }, + "source": [ + "### Extract layers\n", + "\n", + "You can also extract layers using the `extract` function. This function returns a new flattened Component that contains the extracted layers.\n", + "A flat Component does not have references, and all the polygons are absorbed into the top cell." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a475d9da", + "metadata": { + "id": "a475d9da" + }, + "outputs": [], + "source": [ + "from gdsfactory.generic_tech import get_generic_pdk\n", + "\n", + "PDK = get_generic_pdk()\n", + "PDK.activate()\n", + "\n", + "LAYER_VIEWS = PDK.layer_views\n", + "c = LAYER_VIEWS.preview_layerset()\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "2e21eb2f", + "metadata": { + "id": "2e21eb2f" + }, + "source": [ + "## LayerStack\n", + "\n", + "Each layer also includes the information of thickness and position of each layer after fabrication.\n", + "\n", + "This LayerStack can be used for creating a 3D model with `Component.to_3d` or running Simulations.\n", + "\n", + "A GDS has different layers to describe the different fabrication process steps. And each grown layer needs thickness information and z-position in the stack.\n", + "\n", + "![layer stack](https://i.imgur.com/GUb1Kav.png)\n", + "\n", + "Lets define the layer stack for the generic layers in the generic_technology." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1959c56d", + "metadata": { + "id": "1959c56d" + }, + "outputs": [], + "source": [ + "from gdsfactory.generic_tech.layer_map import LAYER\n", + "from gdsfactory.technology import LayerLevel, LayerStack\n", + "\n", + "nm = 1e-3\n", + "\n", + "\n", + "def get_layer_stack(\n", + " thickness_wg: float = 220 * nm,\n", + " thickness_slab_deep_etch: float = 90 * nm,\n", + " thickness_slab_shallow_etch: float = 150 * nm,\n", + " sidewall_angle_wg: float = 10,\n", + " thickness_clad: float = 3.0,\n", + " thickness_nitride: float = 350 * nm,\n", + " thickness_ge: float = 500 * nm,\n", + " gap_silicon_to_nitride: float = 100 * nm,\n", + " zmin_heater: float = 1.1,\n", + " zmin_metal1: float = 1.1,\n", + " thickness_metal1: float = 700 * nm,\n", + " zmin_metal2: float = 2.3,\n", + " thickness_metal2: float = 700 * nm,\n", + " zmin_metal3: float = 3.2,\n", + " thickness_metal3: float = 2000 * nm,\n", + " substrate_thickness: float = 10.0,\n", + " box_thickness: float = 3.0,\n", + " undercut_thickness: float = 5.0,\n", + ") -> LayerStack:\n", + " \"\"\"Returns generic LayerStack.\n", + "\n", + " based on paper https://www.degruyter.com/document/doi/10.1515/nanoph-2013-0034/html\n", + "\n", + " Args:\n", + " thickness_wg: waveguide thickness in um.\n", + " thickness_slab_deep_etch: for deep etched slab.\n", + " thickness_shallow_etch: thickness for the etch.\n", + " sidewall_angle_wg: waveguide side angle.\n", + " thickness_clad: cladding thickness in um.\n", + " thickness_nitride: nitride thickness in um.\n", + " thickness_ge: germanium thickness.\n", + " gap_silicon_to_nitride: distance from silicon to nitride in um.\n", + " zmin_heater: TiN heater.\n", + " zmin_metal1: metal1.\n", + " thickness_metal1: metal1 thickness.\n", + " zmin_metal2: metal2.\n", + " thickness_metal2: metal2 thickness.\n", + " zmin_metal3: metal3.\n", + " thickness_metal3: metal3 thickness.\n", + " substrate_thickness: substrate thickness in um.\n", + " box_thickness: bottom oxide thickness in um.\n", + " undercut_thickness: thickness of the silicon undercut.\n", + " \"\"\"\n", + "\n", + " thickness_deep_etch = thickness_wg - thickness_slab_deep_etch\n", + " thickness_shallow_etch = thickness_wg - thickness_slab_shallow_etch\n", + "\n", + " class GenericLayerStack(LayerStack):\n", + " substrate = LayerLevel(\n", + " layer=LAYER.WAFER,\n", + " thickness=substrate_thickness,\n", + " zmin=-substrate_thickness - box_thickness,\n", + " material=\"si\",\n", + " mesh_order=99,\n", + " )\n", + " box = LayerLevel(\n", + " layer=LAYER.WAFER,\n", + " thickness=box_thickness,\n", + " zmin=-box_thickness,\n", + " material=\"sio2\",\n", + " mesh_order=99,\n", + " )\n", + " core = LayerLevel(\n", + " layer=(1, 0),\n", + " thickness=thickness_wg,\n", + " zmin=0.0,\n", + " material=\"si\",\n", + " mesh_order=2,\n", + " sidewall_angle=sidewall_angle_wg,\n", + " width_to_z=0.5,\n", + " )\n", + " shallow_etch = LayerLevel(\n", + " layer=LAYER.SHALLOW_ETCH,\n", + " thickness=thickness_shallow_etch,\n", + " zmin=0.0,\n", + " material=\"si\",\n", + " mesh_order=1,\n", + " layer_type=\"etch\",\n", + " into=[\"core\"],\n", + " derived_layer=(2, 0),\n", + " )\n", + " deep_etch = LayerLevel(\n", + " layer=LAYER.DEEP_ETCH,\n", + " thickness=thickness_deep_etch,\n", + " zmin=0.0,\n", + " material=\"si\",\n", + " mesh_order=1,\n", + " layer_type=\"etch\",\n", + " into=[\"core\"],\n", + " derived_layer=LAYER.SLAB90,\n", + " )\n", + " clad = LayerLevel(\n", + " # layer=LAYER.WGCLAD,\n", + " layer=LAYER.WAFER,\n", + " zmin=0.0,\n", + " material=\"sio2\",\n", + " thickness=thickness_clad,\n", + " mesh_order=10,\n", + " )\n", + " slab150 = LayerLevel(\n", + " layer=(2, 0),\n", + " thickness=150e-3,\n", + " zmin=0,\n", + " material=\"si\",\n", + " mesh_order=3,\n", + " )\n", + " slab90 = LayerLevel(\n", + " layer=LAYER.SLAB90,\n", + " thickness=thickness_slab_deep_etch,\n", + " zmin=0.0,\n", + " material=\"si\",\n", + " mesh_order=2,\n", + " )\n", + " nitride = LayerLevel(\n", + " layer=LAYER.WGN,\n", + " thickness=thickness_nitride,\n", + " zmin=thickness_wg + gap_silicon_to_nitride,\n", + " material=\"sin\",\n", + " mesh_order=2,\n", + " )\n", + " ge = LayerLevel(\n", + " layer=LAYER.GE,\n", + " thickness=thickness_ge,\n", + " zmin=thickness_wg,\n", + " material=\"ge\",\n", + " mesh_order=1,\n", + " )\n", + " undercut = LayerLevel(\n", + " layer=LAYER.UNDERCUT,\n", + " thickness=-undercut_thickness,\n", + " zmin=-box_thickness,\n", + " material=\"air\",\n", + " z_to_bias=[\n", + " [0, 0.3, 0.6, 0.8, 0.9, 1],\n", + " [-0, -0.5, -1, -1.5, -2, -2.5],\n", + " ],\n", + " mesh_order=1,\n", + " )\n", + " via_contact = LayerLevel(\n", + " layer=LAYER.VIAC,\n", + " thickness=zmin_metal1 - thickness_slab_deep_etch,\n", + " zmin=thickness_slab_deep_etch,\n", + " material=\"Aluminum\",\n", + " mesh_order=1,\n", + " sidewall_angle=-10,\n", + " width_to_z=0,\n", + " )\n", + " metal1 = LayerLevel(\n", + " layer=LAYER.M1,\n", + " thickness=thickness_metal1,\n", + " zmin=zmin_metal1,\n", + " material=\"Aluminum\",\n", + " mesh_order=2,\n", + " )\n", + " heater = LayerLevel(\n", + " layer=LAYER.HEATER,\n", + " thickness=750e-3,\n", + " zmin=zmin_heater,\n", + " material=\"TiN\",\n", + " mesh_order=1,\n", + " )\n", + " via1 = LayerLevel(\n", + " layer=LAYER.VIA1,\n", + " thickness=zmin_metal2 - (zmin_metal1 + thickness_metal1),\n", + " zmin=zmin_metal1 + thickness_metal1,\n", + " material=\"Aluminum\",\n", + " mesh_order=2,\n", + " )\n", + " metal2 = LayerLevel(\n", + " layer=LAYER.M2,\n", + " thickness=thickness_metal2,\n", + " zmin=zmin_metal2,\n", + " material=\"Aluminum\",\n", + " mesh_order=2,\n", + " )\n", + " via2 = LayerLevel(\n", + " layer=LAYER.VIA2,\n", + " thickness=zmin_metal3 - (zmin_metal2 + thickness_metal2),\n", + " zmin=zmin_metal2 + thickness_metal2,\n", + " material=\"Aluminum\",\n", + " mesh_order=1,\n", + " )\n", + " metal3 = LayerLevel(\n", + " layer=LAYER.M3,\n", + " thickness=thickness_metal3,\n", + " zmin=zmin_metal3,\n", + " material=\"Aluminum\",\n", + " mesh_order=2,\n", + " )\n", + "\n", + " return GenericLayerStack()\n", + "\n", + "\n", + "LAYER_STACK = get_layer_stack()\n", + "layer_stack220 = LAYER_STACK" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b54a90ab", + "metadata": { + "id": "b54a90ab" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "c = gf.components.straight_heater_metal(length=40)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26483a57", + "metadata": { + "id": "26483a57" + }, + "outputs": [], + "source": [ + "scene = c.to_3d(layer_stack=layer_stack220)\n", + "scene.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "055d1178", + "metadata": { + "id": "055d1178" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "c = gf.components.taper_strip_to_ridge_trenches()\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1621b195", + "metadata": { + "id": "1621b195" + }, + "outputs": [], + "source": [ + "scene = c.to_3d(layer_stack=layer_stack220)\n", + "scene.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "963f2dd9", + "metadata": { + "id": "963f2dd9" + }, + "outputs": [], + "source": [ + "# lets assume we have 900nm silicon instead of 220nm, You will see a much thicker waveguide under the metal heater.\n", + "layer_stack900 = get_layer_stack(thickness_wg=900 * nm)\n", + "scene = c.to_3d(layer_stack=layer_stack900)\n", + "scene.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f229127", + "metadata": { + "id": "1f229127" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "c = gf.components.grating_coupler_elliptical_trenches()\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42476c5c", + "metadata": { + "id": "42476c5c" + }, + "outputs": [], + "source": [ + "scene = c.to_3d()\n", + "scene.show()" + ] + }, + { + "cell_type": "markdown", + "id": "da50e315", + "metadata": { + "id": "da50e315" + }, + "source": [ + "## 3D rendering\n", + "\n", + "To render components in 3D you will need to define two things:\n", + "\n", + "1. LayerStack: for each layer contains thickness of each material and z position\n", + "2. LayerViews: for each layer contains view (color, pattern, opacity). You can load it with `gf.technology.LayerView.load_lyp()`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f67c093d", + "metadata": { + "id": "f67c093d" + }, + "outputs": [], + "source": [ + "heater = gf.components.straight_heater_metal(length=50)\n", + "heater.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01f1769a", + "metadata": { + "id": "01f1769a" + }, + "outputs": [], + "source": [ + "scene = heater.to_3d()\n", + "scene.show()" + ] + }, + { + "cell_type": "markdown", + "id": "5MPfDHSDp5Q5", + "metadata": { + "id": "5MPfDHSDp5Q5" + }, + "source": [] + }, + { + "cell_type": "markdown", + "id": "vTxLqeCBp74n", + "metadata": { + "id": "vTxLqeCBp74n" + }, + "source": [] + }, + { + "cell_type": "markdown", + "id": "3f251bfe", + "metadata": { + "id": "3f251bfe" + }, + "source": [ + "As the sequence is defined as a string you can use the string operations to easily build complex sequences" + ] + }, + { + "cell_type": "markdown", + "id": "-4-PBhwOnAC2", + "metadata": { + "id": "-4-PBhwOnAC2" + }, + "source": [] + }, + { + "cell_type": "markdown", + "id": "a899fccf", + "metadata": { + "id": "a899fccf" + }, + "source": [ + "# References and ports\n", + "\n", + "gdsfactory defines your component once in memory and can add multiple References (Instances) to the same component." + ] + }, + { + "cell_type": "markdown", + "id": "1964be47", + "metadata": { + "id": "1964be47" + }, + "source": [ + "As you build components you can include references to other components. Adding a reference is like having a pointer to a component.\n", + "\n", + "The GDSII specification allows the use of references, and similarly gdsfactory uses them (with the `add_ref()` function).\n", + "what is a reference? Simply put: **A reference does not contain any geometry. It only *points* to an existing geometry**.\n", + "\n", + "Say you have a ridiculously large polygon with 100 billion vertices that you call BigPolygon. It's huge, and you need to use it in your design 250 times.\n", + "Well, a single copy of BigPolygon takes up 1MB of memory, so you don't want to make 250 copies of it\n", + "You can instead *references* the polygon 250 times.\n", + "Each reference only uses a few bytes of memory -- it only needs to know the memory address of BigPolygon, position, rotation and mirror.\n", + "This way, you can keep one copy of BigPolygon and use it again and again.\n", + "\n", + "You can start by making a blank `Component` and add a single polygon to it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7fe5dcc", + "metadata": { + "id": "c7fe5dcc" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "from gdsfactory.generic_tech import get_generic_pdk\n", + "\n", + "gf.config.rich_output()\n", + "PDK = get_generic_pdk()\n", + "PDK.activate()\n", + "\n", + "# Create a blank Component\n", + "p = gf.Component(\"component_with_polygon\")\n", + "\n", + "# Add a polygon\n", + "xpts = [0, 0, 5, 6, 9, 12]\n", + "ypts = [0, 1, 1, 2, 2, 0]\n", + "p.add_polygon([xpts, ypts], layer=(2, 0))\n", + "\n", + "# plot the Component with the polygon in it\n", + "p.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "ec3e3272", + "metadata": { + "id": "ec3e3272" + }, + "source": [ + "Now, you want to reuse this polygon repeatedly without creating multiple copies of it.\n", + "\n", + "To do so, you need to make a second blank `Component`, this time called `c`.\n", + "\n", + "In this new Component you *reference* our Component `p` which contains our polygon." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b86841d2", + "metadata": { + "id": "b86841d2" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"Component_with_references\") # Create a new blank Component\n", + "poly_ref = c.add_ref(p) # Reference the Component \"p\" that has the polygon in it\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "b71a2bc4", + "metadata": { + "id": "b71a2bc4" + }, + "source": [ + "you just made a copy of your polygon -- but remember, you didn't actually\n", + "make a second polygon, you just made a reference (aka pointer) to the original\n", + "polygon. Let's add two more references to `c`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dffa672a", + "metadata": { + "id": "dffa672a" + }, + "outputs": [], + "source": [ + "poly_ref2 = c.add_ref(p) # Reference the Component \"p\" that has the polygon in it\n", + "poly_ref3 = c.add_ref(p) # Reference the Component \"p\" that has the polygon in it\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "ea6c483d", + "metadata": { + "id": "ea6c483d" + }, + "source": [ + "Now you have 3x polygons all on top of each other. Again, this would appear\n", + "useless, except that you can manipulate each reference independently. Notice that\n", + "when you called `c.add_ref(p)` above, we saved the result to a new variable each\n", + "time (`poly_ref`, `poly_ref2`, and `poly_ref3`)? You can use those variables to\n", + "reposition the references." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1898e38", + "metadata": { + "id": "d1898e38" + }, + "outputs": [], + "source": [ + "poly_ref2.rotate(90) # Rotate the 2nd reference we made 90 degrees\n", + "poly_ref3.rotate(180) # Rotate the 3rd reference we made 180 degrees\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "d0aa0866", + "metadata": { + "id": "d0aa0866" + }, + "source": [ + "Now you're getting somewhere! You've only had to make the polygon once, but you're\n", + "able to reuse it as many times as you want.\n", + "\n", + "## Modifying the referenced geometry\n", + "\n", + "What happens when you change the original geometry that the reference points to? In your case, your references in\n", + "`c` all point to the Component `p` that with the original polygon. Let's try\n", + "adding a second polygon to `p`.\n", + "\n", + "First you add the second polygon and make sure `P` looks like you expect:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad32843e", + "metadata": { + "id": "ad32843e" + }, + "outputs": [], + "source": [ + "# Add a 2nd polygon to \"p\"\n", + "xpts = [14, 14, 16, 16]\n", + "ypts = [0, 2, 2, 0]\n", + "p.add_polygon([xpts, ypts], layer=(1, 0))\n", + "p.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "9ad4db54", + "metadata": { + "id": "9ad4db54" + }, + "source": [ + "That looks good. Now let's find out what happened to `c` that contains the\n", + "three references. Keep in mind that you have not modified `c` or executed any\n", + "functions/operations on `c` -- all you have done is modify `p`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a598c2b7", + "metadata": { + "id": "a598c2b7" + }, + "outputs": [], + "source": [ + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "39f210b2", + "metadata": { + "id": "39f210b2" + }, + "source": [ + " **When you modify the original geometry, all of the\n", + "references automatically reflect the modifications.** This is very powerful,\n", + "because you can use this to make very complicated designs from relatively simple\n", + "elements in a computation- and memory-efficient way.\n", + "\n", + "Let's try making references a level deeper by referencing `c`. Note here we use\n", + "the `<<` operator to add the references -- this is just shorthand, and is\n", + "exactly equivalent to using `add_ref()`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb70c0c1", + "metadata": { + "id": "fb70c0c1" + }, + "outputs": [], + "source": [ + "c2 = gf.Component(\"array_sample\") # Create a new blank Component\n", + "d_ref1 = c2.add_ref(c) # Reference the Component \"c\" that 3 references in it\n", + "d_ref2 = c2 << c # Use the \"<<\" operator to create a 2nd reference to c.plot()\n", + "d_ref3 = c2 << c # Use the \"<<\" operator to create a 3rd reference to c.plot()\n", + "\n", + "d_ref1.move([20, 0])\n", + "d_ref2.move([40, 0])\n", + "\n", + "c2.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "2659e1ea", + "metadata": { + "id": "2659e1ea" + }, + "source": [ + "As you've seen you have two ways to add a reference to our component:\n", + "\n", + "1. create the reference and add it to the component" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f15dac63", + "metadata": { + "id": "f15dac63" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"reference_sample\")\n", + "w = gf.components.straight(width=0.6)\n", + "wr = w.ref()\n", + "c.add(wr)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "77717357", + "metadata": { + "id": "77717357" + }, + "source": [ + "2. or do it in a single line" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48859d0d", + "metadata": { + "id": "48859d0d" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"reference_sample_shorter_syntax\")\n", + "wr = c << gf.components.straight(width=0.6)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "1b979cd0", + "metadata": { + "id": "1b979cd0" + }, + "source": [ + "in both cases you can move the reference `wr` after created" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "617f6c64", + "metadata": { + "id": "617f6c64" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"two_references\")\n", + "wr1 = c << gf.components.straight(width=0.6)\n", + "wr2 = c << gf.components.straight(width=0.6)\n", + "wr2.movey(10)\n", + "c.add_ports(wr1.get_ports_list(), prefix=\"bot_\")\n", + "c.add_ports(wr2.get_ports_list(), prefix=\"top_\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46c06793", + "metadata": { + "id": "46c06793" + }, + "outputs": [], + "source": [ + "c.ports" + ] + }, + { + "cell_type": "markdown", + "id": "7cbbe71b", + "metadata": { + "id": "7cbbe71b" + }, + "source": [ + "You can also auto_rename ports using gdsfactory default convention, where ports are numbered clockwise starting from the bottom left" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c66d68fe", + "metadata": { + "id": "c66d68fe" + }, + "outputs": [], + "source": [ + "c.auto_rename_ports()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a3a546f", + "metadata": { + "id": "0a3a546f" + }, + "outputs": [], + "source": [ + "c.ports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aca431f4", + "metadata": { + "id": "aca431f4" + }, + "outputs": [], + "source": [ + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "6219b993", + "metadata": { + "id": "6219b993" + }, + "source": [ + "## Arrays of references\n", + "\n", + "In GDS, there's a type of structure called a \"ComponentReference\" which takes a cell and repeats it NxM times on a fixed grid spacing. For convenience, `Component` includes this functionality with the add_array() function.\n", + "Note that CellArrays are not compatible with ports (since there is no way to access/modify individual elements in a GDS cellarray)\n", + "\n", + "gdsfactory also provides with more flexible arrangement options if desired, see for example `grid()` and `packer()`.\n", + "\n", + "As well as `gf.components.array`\n", + "\n", + "Let's make a new Component and put a big array of our Component `c` in it:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "afdb6767", + "metadata": { + "id": "afdb6767" + }, + "outputs": [], + "source": [ + "c3 = gf.Component(\"array_of_references\") # Create a new blank Component\n", + "aref = c3.add_array(\n", + " c, columns=6, rows=3, spacing=[20, 15]\n", + ") # Reference the Component \"c\" 3 references in it with a 3 rows, 6 columns array\n", + "c3" + ] + }, + { + "cell_type": "markdown", + "id": "d2590c0c", + "metadata": { + "id": "d2590c0c" + }, + "source": [ + "CellArrays don't have ports and there is no way to access/modify individual elements in a GDS cellarray.\n", + "\n", + "gdsfactory provides you with similar functions in `gf.components.array` and `gf.components.array_2d`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0dff1293", + "metadata": { + "id": "0dff1293", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "c4 = gf.Component(\"demo_array\") # Create a new blank Component\n", + "aref = c4 << gf.components.array(component=c, columns=3, rows=2)\n", + "c4.add_ports(aref.get_ports_list())\n", + "c4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd0db0af", + "metadata": { + "id": "cd0db0af", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "help(gf.components.array)" + ] + }, + { + "cell_type": "markdown", + "id": "e52fd1e7", + "metadata": { + "id": "e52fd1e7", + "lines_to_next_cell": 2 + }, + "source": [ + "You can also create an array of references for periodic structures. Let's create a [Distributed Bragg Reflector](https://picwriter.readthedocs.io/en/latest/components/dbr.html)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9fd2d7ef", + "metadata": { + "id": "9fd2d7ef" + }, + "outputs": [], + "source": [ + "@gf.cell\n", + "def dbr_period(w1=0.5, w2=0.6, l1=0.2, l2=0.4, straight=gf.components.straight):\n", + " \"\"\"Return one DBR period.\"\"\"\n", + " c = gf.Component()\n", + " r1 = c << straight(length=l1, width=w1)\n", + " r2 = c << straight(length=l2, width=w2)\n", + " r2.connect(port=\"o1\", destination=r1.ports[\"o2\"])\n", + " c.add_port(\"o1\", port=r1.ports[\"o1\"])\n", + " c.add_port(\"o2\", port=r2.ports[\"o2\"])\n", + " return c\n", + "\n", + "\n", + "l1 = 0.2\n", + "l2 = 0.4\n", + "n = 3\n", + "period = dbr_period(l1=l1, l2=l2)\n", + "period.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e71f5839", + "metadata": { + "id": "e71f5839" + }, + "outputs": [], + "source": [ + "dbr = gf.Component(\"DBR\")\n", + "dbr.add_array(period, columns=n, rows=1, spacing=(l1 + l2, 100))\n", + "dbr.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "34335b79", + "metadata": { + "id": "34335b79" + }, + "source": [ + "Finally we need to add ports to the new component" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7bf3eec3", + "metadata": { + "id": "7bf3eec3" + }, + "outputs": [], + "source": [ + "p0 = dbr.add_port(\"o1\", port=period.ports[\"o1\"])\n", + "p1 = dbr.add_port(\"o2\", port=period.ports[\"o2\"])\n", + "\n", + "p1.center = [(l1 + l2) * n, 0]\n", + "dbr.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "72de73dc", + "metadata": { + "id": "72de73dc" + }, + "source": [ + "## Connect references\n", + "\n", + "We have seen that once you create a reference you can manipulate the reference to move it to a location. Here we are going to connect that reference to a port. Remember that we follow that a certain reference `source` connects to a `destination` port" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "09351861", + "metadata": { + "id": "09351861" + }, + "outputs": [], + "source": [ + "bend = gf.components.bend_circular()\n", + "bend.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88d3dc55", + "metadata": { + "id": "88d3dc55" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"sample_reference_connect\")\n", + "\n", + "mmi = c << gf.components.mmi1x2()\n", + "b = c << gf.components.bend_circular()\n", + "b.connect(\"o1\", destination=mmi.ports[\"o2\"])\n", + "\n", + "c.add_port(\"o1\", port=mmi.ports[\"o1\"])\n", + "c.add_port(\"o2\", port=b.ports[\"o2\"])\n", + "c.add_port(\"o3\", port=mmi.ports[\"o3\"])\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "3b5cb9ea", + "metadata": { + "id": "3b5cb9ea" + }, + "source": [ + "You can also access the ports directly from the references" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0fb1b2ed", + "metadata": { + "id": "0fb1b2ed" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"sample_reference_connect_simpler\")\n", + "\n", + "mmi = c << gf.components.mmi1x2()\n", + "b = c << gf.components.bend_circular()\n", + "b.connect(\"o1\", destination=mmi[\"o2\"])\n", + "\n", + "c.add_port(\"o1\", port=mmi[\"o1\"])\n", + "c.add_port(\"o2\", port=b[\"o2\"])\n", + "c.add_port(\"o3\", port=mmi[\"o3\"])\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "339cb714", + "metadata": { + "id": "339cb714" + }, + "source": [ + "Notice that `connect` mates two ports together and does not imply that ports will remain connected.\n" + ] + }, + { + "cell_type": "markdown", + "id": "0b95a477", + "metadata": { + "id": "0b95a477" + }, + "source": [ + "## Port\n", + "\n", + "You can name the ports as you want and use `gf.port.auto_rename_ports(prefix='o')` to rename them later on.\n", + "\n", + "Here is the default naming convention.\n", + "\n", + "Ports are numbered clock-wise starting from the bottom left corner.\n", + "\n", + "Optical ports have `o` prefix and Electrical ports `e` prefix.\n", + "\n", + "The port naming comes in most cases from the `gdsfactory.cross_section`. For example:\n", + "\n", + "- `gdsfactory.cross_section.strip` has ports `o1` for input and `o2` for output.\n", + "- `gdsfactory.cross_section.metal1` has ports `e1` for input and `e2` for output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6ed4ac1", + "metadata": { + "id": "e6ed4ac1" + }, + "outputs": [], + "source": [ + "size = 4\n", + "c = gf.components.nxn(west=2, south=2, north=2, east=2, xsize=size, ysize=size)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bf640ce3", + "metadata": { + "id": "bf640ce3" + }, + "outputs": [], + "source": [ + "c = gf.components.straight_heater_metal(length=30)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "adade3db", + "metadata": { + "id": "adade3db" + }, + "outputs": [], + "source": [ + "c.ports" + ] + }, + { + "cell_type": "markdown", + "id": "da96738f", + "metadata": { + "id": "da96738f" + }, + "source": [ + "You can get the optical ports by `layer`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34e988fa", + "metadata": { + "id": "34e988fa" + }, + "outputs": [], + "source": [ + "c.get_ports_dict(layer=(1, 0))" + ] + }, + { + "cell_type": "markdown", + "id": "5339aa2a", + "metadata": { + "id": "5339aa2a" + }, + "source": [ + "or by `width`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1bf81664", + "metadata": { + "id": "1bf81664" + }, + "outputs": [], + "source": [ + "c.get_ports_dict(width=0.5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "475a1ba0", + "metadata": { + "id": "475a1ba0" + }, + "outputs": [], + "source": [ + "c0 = gf.components.straight_heater_metal()\n", + "c0.ports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00325e72", + "metadata": { + "id": "00325e72" + }, + "outputs": [], + "source": [ + "c1 = c0.copy()\n", + "c1.auto_rename_ports_layer_orientation()\n", + "c1.ports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a49b679c", + "metadata": { + "id": "a49b679c" + }, + "outputs": [], + "source": [ + "c2 = c0.copy()\n", + "c2.auto_rename_ports()\n", + "c2.ports" + ] + }, + { + "cell_type": "markdown", + "id": "d3897c76", + "metadata": { + "id": "d3897c76" + }, + "source": [ + "You can also rename them with a different port naming convention\n", + "\n", + "- prefix: add `e` for electrical `o` for optical\n", + "- clockwise\n", + "- counter-clockwise\n", + "- orientation `E` East, `W` West, `N` North, `S` South\n", + "\n", + "\n", + "Here is the default one we use (clockwise starting from bottom left west facing port)\n", + "\n", + "```\n", + " 3 4\n", + " |___|_\n", + " 2 -| |- 5\n", + " | |\n", + " 1 -|______|- 6\n", + " | |\n", + " 8 7\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "381765cd", + "metadata": { + "id": "381765cd" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"demo_ports\")\n", + "nxn = gf.components.nxn(west=2, north=2, east=2, south=2, xsize=4, ysize=4)\n", + "ref = c.add_ref(nxn)\n", + "c.add_ports(ref.ports)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1805d846", + "metadata": { + "id": "1805d846" + }, + "outputs": [], + "source": [ + "ref.get_ports_list() # by default returns ports clockwise starting from bottom left west facing port" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93498f52", + "metadata": { + "id": "93498f52" + }, + "outputs": [], + "source": [ + "c.auto_rename_ports()\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "434a8597", + "metadata": { + "id": "434a8597" + }, + "source": [ + "You can also get the ports counter-clockwise\n", + "\n", + "```\n", + " 4 3\n", + " |___|_\n", + " 5 -| |- 2\n", + " | |\n", + " 6 -|______|- 1\n", + " | |\n", + " 7 8\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a89f5f01", + "metadata": { + "id": "a89f5f01" + }, + "outputs": [], + "source": [ + "c.auto_rename_ports_counter_clockwise()\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e64d75b", + "metadata": { + "id": "5e64d75b" + }, + "outputs": [], + "source": [ + "c.get_ports_list(clockwise=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1fb91cac", + "metadata": { + "id": "1fb91cac" + }, + "outputs": [], + "source": [ + "c.ports_layer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4cf2cb74", + "metadata": { + "id": "4cf2cb74" + }, + "outputs": [], + "source": [ + "c.port_by_orientation_cw(\"W0\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5aa8e2a6", + "metadata": { + "id": "5aa8e2a6" + }, + "outputs": [], + "source": [ + "c.port_by_orientation_ccw(\"W1\")" + ] + }, + { + "cell_type": "markdown", + "id": "e49a5258", + "metadata": { + "id": "e49a5258" + }, + "source": [ + "Lets extend the East facing ports (orientation = 0 deg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39a4e83b", + "metadata": { + "id": "39a4e83b" + }, + "outputs": [], + "source": [ + "cross_section = gf.cross_section.strip()\n", + "\n", + "nxn = gf.components.nxn(\n", + " west=2, north=2, east=2, south=2, xsize=4, ysize=4, cross_section=cross_section\n", + ")\n", + "c = gf.components.extension.extend_ports(component=nxn, orientation=0)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c433ace", + "metadata": { + "id": "3c433ace" + }, + "outputs": [], + "source": [ + "c.ports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ddffac7", + "metadata": { + "id": "9ddffac7" + }, + "outputs": [], + "source": [ + "df = c.get_ports_pandas()\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45d120c8", + "metadata": { + "id": "45d120c8" + }, + "outputs": [], + "source": [ + "df[df.port_type == \"optical\"]" + ] + }, + { + "cell_type": "markdown", + "id": "1115a662", + "metadata": { + "id": "1115a662" + }, + "source": [ + "## Port markers (Pins)\n", + "\n", + "You can add pins (port markers) to each port. Different foundries do this differently, so gdsfactory supports all of them.\n", + "\n", + "- square with port inside the component.\n", + "- square centered (half inside, half outside component).\n", + "- triangular pointing towards the outside of the port.\n", + "- path (SiEPIC).\n", + "\n", + "\n", + "by default Component.show() will add triangular pins, so you can see the direction of the port in Klayout." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88473af0", + "metadata": { + "id": "88473af0" + }, + "outputs": [], + "source": [ + "gf.components.mmi1x2(decorator=gf.add_pins.add_pins)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6a000a6", + "metadata": { + "id": "f6a000a6" + }, + "outputs": [], + "source": [ + "gf.components.mmi1x2(decorator=gf.add_pins.add_pins_triangle)" + ] + }, + { + "cell_type": "markdown", + "id": "8f2f6c9a", + "metadata": { + "id": "8f2f6c9a" + }, + "source": [ + "## Component_sequence\n", + "\n", + "When you have repetitive connections you can describe the connectivity as an ASCII map" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1c47676", + "metadata": { + "id": "c1c47676" + }, + "outputs": [], + "source": [ + "bend180 = gf.components.bend_circular180()\n", + "wg_pin = gf.components.straight_pin(length=40)\n", + "wg = gf.components.straight()\n", + "\n", + "# Define a map between symbols and (component, input port, output port)\n", + "symbol_to_component = {\n", + " \"D\": (bend180, \"o1\", \"o2\"),\n", + " \"C\": (bend180, \"o2\", \"o1\"),\n", + " \"P\": (wg_pin, \"o1\", \"o2\"),\n", + " \"-\": (wg, \"o1\", \"o2\"),\n", + "}\n", + "\n", + "# Generate a sequence\n", + "# This is simply a chain of characters. Each of them represents a component\n", + "# with a given input and and a given output\n", + "\n", + "sequence = \"DC-P-P-P-P-CD\"\n", + "component = gf.components.component_sequence(\n", + " sequence=sequence, symbol_to_component=symbol_to_component\n", + ")\n", + "component.name = \"component_sequence\"\n", + "component.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "461099cd", + "metadata": { + "id": "461099cd" + }, + "source": [ + "You can install the klayout klive plugin to be able to see live updates on your GDS files:" + ] + }, + { + "cell_type": "markdown", + "id": "b806af34", + "metadata": { + "id": "b806af34" + }, + "source": [ + "![KLayout package](https://i.imgur.com/IZWH6U0.png)" + ] + }, + { + "cell_type": "markdown", + "id": "0384f6f8", + "metadata": { + "id": "0384f6f8" + }, + "source": [ + "# Movement\n", + "\n", + "You can move, rotate and mirror ComponentReference as well as `Port`, `Polygon`, `ComponentReference`, `Label`, and `Group`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "153ac6d4", + "metadata": { + "id": "153ac6d4" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "from gdsfactory.generic_tech import get_generic_pdk\n", + "\n", + "gf.config.rich_output()\n", + "\n", + "PDK = get_generic_pdk()\n", + "\n", + "PDK.activate()\n", + "\n", + "# Start with a blank Component\n", + "c = gf.Component(\"demo_movement\")\n", + "\n", + "# Create some more Components with shapes\n", + "T = gf.components.text(\"hello\", size=10, layer=(1, 0))\n", + "E = gf.components.ellipse(radii=(10, 5), layer=(2, 0))\n", + "R = gf.components.rectangle(size=(10, 3), layer=(3, 0))\n", + "\n", + "# Add the shapes to D as references\n", + "text = c << T\n", + "ellipse = c << E\n", + "rect1 = c << R\n", + "rect2 = c << R\n", + "\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a07392c0", + "metadata": { + "id": "a07392c0" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"move_one_ellipse\")\n", + "e1 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))\n", + "e2 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))\n", + "e1.movex(10)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "911985e1", + "metadata": { + "id": "911985e1" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"move_one_ellipse_xmin\")\n", + "e1 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))\n", + "e2 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))\n", + "e2.xmin = e1.xmax\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "c0d211ec", + "metadata": { + "id": "c0d211ec" + }, + "source": [ + "Now you can practice move and rotate the objects." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6f1d9a89", + "metadata": { + "id": "6f1d9a89" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"two_ellipses_on_top_of_each_other\")\n", + "E = gf.components.ellipse(radii=(10, 5), layer=(2, 0))\n", + "e1 = c << E\n", + "e2 = c << E\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2c7351d", + "metadata": { + "id": "e2c7351d" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"ellipse_moved\")\n", + "e = gf.components.ellipse(radii=(10, 5), layer=(2, 0))\n", + "e1 = c << e\n", + "e2 = c << e\n", + "e2.move(origin=[5, 5], destination=[10, 10]) # Translate by dx = 5, dy = 5\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb0873c7", + "metadata": { + "id": "fb0873c7" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"ellipse_moved_v2\")\n", + "e = gf.components.ellipse(radii=(10, 5), layer=(2, 0))\n", + "e1 = c << e\n", + "e2 = c << e\n", + "e2.move([5, 5]) # Translate by dx = 5, dy = 5\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33013003", + "metadata": { + "id": "33013003" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"rectangles\")\n", + "r = gf.components.rectangle(size=(10, 5), layer=(2, 0))\n", + "rect1 = c << r\n", + "rect2 = c << r\n", + "\n", + "rect1.rotate(45) # Rotate the first straight by 45 degrees around (0,0)\n", + "rect2.rotate(\n", + " -30, center=[1, 1]\n", + ") # Rotate the second straight by -30 degrees around (1,1)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10bfe651", + "metadata": { + "id": "10bfe651" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"mirror_demo\")\n", + "text = c << gf.components.text(\"hello\")\n", + "text.mirror(p1=[1, 1], p2=[1, 3]) # Reflects across the line formed by p1 and p2\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20f97fbc", + "metadata": { + "id": "20f97fbc" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"hello\")\n", + "text = c << gf.components.text(\"hello\")\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "81f91f70", + "metadata": { + "id": "81f91f70" + }, + "source": [ + "Each Component and ComponentReference object has several properties which can be\n", + "used\n", + "to learn information about the object (for instance where it's center coordinate\n", + "is). Several of these properties can actually be used to move the geometry by\n", + "assigning them new values.\n", + "\n", + "Available properties are:\n", + "\n", + "- `xmin` / `xmax`: minimum and maximum x-values of all points within the object\n", + "- `ymin` / `ymax`: minimum and maximum y-values of all points within the object\n", + "- `x`: centerpoint between minimum and maximum x-values of all points within the\n", + "object\n", + "- `y`: centerpoint between minimum and maximum y-values of all points within the\n", + "object\n", + "- `bbox`: bounding box (see note below) in format ((xmin,ymin),(xmax,ymax))\n", + "- `center`: center of bounding box" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "254b7855", + "metadata": { + "id": "254b7855" + }, + "outputs": [], + "source": [ + "print(\"bounding box:\")\n", + "print(\n", + " text.bbox\n", + ") # Will print the bounding box of text in terms of [(xmin, ymin), (xmax, ymax)]\n", + "print(\"xsize and ysize:\")\n", + "print(text.xsize) # Will print the width of text in the x dimension\n", + "print(text.ysize) # Will print the height of text in the y dimension\n", + "print(\"center:\")\n", + "print(text.center) # Gives you the center coordinate of its bounding box\n", + "print(\"xmax\")\n", + "print(text.xmax) # Gives you the rightmost (+x) edge of the text bounding box" + ] + }, + { + "cell_type": "markdown", + "id": "3e6393de", + "metadata": { + "id": "3e6393de" + }, + "source": [ + "Let's use these properties to manipulate our shapes to arrange them a little\n", + "better" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1bc02048", + "metadata": { + "id": "1bc02048" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"canvas\")\n", + "text = c << gf.components.text(\"hello\")\n", + "E = gf.components.ellipse(radii=(10, 5), layer=(3, 0))\n", + "R = gf.components.rectangle(size=(10, 5), layer=(2, 0))\n", + "rect1 = c << R\n", + "rect2 = c << R\n", + "ellipse = c << E\n", + "\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69a64d6d", + "metadata": { + "id": "69a64d6d" + }, + "outputs": [], + "source": [ + "# First let's center the ellipse\n", + "ellipse.center = [\n", + " 0,\n", + " 0,\n", + "] # Move the ellipse such that the bounding box center is at (0,0)\n", + "\n", + "# Next, let's move the text to the left edge of the ellipse\n", + "text.y = (\n", + " ellipse.y\n", + ") # Move the text so that its y-center is equal to the y-center of the ellipse\n", + "text.xmax = ellipse.xmin # Moves the ellipse so its xmax == the ellipse's xmin\n", + "\n", + "# Align the right edge of the rectangles with the x=0 axis\n", + "rect1.xmax = 0\n", + "rect2.xmax = 0\n", + "\n", + "# Move the rectangles above and below the ellipse\n", + "rect1.ymin = ellipse.ymax + 5\n", + "rect2.ymax = ellipse.ymin - 5\n", + "\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "e2ee53ab", + "metadata": { + "id": "e2ee53ab" + }, + "source": [ + "In addition to working with the properties of the references inside the\n", + "Component,\n", + "we can also manipulate the whole Component if we want. Let's try mirroring the\n", + "whole Component `D`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84a8af68", + "metadata": { + "id": "84a8af68" + }, + "outputs": [], + "source": [ + "print(c.xmax) # Prints out '10.0'\n", + "\n", + "c2 = c.mirror((0, 1)) # Mirror across line made by (0,0) and (0,1)\n", + "c2.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "bd2d47bd", + "metadata": { + "id": "bd2d47bd" + }, + "source": [ + "A bounding box is the smallest enclosing box which contains all points of the geometry." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b339180", + "metadata": { + "id": "5b339180" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"hi_bbox\")\n", + "text = c << gf.components.text(\"hi\")\n", + "bbox = text.bbox\n", + "c << gf.components.bbox(bbox=bbox, layer=(2, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fccbfc3c", + "metadata": { + "id": "fccbfc3c" + }, + "outputs": [], + "source": [ + "# gf.get_padding_points can also add a bbox with respect to the bounding box edges\n", + "c = gf.Component(\"sample_padding\")\n", + "text = c << gf.components.text(\"bye\")\n", + "device_bbox = text.bbox\n", + "c.add_polygon(gf.get_padding_points(text, default=1), layer=(2, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "47b3064a", + "metadata": { + "id": "47b3064a" + }, + "source": [ + "When we query the properties of D, they will be calculated with respect to this\n", + "bounding-rectangle. For instance:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "389cd7a4", + "metadata": { + "id": "389cd7a4" + }, + "outputs": [], + "source": [ + "print(\"Center of Component c:\")\n", + "print(c.center)\n", + "\n", + "print(\"X-max of Component c:\")\n", + "print(c.xmax)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "505a11be", + "metadata": { + "id": "505a11be" + }, + "outputs": [], + "source": [ + "D = gf.Component(\"rect\")\n", + "R = gf.components.rectangle(size=(10, 3), layer=(2, 0))\n", + "rect1 = D << R\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "57733ef8", + "metadata": { + "id": "57733ef8" + }, + "source": [ + "You can chain many of the movement/manipulation functions because they all return the object they manipulate.\n", + "\n", + "For instance you can combine two expressions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1ded436", + "metadata": { + "id": "d1ded436" + }, + "outputs": [], + "source": [ + "rect1.rotate(angle=37)\n", + "rect1.move([10, 20])\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "0c8a7e54", + "metadata": { + "id": "0c8a7e54" + }, + "source": [ + "...into this single-line expression" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4fd28010", + "metadata": { + "id": "4fd28010" + }, + "outputs": [], + "source": [ + "D = gf.Component(\"single_expression\")\n", + "R = gf.components.rectangle(size=(10, 3), layer=(2, 0))\n", + "rect1 = D << R\n", + "rect1.rotate(angle=37).move([10, 20])\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "4WM_UvsYm_1T", + "metadata": { + "id": "4WM_UvsYm_1T" + }, + "source": [] + }, + { + "cell_type": "markdown", + "id": "i1bXu3wAnJm3", + "metadata": { + "id": "i1bXu3wAnJm3" + }, + "source": [] + }, + { + "cell_type": "markdown", + "id": "1f7b5623", + "metadata": { + "id": "1f7b5623" + }, + "source": [ + "# Cell\n", + "\n", + "A cell is a function that returns a Component.\n", + "\n", + "Make sure you add the `@cell` decorator to each function that returns a Component.\n", + "\n", + "`@cell` comes from PCell `parametric cell`, where the function returns a different Component depending on the input parameters.\n", + "\n", + "Why do we need cells?\n", + "\n", + "- In GDS each component must have a unique name. Ideally the name is also consistent from run to run, in case you want to merge GDS files that were created at different times or computers.\n", + "- Two components stored in the GDS file cannot have the same name. They need to be references (instances) of the same component. See `References tutorial`. That way we only have to store the component in memory once and all the references are just pointers to that component.\n", + "\n", + "What does the `@cell` decorator does?\n", + "\n", + "1. Gives the component a unique name depending on the parameters that you pass to it.\n", + "2. Creates a cache of components where we use the name as the key. The first time the function runs, the cache stores the component, so the second time, you get the component directly from the cache, so you don't create the same component twice.\n", + "\n", + "\n", + "A decorator is a function that runs over a function, so when you do.\n", + "\n", + "```python\n", + "@gf.cell\n", + "def mzi_with_bend():\n", + " c = gf.Component()\n", + " mzi = c << gf.components.mzi()\n", + " bend = c << gf.components.bend_euler()\n", + " return c\n", + "```\n", + "it's equivalent to\n", + "\n", + "```python\n", + "def mzi_with_bend():\n", + " c = gf.Component()\n", + " mzi = c << gf.components.mzi()\n", + " bend = c << gf.components.bend_euler(radius=radius)\n", + " return c\n", + "\n", + "\n", + "mzi_with_bend_decorated = gf.cell(mzi_with_bend)\n", + "```\n", + "\n", + "Lets see how it works." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "831747be", + "metadata": { + "id": "831747be" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "from gdsfactory.cell import print_cache\n", + "from gdsfactory.generic_tech import get_generic_pdk\n", + "\n", + "gf.config.rich_output()\n", + "\n", + "PDK = get_generic_pdk()\n", + "PDK.activate()\n", + "\n", + "\n", + "def mzi_with_bend(radius: float = 10.0) -> gf.Component:\n", + " c = gf.Component(\"Unnamed_cells_can_cause_issues\")\n", + " mzi = c << gf.components.mzi()\n", + " bend = c << gf.components.bend_euler(radius=radius)\n", + " bend.connect(\"o1\", mzi.ports[\"o2\"])\n", + " return c\n", + "\n", + "\n", + "c = mzi_with_bend()\n", + "print(f\"this cell {c.name!r} does NOT get automatic name\")\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ea3ea4d3", + "metadata": { + "id": "ea3ea4d3" + }, + "outputs": [], + "source": [ + "mzi_with_bend_decorated = gf.cell(mzi_with_bend)\n", + "c = mzi_with_bend_decorated(radius=10)\n", + "print(f\"this cell {c.name!r} gets automatic name thanks to the `cell` decorator\")\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74330d4d", + "metadata": { + "id": "74330d4d", + "lines_to_end_of_cell_marker": 2, + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "@gf.cell\n", + "def mzi_with_bend(radius: float = 10.0) -> gf.Component:\n", + " c = gf.Component()\n", + " mzi = c << gf.components.mzi()\n", + " bend = c << gf.components.bend_euler(radius=radius)\n", + " bend.connect(\"o1\", mzi.ports[\"o2\"])\n", + " return c\n", + "\n", + "\n", + "print(f\"this cell {c.name!r} gets automatic name thanks to the `cell` decorator\")\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "9cced668", + "metadata": { + "id": "9cced668" + }, + "source": [ + "# Path and CrossSection\n", + "\n", + "You can create a `Path` in gdsfactory and extrude it with an arbitrary `CrossSection`.\n", + "\n", + "Lets create a path:\n", + "\n", + "- Create a blank `Path`.\n", + "- Append points to the `Path` either using the built-in functions (`arc()`, `straight()`, `euler()` ...) or by providing your own lists of points\n", + "- Specify `CrossSection` with layers and offsets.\n", + "- Extrude `Path` with a `CrossSection` to create a Component with the path polygons in it.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16c7b67a", + "metadata": { + "id": "16c7b67a" + }, + "outputs": [], + "source": [ + "from functools import partial\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "import gdsfactory as gf\n", + "from gdsfactory.cross_section import Section\n", + "from gdsfactory.generic_tech import get_generic_pdk\n", + "\n", + "gf.config.rich_output()\n", + "PDK = get_generic_pdk()\n", + "PDK.activate()" + ] + }, + { + "cell_type": "markdown", + "id": "3d01bec8", + "metadata": { + "id": "3d01bec8" + }, + "source": [ + "## Path\n", + "\n", + "The first step is to generate the list of points we want the path to follow.\n", + "Let's start out by creating a blank `Path` and using the built-in functions to\n", + "make a few smooth turns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "827e352d", + "metadata": { + "id": "827e352d" + }, + "outputs": [], + "source": [ + "p1 = gf.path.straight(length=5)\n", + "p2 = gf.path.euler(radius=5, angle=45, p=0.5, use_eff=False)\n", + "p = p1 + p2\n", + "f = p.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0668b20c", + "metadata": { + "id": "0668b20c" + }, + "outputs": [], + "source": [ + "p1 = gf.path.straight(length=5)\n", + "p2 = gf.path.euler(radius=5, angle=45, p=0.5, use_eff=False)\n", + "p = p2 + p1\n", + "f = p.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72427613", + "metadata": { + "id": "72427613" + }, + "outputs": [], + "source": [ + "P = gf.Path()\n", + "P += gf.path.arc(radius=10, angle=90) # Circular arc\n", + "P += gf.path.straight(length=10) # Straight section\n", + "P += gf.path.euler(radius=3, angle=-90) # Euler bend (aka \"racetrack\" curve)\n", + "P += gf.path.straight(length=40)\n", + "P += gf.path.arc(radius=8, angle=-45)\n", + "P += gf.path.straight(length=10)\n", + "P += gf.path.arc(radius=8, angle=45)\n", + "P += gf.path.straight(length=10)\n", + "\n", + "f = P.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e220227", + "metadata": { + "id": "7e220227" + }, + "outputs": [], + "source": [ + "p2 = P.copy().rotate()\n", + "f = p2.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5de8dbb5", + "metadata": { + "id": "5de8dbb5" + }, + "outputs": [], + "source": [ + "P.points - p2.points" + ] + }, + { + "cell_type": "markdown", + "id": "d7a67a12", + "metadata": { + "id": "d7a67a12" + }, + "source": [ + "You can also modify our Path in the same ways as any other gdsfactory object:\n", + "\n", + "- Manipulation with `move()`, `rotate()`, `mirror()`, etc\n", + "- Accessing properties like `xmin`, `y`, `center`, `bbox`, etc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8afec04e", + "metadata": { + "id": "8afec04e" + }, + "outputs": [], + "source": [ + "P.movey(10)\n", + "P.xmin = 20\n", + "f = P.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "2182a3be", + "metadata": { + "id": "2182a3be" + }, + "source": [ + "You can also check the length of the curve with the `length()` method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "839c771a", + "metadata": { + "id": "839c771a" + }, + "outputs": [], + "source": [ + "P.length()" + ] + }, + { + "cell_type": "markdown", + "id": "5a4e6bf2", + "metadata": { + "id": "5a4e6bf2" + }, + "source": [ + "## CrossSection\n", + "\n", + "Now that you've got your path defined, the next step is to define the cross-section of the path. To do this, you can create a blank `CrossSection` and add whatever cross-sections you want to it.\n", + "You can then combine the `Path` and the `CrossSection` using the `gf.path.extrude()` function to generate a Component:\n", + "\n", + "\n", + "### Option 1: Single layer and width cross-section\n", + "\n", + "The simplest option is to just set the cross-section to be a constant width by passing a number to `extrude()` like so:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a502d743", + "metadata": { + "id": "a502d743" + }, + "outputs": [], + "source": [ + "# Extrude the Path and the CrossSection\n", + "c = gf.path.extrude(P, layer=(1, 0), width=1.5)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "e4807989", + "metadata": { + "id": "e4807989" + }, + "source": [ + "### Option 2: Linearly-varying width\n", + "\n", + "A slightly more advanced version is to make the cross-section width vary linearly from start to finish by passing a 2-element list to `extrude()` like so:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b62473fc", + "metadata": { + "id": "b62473fc" + }, + "outputs": [], + "source": [ + "# Extrude the Path and the CrossSection\n", + "c = gf.path.extrude(P, layer=(1, 0), widths=(1, 3))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "a6bc6b11", + "metadata": { + "id": "a6bc6b11" + }, + "source": [ + "### Option 3: Arbitrary Cross-section\n", + "\n", + "You can also extrude an arbitrary cross_section" + ] + }, + { + "cell_type": "markdown", + "id": "fedd19c6", + "metadata": { + "id": "fedd19c6" + }, + "source": [ + "Now, what if we want a more complicated straight? For instance, in some\n", + "photonic applications it's helpful to have a shallow etch that appears on either\n", + "side of the straight (often called a trench or sleeve). Additionally, it might be nice\n", + "to have a Port on either end of the center section so we can snap other\n", + "geometries to it. Let's try adding something like that in:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a1f7c01", + "metadata": { + "id": "0a1f7c01" + }, + "outputs": [], + "source": [ + "p = gf.path.straight()\n", + "\n", + "# Add a few \"sections\" to the cross-section\n", + "s1 = gf.Section(width=2, offset=2, layer=(2, 0))\n", + "s2 = gf.Section(width=2, offset=-2, layer=(2, 0))\n", + "x = gf.CrossSection(\n", + " width=1, offset=0, layer=(1, 0), port_names=(\"in\", \"out\"), sections=[s1, s2]\n", + ")\n", + "\n", + "c = gf.path.extrude(p, cross_section=x)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e09a503d", + "metadata": { + "id": "e09a503d" + }, + "outputs": [], + "source": [ + "p = gf.path.arc()\n", + "\n", + "# Combine the Path and the CrossSection\n", + "b = gf.path.extrude(p, cross_section=x)\n", + "b.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "bf8c2af4", + "metadata": { + "id": "bf8c2af4" + }, + "source": [ + "An arbitrary cross-section can also help place components along a path.\n", + "This component can be useful for defining wiring vias." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cdd256a3", + "metadata": { + "id": "cdd256a3" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "from gdsfactory.cross_section import ComponentAlongPath\n", + "\n", + "# Create the path\n", + "p = gf.path.straight()\n", + "p += gf.path.arc(10)\n", + "p += gf.path.straight()\n", + "\n", + "# Define a cross-section with a via\n", + "via = ComponentAlongPath(\n", + " component=gf.c.rectangle(size=(1, 1), centered=True), spacing=5, padding=2\n", + ")\n", + "x = gf.CrossSection(\n", + " width=0.5, offset=0, layer=(1, 0), port_names=(\"in\", \"out\"), vias=[via]\n", + ")\n", + "\n", + "# Combine the path with the cross-section\n", + "c = gf.path.extrude(p, cross_section=x)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "abff41ca", + "metadata": { + "id": "abff41ca" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "from gdsfactory.cross_section import ComponentAlongPath\n", + "\n", + "# Create the path\n", + "p = gf.path.straight()\n", + "p += gf.path.arc(10)\n", + "p += gf.path.straight()\n", + "\n", + "# Define a cross-section with a via\n", + "via0 = ComponentAlongPath(component=gf.c.via1(), spacing=5, padding=2, offset=0)\n", + "viap = ComponentAlongPath(component=gf.c.via1(), spacing=5, padding=2, offset=+2)\n", + "vian = ComponentAlongPath(component=gf.c.via1(), spacing=5, padding=2, offset=-2)\n", + "x = gf.CrossSection(\n", + " width=0.5,\n", + " offset=0,\n", + " layer=(1, 0),\n", + " port_names=(\"in\", \"out\"),\n", + " vias=[via0, viap, vian],\n", + ")\n", + "\n", + "# Combine the path with the cross-section\n", + "c = gf.path.extrude(p, cross_section=x)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "f54c0294", + "metadata": { + "id": "f54c0294" + }, + "source": [ + "## Building Paths quickly\n", + "\n", + "You can pass `append()` lists of path segments. This makes it easy to combine\n", + "paths very quickly. Below we show 3 examples using this functionality:\n", + "\n", + "**Example 1:** Assemble a complex path by making a list of Paths and passing it\n", + "to `append()`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a49efc48", + "metadata": { + "id": "a49efc48" + }, + "outputs": [], + "source": [ + "P = gf.Path()\n", + "\n", + "# Create the basic Path components\n", + "left_turn = gf.path.euler(radius=4, angle=90)\n", + "right_turn = gf.path.euler(radius=4, angle=-90)\n", + "straight = gf.path.straight(length=10)\n", + "\n", + "# Assemble a complex path by making list of Paths and passing it to `append()`\n", + "P.append(\n", + " [\n", + " straight,\n", + " left_turn,\n", + " straight,\n", + " right_turn,\n", + " straight,\n", + " straight,\n", + " right_turn,\n", + " left_turn,\n", + " straight,\n", + " ]\n", + ")\n", + "\n", + "f = P.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a68ad58c", + "metadata": { + "id": "a68ad58c" + }, + "outputs": [], + "source": [ + "P = (\n", + " straight\n", + " + left_turn\n", + " + straight\n", + " + right_turn\n", + " + straight\n", + " + straight\n", + " + right_turn\n", + " + left_turn\n", + " + straight\n", + ")\n", + "f = P.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "b52bafad", + "metadata": { + "id": "b52bafad" + }, + "source": [ + "**Example 2:** Create an \"S-turn\" just by making a list of `[left_turn,\n", + "right_turn]`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5007e6b9", + "metadata": { + "id": "5007e6b9" + }, + "outputs": [], + "source": [ + "P = gf.Path()\n", + "\n", + "# Create an \"S-turn\" just by making a list\n", + "s_turn = [left_turn, right_turn]\n", + "\n", + "P.append(s_turn)\n", + "f = P.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "e0d9a917", + "metadata": { + "id": "e0d9a917" + }, + "source": [ + "**Example 3:** Repeat the S-turn 3 times by nesting our S-turn list in another\n", + "list" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec4f37e4", + "metadata": { + "id": "ec4f37e4" + }, + "outputs": [], + "source": [ + "P = gf.Path()\n", + "\n", + "# Create an \"S-turn\" using a list\n", + "s_turn = [left_turn, right_turn]\n", + "\n", + "# Repeat the S-turn 3 times by nesting our S-turn list 3x times in another list\n", + "triple_s_turn = [s_turn, s_turn, s_turn]\n", + "\n", + "P.append(triple_s_turn)\n", + "f = P.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "e549b763", + "metadata": { + "id": "e549b763" + }, + "source": [ + "Note you can also use the Path() constructor to immediately construct your Path:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2faddafb", + "metadata": { + "id": "2faddafb" + }, + "outputs": [], + "source": [ + "P = gf.Path([straight, left_turn, straight, right_turn, straight])\n", + "f = P.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "0a366377", + "metadata": { + "id": "0a366377" + }, + "source": [ + "## Waypoint smooth paths\n", + "\n", + "You can also build smooth paths between waypoints with the `smooth()` function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0a3d215", + "metadata": { + "id": "c0a3d215" + }, + "outputs": [], + "source": [ + "points = np.array([(20, 10), (40, 10), (20, 40), (50, 40), (50, 20), (70, 20)])\n", + "plt.plot(points[:, 0], points[:, 1], \".-\")\n", + "plt.axis(\"equal\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ac3e8ab", + "metadata": { + "id": "7ac3e8ab" + }, + "outputs": [], + "source": [ + "points = np.array([(20, 10), (40, 10), (20, 40), (50, 40), (50, 20), (70, 20)])\n", + "\n", + "P = gf.path.smooth(\n", + " points=points,\n", + " radius=2,\n", + " bend=gf.path.euler, # Alternatively, use pp.arc\n", + " use_eff=False,\n", + ")\n", + "f = P.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "c38ad2de", + "metadata": { + "id": "c38ad2de" + }, + "source": [ + "## Waypoint sharp paths\n", + "\n", + "It's also possible to make more traditional angular paths (e.g. electrical wires) in a few different ways.\n", + "\n", + "**Example 1:** Using a simple list of points" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f600bf32", + "metadata": { + "id": "f600bf32" + }, + "outputs": [], + "source": [ + "P = gf.Path([(20, 10), (30, 10), (40, 30), (50, 30), (50, 20), (70, 20)])\n", + "f = P.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "a59758b9", + "metadata": { + "id": "a59758b9" + }, + "source": [ + "**Example 2:** Using the \"turn and move\" method, where you manipulate the end angle of the Path so that when you append points to it, they're in the correct direction. *Note: It is crucial that the number of points per straight section is set to 2 (`gf.path.straight(length, num_pts = 2)`) otherwise the extrusion algorithm will show defects.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a207e813", + "metadata": { + "id": "a207e813" + }, + "outputs": [], + "source": [ + "P = gf.Path()\n", + "P += gf.path.straight(length=10, npoints=2)\n", + "P.end_angle += 90 # \"Turn\" 90 deg (left)\n", + "P += gf.path.straight(length=10, npoints=2) # \"Walk\" length of 10\n", + "P.end_angle += -135 # \"Turn\" -135 degrees (right)\n", + "P += gf.path.straight(length=15, npoints=2) # \"Walk\" length of 10\n", + "P.end_angle = 0 # Force the direction to be 0 degrees\n", + "P += gf.path.straight(length=10, npoints=2) # \"Walk\" length of 10\n", + "f = P.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57aed261", + "metadata": { + "id": "57aed261", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "s1 = gf.Section(width=1.5, offset=2.5, layer=(2, 0))\n", + "s2 = gf.Section(width=1.5, offset=-2.5, layer=(3, 0))\n", + "X = gf.CrossSection(width=1, offset=0, layer=(1, 0), sections=[s1, s2])\n", + "component = gf.path.extrude(P, X)\n", + "component.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "1bec4c0b", + "metadata": { + "id": "1bec4c0b", + "lines_to_next_cell": 2 + }, + "source": [ + "## Custom curves\n", + "\n", + "Now let's have some fun and try to make a loop-de-loop structure with parallel\n", + "straights and several Ports.\n", + "\n", + "To create a new type of curve we simply make a function that produces an array\n", + "of points. The best way to do that is to create a function which allows you to\n", + "specify a large number of points along that curve -- in the case below, the\n", + "`looploop()` function outputs 1000 points along a looping path. Later, if we\n", + "want reduce the number of points in our geometry we can trivially `simplify` the\n", + "path.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a33ddab", + "metadata": { + "id": "7a33ddab" + }, + "outputs": [], + "source": [ + "def looploop(num_pts=1000):\n", + " \"\"\"Simple limacon looping curve\"\"\"\n", + " t = np.linspace(-np.pi, 0, num_pts)\n", + " r = 20 + 25 * np.sin(t)\n", + " x = r * np.cos(t)\n", + " y = r * np.sin(t)\n", + " return np.array((x, y)).T\n", + "\n", + "\n", + "# Create the path points\n", + "P = gf.Path()\n", + "P.append(gf.path.arc(radius=10, angle=90))\n", + "P.append(gf.path.straight())\n", + "P.append(gf.path.arc(radius=5, angle=-90))\n", + "P.append(looploop(num_pts=1000))\n", + "P.rotate(-45)\n", + "\n", + "# Create the crosssection\n", + "s1 = gf.Section(width=0.5, offset=2, layer=(2, 0))\n", + "s2 = gf.Section(width=0.5, offset=4, layer=(3, 0))\n", + "s3 = gf.Section(width=1, offset=0, layer=(4, 0))\n", + "X = gf.CrossSection(\n", + " width=1.5, offset=0, layer=(1, 0), port_names=[\"in\", \"out\"], sections=[s1, s2, s3]\n", + ")\n", + "\n", + "c = gf.path.extrude(P, X)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "e97259b0", + "metadata": { + "id": "e97259b0" + }, + "source": [ + "You can create Paths from any array of points -- just be sure that they form\n", + "smooth curves! If we examine our path `P` we can see that all we've simply\n", + "created a long list of points:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5fb9bdad", + "metadata": { + "id": "5fb9bdad" + }, + "outputs": [], + "source": [ + "path_points = P.points # Curve points are stored as a numpy array in P.points\n", + "print(np.shape(path_points)) # The shape of the array is Nx2\n", + "print(len(P)) # Equivalently, use len(P) to see how many points are inside" + ] + }, + { + "cell_type": "markdown", + "id": "ccdffd91", + "metadata": { + "id": "ccdffd91" + }, + "source": [ + "# Shapes and generic cells\n", + "\n", + "gdsfactory provides some generic parametric cells in `gf.components` that you can customize for your application." + ] + }, + { + "cell_type": "markdown", + "id": "d69cce15", + "metadata": { + "id": "d69cce15" + }, + "source": [ + "## Basic shapes" + ] + }, + { + "cell_type": "markdown", + "id": "a3a6990f", + "metadata": { + "id": "a3a6990f" + }, + "source": [ + "### Rectangle\n", + "\n", + "To create a simple rectangle, there are two functions:\n", + "\n", + "``gf.components.rectangle()`` can create a basic rectangle:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35de5398", + "metadata": { + "id": "35de5398" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "from gdsfactory.generic_tech import get_generic_pdk\n", + "\n", + "gf.config.rich_output()\n", + "\n", + "PDK = get_generic_pdk()\n", + "PDK.activate()\n", + "\n", + "r1 = gf.components.rectangle(size=(4.5, 2), layer=(1, 0))\n", + "r1.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "bf3d5854", + "metadata": { + "id": "bf3d5854" + }, + "source": [ + "``gf.components.bbox()`` can also create a rectangle based on a bounding box.\n", + "This is useful if you want to create a rectangle which exactly surrounds a piece of existing geometry.\n", + "For example, if we have an arc geometry and we want to define a box around it, we can use ``gf.components.bbox()``:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3be8e4fe", + "metadata": { + "id": "3be8e4fe" + }, + "outputs": [], + "source": [ + "c = gf.Component()\n", + "arc = c << gf.components.bend_circular(radius=10, width=0.5, angle=90, layer=(1, 0))\n", + "arc.rotate(90)\n", + "# Draw a rectangle around the arc we created by using the arc's bounding box\n", + "rect = c << gf.components.bbox(bbox=arc.bbox, layer=(0, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "a0ab8177", + "metadata": { + "id": "a0ab8177" + }, + "source": [ + "### Cross\n", + "\n", + "The ``gf.components.cross()`` function creates a cross structure:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "781466d3", + "metadata": { + "id": "781466d3" + }, + "outputs": [], + "source": [ + "gf.components.cross(length=10, width=0.5, layer=(1, 0))" + ] + }, + { + "cell_type": "markdown", + "id": "6377f034", + "metadata": { + "id": "6377f034" + }, + "source": [ + "### Ellipse\n", + "\n", + "The ``gf.components.ellipse()`` function creates an ellipse by defining the major and minor radii:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b013a01", + "metadata": { + "id": "9b013a01" + }, + "outputs": [], + "source": [ + "c = gf.components.ellipse(radii=(10, 5), angle_resolution=2.5, layer=(1, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "0ae228b9", + "metadata": { + "id": "0ae228b9" + }, + "source": [ + "### Circle\n", + "\n", + "The ``gf.components.circle()`` function creates a circle:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bce14d03", + "metadata": { + "id": "bce14d03" + }, + "outputs": [], + "source": [ + "c = gf.components.circle(radius=10, angle_resolution=2.5, layer=(1, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "0271befb", + "metadata": { + "id": "0271befb" + }, + "source": [ + "### Ring\n", + "\n", + "The ``gf.components.ring()`` function creates a ring. The radius refers to the center radius of the ring structure (halfway between the inner and outer radius)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4eca3bf6", + "metadata": { + "id": "4eca3bf6" + }, + "outputs": [], + "source": [ + "c = gf.components.ring(radius=5, width=0.5, angle_resolution=2.5, layer=(1, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2c4b9e5", + "metadata": { + "id": "a2c4b9e5" + }, + "outputs": [], + "source": [ + "c = gf.components.ring_single(\n", + " width=0.5, gap=0.2, radius=10, length_x=4, length_y=2, layer=(1, 0)\n", + ")\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9073db49", + "metadata": { + "id": "9073db49" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "c = gf.components.ring_double(\n", + " width=0.5, gap=0.2, radius=10, length_x=4, length_y=2, layer=(1, 0)\n", + ")\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2722a19c", + "metadata": { + "id": "2722a19c" + }, + "outputs": [], + "source": [ + "c = gf.components.ring_double(\n", + " width=0.5,\n", + " gap=0.2,\n", + " radius=10,\n", + " length_x=4,\n", + " length_y=2,\n", + " layer=(1, 0),\n", + " bend=gf.components.bend_circular,\n", + ")\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "4cba1a0a", + "metadata": { + "id": "4cba1a0a" + }, + "source": [ + "### Bend circular\n", + "\n", + "The ``gf.components.bend_circular()`` function creates an arc. The radius refers to the center radius of the arc (halfway between the inner and outer radius)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e57add3", + "metadata": { + "id": "2e57add3" + }, + "outputs": [], + "source": [ + "c = gf.components.bend_circular(radius=2.0, width=0.5, angle=90, npoints=720, layer=(1, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "f81055a6", + "metadata": { + "id": "f81055a6" + }, + "source": [ + "### Bend euler\n", + "\n", + "The ``gf.components.bend_euler()`` function creates an adiabatic bend in which the bend radius changes gradually. Euler bends have lower loss than circular bends.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd29182f", + "metadata": { + "id": "dd29182f" + }, + "outputs": [], + "source": [ + "c = gf.components.bend_euler(radius=2.0, width=0.5, angle=90, npoints=720, layer=(1, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "52fe4c69", + "metadata": { + "id": "52fe4c69" + }, + "source": [ + "### Tapers\n", + "\n", + "`gf.components.taper()`is defined by setting its length and its start and end length. It has two ports, ``1`` and ``2``, on either end, allowing you to easily connect it to other structures." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "993424a6", + "metadata": { + "id": "993424a6" + }, + "outputs": [], + "source": [ + "c = gf.components.taper(length=10, width1=6, width2=4, port=None, layer=(1, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "9fefcf92", + "metadata": { + "id": "9fefcf92" + }, + "source": [ + "`gf.components.ramp()` is a structure is similar to `taper()` except it is asymmetric. It also has two ports, ``1`` and ``2``, on either end." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8351e44", + "metadata": { + "id": "a8351e44" + }, + "outputs": [], + "source": [ + "c = gf.components.ramp(length=10, width1=4, width2=8, layer=(1, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "ae94418c", + "metadata": { + "id": "ae94418c" + }, + "source": [ + "### Common compound shapes" + ] + }, + { + "cell_type": "markdown", + "id": "7394e7b4", + "metadata": { + "id": "7394e7b4" + }, + "source": [ + "The `gf.components.L()` function creates a \"L\" shape with ports on either end named ``1`` and ``2``." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54765670", + "metadata": { + "id": "54765670" + }, + "outputs": [], + "source": [ + "c = gf.components.L(width=7, size=(10, 20), layer=(1, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "273120ed", + "metadata": { + "id": "273120ed" + }, + "source": [ + "The `gf.components.C()` function creates a \"C\" shape with ports on either end named ``1`` and ``2``." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e4021d9", + "metadata": { + "id": "4e4021d9" + }, + "outputs": [], + "source": [ + "c = gf.components.C(width=7, size=(10, 20), layer=(1, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "6318543b", + "metadata": { + "id": "6318543b" + }, + "source": [ + "## Text\n", + "\n", + "Gdsfactory has an implementation of the DEPLOF font with the majority of english ASCII characters represented (thanks to phidl)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ed6c2e3", + "metadata": { + "id": "3ed6c2e3" + }, + "outputs": [], + "source": [ + "c = gf.components.text(\n", + " text=\"Hello world!\\nMultiline text\\nLeft-justified\",\n", + " size=10,\n", + " justify=\"left\",\n", + " layer=(1, 0),\n", + ")\n", + "# `justify` should be either 'left', 'center', or 'right'\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "bb3bc9e6", + "metadata": { + "id": "bb3bc9e6" + }, + "source": [ + "## Lithography structures\n", + "\n", + "### Step-resolution\n", + "\n", + "The `gf.components.litho_steps()` function creates lithographic test structure that is useful for measuring resolution of photoresist or electron-beam resists. It provides both positive-tone and negative-tone resolution tests." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95bc0330", + "metadata": { + "id": "95bc0330" + }, + "outputs": [], + "source": [ + "D = gf.components.litho_steps(\n", + " line_widths=[1, 2, 4, 8, 16], line_spacing=10, height=100, layer=(1, 0)\n", + ")\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "a5a831a2", + "metadata": { + "id": "a5a831a2" + }, + "source": [ + "### Calipers (inter-layer alignment)" + ] + }, + { + "cell_type": "markdown", + "id": "c8e7923b", + "metadata": { + "id": "c8e7923b" + }, + "source": [ + "The `gf.components.litho_calipers()` function is used to detect offsets in multilayer fabrication. It creates a two sets of notches on different layers. When an fabrication error/offset occurs, it is easy to detect how much the offset is because both center-notches are no longer aligned." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd5f2da9", + "metadata": { + "id": "cd5f2da9" + }, + "outputs": [], + "source": [ + "D = gf.components.litho_calipers(\n", + " notch_size=[1, 5],\n", + " notch_spacing=2,\n", + " num_notches=7,\n", + " offset_per_notch=0.1,\n", + " row_spacing=0,\n", + " layer1=(1, 0),\n", + " layer2=(2, 0),\n", + ")\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "7de695e2", + "metadata": { + "id": "7de695e2" + }, + "source": [ + "## Paths\n", + "\n", + "See **Path tutorial** for more details -- this is just an enumeration of the available built-in Path functions" + ] + }, + { + "cell_type": "markdown", + "id": "3ca391bf", + "metadata": { + "id": "3ca391bf" + }, + "source": [ + "### Circular arc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d53acc4b", + "metadata": { + "id": "d53acc4b" + }, + "outputs": [], + "source": [ + "P = gf.path.arc(radius=10, angle=135, npoints=720)\n", + "f = P.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "bf27793e", + "metadata": { + "id": "bf27793e" + }, + "source": [ + "### Straight" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4de59917", + "metadata": { + "id": "4de59917" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "P = gf.path.straight(length=5, npoints=100)\n", + "f = P.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "01c6d976", + "metadata": { + "id": "01c6d976" + }, + "source": [ + "### Euler curve\n", + "\n", + "Also known as a straight-to-bend, clothoid, racetrack, or track transition, this Path tapers adiabatically from straight to curved. Often used to minimize losses in photonic straights. If `p < 1.0`, will create a \"partial euler\" curve as described in Vogelbacher et. al. https://dx.doi.org/10.1364/oe.27.031394. If the `use_eff` argument is false, `radius` corresponds to minimum radius of curvature of the bend. If `use_eff` is true, `radius` corresponds to the \"effective\" radius of the bend-- The curve will be scaled such that the endpoints match an arc with parameters `radius` and `angle`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df25d2ce", + "metadata": { + "id": "df25d2ce" + }, + "outputs": [], + "source": [ + "P = gf.path.euler(radius=3, angle=90, p=1.0, use_eff=False, npoints=720)\n", + "f = P.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "6b2b4fee", + "metadata": { + "id": "6b2b4fee" + }, + "source": [ + "### Smooth path from waypoints" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94beafe2", + "metadata": { + "id": "94beafe2" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import gdsfactory as gf\n", + "\n", + "points = np.array([(20, 10), (40, 10), (20, 40), (50, 40), (50, 20), (70, 20)])\n", + "\n", + "P = gf.path.smooth(\n", + " points=points,\n", + " radius=2,\n", + " bend=gf.path.euler,\n", + " use_eff=False,\n", + ")\n", + "f = P.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "ab4dd7c9", + "metadata": { + "id": "ab4dd7c9" + }, + "source": [ + "### Delay spiral" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7791f5f1", + "metadata": { + "id": "7791f5f1" + }, + "outputs": [], + "source": [ + "c = gf.components.spiral_double()\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9deb4072", + "metadata": { + "id": "9deb4072" + }, + "outputs": [], + "source": [ + "c = gf.components.spiral_inner_io()\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad599864", + "metadata": { + "id": "ad599864" + }, + "outputs": [], + "source": [ + "c = gf.components.spiral_external_io()\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "0899df44", + "metadata": { + "id": "0899df44" + }, + "source": [ + "## Useful contact pads / connectors\n", + "\n", + "These functions are common shapes with ports, often used to make contact pads" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "711bbf42", + "metadata": { + "id": "711bbf42" + }, + "outputs": [], + "source": [ + "c = gf.components.compass(size=(4, 2), layer=(1, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52f75bf1", + "metadata": { + "id": "52f75bf1" + }, + "outputs": [], + "source": [ + "c = gf.components.nxn(north=3, south=4, east=0, west=0)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a2e468a", + "metadata": { + "id": "4a2e468a" + }, + "outputs": [], + "source": [ + "c = gf.components.pad()\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "103efdad", + "metadata": { + "id": "103efdad" + }, + "outputs": [], + "source": [ + "c = gf.components.pad_array90(columns=3)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "bda78006", + "metadata": { + "id": "bda78006" + }, + "source": [ + "## Chip / die template" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20bfd039", + "metadata": { + "id": "20bfd039" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "D = gf.components.die(\n", + " size=(10000, 5000), # Size of die\n", + " street_width=100, # Width of corner marks for die-sawing\n", + " street_length=1000, # Length of corner marks for die-sawing\n", + " die_name=\"chip99\", # Label text\n", + " text_size=500, # Label text size\n", + " text_location=\"SW\", # Label text compass location e.g. 'S', 'SE', 'SW'\n", + " layer=(2, 0),\n", + " bbox_layer=(3, 0),\n", + ")\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "9b3ef85e", + "metadata": { + "id": "9b3ef85e" + }, + "source": [ + "## Optimal superconducting curves\n", + "\n", + "The following structures are meant to reduce \"current crowding\" in superconducting thin-film structures (such as superconducting nanowires).\n", + "They are the result of conformal mapping equations derived in Clem, J. & Berggren, K. \"[Geometry-dependent critical currents in superconducting nanocircuits.\" Phys. Rev. B 84, 1–27 (2011).](http://dx.doi.org/10.1103/PhysRevB.84.174510)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee8e5b79", + "metadata": { + "id": "ee8e5b79" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "c = gf.components.optimal_hairpin(\n", + " width=0.2, pitch=0.6, length=10, turn_ratio=4, num_pts=50, layer=(2, 0)\n", + ")\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f8a2a57", + "metadata": { + "id": "9f8a2a57" + }, + "outputs": [], + "source": [ + "c = gf.c.optimal_step(\n", + " start_width=10,\n", + " end_width=22,\n", + " num_pts=50,\n", + " width_tol=1e-3,\n", + " anticrowding_factor=1.2,\n", + " symmetric=False,\n", + " layer=(2, 0),\n", + ")\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51bd9c6c", + "metadata": { + "id": "51bd9c6c" + }, + "outputs": [], + "source": [ + "c = gf.c.optimal_90deg(width=100.0, num_pts=15, length_adjust=1, layer=(2, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04ca11ee", + "metadata": { + "id": "04ca11ee" + }, + "outputs": [], + "source": [ + "c = gf.c.snspd(\n", + " wire_width=0.2,\n", + " wire_pitch=0.6,\n", + " size=(10, 8),\n", + " num_squares=None,\n", + " turn_ratio=4,\n", + " terminals_same_side=False,\n", + " layer=(2, 0),\n", + ")\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "d0ceca27", + "metadata": { + "id": "d0ceca27" + }, + "source": [ + "# Geometry\n", + "\n", + "gdsfactory provides you with some geometric functions" + ] + }, + { + "cell_type": "markdown", + "id": "ce6fb437", + "metadata": { + "id": "ce6fb437" + }, + "source": [ + "## Boolean / outline / offset / invert\n", + "There are several common boolean-type operations available in the geometry library. These include typical boolean operations (and/or/not/xor), offsetting (expanding/shrinking polygons), outlining, and inverting." + ] + }, + { + "cell_type": "markdown", + "id": "be279223", + "metadata": { + "id": "be279223" + }, + "source": [ + "### Boolean\n", + "\n", + "\n", + "The ``gf.geometry.boolean()`` function can perform AND/OR/NOT/XOR operations, and will return a new geometry with the result of that operation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "X_h4y-4YrvVd", + "metadata": { + "id": "X_h4y-4YrvVd" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "E = gf.components.ellipse(radii=(10, 5), layer=(1, 0))\n", + "R = gf.components.rectangle(size=[15, 5], layer=(2, 0))\n", + "C = gf.geometry.boolean(A=E, B=R, operation=\"not\", precision=1e-6, layer=(3, 0))\n", + "# Other operations include 'and', 'or', 'xor', or equivalently 'A-B', 'B-A', 'A+B'\n", + "\n", + "# Plot the originals and the result\n", + "D = gf.Component(\"bool\")\n", + "D.add_ref(E)\n", + "D.add_ref(R).movey(-1.5)\n", + "D.add_ref(C).movex(30)\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "708b681a", + "metadata": { + "id": "708b681a" + }, + "source": [ + "To learn how booleans work you can try all the different operations `not`, `and`, `or`, `xor`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c4cc4d0e", + "metadata": { + "id": "c4cc4d0e" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "operation = \"not\"\n", + "operation = \"and\"\n", + "operation = \"or\"\n", + "operation = \"xor\"\n", + "\n", + "r1 = (8, 8)\n", + "r2 = (11, 4)\n", + "r1 = (80, 80)\n", + "r2 = (110, 40)\n", + "\n", + "angle_resolution = 0.1\n", + "\n", + "c1 = gf.components.ellipse(radii=r1, layer=(1, 0), angle_resolution=angle_resolution)\n", + "c2 = gf.components.ellipse(radii=r2, layer=(1, 0), angle_resolution=angle_resolution)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87e2c1b1", + "metadata": { + "id": "87e2c1b1" + }, + "outputs": [], + "source": [ + "%time\n", + "\n", + "c3 = gf.geometry.boolean_klayout(\n", + " c1, c2, operation=operation, layer1=(1, 0), layer2=(1, 0), layer3=(1, 0)\n", + ") # KLayout booleans\n", + "c3.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30de3cc1", + "metadata": { + "id": "30de3cc1" + }, + "outputs": [], + "source": [ + "%time\n", + "c4 = gf.geometry.boolean(c1, c2, operation=operation)\n", + "c4.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "a2fa5a25", + "metadata": { + "id": "a2fa5a25" + }, + "source": [ + "### Offset\n", + "\n", + "The ``offset()`` function takes the polygons of the input geometry, combines them together, and expands/contracts them.\n", + "The function returns polygons on a single layer and does not respect layers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c63ac1b9", + "metadata": { + "id": "c63ac1b9" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "# Create `T`, an ellipse and rectangle which will be offset (expanded / contracted)\n", + "T = gf.Component(\"ellipse_and_rectangle\")\n", + "e = T << gf.components.ellipse(radii=(10, 5), layer=(1, 0))\n", + "r = T << gf.components.rectangle(size=[15, 5], layer=(2, 0))\n", + "r.move([3, -2.5])\n", + "\n", + "Texpanded = gf.geometry.offset(T, distance=2, precision=1e-6, layer=(2, 0))\n", + "Texpanded.name = \"expanded\"\n", + "Tshrink = gf.geometry.offset(T, distance=-1.5, precision=1e-6, layer=(2, 0))\n", + "Tshrink.name = \"shrink\"\n", + "\n", + "# Plot the original geometry, the expanded, and the shrunk versions\n", + "offsets = gf.Component(\"top\")\n", + "t1 = offsets.add_ref(T)\n", + "t2 = offsets.add_ref(Texpanded)\n", + "t3 = offsets.add_ref(Tshrink)\n", + "offsets.distribute([t1, t2, t3], direction=\"x\", spacing=5)\n", + "offsets.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "dfc0cfe2", + "metadata": { + "id": "dfc0cfe2" + }, + "source": [ + "`gf.geometry.offset` is also useful for remove acute angle DRC errors.\n", + "\n", + "You can do a positive offset to grow the polygons followed by a negative offset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bf3f6452", + "metadata": { + "id": "bf3f6452", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "c = gf.Component(\"demo_dataprep\")\n", + "c1 = gf.components.coupler_ring(cladding_layers=[(2, 0)], cladding_offsets=[0.5])\n", + "d = 0.8\n", + "c2 = gf.geometry.offset(c1, distance=+d, layer=(2, 0))\n", + "c3 = gf.geometry.offset(c2, distance=-d, layer=(2, 0))\n", + "c << c1\n", + "c << c3\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "e2a63141", + "metadata": { + "id": "e2a63141" + }, + "source": [ + "### Outline\n", + "\n", + "The ``outline()`` function takes the polygons of the input geometry then performs an offset and \"not\" boolean operation to create an outline. The function returns polygons on a single layer -- it does not respect layers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c12944e0", + "metadata": { + "id": "c12944e0" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "# Create a blank device and add two shapes\n", + "X = gf.Component(\"outline_demo\")\n", + "X.add_ref(gf.components.cross(length=25, width=1, layer=(1, 0)))\n", + "X.add_ref(gf.components.ellipse(radii=[10, 5], layer=(2, 0)))\n", + "\n", + "O = gf.geometry.outline(X, distance=1.5, precision=1e-6, layer=(3, 0))\n", + "\n", + "# Plot the original geometry and the result\n", + "c = gf.Component(\"outline_compare\")\n", + "c.add_ref(X)\n", + "c.add_ref(O).movex(30)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "43b7bb15", + "metadata": { + "id": "43b7bb15" + }, + "source": [ + "The ``open_ports`` argument opens holes in the outlined geometry at each Port location.\n", + "\n", + "- If not False, holes will be cut in the outline such that the Ports are not covered.\n", + "- If True, the holes will have the same width as the Ports.\n", + "- If a float, the holes will be widened by that value.\n", + "- If a float equal to the outline ``distance``, the outline will be flush with the port (useful positive-tone processes)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d214535", + "metadata": { + "id": "3d214535" + }, + "outputs": [], + "source": [ + "c = gf.components.L(width=7, size=(10, 20), layer=(1, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36907613", + "metadata": { + "id": "36907613" + }, + "outputs": [], + "source": [ + "# Outline the geometry and open a hole at each port\n", + "c = gf.geometry.outline(offsets, distance=5, open_ports=False, layer=(2, 0)) # No holes\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9ac805d", + "metadata": { + "id": "f9ac805d" + }, + "outputs": [], + "source": [ + "c = gf.geometry.outline(\n", + " offsets, distance=5, open_ports=True, layer=(2, 0)\n", + ") # Hole is the same width as the port\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b03aa4b", + "metadata": { + "id": "2b03aa4b" + }, + "outputs": [], + "source": [ + "c = gf.geometry.outline(\n", + " offsets, distance=5, open_ports=10, layer=(2, 0)\n", + ") # Change the hole size by entering a float\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02433196", + "metadata": { + "id": "02433196" + }, + "outputs": [], + "source": [ + "c = gf.geometry.outline(\n", + " offsets, distance=5, open_ports=5, layer=(2, 0)\n", + ") # Creates flush opening (open_ports > distance)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "d5d1b5b2", + "metadata": { + "id": "d5d1b5b2" + }, + "source": [ + "### Invert\n", + "\n", + "The ``gf.boolean.invert()`` function creates an inverted version of the input geometry. The function creates a rectangle around the geometry (with extra padding of distance ``border``), then subtract all polygons from all layers from that rectangle, resulting in an inverted version of the geometry." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33950ca5", + "metadata": { + "id": "33950ca5" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "E = gf.components.ellipse(radii=(10, 5))\n", + "D = gf.geometry.invert(E, border=0.5, precision=1e-6, layer=(2, 0))\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "11d3ab04", + "metadata": { + "id": "11d3ab04" + }, + "source": [ + "### Union\n", + "\n", + "The ``union()`` function is a \"join\" function, and is functionally identical to the \"OR\" operation of ``gf.boolean()``. The one difference is it's able to perform this function layer-wise, so each layer can be individually combined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ef38cfd", + "metadata": { + "id": "8ef38cfd" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "D = gf.Component(\"union\")\n", + "e0 = D << gf.components.ellipse(layer=(1, 0))\n", + "e1 = D << gf.components.ellipse(layer=(2, 0))\n", + "e2 = D << gf.components.ellipse(layer=(3, 0))\n", + "e3 = D << gf.components.ellipse(layer=(4, 0))\n", + "e4 = D << gf.components.ellipse(layer=(5, 0))\n", + "e5 = D << gf.components.ellipse(layer=(6, 0))\n", + "\n", + "e1.rotate(15 * 1)\n", + "e2.rotate(15 * 2)\n", + "e3.rotate(15 * 3)\n", + "e4.rotate(15 * 4)\n", + "e5.rotate(15 * 5)\n", + "\n", + "D.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f46c3b8a", + "metadata": { + "id": "f46c3b8a" + }, + "outputs": [], + "source": [ + "# We have two options to unioning - take all polygons, regardless of\n", + "# layer, and join them together (in this case on layer (2,0) like so:\n", + "D_joined = gf.geometry.union(D, by_layer=False, layer=(2, 0))\n", + "D_joined.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "199da361", + "metadata": { + "id": "199da361" + }, + "outputs": [], + "source": [ + "# Or we can perform the union operate by-layer\n", + "D_joined_by_layer = gf.geometry.union(D, by_layer=True)\n", + "D_joined_by_layer.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "875b9b52", + "metadata": { + "id": "875b9b52" + }, + "source": [ + "### XOR / diff\n", + "\n", + "The ``xor_diff()`` function can be used to compare two geometries and identify where they are different. Specifically, it performs a layer-wise XOR operation. If two geometries are identical, the result will be an empty Component. If they are not identical, any areas not shared by the two geometries will remain." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c7c0adc", + "metadata": { + "id": "2c7c0adc" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "A = gf.Component(\"A\")\n", + "A.add_ref(gf.components.ellipse(radii=[10, 5], layer=(1, 0)))\n", + "A.add_ref(gf.components.text(\"A\")).move([3, 0])\n", + "\n", + "B = gf.Component(\"B\")\n", + "B.add_ref(gf.components.ellipse(radii=[11, 4], layer=(1, 0))).movex(4)\n", + "B.add_ref(gf.components.text(\"B\")).move([3.2, 0])\n", + "X = gf.geometry.xor_diff(A=A, B=B, precision=1e-6)\n", + "\n", + "# Plot the original geometry and the result\n", + "# Upper left: A / Upper right: B\n", + "# Lower left: A and B / Lower right: A xor B \"diff\" comparison\n", + "D = gf.Component(\"xor_diff\")\n", + "D.add_ref(A).move([-15, 25])\n", + "D.add_ref(B).move([15, 25])\n", + "D.add_ref(A).movex(-15)\n", + "D.add_ref(B).movex(-15)\n", + "D.add_ref(X).movex(15)\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "6f31040c", + "metadata": { + "id": "6f31040c" + }, + "source": [ + "# Grid / pack / align / distribute" + ] + }, + { + "cell_type": "markdown", + "id": "bc9158ad", + "metadata": { + "id": "bc9158ad" + }, + "source": [ + "## Grid\n", + "\n", + "\n", + "The ``gf.components.grid()`` function can take a list (or 2D array) of objects and arrange them along a grid. This is often useful for making parameter sweeps. If the `separation` argument is true, grid is arranged such that the elements are guaranteed not to touch, with a `spacing` distance between them. If `separation` is false, elements are spaced evenly along a grid. The `align_x`/`align_y` arguments specify intra-row/intra-column alignment. The `edge_x`/`edge_y` arguments specify inter-row/inter-column alignment (unused if `separation = True`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10adb8c2", + "metadata": { + "id": "10adb8c2" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "components_list = []\n", + "for width1 in [1, 6, 9]:\n", + " for width2 in [1, 2, 4, 8]:\n", + " D = gf.components.taper(length=10, width1=width1, width2=width2, layer=(1, 0))\n", + " components_list.append(D)\n", + "\n", + "c = gf.grid(\n", + " components_list,\n", + " spacing=(5, 1),\n", + " separation=True,\n", + " shape=(3, 4),\n", + " align_x=\"x\",\n", + " align_y=\"y\",\n", + " edge_x=\"x\",\n", + " edge_y=\"ymax\",\n", + ")\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "d208e6a4", + "metadata": { + "id": "d208e6a4" + }, + "source": [ + "## Pack\n", + "\n", + "\n", + "The ``gf.pack()`` function packs geometries together into rectangular bins. If a ``max_size`` is specified, the function will create as many bins as is necessary to pack all the geometries and then return a list of the filled-bin Components.\n", + "\n", + "Here we generate several random shapes then pack them together automatically. We allow the bin to be as large as needed to fit all the Components by specifying ``max_size = (None, None)``. By setting ``aspect_ratio = (2,1)``, we specify the rectangular bin it tries to pack them into should be twice as wide as it is tall:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf682313", + "metadata": { + "id": "cf682313" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import gdsfactory as gf\n", + "\n", + "np.random.seed(5)\n", + "D_list = [gf.components.rectangle(size=(i, i)) for i in range(1, 10)]\n", + "\n", + "D_packed_list = gf.pack(\n", + " D_list, # Must be a list or tuple of Components\n", + " spacing=1.25, # Minimum distance between adjacent shapes\n", + " aspect_ratio=(2, 1), # (width, height) ratio of the rectangular bin\n", + " max_size=(None, None), # Limits the size into which the shapes will be packed\n", + " density=1.05, # Values closer to 1 pack tighter but require more computation\n", + " sort_by_area=True, # Pre-sorts the shapes by area\n", + ")\n", + "D = D_packed_list[0] # Only one bin was created, so we plot that\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "8fe88ea4", + "metadata": { + "id": "8fe88ea4" + }, + "source": [ + "Say we need to pack many shapes into multiple 500x500 unit die. If we set ``max_size = (500,500)`` the shapes will be packed into as many 500x500 unit die as required to fit them all:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4da150a", + "metadata": { + "id": "b4da150a" + }, + "outputs": [], + "source": [ + "np.random.seed(1)\n", + "D_list = [\n", + " gf.components.ellipse(radii=tuple(np.random.rand(2) * n + 2)) for n in range(120)\n", + "]\n", + "D_packed_list = gf.pack(\n", + " D_list, # Must be a list or tuple of Components\n", + " spacing=4, # Minimum distance between adjacent shapes\n", + " aspect_ratio=(1, 1), # Shape of the box\n", + " max_size=(500, 500), # Limits the size into which the shapes will be packed\n", + " density=1.05, # Values closer to 1 pack tighter but require more computation\n", + " sort_by_area=True, # Pre-sorts the shapes by area\n", + ")\n", + "\n", + "# Put all packed bins into a single device and spread them out with distribute()\n", + "F = gf.Component(\"packed\")\n", + "[F.add_ref(D) for D in D_packed_list]\n", + "F.distribute(elements=\"all\", direction=\"x\", spacing=100, separation=True)\n", + "F.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "e337e5b4", + "metadata": { + "id": "e337e5b4" + }, + "source": [ + "Note that the packing problem is an NP-complete problem, so ``gf.components.packer()`` may be slow if there are more than a few hundred Components to pack (in that case, try pre-packing a few dozen at a time then packing the resulting bins). Requires the ``rectpack`` python package." + ] + }, + { + "cell_type": "markdown", + "id": "9b1749cc", + "metadata": { + "id": "9b1749cc", + "lines_to_next_cell": 2 + }, + "source": [ + "## Distribute\n", + "\n", + "\n", + "The ``distribute()`` function allows you to space out elements within a Component evenly in the x or y direction. It is meant to duplicate the distribute functionality present in Inkscape / Adobe Illustrator:" + ] + }, + { + "cell_type": "markdown", + "id": "c3bd98cf", + "metadata": { + "id": "c3bd98cf" + }, + "source": [ + "![](https://i.imgur.com/dC74M8x.png)" + ] + }, + { + "cell_type": "markdown", + "id": "a2a7019c", + "metadata": { + "id": "a2a7019c" + }, + "source": [ + "Say we start out with a few random-sized rectangles we want to space out:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a20b0a6", + "metadata": { + "id": "4a20b0a6" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"rectangles\")\n", + "# Create different-sized rectangles and add them to D\n", + "[\n", + " c.add_ref(\n", + " gf.components.rectangle(size=[n * 15 + 20, n * 15 + 20], layer=(2, 0))\n", + " ).move([n, n * 4])\n", + " for n in [0, 2, 3, 1, 2]\n", + "]\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "cbe0d9cc", + "metadata": { + "id": "cbe0d9cc" + }, + "source": [ + "Oftentimes, we want to guarantee some distance between the objects. By setting ``separation = True`` we move each object such that there is ``spacing`` distance between them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be60b0af", + "metadata": { + "id": "be60b0af" + }, + "outputs": [], + "source": [ + "D = gf.Component(\"rectangles_separated\")\n", + "# Create different-sized rectangles and add them to D\n", + "[\n", + " D.add_ref(gf.components.rectangle(size=[n * 15 + 20, n * 15 + 20])).move((n, n * 4))\n", + " for n in [0, 2, 3, 1, 2]\n", + "]\n", + "# Distribute all the rectangles in D along the x-direction with a separation of 5\n", + "D.distribute(\n", + " elements=\"all\", # either 'all' or a list of objects\n", + " direction=\"x\", # 'x' or 'y'\n", + " spacing=5,\n", + " separation=True,\n", + ")\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "943f26b3", + "metadata": { + "id": "943f26b3" + }, + "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:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c63156f3", + "metadata": { + "id": "c63156f3" + }, + "outputs": [], + "source": [ + "D = gf.Component(\"spacing100\")\n", + "[\n", + " D.add_ref(gf.components.rectangle(size=[n * 15 + 20, n * 15 + 20])).move((n, n * 4))\n", + " for n in [0, 2, 3, 1, 2]\n", + "]\n", + "D.distribute(\n", + " elements=\"all\", direction=\"x\", spacing=100, separation=False, edge=\"xmin\"\n", + ") # edge must be either 'xmin' (left), 'xmax' (right), or 'x' (center)\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "1669909f", + "metadata": { + "id": "1669909f" + }, + "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:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a13c698", + "metadata": { + "id": "7a13c698" + }, + "outputs": [], + "source": [ + "D = gf.Component(\"alignment\")\n", + "[\n", + " D.add_ref(gf.components.rectangle(size=[n * 15 + 20, n * 15 + 20])).move(\n", + " (n - 10, n * 4)\n", + " )\n", + " for n in [0, 2, 3, 1, 2]\n", + "]\n", + "D.distribute(\n", + " elements=\"all\", direction=\"x\", spacing=100, separation=False, edge=\"x\"\n", + ") # edge must be either 'xmin' (left), 'xmax' (right), or 'x' (center)\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "9f9e8603", + "metadata": { + "id": "9f9e8603" + }, + "source": [ + "## Align\n", + "\n", + "\n", + "The ``align()`` function allows you to elements within a Component horizontally or vertically. It is meant to duplicate the alignment functionality present in Inkscape / Adobe Illustrator:" + ] + }, + { + "cell_type": "markdown", + "id": "280c89b0", + "metadata": { + "id": "280c89b0" + }, + "source": [ + "![](https://i.imgur.com/rqzunXM.png)" + ] + }, + { + "cell_type": "markdown", + "id": "c8054db0", + "metadata": { + "id": "c8054db0" + }, + "source": [ + "Say we ``distribute()`` a few objects, but they're all misaligned:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52e8c94b", + "metadata": { + "id": "52e8c94b" + }, + "outputs": [], + "source": [ + "D = gf.Component(\"distribute\")\n", + "# Create different-sized rectangles and add them to D then distribute them\n", + "[\n", + " D.add_ref(gf.components.rectangle(size=[n * 15 + 20, n * 15 + 20])).move((n, n * 4))\n", + " for n in [0, 2, 3, 1, 2]\n", + "]\n", + "D.distribute(elements=\"all\", direction=\"x\", spacing=5, separation=True)\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "97c95038", + "metadata": { + "id": "97c95038" + }, + "source": [ + "we can use the ``align()`` function to align their top edges (``alignment = 'ymax'):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0db7d1c6", + "metadata": { + "id": "0db7d1c6" + }, + "outputs": [], + "source": [ + "D = gf.Component(\"align\")\n", + "# Create different-sized rectangles and add them to D then distribute them\n", + "[\n", + " D.add_ref(gf.components.rectangle(size=[n * 15 + 20, n * 15 + 20])).move((n, n * 4))\n", + " for n in [0, 2, 3, 1, 2]\n", + "]\n", + "D.distribute(elements=\"all\", direction=\"x\", spacing=5, separation=True)\n", + "\n", + "# Align top edges\n", + "D.align(elements=\"all\", alignment=\"ymax\")\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "fcd47e6f", + "metadata": { + "id": "fcd47e6f" + }, + "source": [ + "or align their centers (``alignment = 'y'):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7bbab67", + "metadata": { + "id": "c7bbab67" + }, + "outputs": [], + "source": [ + "D = gf.Component(\"distribute_align_y\")\n", + "# Create different-sized rectangles and add them to D then distribute them\n", + "[\n", + " D.add_ref(gf.components.rectangle(size=[n * 15 + 20, n * 15 + 20])).move((n, n * 4))\n", + " for n in [0, 2, 3, 1, 2]\n", + "]\n", + "D.distribute(elements=\"all\", direction=\"x\", spacing=5, separation=True)\n", + "\n", + "# Align top edges\n", + "D.align(elements=\"all\", alignment=\"y\")\n", + "D.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "ed27a41a", + "metadata": { + "id": "ed27a41a" + }, + "source": [ + "other valid alignment options include ``'xmin', 'x', 'xmax', 'ymin', 'y', and 'ymax'``" + ] + }, + { + "cell_type": "markdown", + "id": "130854ea", + "metadata": { + "id": "130854ea" + }, + "source": [ + "# Components with hierarchy\n", + "\n", + "![](https://i.imgur.com/3pczkyM.png)\n", + "\n", + "You can define components Parametric cells (waveguides, bends, couplers) with basic input parameters (width, length, radius ...) and reuse the PCells in more complex PCells." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dac6d91e", + "metadata": { + "id": "dac6d91e", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "from functools import partial\n", + "\n", + "import toolz\n", + "\n", + "import gdsfactory as gf\n", + "from gdsfactory.generic_tech import get_generic_pdk\n", + "from gdsfactory.typings import ComponentSpec, CrossSectionSpec\n", + "\n", + "gf.config.rich_output()\n", + "PDK = get_generic_pdk()\n", + "PDK.activate()" + ] + }, + { + "cell_type": "markdown", + "id": "e29e1d3c", + "metadata": { + "id": "e29e1d3c", + "lines_to_next_cell": 2 + }, + "source": [ + "**Problem**\n", + "\n", + "When using hierarchical cells where you pass `N` subcells with `M` parameters you can end up with `N*M` parameters. This is make code hard to read.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c3fed51", + "metadata": { + "id": "8c3fed51", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "@gf.cell\n", + "def bend_with_straight_with_too_many_input_parameters(\n", + " bend=gf.components.bend_euler,\n", + " straight=gf.components.straight,\n", + " length: float = 3,\n", + " angle: float = 90.0,\n", + " p: float = 0.5,\n", + " with_arc_floorplan: bool = True,\n", + " npoints: int | None = None,\n", + " direction: str = \"ccw\",\n", + " with_bbox: bool = True,\n", + " cross_section: CrossSectionSpec = \"strip\",\n", + ") -> gf.Component:\n", + " \"\"\" \"As hierarchical cells become more complex, the number of input parameters can increase significantly.\"\"\"\n", + " c = gf.Component()\n", + " b = bend(\n", + " angle=angle,\n", + " p=p,\n", + " with_arc_floorplan=with_arc_floorplan,\n", + " npoints=npoints,\n", + " direction=direction,\n", + " with_bbox=with_bbox,\n", + " cross_section=cross_section,\n", + " )\n", + " s = straight(length=length, with_bbox=with_bbox, cross_section=cross_section)\n", + "\n", + " bref = c << b\n", + " sref = c << s\n", + "\n", + " sref.connect(\"o2\", bref.ports[\"o2\"])\n", + " c.info[\"length\"] = b.info[\"length\"] + s.info[\"length\"]\n", + " return c\n", + "\n", + "\n", + "c = bend_with_straight_with_too_many_input_parameters()\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "18aed6ed", + "metadata": { + "id": "18aed6ed", + "lines_to_next_cell": 2 + }, + "source": [ + "**Solution**\n", + "\n", + "You can use a ComponentSpec parameter for every subcell. The ComponentSpec can be a dictionary with arbitrary number of settings, a string, or a function.\n", + "\n", + "## ComponentSpec\n", + "\n", + "When defining a `Parametric cell` you can use other `ComponentSpec` as an arguments. It can be a:\n", + "\n", + "1. string: function name of a cell registered on the active PDK. `\"bend_circular\"`\n", + "2. dict: `dict(component='bend_circular', settings=dict(radius=20))`\n", + "3. function: Using `functools.partial` you can customize the default parameters of a function.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0f74a62", + "metadata": { + "id": "d0f74a62" + }, + "outputs": [], + "source": [ + "@gf.cell\n", + "def bend_with_straight(\n", + " bend: ComponentSpec = gf.components.bend_euler,\n", + " straight: ComponentSpec = gf.components.straight,\n", + ") -> gf.Component:\n", + " \"\"\"Much simpler version.\n", + "\n", + " Args:\n", + " bend: input bend.\n", + " straight: output straight.\n", + " \"\"\"\n", + " c = gf.Component()\n", + " b = gf.get_component(bend)\n", + " s = gf.get_component(straight)\n", + "\n", + " bref = c << b\n", + " sref = c << s\n", + "\n", + " sref.connect(\"o2\", bref.ports[\"o2\"])\n", + " c.info[\"length\"] = b.info[\"length\"] + s.info[\"length\"]\n", + " return c\n", + "\n", + "\n", + "c = bend_with_straight()\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "b3170703", + "metadata": { + "id": "b3170703" + }, + "source": [ + "### 1. string\n", + "\n", + "You can use any string registered in the `Pdk`. Go to the PDK tutorial to learn how to register cells in a PDK." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "743ebbcb", + "metadata": { + "id": "743ebbcb" + }, + "outputs": [], + "source": [ + "c = bend_with_straight(bend=\"bend_circular\")\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "cacfd150", + "metadata": { + "id": "cacfd150" + }, + "source": [ + "### 2. dict\n", + "\n", + "You can pass a dict of settings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01b3e651", + "metadata": { + "id": "01b3e651" + }, + "outputs": [], + "source": [ + "c = bend_with_straight(bend=dict(component=\"bend_circular\", settings=dict(radius=20)))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "712a9442", + "metadata": { + "id": "712a9442" + }, + "source": [ + "### 3. function\n", + "\n", + "You can pass a function of a function with customized default input parameters `from functools import partial`\n", + "\n", + "Partial lets you define different default parameters for a function, so you can modify the default settings for each child cell." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8621584b", + "metadata": { + "id": "8621584b" + }, + "outputs": [], + "source": [ + "c = bend_with_straight(bend=partial(gf.components.bend_circular, radius=30))\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04cc16fe", + "metadata": { + "id": "04cc16fe" + }, + "outputs": [], + "source": [ + "bend20 = partial(gf.components.bend_circular, radius=20)\n", + "b = bend20()\n", + "b.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50773c23", + "metadata": { + "id": "50773c23" + }, + "outputs": [], + "source": [ + "type(bend20)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90779bdc", + "metadata": { + "id": "90779bdc" + }, + "outputs": [], + "source": [ + "bend20.func.__name__" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92e724b2", + "metadata": { + "id": "92e724b2" + }, + "outputs": [], + "source": [ + "bend20.keywords" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "83888d06", + "metadata": { + "id": "83888d06" + }, + "outputs": [], + "source": [ + "b = bend_with_straight(bend=bend20)\n", + "print(b.metadata[\"info\"][\"length\"])\n", + "b.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "452b394f", + "metadata": { + "id": "452b394f" + }, + "outputs": [], + "source": [ + "# You can still modify the bend to have any bend radius\n", + "b3 = bend20(radius=10)\n", + "b3.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "d62b08f5", + "metadata": { + "id": "d62b08f5" + }, + "source": [ + "## PDK custom fab\n", + "\n", + "You can define a new PDK by creating function that customize partial parameters of the generic functions.\n", + "\n", + "Lets say that this PDK uses layer (41, 0) for the pads (instead of the layer used in the generic pad function)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e08ef064", + "metadata": { + "id": "e08ef064" + }, + "outputs": [], + "source": [ + "pad_custom_layer = partial(gf.components.pad, layer=(41, 0))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b854e54", + "metadata": { + "id": "6b854e54" + }, + "outputs": [], + "source": [ + "c = pad_custom_layer()\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "11c2e999", + "metadata": { + "id": "11c2e999" + }, + "source": [ + "## Composing functions\n", + "\n", + "You can combine more complex functions out of smaller functions.\n", + "\n", + "Lets say that we want to add tapers and grating couplers to a wide waveguide." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f76fb265", + "metadata": { + "id": "f76fb265" + }, + "outputs": [], + "source": [ + "c1 = gf.components.straight()\n", + "c1.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c75d99bb", + "metadata": { + "id": "c75d99bb" + }, + "outputs": [], + "source": [ + "straight_wide = partial(gf.components.straight, width=3)\n", + "c3 = straight_wide()\n", + "c3.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4cc24fb7", + "metadata": { + "id": "4cc24fb7" + }, + "outputs": [], + "source": [ + "c1 = gf.components.straight(width=3)\n", + "c1.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1a189bd", + "metadata": { + "id": "b1a189bd" + }, + "outputs": [], + "source": [ + "c2 = gf.add_tapers(c1)\n", + "c2.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0643f726", + "metadata": { + "id": "0643f726" + }, + "outputs": [], + "source": [ + "c2.metadata_child[\"changed\"] # You can still access the child metadata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "835304e7", + "metadata": { + "id": "835304e7" + }, + "outputs": [], + "source": [ + "c3 = gf.routing.add_fiber_array(c2, with_loopback=False)\n", + "c3.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78e86be2", + "metadata": { + "id": "78e86be2" + }, + "outputs": [], + "source": [ + "c3.metadata_child[\"changed\"] # You can still access the child metadata" + ] + }, + { + "cell_type": "markdown", + "id": "2981940d", + "metadata": { + "id": "2981940d" + }, + "source": [ + "Lets do it with a **single** step thanks to `toolz.pipe`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28ab65c0", + "metadata": { + "id": "28ab65c0" + }, + "outputs": [], + "source": [ + "add_fiber_array = partial(gf.routing.add_fiber_array, with_loopback=False)\n", + "add_tapers = gf.add_tapers\n", + "\n", + "# pipe is more readable than the equivalent add_fiber_array(add_tapers(c1))\n", + "c3 = toolz.pipe(c1, add_tapers, add_fiber_array)\n", + "c3.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "78c26b51", + "metadata": { + "id": "78c26b51" + }, + "source": [ + "we can even combine `add_tapers` and `add_fiber_array` thanks to `toolz.compose` or `toolz.compose`\n", + "\n", + "For example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "701a01cd", + "metadata": { + "id": "701a01cd" + }, + "outputs": [], + "source": [ + "add_tapers_fiber_array = toolz.compose_left(add_tapers, add_fiber_array)\n", + "c4 = add_tapers_fiber_array(c1)\n", + "c4.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "d5902091", + "metadata": { + "id": "d5902091" + }, + "source": [ + "is equivalent to" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9dfacb7a", + "metadata": { + "id": "9dfacb7a" + }, + "outputs": [], + "source": [ + "c5 = add_fiber_array(add_tapers(c1))\n", + "c5.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "f2ac8c72", + "metadata": { + "id": "f2ac8c72" + }, + "source": [ + "as well as equivalent to" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14c28b54", + "metadata": { + "id": "14c28b54" + }, + "outputs": [], + "source": [ + "add_tapers_fiber_array = toolz.compose(add_fiber_array, add_tapers)\n", + "c6 = add_tapers_fiber_array(c1)\n", + "c6.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "62d9ea17", + "metadata": { + "id": "62d9ea17" + }, + "source": [ + "or" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efecd8ee", + "metadata": { + "id": "efecd8ee" + }, + "outputs": [], + "source": [ + "c7 = toolz.pipe(c1, add_tapers, add_fiber_array)\n", + "c7.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4664b872", + "metadata": { + "id": "4664b872" + }, + "outputs": [], + "source": [ + "c7.metadata_child[\"changed\"] # You can still access the child metadata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f749ae41", + "metadata": { + "id": "f749ae41" + }, + "outputs": [], + "source": [ + "c7.metadata[\"child\"][\"child\"][\"name\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22110a13", + "metadata": { + "id": "22110a13" + }, + "outputs": [], + "source": [ + "c7.metadata[\"child\"][\"child\"][\"function_name\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37010b53", + "metadata": { + "id": "37010b53" + }, + "outputs": [], + "source": [ + "c7.metadata[\"changed\"].keys()" + ] + }, + { + "cell_type": "markdown", + "id": "6266fdee", + "metadata": { + "id": "6266fdee" + }, + "source": [ + "# Routing\n", + "\n", + "Optical and high speed RF ports have an orientation that routes need to follow to avoid sharp turns that produce reflections.\n", + "\n", + "we have routing functions that route:\n", + "\n", + "- single route between 2 ports\n", + " - `get_route`\n", + " - `get_route_from_steps`\n", + " - `get_route_astar`\n", + "- group of routes between 2 groups of ports using a river/bundle/bus router. At the moment it works only when all ports on each group have the same orientation.\n", + " - `get_bundle`\n", + " - `get_bundle_from_steps`\n", + "\n", + "\n", + "The most useful function is `get_bundle` which supports both single and groups of routes, and can also route with length matching, which ensures that all routes have the same length.\n", + "\n", + "The biggest limitation is that it requires to have all the ports with the same orientation, for that you can use `gf.routing.route_ports_to_side`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b9fd9534", + "metadata": { + "id": "b9fd9534" + }, + "outputs": [], + "source": [ + "from functools import partial\n", + "\n", + "import gdsfactory as gf\n", + "from gdsfactory.cell import cell\n", + "from gdsfactory.component import Component\n", + "from gdsfactory.generic_tech import get_generic_pdk\n", + "from gdsfactory.port import Port\n", + "\n", + "gf.config.rich_output()\n", + "PDK = get_generic_pdk()\n", + "PDK.activate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf89285f", + "metadata": { + "id": "cf89285f" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"sample_no_routes\")\n", + "mmi1 = c << gf.components.mmi1x2()\n", + "mmi2 = c << gf.components.mmi1x2()\n", + "mmi2.move((100, 50))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "08100fe6", + "metadata": { + "id": "08100fe6" + }, + "source": [ + "## get_route\n", + "\n", + "`get_route` returns a Manhattan route between 2 ports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67fea8f4", + "metadata": { + "id": "67fea8f4" + }, + "outputs": [], + "source": [ + "help(gf.routing.get_route)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45d1892a", + "metadata": { + "id": "45d1892a" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"sample_connect\")\n", + "mmi1 = c << gf.components.mmi1x2()\n", + "mmi2 = c << gf.components.mmi1x2()\n", + "mmi2.move((100, 50))\n", + "route = gf.routing.get_route(mmi1.ports[\"o2\"], mmi2.ports[\"o1\"])\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b657c5b", + "metadata": { + "id": "8b657c5b" + }, + "outputs": [], + "source": [ + "route" + ] + }, + { + "cell_type": "markdown", + "id": "510966f2", + "metadata": { + "id": "510966f2" + }, + "source": [ + "**Problem**: get_route with obstacles\n", + "\n", + "sometimes there are obstacles that connect strip does not see!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "274962bb", + "metadata": { + "id": "274962bb" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"sample_problem\")\n", + "mmi1 = c << gf.components.mmi1x2()\n", + "mmi2 = c << gf.components.mmi1x2()\n", + "mmi2.move((110, 50))\n", + "x = c << gf.components.cross(length=20)\n", + "x.move((135, 20))\n", + "route = gf.routing.get_route(mmi1.ports[\"o2\"], mmi2.ports[\"o2\"])\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "d729a8f3", + "metadata": { + "id": "d729a8f3" + }, + "source": [ + "**Solutions:**\n", + "\n", + "- specify the route steps\n", + "\n", + "## get_route_from_steps\n", + "\n", + "`get_route_from_steps` is a manual version of `get_route` where you can define only the new steps `x` or `y` together with increments `dx` or `dy`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ccfda81", + "metadata": { + "id": "7ccfda81" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_route_from_steps\")\n", + "w = gf.components.straight()\n", + "left = c << w\n", + "right = c << w\n", + "right.move((100, 80))\n", + "\n", + "obstacle = gf.components.rectangle(size=(100, 10))\n", + "obstacle1 = c << obstacle\n", + "obstacle2 = c << obstacle\n", + "obstacle1.ymin = 40\n", + "obstacle2.xmin = 25\n", + "\n", + "port1 = left.ports[\"o2\"]\n", + "port2 = right.ports[\"o2\"]\n", + "\n", + "routes = gf.routing.get_route_from_steps(\n", + " port1=port1,\n", + " port2=port2,\n", + " steps=[\n", + " {\"x\": 20, \"y\": 0},\n", + " {\"x\": 20, \"y\": 20},\n", + " {\"x\": 120, \"y\": 20},\n", + " {\"x\": 120, \"y\": 80},\n", + " ],\n", + ")\n", + "c.add(routes.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d6e9253", + "metadata": { + "id": "5d6e9253" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_route_from_steps_shorter_syntax\")\n", + "w = gf.components.straight()\n", + "left = c << w\n", + "right = c << w\n", + "right.move((100, 80))\n", + "\n", + "obstacle = gf.components.rectangle(size=(100, 10))\n", + "obstacle1 = c << obstacle\n", + "obstacle2 = c << obstacle\n", + "obstacle1.ymin = 40\n", + "obstacle2.xmin = 25\n", + "\n", + "port1 = left.ports[\"o2\"]\n", + "port2 = right.ports[\"o2\"]\n", + "\n", + "routes = gf.routing.get_route_from_steps(\n", + " port1=port1,\n", + " port2=port2,\n", + " steps=[\n", + " {\"x\": 20},\n", + " {\"y\": 20},\n", + " {\"x\": 120},\n", + " {\"y\": 80},\n", + " ],\n", + ")\n", + "c.add(routes.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "a98264e0", + "metadata": { + "id": "a98264e0" + }, + "source": [ + "## get_route_astar\n", + "\n", + "A* is a routing algorithm to avoid obstacles.\n", + "See [wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm) and [animation](https://github.com/zhm-real/PathPlanning)\n", + "\n", + "The main issue is that it only works for a single route." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86cd03c8", + "metadata": { + "id": "86cd03c8" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_route_astar\")\n", + "cross_section = gf.get_cross_section(\"metal1\", width=3)\n", + "w = gf.components.straight(cross_section=cross_section)\n", + "\n", + "left = c << w\n", + "right = c << w\n", + "right.move((100, 80))\n", + "\n", + "obstacle = gf.components.rectangle(size=(100, 3), layer=\"M1\")\n", + "obstacle1 = c << obstacle\n", + "obstacle2 = c << obstacle\n", + "obstacle1.ymin = 40\n", + "obstacle2.xmin = 25\n", + "\n", + "port1 = left.ports[\"e2\"]\n", + "port2 = right.ports[\"e2\"]\n", + "\n", + "routes = gf.routing.get_route_astar(\n", + " component=c,\n", + " port1=port1,\n", + " port2=port2,\n", + " cross_section=cross_section,\n", + " resolution=5,\n", + " distance=6.5,\n", + ")\n", + "\n", + "c.add(routes.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "7f8b036b", + "metadata": { + "id": "7f8b036b" + }, + "source": [ + "By default it avoids all obstacles on every layer, but you can explicitly define the layers to avoid using `avoid_layers`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "110cff5c", + "metadata": { + "id": "110cff5c" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_route_astar_avoid_layers\")\n", + "cross_section = gf.get_cross_section(\"metal1\", width=3)\n", + "w = gf.components.straight(cross_section=cross_section)\n", + "\n", + "left = c << w\n", + "right = c << w\n", + "right.move((100, 80))\n", + "\n", + "obstacle = gf.components.rectangle(size=(100, 3), layer=\"WG\")\n", + "obstacle1 = c << obstacle\n", + "obstacle2 = c << obstacle\n", + "obstacle1.ymin = 40\n", + "obstacle2.xmin = 25\n", + "\n", + "port1 = left.ports[\"e2\"]\n", + "port2 = right.ports[\"e2\"]\n", + "\n", + "routes = gf.routing.get_route_astar(\n", + " component=c,\n", + " port1=port1,\n", + " port2=port2,\n", + " cross_section=cross_section,\n", + " resolution=10,\n", + " distance=6.5,\n", + " avoid_layers=(\"M1\",),\n", + ")\n", + "\n", + "c.add(routes.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8abc7da", + "metadata": { + "id": "b8abc7da" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_route_astar_strip\")\n", + "rect1 = c << gf.components.rectangle()\n", + "rect2 = c << gf.components.rectangle()\n", + "rect3 = c << gf.components.rectangle((2, 2), layer=(1, 0))\n", + "rect2.move(destination=(8, 4))\n", + "rect3.move(destination=(5.5, 1.5))\n", + "\n", + "port1 = gf.Port(\n", + " \"o1\", 0, rect1.center + (0, 3), cross_section=gf.get_cross_section(\"strip\")\n", + ")\n", + "port2 = port1.copy(\"o2\")\n", + "port2.orientation = 180\n", + "port2.center = rect2.center + (0, -3)\n", + "c.add_ports([port1, port2])\n", + "route = gf.routing.get_route_astar(c, port1, port2, radius=0.5, width=0.5, distance=0.5)\n", + "c.add(route.references)\n", + "\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f659d39", + "metadata": { + "id": "1f659d39" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_route_astar_strip_avoid_layers\")\n", + "rect1 = c << gf.components.rectangle()\n", + "rect2 = c << gf.components.rectangle()\n", + "rect3 = c << gf.components.rectangle((2, 2), layer=(2, 0))\n", + "rect2.move(destination=(8, 4))\n", + "rect3.move(destination=(5.5, 1.5))\n", + "\n", + "port1 = gf.Port(\n", + " \"o1\", 0, rect1.center + (0, 3), cross_section=gf.get_cross_section(\"strip\")\n", + ")\n", + "port2 = port1.copy(\"o2\")\n", + "port2.orientation = 180\n", + "port2.center = rect2.center + (0, -3)\n", + "c.add_ports([port1, port2])\n", + "route = gf.routing.get_route_astar(\n", + " c, port1, port2, radius=0.5, width=0.5, avoid_layers=[(1, 0)]\n", + ")\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "ce6acda6", + "metadata": { + "id": "ce6acda6" + }, + "source": [ + "The resolution decides how many \"leaps/hops\" the algorithm has to do. For a layout like this, where the default resolution (1 micron) is much smaller than the distance between the obstacles (~15+ microns), it has to step through too many points and that takes a long time. Increasing the resolution to about 5 microns fixes it (for this layout)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3608f731", + "metadata": { + "id": "3608f731" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_route_astar_resolution\")\n", + "w = gf.components.straight()\n", + "left = c << w\n", + "right = c << w\n", + "right.move((100, 80))\n", + "\n", + "obstacle = gf.components.rectangle(size=(100, 10))\n", + "obstacle1 = c << obstacle\n", + "obstacle2 = c << obstacle\n", + "obstacle1.ymin = 40\n", + "obstacle2.xmin = 25\n", + "\n", + "port1 = left.ports[\"o2\"]\n", + "port2 = right.ports[\"o2\"]\n", + "\n", + "route = gf.routing.get_route_astar(\n", + " component=c,\n", + " port1=port1,\n", + " port2=port2,\n", + " resolution=5,\n", + " distance=5.5,\n", + " radius=5,\n", + ")\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "81c458ea", + "metadata": { + "id": "81c458ea" + }, + "source": [ + "## get_bundle\n", + "\n", + "To route groups of ports avoiding waveguide collisions, you should use `get_bundle` instead of `get_route`.\n", + "\n", + "`get_bundle` uses a river/bundle/bus router.\n", + "\n", + "At the moment it works only when each group of ports have the same orientation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f5c6a27c", + "metadata": { + "id": "f5c6a27c" + }, + "outputs": [], + "source": [ + "ys_right = [0, 10, 20, 40, 50, 80]\n", + "pitch = 127.0\n", + "N = len(ys_right)\n", + "ys_left = [(i - N / 2) * pitch for i in range(N)]\n", + "layer = (1, 0)\n", + "\n", + "right_ports = [\n", + " gf.Port(f\"R_{i}\", center=(0, ys_right[i]), width=0.5, orientation=180, layer=layer)\n", + " for i in range(N)\n", + "]\n", + "left_ports = [\n", + " gf.Port(f\"L_{i}\", center=(-200, ys_left[i]), width=0.5, orientation=0, layer=layer)\n", + " for i in range(N)\n", + "]\n", + "\n", + "# you can also mess up the port order and it will sort them by default\n", + "left_ports.reverse()\n", + "\n", + "c = gf.Component(name=\"connect_bundle_v2\")\n", + "routes = gf.routing.get_bundle(\n", + " left_ports, right_ports, sort_ports=True, start_straight_length=100\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be03f99c", + "metadata": { + "id": "be03f99c" + }, + "outputs": [], + "source": [ + "xs_top = [0, 10, 20, 40, 50, 80]\n", + "pitch = 127.0\n", + "N = len(xs_top)\n", + "xs_bottom = [(i - N / 2) * pitch for i in range(N)]\n", + "layer = (1, 0)\n", + "\n", + "top_ports = [\n", + " gf.Port(f\"top_{i}\", center=(xs_top[i], 0), width=0.5, orientation=270, layer=layer)\n", + " for i in range(N)\n", + "]\n", + "\n", + "bot_ports = [\n", + " gf.Port(\n", + " f\"bot_{i}\",\n", + " center=(xs_bottom[i], -300),\n", + " width=0.5,\n", + " orientation=90,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + "]\n", + "\n", + "c = gf.Component(name=\"connect_bundle_separation\")\n", + "routes = gf.routing.get_bundle(\n", + " top_ports, bot_ports, separation=5.0, end_straight_length=100\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + "\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "8ce7f5d0", + "metadata": { + "id": "8ce7f5d0", + "lines_to_next_cell": 2 + }, + "source": [ + "`get_bundle` can also route bundles through corners\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e69035f", + "metadata": { + "id": "2e69035f" + }, + "outputs": [], + "source": [ + "@cell\n", + "def test_connect_corner(N=6, config=\"A\"):\n", + " d = 10.0\n", + " sep = 5.0\n", + " top_cell = gf.Component()\n", + " layer = (1, 0)\n", + "\n", + " if config in [\"A\", \"B\"]:\n", + " a = 100.0\n", + " ports_A_TR = [\n", + " Port(\n", + " f\"A_TR_{i}\",\n", + " center=(d, a / 2 + i * sep),\n", + " width=0.5,\n", + " orientation=0,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_A_TL = [\n", + " Port(\n", + " f\"A_TL_{i}\",\n", + " center=(-d, a / 2 + i * sep),\n", + " width=0.5,\n", + " orientation=180,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_A_BR = [\n", + " Port(\n", + " f\"A_BR_{i}\",\n", + " center=(d, -a / 2 - i * sep),\n", + " width=0.5,\n", + " orientation=0,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_A_BL = [\n", + " Port(\n", + " f\"A_BL_{i}\",\n", + " center=(-d, -a / 2 - i * sep),\n", + " width=0.5,\n", + " orientation=180,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL]\n", + "\n", + " ports_B_TR = [\n", + " Port(\n", + " f\"B_TR_{i}\",\n", + " center=(a / 2 + i * sep, d),\n", + " width=0.5,\n", + " orientation=90,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_B_TL = [\n", + " Port(\n", + " f\"B_TL_{i}\",\n", + " center=(-a / 2 - i * sep, d),\n", + " width=0.5,\n", + " orientation=90,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_B_BR = [\n", + " Port(\n", + " f\"B_BR_{i}\",\n", + " center=(a / 2 + i * sep, -d),\n", + " width=0.5,\n", + " orientation=270,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_B_BL = [\n", + " Port(\n", + " f\"B_BL_{i}\",\n", + " center=(-a / 2 - i * sep, -d),\n", + " width=0.5,\n", + " orientation=270,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL]\n", + "\n", + " elif config in [\"C\", \"D\"]:\n", + " a = N * sep + 2 * d\n", + " ports_A_TR = [\n", + " Port(\n", + " f\"A_TR_{i}\",\n", + " center=(a, d + i * sep),\n", + " width=0.5,\n", + " orientation=0,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_A_TL = [\n", + " Port(\n", + " f\"A_TL_{i}\",\n", + " center=(-a, d + i * sep),\n", + " width=0.5,\n", + " orientation=180,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_A_BR = [\n", + " Port(\n", + " f\"A_BR_{i}\",\n", + " center=(a, -d - i * sep),\n", + " width=0.5,\n", + " orientation=0,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_A_BL = [\n", + " Port(\n", + " f\"A_BL_{i}\",\n", + " center=(-a, -d - i * sep),\n", + " width=0.5,\n", + " orientation=180,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL]\n", + "\n", + " ports_B_TR = [\n", + " Port(\n", + " f\"B_TR_{i}\",\n", + " center=(d + i * sep, a),\n", + " width=0.5,\n", + " orientation=90,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_B_TL = [\n", + " Port(\n", + " f\"B_TL_{i}\",\n", + " center=(-d - i * sep, a),\n", + " width=0.5,\n", + " orientation=90,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_B_BR = [\n", + " Port(\n", + " f\"B_BR_{i}\",\n", + " center=(d + i * sep, -a),\n", + " width=0.5,\n", + " orientation=270,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_B_BL = [\n", + " Port(\n", + " f\"B_BL_{i}\",\n", + " center=(-d - i * sep, -a),\n", + " width=0.5,\n", + " orientation=270,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL]\n", + "\n", + " if config in [\"A\", \"C\"]:\n", + " for ports1, ports2 in zip(ports_A, ports_B):\n", + " routes = gf.routing.get_bundle(ports1, ports2, layer=(2, 0), radius=5)\n", + " for route in routes:\n", + " top_cell.add(route.references)\n", + "\n", + " elif config in [\"B\", \"D\"]:\n", + " for ports1, ports2 in zip(ports_A, ports_B):\n", + " routes = gf.routing.get_bundle(ports2, ports1, layer=(2, 0), radius=5)\n", + " for route in routes:\n", + " top_cell.add(route.references)\n", + "\n", + " return top_cell\n", + "\n", + "\n", + "c = test_connect_corner(config=\"A\")\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "599a4b5b", + "metadata": { + "id": "599a4b5b" + }, + "outputs": [], + "source": [ + "c = test_connect_corner(config=\"C\")\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "847dfbbe", + "metadata": { + "id": "847dfbbe" + }, + "outputs": [], + "source": [ + "@cell\n", + "def test_connect_bundle_udirect(dy=200, orientation=270, layer=(1, 0)):\n", + " xs1 = [-100, -90, -80, -55, -35, 24, 0] + [200, 210, 240]\n", + " axis = \"X\" if orientation in [0, 180] else \"Y\"\n", + " pitch = 10.0\n", + " N = len(xs1)\n", + " xs2 = [70 + i * pitch for i in range(N)]\n", + "\n", + " if axis == \"X\":\n", + " ports1 = [\n", + " Port(\n", + " f\"top_{i}\",\n", + " center=(0, xs1[i]),\n", + " width=0.5,\n", + " orientation=orientation,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports2 = [\n", + " Port(\n", + " f\"bottom_{i}\",\n", + " center=(dy, xs2[i]),\n", + " width=0.5,\n", + " orientation=orientation,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " else:\n", + " ports1 = [\n", + " Port(\n", + " f\"top_{i}\",\n", + " center=(xs1[i], 0),\n", + " width=0.5,\n", + " orientation=orientation,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports2 = [\n", + " Port(\n", + " f\"bottom_{i}\",\n", + " center=(xs2[i], dy),\n", + " width=0.5,\n", + " orientation=orientation,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " top_cell = Component()\n", + " routes = gf.routing.get_bundle(ports1, ports2, radius=10.0)\n", + " for route in routes:\n", + " top_cell.add(route.references)\n", + "\n", + " return top_cell\n", + "\n", + "\n", + "c = test_connect_bundle_udirect()\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7819d90a", + "metadata": { + "id": "7819d90a" + }, + "outputs": [], + "source": [ + "@cell\n", + "def test_connect_bundle_u_indirect(dy=-200, orientation=180, layer=(1, 0)):\n", + " xs1 = [-100, -90, -80, -55, -35] + [200, 210, 240]\n", + " axis = \"X\" if orientation in [0, 180] else \"Y\"\n", + " pitch = 10.0\n", + " N = len(xs1)\n", + " xs2 = [50 + i * pitch for i in range(N)]\n", + "\n", + " a1 = orientation\n", + " a2 = a1 + 180\n", + "\n", + " if axis == \"X\":\n", + " ports1 = [\n", + " Port(f\"top_{i}\", center=(0, xs1[i]), width=0.5, orientation=a1, layer=layer)\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports2 = [\n", + " Port(\n", + " f\"bot_{i}\",\n", + " center=(dy, xs2[i]),\n", + " width=0.5,\n", + " orientation=a2,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " else:\n", + " ports1 = [\n", + " Port(f\"top_{i}\", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports2 = [\n", + " Port(\n", + " f\"bot_{i}\",\n", + " center=(xs2[i], dy),\n", + " width=0.5,\n", + " orientation=a2,\n", + " layer=layer,\n", + " )\n", + " for i in range(N)\n", + " ]\n", + "\n", + " top_cell = Component()\n", + " routes = gf.routing.get_bundle(\n", + " ports1,\n", + " ports2,\n", + " bend=gf.components.bend_euler,\n", + " radius=5,\n", + " )\n", + " for route in routes:\n", + " top_cell.add(route.references)\n", + "\n", + " return top_cell\n", + "\n", + "\n", + "c = test_connect_bundle_u_indirect(orientation=0)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ab82af5", + "metadata": { + "id": "0ab82af5" + }, + "outputs": [], + "source": [ + "@gf.cell\n", + "def test_north_to_south(layer=(1, 0)):\n", + " dy = 200.0\n", + " xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]\n", + "\n", + " pitch = 10.0\n", + " N = len(xs1)\n", + " xs2 = [-20 + i * pitch for i in range(N // 2)]\n", + " xs2 += [400 + i * pitch for i in range(N // 2)]\n", + "\n", + " a1 = 90\n", + " a2 = a1 + 180\n", + "\n", + " ports1 = [\n", + " gf.Port(f\"top_{i}\", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)\n", + " for i in range(N)\n", + " ]\n", + "\n", + " ports2 = [\n", + " gf.Port(f\"bot_{i}\", center=(xs2[i], dy), width=0.5, orientation=a2, layer=layer)\n", + " for i in range(N)\n", + " ]\n", + "\n", + " c = gf.Component()\n", + " routes = gf.routing.get_bundle(ports1, ports2, auto_widen=False)\n", + " for route in routes:\n", + " c.add(route.references)\n", + "\n", + " return c\n", + "\n", + "\n", + "c = test_north_to_south()\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "117493ff", + "metadata": { + "id": "117493ff" + }, + "outputs": [], + "source": [ + "@gf.cell\n", + "def demo_connect_bundle():\n", + " \"\"\"combines all the connect_bundle tests\"\"\"\n", + " y = 400.0\n", + " x = 500\n", + " y0 = 900\n", + " dy = 200.0\n", + " c = gf.Component()\n", + " for j, s in enumerate([-1, 1]):\n", + " for i, orientation in enumerate([0, 90, 180, 270]):\n", + " ci = test_connect_bundle_u_indirect(dy=s * dy, orientation=orientation)\n", + " ref = ci.ref(position=(i * x, j * y))\n", + " c.add(ref)\n", + "\n", + " ci = test_connect_bundle_udirect(dy=s * dy, orientation=orientation)\n", + " ref = ci.ref(position=(i * x, j * y + y0))\n", + " c.add(ref)\n", + "\n", + " for i, config in enumerate([\"A\", \"B\", \"C\", \"D\"]):\n", + " ci = test_connect_corner(config=config)\n", + " ref = ci.ref(position=(i * x, 1700))\n", + " c.add(ref)\n", + "\n", + " return c\n", + "\n", + "\n", + "c = demo_connect_bundle()\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fbeb1f49", + "metadata": { + "id": "fbeb1f49" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"route_bend_5um\")\n", + "c1 = c << gf.components.mmi2x2()\n", + "c2 = c << gf.components.mmi2x2()\n", + "\n", + "c2.move((100, 50))\n", + "routes = gf.routing.get_bundle(\n", + " [c1.ports[\"o4\"], c1.ports[\"o3\"]], [c2.ports[\"o1\"], c2.ports[\"o2\"]], radius=5\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "656de595", + "metadata": { + "id": "656de595" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"electrical\")\n", + "c1 = c << gf.components.pad()\n", + "c2 = c << gf.components.pad()\n", + "c2.move((200, 100))\n", + "routes = gf.routing.get_bundle(\n", + " [c1.ports[\"e3\"]], [c2.ports[\"e1\"]], cross_section=gf.cross_section.metal1\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7af15f3", + "metadata": { + "id": "a7af15f3" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_bundle_with_ubends_bend_from_top\")\n", + "pad_array = gf.components.pad_array()\n", + "\n", + "c1 = c << pad_array\n", + "c2 = c << pad_array\n", + "c2.rotate(90)\n", + "c2.movex(1000)\n", + "c2.ymax = -200\n", + "\n", + "routes_bend180 = gf.routing.get_routes_bend180(\n", + " ports=c2.get_ports_list(),\n", + " radius=75 / 2,\n", + " cross_section=gf.cross_section.metal1,\n", + " bend_port1=\"e1\",\n", + " bend_port2=\"e2\",\n", + ")\n", + "c.add(routes_bend180.references)\n", + "\n", + "routes = gf.routing.get_bundle(\n", + " c1.get_ports_list(), routes_bend180.ports, cross_section=gf.cross_section.metal1\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d9d39b05", + "metadata": { + "id": "d9d39b05" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_bundle_with_ubends_bend_from_bottom\")\n", + "pad_array = gf.components.pad_array()\n", + "\n", + "c1 = c << pad_array\n", + "c2 = c << pad_array\n", + "c2.rotate(90)\n", + "c2.movex(1000)\n", + "c2.ymax = -200\n", + "\n", + "routes_bend180 = gf.routing.get_routes_bend180(\n", + " ports=c2.get_ports_list(),\n", + " radius=75 / 2,\n", + " cross_section=gf.cross_section.metal1,\n", + " bend_port1=\"e2\",\n", + " bend_port2=\"e1\",\n", + ")\n", + "c.add(routes_bend180.references)\n", + "\n", + "routes = gf.routing.get_bundle(\n", + " c1.get_ports_list(), routes_bend180.ports, cross_section=gf.cross_section.metal1\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "e2697c11", + "metadata": { + "id": "e2697c11" + }, + "source": [ + "**Problem**\n", + "\n", + "Sometimes 90 degrees routes do not have enough space for a Manhattan route" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7e757f4", + "metadata": { + "id": "c7e757f4" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"route_fail_1\")\n", + "c1 = c << gf.components.nxn(east=3, ysize=20)\n", + "c2 = c << gf.components.nxn(west=3)\n", + "c2.move((80, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80c35fd6", + "metadata": { + "id": "80c35fd6" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"route_fail_v2\")\n", + "c1 = c << gf.components.nxn(east=3, ysize=20)\n", + "c2 = c << gf.components.nxn(west=3)\n", + "c2.move((80, 0))\n", + "routes = gf.routing.get_bundle(\n", + " c1.get_ports_list(orientation=0),\n", + " c2.get_ports_list(orientation=180),\n", + " auto_widen=False,\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20c025f1", + "metadata": { + "id": "20c025f1" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"route_fail_v3\")\n", + "pitch = 2.0\n", + "ys_left = [0, 10, 20]\n", + "N = len(ys_left)\n", + "ys_right = [(i - N / 2) * pitch for i in range(N)]\n", + "layer = (1, 0)\n", + "\n", + "right_ports = [\n", + " gf.Port(f\"R_{i}\", center=(0, ys_right[i]), width=0.5, orientation=180, layer=layer)\n", + " for i in range(N)\n", + "]\n", + "left_ports = [\n", + " gf.Port(f\"L_{i}\", center=(-50, ys_left[i]), width=0.5, orientation=0, layer=layer)\n", + " for i in range(N)\n", + "]\n", + "left_ports.reverse()\n", + "routes = gf.routing.get_bundle(right_ports, left_ports, radius=5)\n", + "\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "51730078", + "metadata": { + "id": "51730078" + }, + "source": [ + "**Solution**\n", + "\n", + "Add Sbend routes using `get_bundle_sbend`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e2c6333", + "metadata": { + "id": "9e2c6333" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"route_solution_1_get_bundle_sbend\")\n", + "c1 = c << gf.components.nxn(east=3, ysize=20)\n", + "c2 = c << gf.components.nxn(west=3)\n", + "c2.move((80, 0))\n", + "routes = gf.routing.get_bundle_sbend(\n", + " c1.get_ports_list(orientation=0), c2.get_ports_list(orientation=180)\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "d9a28362", + "metadata": { + "id": "d9a28362" + }, + "source": [ + "You can also `get_bundle` adding `with_sbend=True`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bef4e9e4", + "metadata": { + "id": "bef4e9e4", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "c = gf.Component(\"route_solution_2_get_bundle\")\n", + "c1 = c << gf.components.nxn(east=3, ysize=20)\n", + "c2 = c << gf.components.nxn(west=3)\n", + "c2.move((80, 0))\n", + "routes = gf.routing.get_bundle(\n", + " c1.get_ports_list(orientation=0),\n", + " c2.get_ports_list(orientation=180),\n", + " with_sbend=True,\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "2256c9ed", + "metadata": { + "id": "2256c9ed" + }, + "source": [ + "### get_bundle with path_length_match\n", + "\n", + "Sometimes you need to route two groups of ports keeping the same route lengths." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74ef586f", + "metadata": { + "id": "74ef586f" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"path_length_match_routing\")\n", + "dy = 2000.0\n", + "xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]\n", + "\n", + "pitch = 100.0\n", + "N = len(xs1)\n", + "xs2 = [-20 + i * pitch for i in range(N)]\n", + "\n", + "a1 = 90\n", + "a2 = a1 + 180\n", + "layer = (1, 0)\n", + "\n", + "ports1 = [\n", + " gf.Port(f\"top_{i}\", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)\n", + " for i in range(N)\n", + "]\n", + "ports2 = [\n", + " gf.Port(f\"bot_{i}\", center=(xs2[i], dy), width=0.5, orientation=a2, layer=layer)\n", + " for i in range(N)\n", + "]\n", + "\n", + "routes = gf.routing.get_bundle(\n", + " ports1,\n", + " ports2,\n", + " path_length_match_loops=1,\n", + " path_length_match_modify_segment_i=-2,\n", + " end_straight_length=800,\n", + ")\n", + "\n", + "for route in routes:\n", + " c.add(route.references)\n", + " print(route.length)\n", + "c.plot_klayout()\n", + "c.show()" + ] + }, + { + "cell_type": "markdown", + "id": "7868c12c", + "metadata": { + "id": "7868c12c" + }, + "source": [ + "### path_length_match with extra length\n", + "\n", + "You can also add some extra length to all the routes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13eaa3ae", + "metadata": { + "id": "13eaa3ae" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_bundle_path_length_match_extra_length\")\n", + "\n", + "dy = 2000.0\n", + "xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]\n", + "\n", + "pitch = 100.0\n", + "N = len(xs1)\n", + "xs2 = [-20 + i * pitch for i in range(N)]\n", + "\n", + "a1 = 90\n", + "a2 = a1 + 180\n", + "layer = (1, 0)\n", + "\n", + "ports1 = [\n", + " gf.Port(f\"top_{i}\", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)\n", + " for i in range(N)\n", + "]\n", + "ports2 = [\n", + " gf.Port(f\"bot_{i}\", center=(xs2[i], dy), width=0.5, orientation=a2, layer=layer)\n", + " for i in range(N)\n", + "]\n", + "\n", + "routes = gf.routing.get_bundle(\n", + " ports1,\n", + " ports2,\n", + " path_length_match_extra_length=44,\n", + " path_length_match_loops=2,\n", + " end_straight_length=800,\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + " print(route.length)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "b64d4782", + "metadata": { + "id": "b64d4782" + }, + "source": [ + "### path length match with extra loops\n", + "\n", + "You can also increase the number of loops" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c84c94e", + "metadata": { + "id": "2c84c94e" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_route_path_length_match_nb_loops\")\n", + "\n", + "dy = 2000.0\n", + "xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]\n", + "\n", + "pitch = 200.0\n", + "N = len(xs1)\n", + "xs2 = [-20 + i * pitch for i in range(N)]\n", + "\n", + "a1 = 90\n", + "a2 = a1 + 180\n", + "layer = (1, 0)\n", + "\n", + "ports1 = [\n", + " gf.Port(f\"top_{i}\", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)\n", + " for i in range(N)\n", + "]\n", + "ports2 = [\n", + " gf.Port(f\"bot_{i}\", center=(xs2[i], dy), width=0.5, orientation=a2, layer=layer)\n", + " for i in range(N)\n", + "]\n", + "\n", + "routes = gf.routing.get_bundle(\n", + " ports1,\n", + " ports2,\n", + " path_length_match_loops=2,\n", + " auto_widen=False,\n", + " end_straight_length=800,\n", + " separation=30,\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + " print(route.length)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "b59aed82", + "metadata": { + "id": "b59aed82" + }, + "source": [ + "Sometimes you need to modify `separation` to ensure waveguides don't overlap." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59b1c2e2", + "metadata": { + "id": "59b1c2e2" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"problem_path_length_match\")\n", + "c1 = c << gf.components.straight_array(spacing=90)\n", + "c2 = c << gf.components.straight_array(spacing=5)\n", + "c2.movex(200)\n", + "c1.y = 0\n", + "c2.y = 0\n", + "\n", + "routes = gf.routing.get_bundle(\n", + " c1.get_ports_list(orientation=0),\n", + " c2.get_ports_list(orientation=180),\n", + " end_straight_length=0,\n", + " start_straight_length=0,\n", + " separation=30, # not enough\n", + " radius=5,\n", + " path_length_match_loops=1,\n", + ")\n", + "\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "682ce6c1", + "metadata": { + "id": "682ce6c1" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"solution_path_length_match\")\n", + "c1 = c << gf.components.straight_array(spacing=90)\n", + "c2 = c << gf.components.straight_array(spacing=5)\n", + "c2.movex(200)\n", + "c1.y = 0\n", + "c2.y = 0\n", + "\n", + "routes = gf.routing.get_bundle(\n", + " c1.get_ports_list(orientation=0),\n", + " c2.get_ports_list(orientation=180),\n", + " end_straight_length=0,\n", + " start_straight_length=0,\n", + " separation=80, # increased\n", + " path_length_match_loops=1,\n", + " radius=5,\n", + ")\n", + "\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "0442b264", + "metadata": { + "id": "0442b264" + }, + "source": [ + "### get bundle with different orientation ports\n", + "\n", + "When trying to route ports with different orientations you need to bring them to a common `x` or `y`\n", + "\n", + "\n", + "1. Use `route_ports_to_side` to bring all the ports to a common angle orientation and x or y.\n", + "2. Use `get_bundle` to connect to the other group of ports." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3adaa8f", + "metadata": { + "id": "c3adaa8f" + }, + "outputs": [], + "source": [ + "from gdsfactory.samples.big_device import big_device\n", + "\n", + "c = gf.Component(\"sample_route\")\n", + "c1 = c << big_device()\n", + "c2 = c << gf.components.grating_coupler_array(n=len(c1.ports), rotation=-90)\n", + "\n", + "routes, ports = gf.routing.route_ports_to_side(c1.ports, side=\"south\")\n", + "for route in routes:\n", + " c.add(route.references)\n", + "\n", + "c2.ymin = -600\n", + "c2.x = 0\n", + "\n", + "routes = gf.routing.get_bundle(ports, c2.ports)\n", + "for route in routes:\n", + " c.add(route.references)\n", + "\n", + "c.plot_klayout()\n", + "c.show()" + ] + }, + { + "cell_type": "markdown", + "id": "d50567e7", + "metadata": { + "id": "d50567e7" + }, + "source": [ + "## get_bundle_from_steps\n", + "\n", + "This is a manual version of `get_bundle` that is more convenient than defining the waypoints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e90c02ca", + "metadata": { + "id": "e90c02ca" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_route_from_steps_sample\")\n", + "w = gf.components.array(\n", + " partial(gf.components.straight, layer=(2, 0)),\n", + " rows=3,\n", + " columns=1,\n", + " spacing=(0, 50),\n", + ")\n", + "\n", + "left = c << w\n", + "right = c << w\n", + "right.move((200, 100))\n", + "p1 = left.get_ports_list(orientation=0)\n", + "p2 = right.get_ports_list(orientation=180)\n", + "\n", + "routes = gf.routing.get_bundle_from_steps(\n", + " p1,\n", + " p2,\n", + " steps=[{\"x\": 150}],\n", + ")\n", + "\n", + "for route in routes:\n", + " c.add(route.references)\n", + "\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9kxaUzRmtJty", + "metadata": { + "id": "9kxaUzRmtJty" + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dd481d4f", + "metadata": { + "id": "dd481d4f" + }, + "source": [ + "# Routing to IO\n", + "\n", + "## Routing electrical\n", + "\n", + "For routing low speed DC electrical ports you can use sharp corners instead of smooth bends.\n", + "\n", + "You can also define `port.orientation = None` to ignore the port orientation for low speed DC ports." + ] + }, + { + "cell_type": "markdown", + "id": "4ebaf7f0", + "metadata": { + "id": "4ebaf7f0" + }, + "source": [ + "For single route between ports you can use `get_route_electrical`\n", + "\n", + "### get_route_electrical\n", + "\n", + "\n", + "`get_route_electrical` has `bend = wire_corner` with a 90deg bend corner." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6174479", + "metadata": { + "id": "f6174479" + }, + "outputs": [], + "source": [ + "from functools import partial\n", + "\n", + "import gdsfactory as gf\n", + "from gdsfactory.generic_tech import get_generic_pdk\n", + "from gdsfactory.samples.big_device import big_device\n", + "\n", + "gf.config.rich_output()\n", + "PDK = get_generic_pdk()\n", + "PDK.activate()\n", + "\n", + "c = gf.Component(\"pads\")\n", + "pt = c << gf.components.pad_array(orientation=270, columns=3)\n", + "pb = c << gf.components.pad_array(orientation=90, columns=3)\n", + "pt.move((70, 200))\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9584b957", + "metadata": { + "id": "9584b957" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"pads_with_routes_with_bends\")\n", + "pt = c << gf.components.pad_array(orientation=270, columns=3)\n", + "pb = c << gf.components.pad_array(orientation=90, columns=3)\n", + "pt.move((70, 200))\n", + "route = gf.routing.get_route_electrical(\n", + " pt.ports[\"e11\"], pb.ports[\"e11\"], bend=\"bend_euler\", radius=30\n", + ")\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "374342ed", + "metadata": { + "id": "374342ed" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"pads_with_routes_with_wire_corners\")\n", + "pt = c << gf.components.pad_array(orientation=270, columns=3)\n", + "pb = c << gf.components.pad_array(orientation=90, columns=3)\n", + "pt.move((70, 200))\n", + "route = gf.routing.get_route_electrical(\n", + " pt.ports[\"e11\"], pb.ports[\"e11\"], bend=\"wire_corner\"\n", + ")\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd134375", + "metadata": { + "id": "bd134375" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"pads_with_routes_with_wire_corners_no_orientation\")\n", + "pt = c << gf.components.pad_array(orientation=None, columns=3)\n", + "pb = c << gf.components.pad_array(orientation=None, columns=3)\n", + "pt.move((70, 200))\n", + "route = gf.routing.get_route_electrical(\n", + " pt.ports[\"e11\"], pb.ports[\"e11\"], bend=\"wire_corner\"\n", + ")\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7df51530", + "metadata": { + "id": "7df51530" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"multi-layer\")\n", + "columns = 2\n", + "ptop = c << gf.components.pad_array(columns=columns)\n", + "pbot = c << gf.components.pad_array(orientation=90, columns=columns)\n", + "\n", + "ptop.movex(300)\n", + "ptop.movey(300)\n", + "route = gf.routing.get_route_electrical_multilayer(\n", + " ptop.ports[\"e11\"],\n", + " pbot.ports[\"e11\"],\n", + " end_straight_length=100,\n", + ")\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "4421d52b", + "metadata": { + "id": "4421d52b" + }, + "source": [ + "There is also `bend = wire_corner45` for 45deg bend corner with parametrizable \"radius\":" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ad5812e", + "metadata": { + "id": "3ad5812e" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"pads_with_routes_with_wire_corner45\")\n", + "pt = c << gf.components.pad_array(orientation=270, columns=1)\n", + "pb = c << gf.components.pad_array(orientation=90, columns=1)\n", + "pt.move((300, 300))\n", + "route = gf.routing.get_route_electrical(\n", + " pt.ports[\"e11\"], pb.ports[\"e11\"], bend=\"wire_corner45\", radius=30\n", + ")\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aeff59d5", + "metadata": { + "id": "aeff59d5" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"pads_with_routes_with_wire_corner45\")\n", + "pt = c << gf.components.pad_array(orientation=270, columns=1)\n", + "pb = c << gf.components.pad_array(orientation=90, columns=1)\n", + "pt.move((300, 300))\n", + "route = gf.routing.get_route_electrical(\n", + " pt.ports[\"e11\"], pb.ports[\"e11\"], bend=\"wire_corner45\", radius=100\n", + ")\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "eee799ac", + "metadata": { + "id": "eee799ac" + }, + "source": [ + "### route_quad" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "875b7176", + "metadata": { + "id": "875b7176" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"pads_route_quad\")\n", + "pt = c << gf.components.pad_array(orientation=270, columns=3)\n", + "pb = c << gf.components.pad_array(orientation=90, columns=3)\n", + "pt.move((100, 200))\n", + "route = c << gf.routing.route_quad(pt.ports[\"e11\"], pb.ports[\"e11\"], layer=(49, 0))\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "dbf8904c", + "metadata": { + "id": "dbf8904c" + }, + "source": [ + "### get_route_from_steps" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "235071b3", + "metadata": { + "id": "235071b3" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"pads_route_from_steps\")\n", + "pt = c << gf.components.pad_array(orientation=270, columns=3)\n", + "pb = c << gf.components.pad_array(orientation=90, columns=3)\n", + "pt.move((100, 200))\n", + "route = gf.routing.get_route_from_steps(\n", + " pb.ports[\"e11\"],\n", + " pt.ports[\"e11\"],\n", + " steps=[\n", + " {\"y\": 200},\n", + " ],\n", + " cross_section=\"metal_routing\",\n", + " bend=gf.components.wire_corner,\n", + ")\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "561e463f", + "metadata": { + "id": "561e463f" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"pads_route_from_steps_None_orientation\")\n", + "pt = c << gf.components.pad_array(orientation=None, columns=3)\n", + "pb = c << gf.components.pad_array(orientation=None, columns=3)\n", + "pt.move((100, 200))\n", + "route = gf.routing.get_route_from_steps(\n", + " pb.ports[\"e11\"],\n", + " pt.ports[\"e11\"],\n", + " steps=[\n", + " {\"y\": 200},\n", + " ],\n", + " cross_section=\"metal_routing\",\n", + " bend=gf.components.wire_corner,\n", + ")\n", + "c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "6eef6569", + "metadata": { + "id": "6eef6569" + }, + "source": [ + "### get_bundle_electrical\n", + "\n", + "For routing groups of ports you can use `get_bundle` that returns a bundle of routes using a bundle router (also known as bus or river router)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "889626ce", + "metadata": { + "id": "889626ce" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"pads_bundle\")\n", + "pt = c << gf.components.pad_array(orientation=270, columns=3)\n", + "pb = c << gf.components.pad_array(orientation=90, columns=3)\n", + "pt.move((100, 200))\n", + "\n", + "routes = gf.routing.get_bundle_electrical(\n", + " pb.ports, pt.ports, end_straight_length=60, separation=30\n", + ")\n", + "\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "aa6005f4", + "metadata": { + "id": "aa6005f4" + }, + "source": [ + "### get_bundle_from_steps_electrical" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ecf7a272", + "metadata": { + "id": "ecf7a272" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"pads_bundle_steps\")\n", + "pt = c << gf.components.pad_array(\n", + " partial(gf.components.pad, size=(30, 30)),\n", + " orientation=270,\n", + " columns=3,\n", + " spacing=(50, 0),\n", + ")\n", + "pb = c << gf.components.pad_array(orientation=90, columns=3)\n", + "pt.move((300, 500))\n", + "\n", + "routes = gf.routing.get_bundle_from_steps_electrical(\n", + " pb.ports, pt.ports, end_straight_length=60, separation=30, steps=[{\"dy\": 100}]\n", + ")\n", + "\n", + "for route in routes:\n", + " c.add(route.references)\n", + "\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "55e35e6f", + "metadata": { + "id": "55e35e6f" + }, + "source": [ + "### get_bundle_electrical_multilayer\n", + "\n", + "To avoid metal crossings you can use one metal layer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22e855c1", + "metadata": { + "id": "22e855c1" + }, + "outputs": [], + "source": [ + "c = gf.Component(\"get_bundle_multi_layer\")\n", + "columns = 2\n", + "ptop = c << gf.components.pad_array(columns=columns)\n", + "pbot = c << gf.components.pad_array(orientation=90, columns=columns)\n", + "\n", + "ptop.movex(300)\n", + "ptop.movey(300)\n", + "routes = gf.routing.get_bundle_electrical_multilayer(\n", + " ptop.ports, pbot.ports, end_straight_length=100, separation=20\n", + ")\n", + "for route in routes:\n", + " c.add(route.references)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "1460b976", + "metadata": { + "id": "1460b976" + }, + "source": [ + "## Routing to pads\n", + "\n", + "You can also route to electrical pads." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd17582d", + "metadata": { + "id": "cd17582d" + }, + "outputs": [], + "source": [ + "c = gf.components.straight_heater_metal(length=100.0)\n", + "cc = gf.routing.add_pads_bot(component=c, port_names=(\"l_e4\", \"r_e4\"), fanout_length=50)\n", + "cc.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "230676fa", + "metadata": { + "id": "230676fa", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "c = gf.components.straight_heater_metal(length=100.0)\n", + "cc = gf.routing.add_pads_top(component=c)\n", + "cc.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e28720d5", + "metadata": { + "id": "e28720d5" + }, + "outputs": [], + "source": [ + "c = gf.components.straight_heater_metal(length=100.0)\n", + "cc = gf.routing.add_pads_top(component=c, port_names=(\"l_e2\", \"r_e2\"))\n", + "cc.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33340c1f", + "metadata": { + "id": "33340c1f" + }, + "outputs": [], + "source": [ + "n = west = north = south = east = 10\n", + "spacing = 20\n", + "c = gf.components.nxn(\n", + " xsize=n * spacing,\n", + " ysize=n * spacing,\n", + " west=west,\n", + " east=east,\n", + " north=north,\n", + " south=south,\n", + " port_type=\"electrical\",\n", + " wg_width=10,\n", + ")\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13fd2656", + "metadata": { + "id": "13fd2656" + }, + "outputs": [], + "source": [ + "cc = gf.routing.add_pads_top(component=c)\n", + "cc.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "daccc0ba", + "metadata": { + "id": "daccc0ba" + }, + "source": [ + "## Routing to optical terminations\n", + "\n", + "### Route to Fiber Array\n", + "\n", + "You can route to a fiber array." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8f188ca", + "metadata": { + "id": "f8f188ca" + }, + "outputs": [], + "source": [ + "component = big_device(nports=10)\n", + "c = gf.routing.add_fiber_array(component=component, radius=10.0, fanout_length=60.0)\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "e74d5ed4", + "metadata": { + "id": "e74d5ed4" + }, + "source": [ + "You can also mix and match TE and TM grating couplers. Notice that the `TM` polarization grating coupler is bigger." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c477819", + "metadata": { + "id": "2c477819" + }, + "outputs": [], + "source": [ + "import gdsfactory as gf\n", + "\n", + "c = gf.components.mzi_phase_shifter()\n", + "gcte = gf.components.grating_coupler_te\n", + "\n", + "cc = gf.routing.add_fiber_array(\n", + " component=c,\n", + " optical_routing_type=2,\n", + " grating_coupler=[\n", + " gf.components.grating_coupler_te,\n", + " gf.components.grating_coupler_tm,\n", + " ],\n", + " radius=20,\n", + ")\n", + "cc.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "7e2fbecd", + "metadata": { + "id": "7e2fbecd" + }, + "source": [ + "### Route to Single fibers\n", + "\n", + "You can route to a single fiber input and single fiber output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "165f7c06", + "metadata": { + "id": "165f7c06" + }, + "outputs": [], + "source": [ + "c = gf.components.ring_single()\n", + "cc = gf.routing.add_fiber_single(component=c)\n", + "cc.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "ca763ce2", + "metadata": { + "id": "ca763ce2" + }, + "source": [ + "### Route to edge couplers\n", + "\n", + "You can also route Edge couplers to a fiber array or to both sides of the chip.\n", + "\n", + "For routing to both sides you can follow different strategies:\n", + "\n", + "1. Place the edge couplers and route your components to the edge couplers.\n", + "2. Extend your component ports to each side.\n", + "3. Anything you imagine ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f60030d", + "metadata": { + "id": "2f60030d", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import gdsfactory as gf\n", + "from gdsfactory.generic_tech import LAYER\n", + "\n", + "\n", + "@gf.cell\n", + "def sample_die(size=(8e3, 40e3), y_spacing: float = 10) -> gf.Component:\n", + " \"\"\"Returns a sample die\n", + "\n", + " Args:\n", + " size: size of the die.\n", + " y_spacing: spacing between components.\n", + "\n", + " Returns:\n", + " c: a sample die.\n", + "\n", + " \"\"\"\n", + " c = gf.Component()\n", + "\n", + " die = c << gf.c.rectangle(size=np.array(size), layer=LAYER.FLOORPLAN, centered=True)\n", + " die = c << gf.c.rectangle(\n", + " size=np.array(size) - 2 * np.array((50, 50)),\n", + " layer=LAYER.FLOORPLAN,\n", + " centered=True,\n", + " )\n", + " ymin = die.ymin\n", + " ec = gf.components.edge_coupler_silicon()\n", + "\n", + " cells = gf.components.cells\n", + " skip = [\n", + " \"component_lattice\",\n", + " \"component_sequence\",\n", + " \"extend_port\",\n", + " \"extend_ports_list\",\n", + " \"die\",\n", + " \"wafer\",\n", + " ]\n", + " for component_name in skip:\n", + " cells.pop(component_name, None)\n", + "\n", + " for component in cells.values():\n", + " ci = component()\n", + " ci = (\n", + " gf.routing.add_pads_top(\n", + " ci,\n", + " pad=gf.components.pad,\n", + " pad_spacing=150,\n", + " )\n", + " if ci.get_ports_list(port_type=\"electrical\")\n", + " else ci\n", + " )\n", + " ref = c << ci\n", + " ref.ymin = ymin\n", + " ref.x = 0\n", + " ymin = ref.ymax + y_spacing\n", + "\n", + " routes_left, ports_left = gf.routing.route_ports_to_side(\n", + " ref.get_ports_list(orientation=180),\n", + " cross_section=\"strip\",\n", + " side=\"west\",\n", + " x=die.xmin + ec.xsize,\n", + " )\n", + " for route in routes_left:\n", + " c.add(route.references)\n", + "\n", + " routes_right, ports_right = gf.routing.route_ports_to_side(\n", + " ref.get_ports_list(orientation=0),\n", + " cross_section=\"strip\",\n", + " x=die.xmax - ec.xsize,\n", + " side=\"east\",\n", + " )\n", + " for route in routes_right:\n", + " c.add(route.references)\n", + "\n", + " for port in ports_right:\n", + " ref = c << ec\n", + " ref.connect(\"o1\", port)\n", + " text = c << gf.c.text(\n", + " text=f\"{ci.name}-{port.name.split('_')[0]}\", size=10, layer=LAYER.MTOP\n", + " )\n", + " text.xmax = ref.xmax - 10\n", + " text.y = ref.y\n", + "\n", + " for port in ports_left:\n", + " ref = c << ec\n", + " ref.connect(\"o1\", port)\n", + " text = c << gf.c.text(\n", + " text=f\"{ci.name}-{port.name.split('_')[0]}\", size=10, layer=LAYER.MTOP\n", + " )\n", + " text.xmin = ref.xmin + 10\n", + " text.y = ref.y\n", + "\n", + " return c\n", + "\n", + "\n", + "if __name__ == \"__main__\":\n", + " c = sample_die(cache=False)\n", + " c.show(show_ports=True)\n", + " c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc012d22", + "metadata": { + "id": "cc012d22" + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "26dc1285", + "metadata": { + "id": "26dc1285" + }, + "source": [ + "# PDK examples\n", + "\n", + "Different PDKs have different component libraries, design rules and layer stacks (GDS layers, materials and thickness).\n", + "\n", + "When you install a PDK you have to make sure you also installed the correct version of gdsfactory.\n", + "\n", + "Notice that some PDKs may have require different gdsfactory versions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c90e444", + "metadata": { + "id": "2c90e444", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "from collections.abc import Callable\n", + "from functools import partial\n", + "\n", + "from pydantic import BaseModel\n", + "\n", + "import gdsfactory as gf\n", + "from gdsfactory.add_pins import add_pin_rectangle_inside\n", + "from gdsfactory.component import Component\n", + "from gdsfactory.config import CONF\n", + "from gdsfactory.cross_section import cross_section\n", + "from gdsfactory.technology import (\n", + " LayerLevel,\n", + " LayerStack,\n", + " LayerView,\n", + " LayerViews,\n", + ")\n", + "from gdsfactory.typings import Layer\n", + "from gdsfactory.config import print_version_pdks, print_version_plugins\n", + "from gdsfactory.generic_tech import get_generic_pdk\n", + "\n", + "gf.config.rich_output()\n", + "nm = 1e-3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ea3e96ab", + "metadata": { + "id": "ea3e96ab" + }, + "outputs": [], + "source": [ + "CONF.display_type = \"klayout\"\n", + "\n", + "p = gf.get_active_pdk()\n", + "p.name" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "915d35d0", + "metadata": { + "id": "915d35d0" + }, + "outputs": [], + "source": [ + "print_version_plugins()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4db183b8", + "metadata": { + "id": "4db183b8" + }, + "outputs": [], + "source": [ + "print_version_pdks()" + ] + }, + { + "cell_type": "markdown", + "id": "f5709771", + "metadata": { + "id": "f5709771", + "lines_to_next_cell": 2 + }, + "source": [ + "### FabA\n", + "\n", + "FabA only has one waveguide layer available that is defined in GDS layer (30, 0)\n", + "\n", + "The waveguide traces are 2um wide." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85cf7ffa", + "metadata": { + "id": "85cf7ffa" + }, + "outputs": [], + "source": [ + "class LayerMap(BaseModel):\n", + " WG: Layer = (34, 0)\n", + " SLAB150: Layer = (2, 0)\n", + " DEVREC: Layer = (68, 0)\n", + " PORT: Layer = (1, 10)\n", + " PORTE: Layer = (1, 11)\n", + " TE: Layer = (203, 0)\n", + " TM: Layer = (204, 0)\n", + " TEXT: Layer = (66, 0)\n", + "\n", + "\n", + "LAYER = LayerMap()\n", + "\n", + "\n", + "class FabALayerViews(LayerViews):\n", + " WG = LayerView(color=\"gold\")\n", + " SLAB150 = LayerView(color=\"red\")\n", + " TE = LayerView(color=\"green\")\n", + "\n", + "\n", + "LAYER_VIEWS = FabALayerViews(layer_map=LAYER.dict())\n", + "\n", + "\n", + "def get_layer_stack_faba(\n", + " thickness_wg: float = 500 * nm, thickness_slab: float = 150 * nm\n", + ") -> LayerStack:\n", + " \"\"\"Returns fabA LayerStack\"\"\"\n", + "\n", + " class FabALayerStack(LayerStack):\n", + " strip = LayerLevel(\n", + " layer=LAYER.WG,\n", + " thickness=thickness_wg,\n", + " zmin=0.0,\n", + " material=\"si\",\n", + " )\n", + " strip2 = LayerLevel(\n", + " layer=LAYER.SLAB150,\n", + " thickness=thickness_slab,\n", + " zmin=0.0,\n", + " material=\"si\",\n", + " )\n", + "\n", + " return FabALayerStack()\n", + "\n", + "\n", + "LAYER_STACK = get_layer_stack_faba()\n", + "\n", + "WIDTH = 2\n", + "\n", + "# Specify a cross_section to use\n", + "strip = partial(gf.cross_section.cross_section, width=WIDTH, layer=LAYER.WG)\n", + "\n", + "mmi1x2 = partial(\n", + " gf.components.mmi1x2,\n", + " width=WIDTH,\n", + " width_taper=WIDTH,\n", + " width_mmi=3 * WIDTH,\n", + " cross_section=strip,\n", + ")\n", + "\n", + "generic_pdk = get_generic_pdk()\n", + "\n", + "fab_a = gf.Pdk(\n", + " name=\"Fab_A\",\n", + " cells=dict(mmi1x2=mmi1x2),\n", + " cross_sections=dict(strip=strip),\n", + " layers=LAYER.dict(),\n", + " base_pdk=generic_pdk,\n", + " sparameters_path=gf.config.sparameters_path,\n", + " layer_views=LAYER_VIEWS,\n", + " layer_stack=LAYER_STACK,\n", + ")\n", + "fab_a.activate()\n", + "\n", + "gc = partial(\n", + " gf.components.grating_coupler_elliptical_te, layer=LAYER.WG, cross_section=strip\n", + ")\n", + "\n", + "c = gf.components.mzi()\n", + "c_gc = gf.routing.add_fiber_array(component=c, grating_coupler=gc, with_loopback=False)\n", + "c_gc.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f070fd1", + "metadata": { + "id": "9f070fd1" + }, + "outputs": [], + "source": [ + "scene = c_gc.to_3d()\n", + "scene.show(show_ports=True)" + ] + }, + { + "cell_type": "markdown", + "id": "f779a815", + "metadata": { + "id": "f779a815" + }, + "source": [ + "### FabB\n", + "\n", + "FabB has photonic waveguides that require rectangular cladding layers to avoid dopants\n", + "\n", + "Lets say that the waveguides are defined in layer (2, 0) and are 0.3um wide, 1um thick\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8d3ab3ee", + "metadata": { + "id": "8d3ab3ee" + }, + "outputs": [], + "source": [ + "nm = 1e-3\n", + "\n", + "\n", + "class LayerMap(BaseModel):\n", + " WG: Layer = (2, 0)\n", + " SLAB150: Layer = (3, 0)\n", + " DEVREC: Layer = (68, 0)\n", + " PORT: Layer = (1, 10)\n", + " PORTE: Layer = (1, 11)\n", + " TE: Layer = (203, 0)\n", + " TM: Layer = (204, 0)\n", + " TEXT: Layer = (66, 0)\n", + " LABEL: Layer = (201, 0)\n", + " DOPING_BLOCK1: Layer = (61, 0)\n", + " DOPING_BLOCK2: Layer = (62, 0)\n", + "\n", + "\n", + "LAYER = LayerMap()\n", + "\n", + "\n", + "# The LayerViews class supports grouping LayerViews within each other.\n", + "# These groups are maintained when exporting a LayerViews object to a KLayout layer properties (.lyp) file.\n", + "class FabBLayerViews(LayerViews):\n", + " WG = LayerView(color=\"red\")\n", + " SLAB150 = LayerView(color=\"blue\")\n", + " TE = LayerView(color=\"green\")\n", + " PORT = LayerView(color=\"green\", alpha=0)\n", + "\n", + " class DopingBlockGroup(LayerView):\n", + " DOPING_BLOCK1 = LayerView(color=\"green\", alpha=0)\n", + " DOPING_BLOCK2 = LayerView(color=\"green\", alpha=0)\n", + "\n", + " DopingBlocks = DopingBlockGroup()\n", + "\n", + "\n", + "LAYER_VIEWS = FabBLayerViews(layer_map=LAYER)\n", + "\n", + "\n", + "def get_layer_stack_fab_b(\n", + " thickness_wg: float = 1000 * nm, thickness_slab: float = 150 * nm\n", + ") -> LayerStack:\n", + " \"\"\"Returns fabA LayerStack.\"\"\"\n", + "\n", + " class FabBLayerStack(LayerStack):\n", + " strip = LayerLevel(\n", + " layer=LAYER.WG,\n", + " thickness=thickness_wg,\n", + " zmin=0.0,\n", + " material=\"si\",\n", + " )\n", + " strip2 = LayerLevel(\n", + " layer=LAYER.SLAB150,\n", + " thickness=thickness_slab,\n", + " zmin=0.0,\n", + " material=\"si\",\n", + " )\n", + "\n", + " return FabBLayerStack()\n", + "\n", + "\n", + "LAYER_STACK = get_layer_stack_fab_b()\n", + "\n", + "\n", + "WIDTH = 0.3\n", + "BBOX_LAYERS = (LAYER.DOPING_BLOCK1, LAYER.DOPING_BLOCK2)\n", + "BBOX_OFFSETS = (3, 3)\n", + "\n", + "# use cladding_layers and cladding_offsets if the foundry prefers conformal blocking doping layers instead of squared\n", + "# bbox_layers and bbox_offsets makes rectangular waveguides.\n", + "strip = partial(\n", + " gf.cross_section.cross_section,\n", + " width=WIDTH,\n", + " layer=LAYER.WG,\n", + " # bbox_layers=BBOX_LAYERS,\n", + " # bbox_offsets=BBOX_OFFSETS,\n", + " cladding_layers=BBOX_LAYERS,\n", + " cladding_offsets=BBOX_OFFSETS,\n", + ")\n", + "\n", + "straight = partial(gf.components.straight, cross_section=strip)\n", + "bend_euler = partial(gf.components.bend_euler, cross_section=strip)\n", + "mmi1x2 = partial(\n", + " gf.components.mmi1x2,\n", + " cross_section=strip,\n", + " width=WIDTH,\n", + " width_taper=WIDTH,\n", + " width_mmi=4 * WIDTH,\n", + ")\n", + "mzi = partial(gf.components.mzi, cross_section=strip, splitter=mmi1x2)\n", + "gc = partial(\n", + " gf.components.grating_coupler_elliptical_te, layer=LAYER.WG, cross_section=strip\n", + ")\n", + "\n", + "cells = dict(\n", + " gc=gc,\n", + " mzi=mzi,\n", + " mmi1x2=mmi1x2,\n", + " bend_euler=bend_euler,\n", + " straight=straight,\n", + " taper=gf.components.taper,\n", + ")\n", + "cross_sections = dict(strip=strip)\n", + "\n", + "pdk = gf.Pdk(\n", + " name=\"fab_b\",\n", + " cells=cells,\n", + " cross_sections=cross_sections,\n", + " layers=LAYER.dict(),\n", + " sparameters_path=gf.config.sparameters_path,\n", + " layer_views=LAYER_VIEWS,\n", + " layer_stack=LAYER_STACK,\n", + ")\n", + "pdk.activate()\n", + "\n", + "\n", + "c = mzi()\n", + "wg_gc = gf.routing.add_fiber_array(\n", + " component=c, grating_coupler=gc, cross_section=strip, with_loopback=False\n", + ")\n", + "wg_gc.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1e7bab9", + "metadata": { + "id": "c1e7bab9" + }, + "outputs": [], + "source": [ + "scene = wg_gc.to_3d()\n", + "scene.show(show_ports=True)" + ] + }, + { + "cell_type": "markdown", + "id": "15022c72", + "metadata": { + "id": "15022c72" + }, + "source": [ + "### FabC\n", + "\n", + "Lets assume that fab C has similar technology to the generic PDK in gdsfactory and that you just want to remap some layers, and adjust the widths.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "413b8b61", + "metadata": { + "id": "413b8b61", + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "nm = 1e-3\n", + "\n", + "\n", + "class LayerMap(BaseModel):\n", + " WG: Layer = (10, 1)\n", + " WG_CLAD: Layer = (10, 2)\n", + " WGN: Layer = (34, 0)\n", + " WGN_CLAD: Layer = (36, 0)\n", + " SLAB150: Layer = (2, 0)\n", + " DEVREC: Layer = (68, 0)\n", + " PORT: Layer = (1, 10)\n", + " PORTE: Layer = (1, 11)\n", + " TE: Layer = (203, 0)\n", + " TM: Layer = (204, 0)\n", + " TEXT: Layer = (66, 0)\n", + " LABEL: Layer = (201, 0)\n", + "\n", + "\n", + "LAYER = LayerMap()\n", + "WIDTH_NITRIDE_OBAND = 0.9\n", + "WIDTH_NITRIDE_CBAND = 1.0\n", + "PORT_TYPE_TO_LAYER = dict(optical=(100, 0))\n", + "\n", + "\n", + "# This is something you usually define in KLayout\n", + "class FabCLayerViews(LayerViews):\n", + " WG = LayerView(color=\"black\")\n", + " SLAB150 = LayerView(color=\"blue\")\n", + " WGN = LayerView(color=\"orange\")\n", + " WGN_CLAD = LayerView(color=\"blue\", alpha=0, visible=False)\n", + "\n", + " class SimulationGroup(LayerView):\n", + " TE = LayerView(color=\"green\")\n", + " PORT = LayerView(color=\"green\", alpha=0)\n", + "\n", + " Simulation = SimulationGroup()\n", + "\n", + " class DopingGroup(LayerView):\n", + " DOPING_BLOCK1 = LayerView(color=\"green\", alpha=0, visible=False)\n", + " DOPING_BLOCK2 = LayerView(color=\"green\", alpha=0, visible=False)\n", + "\n", + " Doping = DopingGroup()\n", + "\n", + "\n", + "LAYER_VIEWS = FabCLayerViews(layer_map=LAYER)\n", + "\n", + "\n", + "def get_layer_stack_fab_c(\n", + " thickness_wg: float = 350.0 * nm, thickness_clad: float = 3.0\n", + ") -> LayerStack:\n", + " \"\"\"Returns generic LayerStack\"\"\"\n", + "\n", + " class FabCLayerStack(LayerStack):\n", + " core = LayerLevel(\n", + " layer=LAYER.WGN,\n", + " thickness=thickness_wg,\n", + " zmin=0,\n", + " )\n", + " clad = LayerLevel(\n", + " layer=LAYER.WGN_CLAD,\n", + " thickness=thickness_clad,\n", + " zmin=0,\n", + " )\n", + "\n", + " return FabCLayerStack()\n", + "\n", + "\n", + "LAYER_STACK = get_layer_stack_fab_c()\n", + "\n", + "\n", + "def add_pins(\n", + " component: Component,\n", + " function: Callable = add_pin_rectangle_inside,\n", + " pin_length: float = 0.5,\n", + " port_layer: Layer = LAYER.PORT,\n", + " **kwargs,\n", + ") -> Component:\n", + " \"\"\"Add Pin port markers.\n", + "\n", + " Args:\n", + " component: to add ports.\n", + " function: to add pins.\n", + " pin_length: in um.\n", + " port_layer: spec.\n", + " kwargs: function kwargs.\n", + " \"\"\"\n", + " for p in component.ports.values():\n", + " function(\n", + " component=component,\n", + " port=p,\n", + " layer=port_layer,\n", + " layer_label=port_layer,\n", + " pin_length=pin_length,\n", + " **kwargs,\n", + " )\n", + " return component\n", + "\n", + "\n", + "# cross_section constants\n", + "bbox_layers = [LAYER.WGN_CLAD]\n", + "bbox_offsets = [3]\n", + "\n", + "# Nitride Cband\n", + "xs_nc = partial(\n", + " cross_section,\n", + " width=WIDTH_NITRIDE_CBAND,\n", + " layer=LAYER.WGN,\n", + " bbox_layers=bbox_layers,\n", + " bbox_offsets=bbox_offsets,\n", + " add_pins=add_pins,\n", + ")\n", + "# Nitride Oband\n", + "xs_no = partial(\n", + " cross_section,\n", + " width=WIDTH_NITRIDE_OBAND,\n", + " layer=LAYER.WGN,\n", + " bbox_layers=bbox_layers,\n", + " bbox_offsets=bbox_offsets,\n", + " add_pins=add_pins,\n", + ")\n", + "\n", + "\n", + "cross_sections = dict(xs_nc=xs_nc, xs_no=xs_no, strip=xs_nc)\n", + "\n", + "# LEAF cells have pins\n", + "mmi1x2_nc = partial(\n", + " gf.components.mmi1x2,\n", + " width=WIDTH_NITRIDE_CBAND,\n", + " cross_section=xs_nc,\n", + ")\n", + "mmi1x2_no = partial(\n", + " gf.components.mmi1x2,\n", + " width=WIDTH_NITRIDE_OBAND,\n", + " cross_section=xs_no,\n", + ")\n", + "bend_euler_nc = partial(\n", + " gf.components.bend_euler,\n", + " cross_section=xs_nc,\n", + ")\n", + "straight_nc = partial(\n", + " gf.components.straight,\n", + " cross_section=xs_nc,\n", + ")\n", + "bend_euler_no = partial(\n", + " gf.components.bend_euler,\n", + " cross_section=xs_no,\n", + ")\n", + "straight_no = partial(\n", + " gf.components.straight,\n", + " cross_section=xs_no,\n", + ")\n", + "\n", + "gc_nc = partial(\n", + " gf.components.grating_coupler_elliptical_te,\n", + " grating_line_width=0.6,\n", + " layer=LAYER.WGN,\n", + " cross_section=xs_nc,\n", + ")\n", + "\n", + "# HIERARCHICAL cells are made of leaf cells\n", + "mzi_nc = partial(\n", + " gf.components.mzi,\n", + " cross_section=xs_nc,\n", + " splitter=mmi1x2_nc,\n", + " straight=straight_nc,\n", + " bend=bend_euler_nc,\n", + ")\n", + "mzi_no = partial(\n", + " gf.components.mzi,\n", + " cross_section=xs_no,\n", + " splitter=mmi1x2_no,\n", + " straight=straight_no,\n", + " bend=bend_euler_no,\n", + ")\n", + "\n", + "\n", + "cells = dict(\n", + " mmi1x2_nc=mmi1x2_nc,\n", + " mmi1x2_no=mmi1x2_no,\n", + " bend_euler_nc=bend_euler_nc,\n", + " bend_euler_no=bend_euler_no,\n", + " straight_nc=straight_nc,\n", + " straight_no=straight_no,\n", + " gc_nc=gc_nc,\n", + " mzi_nc=mzi_nc,\n", + " mzi_no=mzi_no,\n", + ")\n", + "\n", + "pdk = gf.Pdk(\n", + " name=\"fab_c\",\n", + " cells=cells,\n", + " cross_sections=cross_sections,\n", + " layers=LAYER.dict(),\n", + " sparameters_path=gf.config.sparameters_path,\n", + " layer_views=LAYER_VIEWS,\n", + " layer_stack=LAYER_STACK,\n", + ")\n", + "pdk.activate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57d1ed8d", + "metadata": { + "id": "57d1ed8d" + }, + "outputs": [], + "source": [ + "LAYER_VIEWS.layer_map.values()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c209824d", + "metadata": { + "id": "c209824d" + }, + "outputs": [], + "source": [ + "mzi = mzi_nc()\n", + "mzi_gc = gf.routing.add_fiber_single(\n", + " component=mzi,\n", + " grating_coupler=gc_nc,\n", + " cross_section=xs_nc,\n", + " optical_routing_type=1,\n", + " straight=straight_nc,\n", + " bend=bend_euler_nc,\n", + ")\n", + "mzi_gc.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "abe29fdd", + "metadata": { + "id": "abe29fdd" + }, + "outputs": [], + "source": [ + "c = mzi_gc.to_3d()\n", + "c.show(show_ports=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "683f676b", + "metadata": { + "id": "683f676b" + }, + "outputs": [], + "source": [ + "ls = get_layer_stack_fab_c()" + ] + }, + { + "cell_type": "markdown", + "id": "7e5e5867", + "metadata": { + "id": "7e5e5867" + }, + "source": [ + "# Import PDK\n", + "\n", + "## Import PDK from GDS files\n", + "\n", + "To import a PDK from GDS files into gdsfactory you need:\n", + "\n", + "- GDS file with all the cells that you want to import in the PDK (or separate GDS files, where each file contains a GDS design).\n", + "\n", + "Ideally you also get:\n", + "\n", + "- Klayout layer properties files, to define the Layers that you can use when creating new custom Components. This allows you to define the LayerMap that maps Layer_name to (GDS_LAYER, GDS_PuRPOSE).\n", + "- layer_stack information (material index, thickness, z positions of each layer).\n", + "- DRC rules. If you don't get this you can easily build one using klayout.\n", + "\n", + "GDS files are great for describing geometry thanks to the concept of References, where you store any geometry only once in memory.\n", + "\n", + "For storing device metadata (settings, port locations, port widths, port angles ...) there is no clear standard.\n", + "\n", + "`gdsfactory` stores the that metadata in `YAML` files, and also has functions to add pins\n", + "\n", + "- `Component.write_gds()` saves GDS\n", + "- `Component.write_gds_metadata()` save GDS + YAML metadata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "183c5ddd", + "metadata": { + "id": "183c5ddd" + }, + "outputs": [], + "source": [ + "\n", + "# Lets generate the script that we need to have to each GDS cell into gdsfactory\n", + "import gdsfactory as gf\n", + "from gdsfactory.config import PATH\n", + "from gdsfactory.generic_tech import get_generic_pdk\n", + "from gdsfactory.technology import lyp_to_dataclass\n", + "\n", + "gf.config.rich_output()\n", + "PDK = get_generic_pdk()\n", + "PDK.activate()\n", + "\n", + "c = gf.components.mzi()\n", + "c.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "a03cec61", + "metadata": { + "id": "a03cec61" + }, + "source": [ + "You can write **GDS** files only" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53cf4e8c", + "metadata": { + "id": "53cf4e8c" + }, + "outputs": [], + "source": [ + "gdspath = c.write_gds(\"extra/mzi.gds\")" + ] + }, + { + "cell_type": "markdown", + "id": "5fa01ea9", + "metadata": { + "id": "5fa01ea9" + }, + "source": [ + "Or **GDS** with **YAML** metadata information (ports, settings, cells ...)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "caa08555", + "metadata": { + "id": "caa08555" + }, + "outputs": [], + "source": [ + "gdspath = c.write_gds(\"extra/mzi.gds\", with_metadata=True)" + ] + }, + { + "cell_type": "markdown", + "id": "9cfa42e7", + "metadata": { + "id": "9cfa42e7" + }, + "source": [ + "This created a `mzi.yml` file that contains:\n", + "- ports\n", + "- cells (flat list of cells)\n", + "- info (function name, module, changed settings, full settings, default settings)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "959d572e", + "metadata": { + "id": "959d572e" + }, + "outputs": [], + "source": [ + "c.metadata.keys()" + ] + }, + { + "cell_type": "markdown", + "id": "a0e0dfe7", + "metadata": { + "id": "a0e0dfe7" + }, + "source": [ + "You can read GDS files into gdsfactory thanks to the `import_gds` function\n", + "\n", + "`import_gds` reads the same GDS file from disk without losing any information" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1726460e", + "metadata": { + "id": "1726460e" + }, + "outputs": [], + "source": [ + "gf.clear_cache()\n", + "\n", + "c = gf.import_gds(gdspath, read_metadata=True)\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "973c7187", + "metadata": { + "id": "973c7187" + }, + "outputs": [], + "source": [ + "c2 = gf.import_gds(gdspath, name=\"mzi_sample\", read_metadata=True)\n", + "c2.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "278967bd", + "metadata": { + "id": "278967bd" + }, + "outputs": [], + "source": [ + "c2.name" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1eeda5b0", + "metadata": { + "id": "1eeda5b0" + }, + "outputs": [], + "source": [ + "c3 = gf.routing.add_fiber_single(c2)\n", + "c3.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20c1ce90", + "metadata": { + "id": "20c1ce90" + }, + "outputs": [], + "source": [ + "gdspath = c3.write_gds(\"extra/pdk.gds\", with_metadata=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df1e1350", + "metadata": { + "id": "df1e1350" + }, + "outputs": [], + "source": [ + "gf.labels.write_labels.write_labels_klayout(gdspath, layer_label=(201, 0))" + ] + }, + { + "cell_type": "markdown", + "id": "4ca55908", + "metadata": { + "id": "4ca55908" + }, + "source": [ + "### add ports from pins\n", + "\n", + "Sometimes the GDS does not have YAML metadata, therefore you need to figure out the port locations, widths and orientations.\n", + "\n", + "gdsfactory provides you with functions that will add ports to the component by looking for pins shapes on a specific layers (port_markers or pins)\n", + "\n", + "There are different pin standards supported to automatically add ports to components:\n", + "\n", + "- PINs towards the inside of the port (port at the outer part of the PIN)\n", + "- PINs with half of the pin inside and half outside (port at the center of the PIN)\n", + "- PIN with only labels (no shapes). You have to manually specify the width of the port.\n", + "\n", + "\n", + "Lets add pins, save a GDS and then import it back." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02fa6caf", + "metadata": { + "id": "02fa6caf" + }, + "outputs": [], + "source": [ + "c = gf.components.straight(\n", + " decorator=gf.add_pins.add_pins\n", + ") # add pins inside the component\n", + "c.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39ff2902", + "metadata": { + "id": "39ff2902" + }, + "outputs": [], + "source": [ + "gdspath = c.write_gds(\"extra/wg.gds\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa6a3b7f", + "metadata": { + "id": "fa6a3b7f" + }, + "outputs": [], + "source": [ + "gf.clear_cache()\n", + "c2 = gf.import_gds(gdspath)\n", + "c2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba5de210", + "metadata": { + "id": "ba5de210" + }, + "outputs": [], + "source": [ + "c2.ports # import_gds does not automatically add the pins" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10362c7b", + "metadata": { + "id": "10362c7b" + }, + "outputs": [], + "source": [ + "c3 = gf.import_gds(gdspath, decorator=gf.add_ports.add_ports_from_markers_inside)\n", + "c3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c765788e", + "metadata": { + "id": "c765788e" + }, + "outputs": [], + "source": [ + "c3.ports" + ] + }, + { + "cell_type": "markdown", + "id": "36a8a926", + "metadata": { + "id": "36a8a926" + }, + "source": [ + "Foundries provide PDKs in different formats and commercial tools.\n", + "\n", + "The easiest way to import a PDK into gdsfactory is to:\n", + "\n", + "1. have each GDS cell into a separate GDS file\n", + "2. have one GDS file with all the cells inside\n", + "3. Have a KLayout layermap. Makes easier to create the layermap.\n", + "\n", + "With that you can easily create the PDK as as python package.\n", + "\n", + "Thanks to having a gdsfactory PDK as a python package you can:\n", + "\n", + "- version control your PDK using GIT to keep track of changes and work on a team\n", + " - write tests of your pdk components to avoid unwanted changes from one component to another.\n", + " - ensure you maintain the quality of the PDK with continuous integration checks\n", + " - pin the version of gdsfactory, so new updates of gdsfactory won't affect your code\n", + "- name your PDK version using [semantic versioning](https://semver.org/). For example patches increase the last number (0.0.1 -> 0.0.2)\n", + "- install your PDK easily `pip install pdk_fab_a` and easily interface with other tools\n", + "\n", + "\n", + "\n", + "To create a **Python** package you can start from a customizable template (thanks to cookiecutter)\n", + "\n", + "You can create a python package by running this 2 commands inside a terminal:\n", + "\n", + "```\n", + "pip install cookiecutter\n", + "cookiecutter https://github.com/joamatab/cookiecutter-pypackage-minimal\n", + "```\n", + "\n", + "It will ask you some questions to fill in the template (name of the package being the most important)\n", + "\n", + "\n", + "Then you can add the information about the GDS files and the Layers inside that package" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5274ec6b", + "metadata": { + "id": "5274ec6b" + }, + "outputs": [], + "source": [ + "print(lyp_to_dataclass(PATH.klayout_lyp))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aaf9b780", + "metadata": { + "id": "aaf9b780" + }, + "outputs": [], + "source": [ + "# lets create a sample PDK (for demo purposes only) using GDSfactory\n", + "# if the PDK is in a commercial tool you can also do this. Make sure you save a single pdk.gds\n", + "\n", + "sample_pdk_cells = gf.grid(\n", + " [\n", + " gf.components.straight,\n", + " gf.components.bend_euler,\n", + " gf.components.grating_coupler_elliptical,\n", + " ]\n", + ")\n", + "sample_pdk_cells.write_gds(\"extra/pdk.gds\")\n", + "sample_pdk_cells" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6f88d633", + "metadata": { + "id": "6f88d633" + }, + "outputs": [], + "source": [ + "sample_pdk_cells.get_dependencies()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e39db396", + "metadata": { + "id": "e39db396" + }, + "outputs": [], + "source": [ + "# we write the sample PDK into a single GDS file\n", + "gf.clear_cache()\n", + "gf.write_cells.write_cells(\n", + " gdspath=\"extra/pdk.gds\", dirpath=\"extra/gds\", recursively=True\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "adfde83a", + "metadata": { + "id": "adfde83a" + }, + "outputs": [], + "source": [ + "print(gf.write_cells.get_import_gds_script(\"extra/gds\"))" + ] + }, + { + "cell_type": "markdown", + "id": "e984571d", + "metadata": { + "id": "e984571d" + }, + "source": [ + "You can also include the code to plot each fix cell in the docstring." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52f08227", + "metadata": { + "id": "52f08227" + }, + "outputs": [], + "source": [ + "print(gf.write_cells.get_import_gds_script(\"extra/gds\", module=\"samplepdk.components\"))" + ] + }, + { + "cell_type": "markdown", + "id": "bfcc1326", + "metadata": { + "id": "bfcc1326" + }, + "source": [ + "## Import PDK from other python packages\n", + "\n", + "You can Write the cells to GDS and use the\n", + "\n", + "Ideally you also start transitioning your legacy code Pcells into gdsfactory syntax. It's a great way to learn the gdsfactory way!\n", + "\n", + "Here is some advice:\n", + "\n", + "- Ask your foundry for the gdsfactory PDK.\n", + "- Leverage the generic pdk cells available in gdsfactory.\n", + "- Write tests for your cells.\n", + "- Break the cells into small reusable functions.\n", + "- use GIT to track changes.\n", + "- review your code with your colleagues and other gdsfactory developers to get feedback. This is key to get better at coding gdsfactory.\n", + "- get rid of any warnings you see." + ] + }, + { + "cell_type": "markdown", + "id": "f21d93b9", + "metadata": { + "id": "f21d93b9" + }, + "source": [ + "## Build your own PDK\n", + "\n", + "You can create a PDK as a python library using a cookiecutter template. For example, you can use this one.\n", + "\n", + "```\n", + "pip install cookiecutter\n", + "cookiecutter https://github.com/joamatab/cookiecutter-pypackage-minimal\n", + "```\n", + "\n", + "Or you can fork the ubcpdk and create new PCell functions that use the correct layers for your foundry. For example.\n", + "\n", + "```\n", + "\n", + "from pydantic import BaseModel\n", + "\n", + "\n", + "class LayerMap(BaseModel):\n", + " WGCORE = (3, 0)\n", + " LABEL = (100, 0)\n", + " DEVREC: Layer = (68, 0)\n", + " LABEL: Layer = (10, 0)\n", + " PORT: Layer = (1, 10) # PinRec\n", + " PORTE: Layer = (1, 11) # PinRecM\n", + " FLOORPLAN: Layer = (99, 0)\n", + "\n", + " TE: Layer = (203, 0)\n", + " TM: Layer = (204, 0)\n", + " TEXT: Layer = (66, 0)\n", + " LABEL_INSTANCE: Layer = (66, 0)\n", + "\n", + "\n", + "LAYER = LayerMap()\n", + "\n", + "```" + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [ + "ccdffd91", + "d0ceca27" + ], + "provenance": [] + }, + "jupytext": { + "cell_metadata_filter": "-all", + "custom_cell_magics": "kql" + }, + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/11_drc.ipynb b/notebooks/11_drc.ipynb index 097a7c2..5ac9cde 100644 --- a/notebooks/11_drc.ipynb +++ b/notebooks/11_drc.ipynb @@ -30,7 +30,8 @@ " rule_width,\n", " rule_density,\n", " write_drc_deck_macro,\n", - ")" + ")\n", + "from gdsfactory.generic_tech import LAYER" ] }, { @@ -66,7 +67,7 @@ "\n", "drc_rule_deck = write_drc_deck_macro(\n", " rules=rules,\n", - " layers=gf.LAYER,\n", + " layers=LAYER,\n", " shortcut=\"Ctrl+Shift+D\",\n", ")" ] @@ -88,10 +89,9 @@ "source": [ "import gdsfactory as gf\n", "from gdsfactory.component import Component\n", - "from gdsfactory.generic_tech import LAYER\n", "from gdsfactory.typings import Float2, Layer\n", "\n", - "layer =LAYER.WG\n", + "layer = LAYER.WG\n", "\n", "\n", "@gf.cell\n", @@ -181,6 +181,22 @@ "This will check for disconnected pins or ports with width mismatch." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "cce46c0a", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " import google.colab\n", + " is_running_on_colab = True\n", + " !pip install gdsfactory gplugins > /dev/null\n", + " \n", + "except ImportError:\n", + " is_running_on_colab = False" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/notebooks/20_modesolver_fem.ipynb b/notebooks/20_modesolver_fem.ipynb index 4102fc9..8cccd9e 100644 --- a/notebooks/20_modesolver_fem.ipynb +++ b/notebooks/20_modesolver_fem.ipynb @@ -16,6 +16,23 @@ "You can also downsample layers from the LayerStack, and modify both the cross-section and LayerStack prior to simulation to change the geometry. You can also define refractive indices on the active PDK." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ae0bc8a", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " import google.colab\n", + " is_running_on_colab = True\n", + " !pip install gdsfactory gplugins[femwell] > /dev/null\n", + " !apt install python3-gmsh gmsh > /dev/null\n", + " \n", + "except ImportError:\n", + " is_running_on_colab = False" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/notebooks/21_modesolver_fdfd.ipynb b/notebooks/21_modesolver_fdfd.ipynb index a9fea9b..0dae979 100644 --- a/notebooks/21_modesolver_fdfd.ipynb +++ b/notebooks/21_modesolver_fdfd.ipynb @@ -18,6 +18,23 @@ "For a 220 nm height x 450 nm width the effective index is 2.466" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "38e5097c", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " import google.colab\n", + " is_running_on_colab = True\n", + " !pip install gdsfactory gplugins[tidy3d] > /dev/null\n", + " !apt install python3-gmsh gmsh > /dev/null\n", + " \n", + "except ImportError:\n", + " is_running_on_colab = False" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/notebooks/22_heater_fem.ipynb b/notebooks/22_heater_fem.ipynb index 34864f7..b5dc76c 100644 --- a/notebooks/22_heater_fem.ipynb +++ b/notebooks/22_heater_fem.ipynb @@ -13,6 +13,23 @@ "You can simulate directly the component layout and include important effects such as metal dummy fill." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "d3edfea5", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " import google.colab\n", + " is_running_on_colab = True\n", + " !pip install gdsfactory gplugins[femwell] > /dev/null\n", + " !apt install python3-gmsh gmsh > /dev/null\n", + " \n", + "except ImportError:\n", + " is_running_on_colab = False" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/notebooks/30_mzi.ipynb b/notebooks/30_mzi.ipynb index d339113..cad3654 100644 --- a/notebooks/30_mzi.ipynb +++ b/notebooks/30_mzi.ipynb @@ -18,6 +18,23 @@ "## Calculations" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "0f01be73", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " import google.colab\n", + " is_running_on_colab = True\n", + " !pip install gdsfactory gplugins[tidy3d,sax] > /dev/null\n", + " !apt install python3-gmsh gmsh > /dev/null\n", + " \n", + "except ImportError:\n", + " is_running_on_colab = False" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/notebooks/31_ring.ipynb b/notebooks/31_ring.ipynb index ee58c86..aac94e9 100644 --- a/notebooks/31_ring.ipynb +++ b/notebooks/31_ring.ipynb @@ -22,6 +22,23 @@ "- Resistance" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "98790cc4", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " import google.colab\n", + " is_running_on_colab = True\n", + " !pip install gdsfactory gplugins[tidy3d,sax] > /dev/null\n", + " !apt install python3-gmsh gmsh > /dev/null\n", + " \n", + "except ImportError:\n", + " is_running_on_colab = False" + ] + }, { "cell_type": "code", "execution_count": null, From 9af200ec7e3a9ee1d4071460319a27dde2526fa0 Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Sun, 10 Sep 2023 21:04:53 -0700 Subject: [PATCH 2/3] remove empty cells --- notebooks/10_layout_full.ipynb | 60 ------------------------------ notebooks/11_drc.ipynb | 8 ---- notebooks/20_modesolver_fem.ipynb | 8 ---- notebooks/21_modesolver_fdfd.ipynb | 8 ---- notebooks/22_heater_fem.ipynb | 8 ---- 5 files changed, 92 deletions(-) diff --git a/notebooks/10_layout_full.ipynb b/notebooks/10_layout_full.ipynb index d4a7c57..5a50c38 100644 --- a/notebooks/10_layout_full.ipynb +++ b/notebooks/10_layout_full.ipynb @@ -1031,22 +1031,6 @@ "scene.show()" ] }, - { - "cell_type": "markdown", - "id": "5MPfDHSDp5Q5", - "metadata": { - "id": "5MPfDHSDp5Q5" - }, - "source": [] - }, - { - "cell_type": "markdown", - "id": "vTxLqeCBp74n", - "metadata": { - "id": "vTxLqeCBp74n" - }, - "source": [] - }, { "cell_type": "markdown", "id": "3f251bfe", @@ -1057,14 +1041,6 @@ "As the sequence is defined as a string you can use the string operations to easily build complex sequences" ] }, - { - "cell_type": "markdown", - "id": "-4-PBhwOnAC2", - "metadata": { - "id": "-4-PBhwOnAC2" - }, - "source": [] - }, { "cell_type": "markdown", "id": "a899fccf", @@ -2620,22 +2596,6 @@ "D.plot()" ] }, - { - "cell_type": "markdown", - "id": "4WM_UvsYm_1T", - "metadata": { - "id": "4WM_UvsYm_1T" - }, - "source": [] - }, - { - "cell_type": "markdown", - "id": "i1bXu3wAnJm3", - "metadata": { - "id": "i1bXu3wAnJm3" - }, - "source": [] - }, { "cell_type": "markdown", "id": "1f7b5623", @@ -7478,16 +7438,6 @@ "c.plot()" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "9kxaUzRmtJty", - "metadata": { - "id": "9kxaUzRmtJty" - }, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "id": "dd481d4f", @@ -8191,16 +8141,6 @@ " c.plot()" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "cc012d22", - "metadata": { - "id": "cc012d22" - }, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "id": "26dc1285", diff --git a/notebooks/11_drc.ipynb b/notebooks/11_drc.ipynb index 5ac9cde..5d848d9 100644 --- a/notebooks/11_drc.ipynb +++ b/notebooks/11_drc.ipynb @@ -244,14 +244,6 @@ "]\n", "script = wc.write_drc_deck_macro(rules=rules, layers=None)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "62129e58", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebooks/20_modesolver_fem.ipynb b/notebooks/20_modesolver_fem.ipynb index 8cccd9e..a36fdb3 100644 --- a/notebooks/20_modesolver_fem.ipynb +++ b/notebooks/20_modesolver_fem.ipynb @@ -214,14 +214,6 @@ " plt.scatter(widths, lams, c=te_fracs, cmap=\"cool\")\n", "plt.colorbar().set_label(\"TE fraction\")" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ab4f29b4-f6d0-4145-a685-476eeb0e34b0", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebooks/21_modesolver_fdfd.ipynb b/notebooks/21_modesolver_fdfd.ipynb index 0dae979..9d147ba 100644 --- a/notebooks/21_modesolver_fdfd.ipynb +++ b/notebooks/21_modesolver_fdfd.ipynb @@ -547,14 +547,6 @@ "ax.legend([\"TE\", \"TM\"])\n", "ax.grid()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7874e12d-cef2-4620-b602-0572951ff194", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebooks/22_heater_fem.ipynb b/notebooks/22_heater_fem.ipynb index b5dc76c..63d55cd 100644 --- a/notebooks/22_heater_fem.ipynb +++ b/notebooks/22_heater_fem.ipynb @@ -292,14 +292,6 @@ "plt.plot(currents * 1e3, neffs)\n", "plt.show()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e318560a", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From db512dc5ff0875119f9d351344fa3d5e10933e65 Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Sun, 10 Sep 2023 21:08:11 -0700 Subject: [PATCH 3/3] improve headers --- notebooks/10_layout.ipynb | 2 +- notebooks/10_layout_full.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/notebooks/10_layout.ipynb b/notebooks/10_layout.ipynb index d241ccf..be7c9ed 100644 --- a/notebooks/10_layout.ipynb +++ b/notebooks/10_layout.ipynb @@ -5,7 +5,7 @@ "id": "30a37197", "metadata": {}, "source": [ - "# Component\n", + "# Layout tutorial (short)\n", "\n", "A `Component` is like an empty canvas, where you can add polygons, references to other Components and ports (to connect to other components)\n", "\n", diff --git a/notebooks/10_layout_full.ipynb b/notebooks/10_layout_full.ipynb index 5a50c38..ea4f7ae 100644 --- a/notebooks/10_layout_full.ipynb +++ b/notebooks/10_layout_full.ipynb @@ -63,7 +63,7 @@ "id": "6c4c0e4c" }, "source": [ - "# Component\n", + "# Layout tutorial\n", "\n", "A `Component` is like an empty canvas, where you can add polygons, references to other Components and ports (to connect to other components)\n", "\n",