├── docs ├── __init__.py ├── notebooks ├── index.md ├── layout.md ├── _toc.yml └── _config.yml ├── notebooks ├── mmi1x2.npz ├── 11_drc.ipynb ├── 22_heater_fem.ipynb ├── 20_modesolver_fem.ipynb ├── 31_ring.ipynb ├── 30_mzi.ipynb ├── 21_modesolver_fdfd.ipynb └── 10_layout_full.ipynb ├── .github ├── dependabot.yml ├── test_doc_errors.py ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── pages.yml ├── Makefile ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── pyproject.toml └── .gitignore /docs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/notebooks: -------------------------------------------------------------------------------- 1 | ../notebooks -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ```{include} ../README.md 2 | ``` 3 | -------------------------------------------------------------------------------- /docs/layout.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | ```{tableofcontents} 4 | ``` 5 | -------------------------------------------------------------------------------- /notebooks/mmi1x2.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdsfactory/gdsfactory-photonics-training/HEAD/notebooks/mmi1x2.npz -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: github-actions 9 | directory: / 10 | schedule: 11 | interval: monthly 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | uv sync --extra docs --extra dev 3 | 4 | developer: install 5 | pre-commit install 6 | 7 | dev: install 8 | sudo apt-get install -y python3-gmsh gmsh libglu1-mesa libxi-dev libxmu-dev libglu1-mesa-dev 9 | 10 | update-pre: 11 | pre-commit autoupdate 12 | 13 | git-rm-merged: 14 | git branch -D `git branch --merged | grep -v \* | xargs` 15 | 16 | docs: 17 | uv run jb build docs 18 | 19 | clean: 20 | uv run nbstripout --drop-empty-cells notebooks/*.ipynb 21 | 22 | .PHONY: docs 23 | -------------------------------------------------------------------------------- /.github/test_doc_errors.py: -------------------------------------------------------------------------------- 1 | if __name__ == "__main__": 2 | import pathlib 3 | 4 | module_path = pathlib.Path(__file__).parent.absolute() 5 | repo = module_path.parent 6 | dirpath = repo / "docs" / "build" / "_html" / "reports" 7 | error_files = list(dirpath.glob("*err.log")) 8 | 9 | if error_files: 10 | for error_file in error_files: 11 | error = error_file.read_text() 12 | print(error) 13 | raise ValueError(f"{len(error_files)} errors in notebooks") 14 | -------------------------------------------------------------------------------- /docs/_toc.yml: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | # Learn more at https://jterbook.org/customize/toc.html 3 | 4 | format: jb-book 5 | root: index 6 | parts: 7 | - caption: Introduction 8 | chapters: 9 | - file: notebooks/10_layout_full.ipynb 10 | - file: notebooks/11_drc.ipynb 11 | - file: notebooks/20_modesolver_fem.ipynb 12 | - file: notebooks/21_modesolver_fdfd.ipynb 13 | - file: notebooks/22_heater_fem.ipynb 14 | - file: notebooks/30_mzi.ipynb 15 | - file: notebooks/31_ring.ipynb 16 | - caption: Reference 17 | chapters: 18 | - url: https://gdsfactory.github.io/gplugins/ 19 | title: Plugins 20 | - url: https://gdsfactory.github.io/gdsfactory 21 | title: gdsfactory 22 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: "9260cbc9c84c06022993bfbcc42fdbf0305c5b8e" 4 | hooks: 5 | - id: check-case-conflict 6 | - id: check-merge-conflict 7 | - id: check-symlinks 8 | - id: check-yaml 9 | - id: debug-statements 10 | - id: end-of-file-fixer 11 | - id: mixed-line-ending 12 | - id: name-tests-test 13 | args: ["--pytest-test-first"] 14 | - id: trailing-whitespace 15 | 16 | - repo: https://github.com/kynan/nbstripout 17 | rev: 0.7.1 18 | hooks: 19 | - id: nbstripout 20 | files: ".ipynb" 21 | - repo: https://github.com/codespell-project/codespell 22 | rev: v2.3.0 23 | hooks: 24 | - id: codespell 25 | additional_dependencies: 26 | - tomli 27 | - repo: https://github.com/astral-sh/ruff-pre-commit 28 | rev: "v0.6.5" 29 | hooks: 30 | - id: ruff 31 | args: [--fix, --exit-non-zero-on-fix] 32 | - id: ruff-format 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | What's the bug? 12 | 13 | **To Reproduce** 14 | What's the code to reproduce the behavior? What commands or code did you write to get the error? 15 | You can add screenshots to help explain your problem. 16 | Make sure you include the code for others to reproduce your issue. 17 | 18 | **Expected behavior** 19 | What would you like to happen? 20 | 21 | **Suggested fix** 22 | How could we fix the bug? As an open-source project, we welcome your suggestions and PRs. 23 | 24 | 25 | **Environment (please complete the following information):** 26 | 27 | ```python 28 | import sys 29 | print(sys.version) 30 | print(sys.executable) 31 | 32 | import gdsfactory as gf 33 | gf.config.print_version_plugins() 34 | ``` 35 | 36 | - [ ] I have checked the documentation and found no answer 37 | - [ ] I have checked the issues and found no answer 38 | - [ ] Im using the latest GDSFactory version, and Im using python 3.10, 3.11 or 3.12 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 gdsfactory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GDSFactory training 2 | 3 | Installation instructions. 4 | 5 | ## 1. Install UV 6 | 7 | ```bash 8 | # On macOS and Linux. 9 | curl -LsSf https://astral.sh/uv/install.sh | sh 10 | ``` 11 | 12 | ```bash 13 | # On Windows. 14 | powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" 15 | ``` 16 | 17 | ## 2. Download the notebooks 18 | 19 | - [ ] Download the training notebooks[here](https://github.com/gdsfactory/gdsfactory-photonics-training/archive/refs/heads/main.zip) 20 | - [ ] Open a shell inside the unzipped folder and run 21 | 22 | ``` 23 | uv venv --python 3.11 24 | uv sync 25 | ``` 26 | 27 | ## 3. Install VSCode and KLayout 28 | 29 | - [ ] Download and install [VSCode](https://code.visualstudio.com/), a Python editor, to open and run the notebooks. When opening VSCode make sure you have selected the python interpreter in the current directory virtual environment `.venv/bin/python` 30 | - [ ] Download and install [klayout](https://www.klayout.de/build.html) 31 | - [ ] Install `GDSFactory` klayout plugin to be able to see live updates on your GDS files from the klayout GUI `Tools --> Manage Packages --> Install New Packages --> GDSFactory` 32 | 33 | ![image.png](https://i.imgur.com/CmCe9Vp.png) 34 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: 9 | schedule: 10 | - cron: '0 0 * * *' # Runs at 00:00 UTC every day 11 | 12 | jobs: 13 | build-docs: 14 | runs-on: ubuntu-latest 15 | name: Sphinx docs to gh-pages 16 | steps: 17 | - name: Cancel Workflow Action 18 | uses: styfle/cancel-workflow-action@0.12.1 19 | - uses: actions/checkout@v4 20 | - uses: actions/cache@v4 21 | with: 22 | path: | 23 | ~/.gdsfactory/ 24 | key: 0.0.1 25 | restore-keys: 0.0.1 26 | - name: Set up Python 3.11 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.11' 30 | - name: Install uv 31 | uses: astral-sh/setup-uv@v5 32 | - name: Installing the library 33 | run: | 34 | make dev 35 | - name: Build the docs 36 | env: 37 | GDSFACTORY_DISPLAY_TYPE: klayout 38 | KFACTORY_DISPLAY_TYPE: image 39 | run: | 40 | make docs 41 | - name: Upload artifact 42 | uses: actions/upload-pages-artifact@v3 43 | with: 44 | path: "./docs/_build/html/" 45 | deploy-docs: 46 | needs: build-docs 47 | if: ${{ github.ref == 'refs/heads/main' }} 48 | permissions: 49 | pages: write 50 | id-token: write 51 | environment: 52 | name: github-pages 53 | url: ${{ steps.deployment.outputs.page_url }} 54 | runs-on: ubuntu-latest 55 | steps: 56 | - name: Deploy to GitHub Pages 57 | id: deployment 58 | uses: actions/deploy-pages@v4 59 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Book settings 2 | # Learn more at https://jupyterbook.org/customize/config.html 3 | 4 | title: pictraining 5 | author: gdsfactory 6 | 7 | # Force re-execution of notebooks on each build. 8 | # See https://jupyterbook.org/content/execute.html 9 | execute: 10 | execute_notebooks: cache 11 | timeout: -1 12 | # allow_errors: true 13 | # timeout: -1 14 | # execute_notebooks: force 15 | # execute_notebooks: "off" 16 | # exclude_patterns: 17 | # - '*notebooks/devsim/01_pin_waveguide*' 18 | 19 | latex: 20 | latex_engine: pdflatex # one of 'pdflatex', 'xelatex' (recommended for unicode), 'luatex', 'platex', 'uplatex' 21 | use_jupyterbook_latex: true # use sphinx-jupyterbook-latex for pdf builds as default 22 | 23 | # Add a bibtex file so that we can create citations 24 | 25 | html: 26 | home_page_in_navbar: true 27 | use_edit_page_button: true 28 | use_repository_button: true 29 | use_issues_button: true 30 | baseurl: https://github.com/gdsfactory/gdsfactory-photonics-training 31 | 32 | # Information about where the book exists on the web 33 | repository: 34 | url: https://github.com/gdsfactory/gdsfactory-photonics-training 35 | path_to_book: docs # Optional path to your book, relative to the repository root 36 | branch: main # Which branch of the repository should be used when creating links (optional) 37 | 38 | # launch_buttons: 39 | # notebook_interface: jupyterlab 40 | # colab_url: "https://colab.research.google.com" 41 | 42 | sphinx: 43 | extra_extensions: 44 | - "sphinx.ext.autodoc" 45 | - "sphinx.ext.autodoc.typehints" 46 | - "sphinx.ext.autosummary" 47 | - "sphinx.ext.napoleon" 48 | - "sphinx.ext.viewcode" 49 | - "matplotlib.sphinxext.plot_directive" 50 | config: 51 | #autodoc_typehints: description 52 | html_js_files: 53 | - https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js 54 | autodoc_type_aliases: 55 | "ComponentSpec": "ComponentSpec" 56 | nb_execution_show_tb: True 57 | nb_execution_raise_on_error: true 58 | nb_custom_formats: 59 | .py: 60 | - jupytext.reads 61 | - fmt: py 62 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html 2 | 3 | [build-system] 4 | build-backend = "flit_core.buildapi" 5 | requires = ["flit_core >=3.2,<4"] 6 | 7 | [project] 8 | authors = [ 9 | {name = "gdsfactory", email = "contact@gdsfactory.com"} 10 | ] 11 | classifiers = [ 12 | "License :: OSI Approved :: MIT License", 13 | "Programming Language :: Python :: 3.10", 14 | "Programming Language :: Python :: 3.11", 15 | "Programming Language :: Python :: 3.12", 16 | "Operating System :: OS Independent" 17 | ] 18 | dependencies = [ 19 | "gdsfactory~=8.28.0", 20 | "gplugins[tidy3d,sax,schematic,femwell]", 21 | "numpy<2", 22 | ] 23 | description = "pic_training" 24 | keywords = ["python"] 25 | license = {file = "LICENSE"} 26 | name = "docs" 27 | readme = "README.md" 28 | requires-python = ">=3.10" 29 | version = "2.1.0" 30 | 31 | [project.optional-dependencies] 32 | dev = [ 33 | "pytest", 34 | "pytest-cov", 35 | "pytest_regressions", 36 | "pre-commit" 37 | ] 38 | docs = [ 39 | "autodoc_pydantic", 40 | "jupytext", 41 | "jupyter-book==1.0.3" 42 | ] 43 | 44 | [tool.mypy] 45 | python_version = "3.11" 46 | strict = true 47 | 48 | [tool.pydocstyle] 49 | add-ignore = ["D100", "D101", "D102", "D103", "D104", "D203", "D405", "D417"] 50 | convention = "google" 51 | inherit = false 52 | match = "(?!test).*\\.py" 53 | 54 | [tool.pylsp-mypy] 55 | enabled = true 56 | live_mode = true 57 | strict = true 58 | 59 | [tool.pytest.ini_options] 60 | python_files = ["tests/*.py"] 61 | testpaths = ["tests"] 62 | 63 | [tool.ruff] 64 | fix = true 65 | ignore = [ 66 | "E501", # line too long, handled by black 67 | "B008", # do not perform function calls in argument defaults 68 | "C901", # too complex 69 | "B905", # `zip()` without an explicit `strict=` parameter 70 | "C408" # C408 Unnecessary `dict` call (rewrite as a literal) 71 | ] 72 | select = [ 73 | "E", # pycodestyle errors 74 | "W", # pycodestyle warnings 75 | "F", # pyflakes 76 | "I", # isort 77 | "C", # flake8-comprehensions 78 | "B", # flake8-bugbear 79 | "UP" 80 | ] 81 | [tool.codespell] 82 | ignore-words-list = "te, te/tm, te, ba, fpr, fpr_spacing, ro, nd, donot, schem" 83 | 84 | [tool.setuptools.packages] 85 | find = {} 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Sims 10 | *.fsp 11 | *.csv 12 | *.msh 13 | *.gds 14 | notebooks/*.yml 15 | *.stl 16 | *.oas 17 | 18 | # Distribution / packaging 19 | .Python 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | extra/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | share/python-wheels/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | MANIFEST 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .nox/ 53 | .coverage 54 | .coverage.* 55 | .cache 56 | nosetests.xml 57 | coverage.xml 58 | *.cover 59 | *.py,cover 60 | .hypothesis/ 61 | .pytest_cache/ 62 | cover/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Django stuff: 69 | *.log 70 | local_settings.py 71 | db.sqlite3 72 | db.sqlite3-journal 73 | 74 | # Flask stuff: 75 | instance/ 76 | .webassets-cache 77 | 78 | # Scrapy stuff: 79 | .scrapy 80 | 81 | # Sphinx documentation 82 | docs/_build/ 83 | 84 | # PyBuilder 85 | .pybuilder/ 86 | target/ 87 | 88 | # Jupyter Notebook 89 | .ipynb_checkpoints 90 | **/*.ipynb_checkpoints/ 91 | 92 | # IPython 93 | profile_default/ 94 | ipython_config.py 95 | 96 | # pyenv 97 | # For a library or package, you might want to ignore these files since the code is 98 | # intended to run in multiple environments; otherwise, check them in: 99 | # .python-version 100 | 101 | # pipenv 102 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 103 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 104 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 105 | # install all needed dependencies. 106 | #Pipfile.lock 107 | 108 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 109 | __pypackages__/ 110 | 111 | # Celery stuff 112 | celerybeat-schedule 113 | celerybeat.pid 114 | 115 | # SageMath parsed files 116 | *.sage.py 117 | 118 | # Environments 119 | .env 120 | .venv 121 | env/ 122 | venv/ 123 | ENV/ 124 | env.bak/ 125 | venv.bak/ 126 | 127 | # Spyder project settings 128 | .spyderproject 129 | .spyproject 130 | 131 | # Rope project settings 132 | .ropeproject 133 | 134 | # mkdocs documentation 135 | /site 136 | 137 | # mypy 138 | .mypy_cache/ 139 | .dmypy.json 140 | dmypy.json 141 | 142 | # Pyre type checker 143 | .pyre/ 144 | 145 | # pytype static type analyzer 146 | .pytype/ 147 | 148 | # Cython debug symbols 149 | cython_debug/ 150 | 151 | *.DS_Store 152 | .DS_Store 153 | *Thumbs.db 154 | -------------------------------------------------------------------------------- /notebooks/11_drc.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Klayout Design Rule Checking (DRC)\n", 9 | "\n", 10 | "To ensure your device can be fabricated correctly, it must meet the Design Rule Checks (DRC) set by the foundry. You can write custom DRC rules directly in GDSFactory and configure shortcuts to run these checks seamlessly within KLayout.\n", 11 | "\n", 12 | "Here are some rules explained in [repo generic DRC technology](https://github.com/klayoutmatthias/si4all) and [video](https://peertube.f-si.org/videos/watch/addc77a0-8ac7-4742-b7fb-7d24360ceb97)\n", 13 | "\n", 14 | "![rules1](https://i.imgur.com/gNP5Npn.png)" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "1", 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "import gdsfactory as gf\n", 25 | "from gdsfactory.component import Component\n", 26 | "from gdsfactory.generic_tech import LAYER\n", 27 | "from gdsfactory.typings import Float2, Layer\n", 28 | "from gplugins.klayout.drc.write_drc import (\n", 29 | " check_area,\n", 30 | " check_density,\n", 31 | " check_enclosing,\n", 32 | " check_separation,\n", 33 | " check_space,\n", 34 | " check_width,\n", 35 | " write_drc_deck_macro,\n", 36 | ")" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "id": "2", 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "help(write_drc_deck_macro)" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "id": "3", 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "rules = [\n", 57 | " check_width(layer=\"WG\", value=0.2),\n", 58 | " check_space(layer=\"WG\", value=0.2),\n", 59 | " check_width(layer=\"M1\", value=1),\n", 60 | " check_width(layer=\"M2\", value=2),\n", 61 | " check_space(layer=\"M2\", value=2),\n", 62 | " check_separation(layer1=\"HEATER\", layer2=\"M1\", value=1.0),\n", 63 | " check_enclosing(layer1=\"M1\", layer2=\"VIAC\", value=0.2),\n", 64 | " check_area(layer=\"WG\", min_area_um2=0.05),\n", 65 | " check_density(\n", 66 | " layer=\"WG\", layer_floorplan=\"FLOORPLAN\", min_density=0.5, max_density=0.6\n", 67 | " ),\n", 68 | "]\n", 69 | "\n", 70 | "drc_check_deck = write_drc_deck_macro(\n", 71 | " rules=rules,\n", 72 | " layers=LAYER,\n", 73 | " shortcut=\"Ctrl+Shift+D\",\n", 74 | ")" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "id": "4", 80 | "metadata": {}, 81 | "source": [ 82 | "Lets create some DRC errors and check them on klayout." 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "id": "5", 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "layer = LAYER.WG\n", 93 | "\n", 94 | "\n", 95 | "@gf.cell\n", 96 | "def width_min(size: Float2 = (0.1, 0.1)) -> Component:\n", 97 | " return gf.components.rectangle(size=size, layer=layer)\n", 98 | "\n", 99 | "\n", 100 | "@gf.cell\n", 101 | "def area_min() -> Component:\n", 102 | " size = (0.2, 0.2)\n", 103 | " return gf.components.rectangle(size=size, layer=layer)\n", 104 | "\n", 105 | "\n", 106 | "@gf.cell\n", 107 | "def gap_min(gap: float = 0.1) -> Component:\n", 108 | " c = gf.Component()\n", 109 | " r1 = c << gf.components.rectangle(size=(1, 1), layer=layer)\n", 110 | " r2 = c << gf.components.rectangle(size=(1, 1), layer=layer)\n", 111 | " r1.dxmax = 0\n", 112 | " r2.dxmin = gap\n", 113 | " return c\n", 114 | "\n", 115 | "\n", 116 | "@gf.cell\n", 117 | "def separation(\n", 118 | " gap: float = 0.1, layer1: Layer = LAYER.HEATER, layer2: Layer = LAYER.M1\n", 119 | ") -> Component:\n", 120 | " c = gf.Component()\n", 121 | " r1 = c << gf.components.rectangle(size=(1, 1), layer=layer1)\n", 122 | " r2 = c << gf.components.rectangle(size=(1, 1), layer=layer2)\n", 123 | " r1.dxmax = 0\n", 124 | " r2.dxmin = gap\n", 125 | " return c\n", 126 | "\n", 127 | "\n", 128 | "@gf.cell\n", 129 | "def enclosing(\n", 130 | " enclosing: float = 0.1, layer1: Layer = LAYER.VIAC, layer2: Layer = LAYER.M1\n", 131 | ") -> Component:\n", 132 | " \"\"\"Layer1 must be enclosed by layer2 by value.\n", 133 | "\n", 134 | " checks if layer1 encloses (is bigger than) layer2 by value\n", 135 | " \"\"\"\n", 136 | " w1 = 1\n", 137 | " w2 = w1 + enclosing\n", 138 | " c = gf.Component()\n", 139 | " c << gf.components.rectangle(size=(w1, w1), layer=layer1, centered=True)\n", 140 | " r2 = c << gf.components.rectangle(size=(w2, w2), layer=layer2, centered=True)\n", 141 | " r2.dmovex(0.5)\n", 142 | " return c\n", 143 | "\n", 144 | "\n", 145 | "@gf.cell\n", 146 | "def snapping_error(gap: float = 1e-3) -> Component:\n", 147 | " c = gf.Component()\n", 148 | " r1 = c << gf.components.rectangle(size=(1, 1), layer=layer)\n", 149 | " r2 = c << gf.components.rectangle(size=(1, 1), layer=layer)\n", 150 | " r1.dxmax = 0\n", 151 | " r2.dxmin = gap\n", 152 | " return c\n", 153 | "\n", 154 | "\n", 155 | "@gf.cell\n", 156 | "def errors() -> Component:\n", 157 | " components = [width_min(), gap_min(), separation(), enclosing()]\n", 158 | " c = gf.pack(components, spacing=1.5)\n", 159 | " c = gf.add_padding_container(c[0], layers=(LAYER.FLOORPLAN,), default=5)\n", 160 | " return c\n", 161 | "\n", 162 | "\n", 163 | "c = errors()\n", 164 | "c.show() # show in klayout\n", 165 | "c.plot()" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "id": "6", 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [] 175 | } 176 | ], 177 | "metadata": { 178 | "jupytext": { 179 | "cell_metadata_filter": "-all", 180 | "custom_cell_magics": "kql" 181 | }, 182 | "kernelspec": { 183 | "display_name": "base", 184 | "language": "python", 185 | "name": "python3" 186 | }, 187 | "language_info": { 188 | "codemirror_mode": { 189 | "name": "ipython", 190 | "version": 3 191 | }, 192 | "file_extension": ".py", 193 | "mimetype": "text/x-python", 194 | "name": "python", 195 | "nbconvert_exporter": "python", 196 | "pygments_lexer": "ipython3", 197 | "version": "3.11.9" 198 | } 199 | }, 200 | "nbformat": 4, 201 | "nbformat_minor": 5 202 | } 203 | -------------------------------------------------------------------------------- /notebooks/22_heater_fem.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": { 7 | "lines_to_next_cell": 2 8 | }, 9 | "source": [ 10 | "# Thermal Simulations\n", 11 | "\n", 12 | "GDSFactory includes an FEM plugin, femwell, which allows you to run thermal simulations. You can simulate the component layout directly and account for critical effects such as metal dummy fills, ensuring more accurate thermal performance analysis." 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "id": "1", 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import gdsfactory as gf\n", 23 | "from gdsfactory.generic_tech import get_generic_pdk\n", 24 | "from skfem.io import from_meshio\n", 25 | "\n", 26 | "gf.config.rich_output()\n", 27 | "PDK = get_generic_pdk()\n", 28 | "PDK.activate()\n", 29 | "\n", 30 | "LAYER_STACK = PDK.layer_stack\n", 31 | "\n", 32 | "LAYER_STACK.layers[\"heater\"].thickness = 0.13\n", 33 | "LAYER_STACK.layers[\"heater\"].zmin = 2.2\n", 34 | "\n", 35 | "heater = gf.components.straight_heater_metal(length=50)\n", 36 | "heater.plot()" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "id": "2", 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "s = heater.to_3d()\n", 47 | "s.show()" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "id": "3", 53 | "metadata": {}, 54 | "source": [ 55 | "Example based on [femwell](https://helgegehring.github.io/femwell/index.html)" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "id": "4", 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | "from collections import OrderedDict\n", 66 | "\n", 67 | "import matplotlib.pyplot as plt\n", 68 | "import numpy as np\n", 69 | "from femwell.maxwell.waveguide import compute_modes\n", 70 | "from femwell.mesh import mesh_from_OrderedDict\n", 71 | "from femwell.thermal import solve_thermal\n", 72 | "from shapely.geometry import LineString, Polygon\n", 73 | "from skfem import Basis, ElementTriP0\n", 74 | "from tqdm import tqdm" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "id": "5", 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "w_sim = 8 * 2\n", 85 | "h_clad = 2.8\n", 86 | "h_box = 2\n", 87 | "w_core = 0.5\n", 88 | "h_core = 0.22\n", 89 | "h_heater = 0.14\n", 90 | "w_heater = 2\n", 91 | "offset_heater = 2 + (h_core + h_heater) / 2\n", 92 | "h_silicon = 0.5\n", 93 | "\n", 94 | "polygons = OrderedDict(\n", 95 | " bottom=LineString(\n", 96 | " [\n", 97 | " (-w_sim / 2, -h_core / 2 - h_box - h_silicon),\n", 98 | " (w_sim / 2, -h_core / 2 - h_box - h_silicon),\n", 99 | " ]\n", 100 | " ),\n", 101 | " core=Polygon(\n", 102 | " [\n", 103 | " (-w_core / 2, -h_core / 2),\n", 104 | " (-w_core / 2, h_core / 2),\n", 105 | " (w_core / 2, h_core / 2),\n", 106 | " (w_core / 2, -h_core / 2),\n", 107 | " ]\n", 108 | " ),\n", 109 | " heater=Polygon(\n", 110 | " [\n", 111 | " (-w_heater / 2, -h_heater / 2 + offset_heater),\n", 112 | " (-w_heater / 2, h_heater / 2 + offset_heater),\n", 113 | " (w_heater / 2, h_heater / 2 + offset_heater),\n", 114 | " (w_heater / 2, -h_heater / 2 + offset_heater),\n", 115 | " ]\n", 116 | " ),\n", 117 | " clad=Polygon(\n", 118 | " [\n", 119 | " (-w_sim / 2, -h_core / 2),\n", 120 | " (-w_sim / 2, -h_core / 2 + h_clad),\n", 121 | " (w_sim / 2, -h_core / 2 + h_clad),\n", 122 | " (w_sim / 2, -h_core / 2),\n", 123 | " ]\n", 124 | " ),\n", 125 | " box=Polygon(\n", 126 | " [\n", 127 | " (-w_sim / 2, -h_core / 2),\n", 128 | " (-w_sim / 2, -h_core / 2 - h_box),\n", 129 | " (w_sim / 2, -h_core / 2 - h_box),\n", 130 | " (w_sim / 2, -h_core / 2),\n", 131 | " ]\n", 132 | " ),\n", 133 | " wafer=Polygon(\n", 134 | " [\n", 135 | " (-w_sim / 2, -h_core / 2 - h_box - h_silicon),\n", 136 | " (-w_sim / 2, -h_core / 2 - h_box),\n", 137 | " (w_sim / 2, -h_core / 2 - h_box),\n", 138 | " (w_sim / 2, -h_core / 2 - h_box - h_silicon),\n", 139 | " ]\n", 140 | " ),\n", 141 | ")\n", 142 | "\n", 143 | "resolutions = dict(\n", 144 | " core={\"resolution\": 0.04, \"distance\": 1},\n", 145 | " clad={\"resolution\": 0.6, \"distance\": 1},\n", 146 | " box={\"resolution\": 0.6, \"distance\": 1},\n", 147 | " heater={\"resolution\": 0.1, \"distance\": 1},\n", 148 | ")\n", 149 | "\n", 150 | "mesh = from_meshio(\n", 151 | " mesh_from_OrderedDict(polygons, resolutions, default_resolution_max=0.6)\n", 152 | ")\n", 153 | "mesh.draw().show()" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "id": "6", 159 | "metadata": {}, 160 | "source": [ 161 | "And then we solve it!" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "id": "7", 168 | "metadata": { 169 | "lines_to_next_cell": 2, 170 | "tags": [ 171 | "remove-stderr" 172 | ] 173 | }, 174 | "outputs": [], 175 | "source": [ 176 | "currents = np.linspace(0.0, 7.4e-3, 10)\n", 177 | "current_densities = currents / polygons[\"heater\"].area\n", 178 | "neffs = []\n", 179 | "\n", 180 | "for current_density in tqdm(current_densities):\n", 181 | " basis0 = Basis(mesh, ElementTriP0(), intorder=4)\n", 182 | " thermal_conductivity_p0 = basis0.zeros()\n", 183 | " for domain, value in {\n", 184 | " \"core\": 90,\n", 185 | " \"box\": 1.38,\n", 186 | " \"clad\": 1.38,\n", 187 | " \"heater\": 28,\n", 188 | " \"wafer\": 148,\n", 189 | " }.items():\n", 190 | " thermal_conductivity_p0[basis0.get_dofs(elements=domain)] = value\n", 191 | " thermal_conductivity_p0 *= 1e-12 # 1e-12 -> conversion from 1/m^2 -> 1/um^2\n", 192 | "\n", 193 | " basis, temperature = solve_thermal(\n", 194 | " basis0,\n", 195 | " thermal_conductivity_p0,\n", 196 | " specific_conductivity={\"heater\": 2.3e6},\n", 197 | " current_densities={\"heater\": current_density},\n", 198 | " fixed_boundaries={\"bottom\": 0},\n", 199 | " )\n", 200 | "\n", 201 | " if current_density == current_densities[-1]:\n", 202 | " basis.plot(temperature, shading=\"gouraud\", colorbar=True)\n", 203 | " plt.show()\n", 204 | "\n", 205 | " temperature0 = basis0.project(basis.interpolate(temperature))\n", 206 | " epsilon = basis0.zeros() + (1.444 + 1.00e-5 * temperature0) ** 2\n", 207 | " epsilon[basis0.get_dofs(elements=\"core\")] = (\n", 208 | " 3.4777 + 1.86e-4 * temperature0[basis0.get_dofs(elements=\"core\")]\n", 209 | " ) ** 2\n", 210 | " # basis0.plot(epsilon, colorbar=True).show()\n", 211 | "\n", 212 | " modes = compute_modes(basis0, epsilon, wavelength=1.55, num_modes=1, solver=\"scipy\")\n", 213 | "\n", 214 | " if current_density == current_densities[-1]:\n", 215 | " modes[0].show(modes[0].E.real)\n", 216 | "\n", 217 | " neffs.append(np.real(modes[0].n_eff))\n", 218 | "\n", 219 | "length = 320 # um\n", 220 | "print(f\"Phase shift: {2 * np.pi / 1.55 * (neffs[-1] - neffs[0]) * length}\")\n", 221 | "plt.xlabel(\"Current / mA\")\n", 222 | "plt.ylabel(\"Effective refractive index $n_{eff}$\")\n", 223 | "plt.plot(currents * 1e3, neffs)\n", 224 | "plt.show()" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": null, 230 | "id": "8", 231 | "metadata": {}, 232 | "outputs": [], 233 | "source": [] 234 | } 235 | ], 236 | "metadata": { 237 | "jupytext": { 238 | "cell_metadata_filter": "tags,-all", 239 | "custom_cell_magics": "kql", 240 | "notebook_metadata_filter": "-all" 241 | }, 242 | "kernelspec": { 243 | "display_name": "Python 3 (ipykernel)", 244 | "language": "python", 245 | "name": "python3" 246 | }, 247 | "language_info": { 248 | "codemirror_mode": { 249 | "name": "ipython", 250 | "version": 3 251 | }, 252 | "file_extension": ".py", 253 | "mimetype": "text/x-python", 254 | "name": "python", 255 | "nbconvert_exporter": "python", 256 | "pygments_lexer": "ipython3", 257 | "version": "3.11.5" 258 | } 259 | }, 260 | "nbformat": 4, 261 | "nbformat_minor": 5 262 | } 263 | -------------------------------------------------------------------------------- /notebooks/20_modesolver_fem.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Finite-Element Mode Solver\n", 9 | "\n", 10 | "Using femwell, you can mesh any component cross-section and solve PDEs with its powerful mode solver.\n", 11 | "\n", 12 | "Unlike other solvers that rely on predefined geometries, femwell works directly with the actual component geometry. You can compute the modes of a GDSFactory cross-section, which internally defines a \"uz\" mesh perpendicular to a straight component using the provided cross-section.\n", 13 | "\n", 14 | "Additionally, you can downsample layers from the LayerStack and modify both the cross-section and LayerStack before running the simulation to adjust the geometry. You can also define refractive indices based on the active PDK." 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "1", 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "import logging\n", 25 | "import sys\n", 26 | "\n", 27 | "import gdsfactory as gf\n", 28 | "import matplotlib.pyplot as plt\n", 29 | "from femwell.maxwell.waveguide import compute_modes\n", 30 | "from femwell.visualization import plot_domains\n", 31 | "from gdsfactory.generic_tech import LAYER_STACK, get_generic_pdk\n", 32 | "from gdsfactory.technology import LayerStack\n", 33 | "from gplugins.gmsh import get_mesh\n", 34 | "from rich.logging import RichHandler\n", 35 | "from skfem import Basis, ElementTriP0\n", 36 | "from skfem.io.meshio import from_meshio\n", 37 | "\n", 38 | "gf.config.rich_output()\n", 39 | "PDK = get_generic_pdk()\n", 40 | "PDK.activate()\n", 41 | "\n", 42 | "logger = logging.getLogger()\n", 43 | "logger.removeHandler(sys.stderr)\n", 44 | "logging.basicConfig(level=\"WARNING\", datefmt=\"[%X]\", handlers=[RichHandler()])" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "id": "2", 50 | "metadata": {}, 51 | "source": [ 52 | "First we choose a component to simulate. Here, a straight strip waveguide:" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": null, 58 | "id": "3", 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "xs = gf.cross_section.strip(width=1)\n", 63 | "\n", 64 | "c = gf.components.straight(cross_section=xs)\n", 65 | "c" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "id": "4", 71 | "metadata": {}, 72 | "source": [ 73 | "Then we choose a Layer Stack. Here, we simply downsample the generic stack:" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "id": "5", 80 | "metadata": {}, 81 | "outputs": [], 82 | "source": [ 83 | "filtered_layer_stack = LayerStack(\n", 84 | " layers={\n", 85 | " k: LAYER_STACK.layers[k]\n", 86 | " for k in (\n", 87 | " \"core\",\n", 88 | " \"clad\",\n", 89 | " \"slab90\",\n", 90 | " \"box\",\n", 91 | " )\n", 92 | " }\n", 93 | ")" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "id": "6", 99 | "metadata": {}, 100 | "source": [ 101 | "We can also change some of the values:" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": null, 107 | "id": "7", 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "filtered_layer_stack.layers[\n", 112 | " \"core\"\n", 113 | "].thickness = 0.22 # Perturb the layer_stack before simulating\n", 114 | "\n", 115 | "filtered_layer_stack.layers[\n", 116 | " \"slab90\"\n", 117 | "].thickness = 0.09 # Perturb the layer_stack before simulating\n", 118 | "\n", 119 | "# When selecting resolutions, the names must match the keys of the layerstack\n", 120 | "# Here, choose a finer mesh inside and close to the core\n", 121 | "resolutions = {\n", 122 | " \"core\": {\"resolution\": 0.02, \"DistMax\": 2, \"SizeMax\": 0.2},\n", 123 | "}" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "id": "8", 129 | "metadata": {}, 130 | "source": [ 131 | "Using gplugins, we quickly generate a cross-sectional mesh:" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "id": "9", 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [ 141 | "mesh_gmsh = get_mesh(\n", 142 | " component=c,\n", 143 | " layer_stack=filtered_layer_stack,\n", 144 | " type=\"uz\", # we want a cross-section\n", 145 | " xsection_bounds=((1, -3), (1, 3)), # the line from which we take a cross-section\n", 146 | " wafer_padding=3, # pad simulation domain 3 microns around the component\n", 147 | " filename=\"mesh.msh\",\n", 148 | " resolutions=resolutions,\n", 149 | " default_characteristic_length=0.5,\n", 150 | ")" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "id": "10", 156 | "metadata": {}, 157 | "source": [ 158 | "We can now throw this mesh into FEMWELL directly:" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "id": "11", 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "mesh = from_meshio(mesh_gmsh)\n", 169 | "mesh.draw().show()\n", 170 | "\n", 171 | "plot_domains(mesh)\n", 172 | "plt.show()" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "id": "12", 178 | "metadata": {}, 179 | "source": [ 180 | "Assign material values" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "id": "13", 187 | "metadata": { 188 | "lines_to_next_cell": 2 189 | }, 190 | "outputs": [], 191 | "source": [ 192 | "basis0 = Basis(mesh, ElementTriP0())\n", 193 | "epsilon = basis0.zeros()\n", 194 | "for subdomain, n in {\"core\": 3.45, \"box\": 1.444, \"clad\": 1.444}.items():\n", 195 | " epsilon[basis0.get_dofs(elements=subdomain)] = n**2\n", 196 | "basis0.plot(epsilon, colorbar=True).show()" 197 | ] 198 | }, 199 | { 200 | "cell_type": "markdown", 201 | "id": "14", 202 | "metadata": {}, 203 | "source": [ 204 | "Solve for the modes:" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": null, 210 | "id": "15", 211 | "metadata": {}, 212 | "outputs": [], 213 | "source": [ 214 | "wavelength = 1.55\n", 215 | "modes = compute_modes(basis0, epsilon, wavelength=wavelength, num_modes=2, order=1)" 216 | ] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "id": "16", 221 | "metadata": {}, 222 | "source": [ 223 | "You can use them as inputs to other [femwell mode solver functions](https://github.com/HelgeGehring/femwell/blob/main/femwell/mode_solver.py) to inspect or analyze the modes:" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": null, 229 | "id": "17", 230 | "metadata": {}, 231 | "outputs": [], 232 | "source": [ 233 | "print(modes[0].te_fraction)" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": null, 239 | "id": "18", 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "modes[0].show(\"E\", part=\"real\")" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": null, 249 | "id": "19", 250 | "metadata": {}, 251 | "outputs": [], 252 | "source": [ 253 | "dir(modes[0])" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": null, 259 | "id": "20", 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [ 263 | "modes[0].plot_component?" 264 | ] 265 | }, 266 | { 267 | "cell_type": "code", 268 | "execution_count": null, 269 | "id": "21", 270 | "metadata": {}, 271 | "outputs": [], 272 | "source": [ 273 | "modes[0].plot_component(\"E\", component=\"x\", part=\"real\", colorbar=True)" 274 | ] 275 | }, 276 | { 277 | "cell_type": "code", 278 | "execution_count": null, 279 | "id": "22", 280 | "metadata": {}, 281 | "outputs": [], 282 | "source": [ 283 | "modes[1].plot_component(\"E\", component=\"x\", part=\"real\", colorbar=True)" 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": null, 289 | "id": "23", 290 | "metadata": {}, 291 | "outputs": [], 292 | "source": [ 293 | "modes[1].show(\"E\", part=\"real\")" 294 | ] 295 | }, 296 | { 297 | "cell_type": "code", 298 | "execution_count": null, 299 | "id": "24", 300 | "metadata": {}, 301 | "outputs": [], 302 | "source": [] 303 | } 304 | ], 305 | "metadata": { 306 | "jupytext": { 307 | "cell_metadata_filter": "-all", 308 | "custom_cell_magics": "kql", 309 | "encoding": "# -*- coding: utf-8 -*-", 310 | "notebook_metadata_filter": "-all" 311 | }, 312 | "kernelspec": { 313 | "display_name": "Python 3 (ipykernel)", 314 | "language": "python", 315 | "name": "python3" 316 | }, 317 | "language_info": { 318 | "codemirror_mode": { 319 | "name": "ipython", 320 | "version": 3 321 | }, 322 | "file_extension": ".py", 323 | "mimetype": "text/x-python", 324 | "name": "python", 325 | "nbconvert_exporter": "python", 326 | "pygments_lexer": "ipython3", 327 | "version": "3.11.5" 328 | } 329 | }, 330 | "nbformat": 4, 331 | "nbformat_minor": 5 332 | } 333 | -------------------------------------------------------------------------------- /notebooks/31_ring.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Ring filter\n", 9 | "\n", 10 | "## Calculations\n", 11 | "\n", 12 | "For a ring resonator we need to define:\n", 13 | "\n", 14 | "Optical parameters:\n", 15 | "\n", 16 | "- coupling coefficient: will define resonance extinction ratio for a particular ring loss.\n", 17 | "- Free spectral range.\n", 18 | "\n", 19 | "Electrical parameters:\n", 20 | "\n", 21 | "- VpiL\n", 22 | "- Resistance" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "id": "1", 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "import gdsfactory as gf\n", 33 | "import numpy as np\n", 34 | "\n", 35 | "\n", 36 | "def ring(\n", 37 | " wl: np.ndarray,\n", 38 | " wl0: float,\n", 39 | " neff: float,\n", 40 | " ng: float,\n", 41 | " ring_length: float,\n", 42 | " coupling: float,\n", 43 | " loss: float,\n", 44 | ") -> np.ndarray:\n", 45 | " \"\"\"Returns Frequency Domain Response of an all pass filter.\n", 46 | "\n", 47 | " Args:\n", 48 | " wl: wavelength in um.\n", 49 | " wl0: center wavelength at which neff and ng are defined.\n", 50 | " neff: effective index.\n", 51 | " ng: group index.\n", 52 | " ring_length: in um.\n", 53 | " coupling: coupling coefficient.\n", 54 | " loss: dB/um.\n", 55 | " \"\"\"\n", 56 | " transmission = 1 - coupling\n", 57 | " neff_wl = (\n", 58 | " neff + (wl0 - wl) * (ng - neff) / wl0\n", 59 | " ) # we expect a linear behavior with respect to wavelength\n", 60 | " out = np.sqrt(transmission) - 10 ** (-loss * ring_length / 20.0) * np.exp(\n", 61 | " 2j * np.pi * neff_wl * ring_length / wl\n", 62 | " )\n", 63 | " out /= 1 - np.sqrt(transmission) * 10 ** (-loss * ring_length / 20.0) * np.exp(\n", 64 | " 2j * np.pi * neff_wl * ring_length / wl\n", 65 | " )\n", 66 | " return abs(out) ** 2\n", 67 | "\n", 68 | "\n", 69 | "if __name__ == \"__main__\":\n", 70 | " import matplotlib.pyplot as plt\n", 71 | "\n", 72 | " loss = 0.03 # [dB/μm] (alpha) waveguide loss\n", 73 | " neff = 2.46 # Effective index of the waveguides\n", 74 | " wl0 = 1.55 # [μm] the wavelength at which neff and ng are defined\n", 75 | " radius = 5\n", 76 | " ring_length = 2 * np.pi * radius # [μm] Length of the ring\n", 77 | " coupling = 0.5 # [] coupling of the coupler\n", 78 | " wl = np.linspace(1.5, 1.6, 1000) # [μm] Wavelengths to sweep over\n", 79 | " wl = np.linspace(1.55, 1.60, 1000) # [μm] Wavelengths to sweep over\n", 80 | " ngs = [4.182551, 4.169563, 4.172917]\n", 81 | " thicknesses = [210, 220, 230]\n", 82 | "\n", 83 | " # widths = np.array([0.4, 0.45, 0.5, 0.55, 0.6])\n", 84 | " # ngs = np.array([4.38215238, 4.27254985, 4.16956338, 4.13283219, 4.05791982])\n", 85 | "\n", 86 | " widths = np.array([0.495, 0.5, 0.505])\n", 87 | " neffs = np.array([2.40197253, 2.46586378, 2.46731758])\n", 88 | " ng = 4.2 # Group index of the waveguides\n", 89 | "\n", 90 | " for width, neff in zip(widths, neffs):\n", 91 | " p = ring(\n", 92 | " wl=wl,\n", 93 | " wl0=wl0,\n", 94 | " neff=neff,\n", 95 | " ng=ng,\n", 96 | " ring_length=ring_length,\n", 97 | " coupling=coupling,\n", 98 | " loss=loss,\n", 99 | " )\n", 100 | " plt.plot(wl, p, label=f\"{int(width*1e3)}nm\")\n", 101 | "\n", 102 | " plt.title(\"ring resonator vs waveguide width\")\n", 103 | " plt.xlabel(\"wavelength (um)\")\n", 104 | " plt.ylabel(\"Power Transmission\")\n", 105 | " plt.grid()\n", 106 | " plt.legend()\n", 107 | " plt.show()" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "id": "2", 113 | "metadata": {}, 114 | "source": [ 115 | "## Layout\n", 116 | "\n", 117 | "gdsfactory easily enables you to layout Component with as many levels of hierarchy as you need.\n", 118 | "\n", 119 | "A `Component` is a canvas where we can add polygons, references to other components or ports.\n", 120 | "\n", 121 | "Lets add two references in a component." 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "id": "3", 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "c = gf.components.ring_single_heater(gap=0.2, radius=10, length_x=4)\n", 132 | "c.show()\n", 133 | "c.plot()" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "id": "4", 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "scene = c.to_3d()\n", 144 | "scene.show()" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "id": "5", 150 | "metadata": {}, 151 | "source": [ 152 | "Lets define a ring function that also accepts other component specs for the subcomponents (straight, coupler, bend)" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": null, 158 | "id": "6", 159 | "metadata": {}, 160 | "outputs": [], 161 | "source": [ 162 | "ring = gf.components.ring_single_heater(\n", 163 | " gap=0.2, radius=10, length_x=4, via_stack_offset=(2, 0)\n", 164 | ")\n", 165 | "ring_with_grating_couplers = gf.routing.add_fiber_array(ring)\n", 166 | "ring_with_grating_couplers.plot()" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "id": "7", 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [ 176 | "port_names = [\"l_e1\", \"r_e3\"]\n", 177 | "port_names = [\"l_e4\", \"r_e4\"]\n", 178 | "c = gf.routing.add_pads_top(ring, port_names=port_names, fanout_length=200)\n", 179 | "c = gf.routing.add_fiber_array(c)\n", 180 | "c" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "id": "8", 186 | "metadata": {}, 187 | "source": [ 188 | "## Top reticle assembly\n", 189 | "\n", 190 | "Once you have your components and circuits defined, you can add them into a top reticle Component for fabrication.\n", 191 | "\n", 192 | "You need to consider:\n", 193 | "\n", 194 | "- what design variations do you want to include in the mask? You need to define your Design Of Experiment or DOE\n", 195 | "- obey DRC (Design rule checking) foundry rules for manufacturability. Foundry usually provides those rules for each layer (min width, min space, min density, max density)\n", 196 | "- make sure you will be able to test te devices after fabrication. Obey DFT (design for testing) rules. For example, if your test setup works only for fiber array, what is the fiber array spacing (127 or 250um?)\n", 197 | "- if you plan to package your device, make sure you follow your packaging guidelines from your packaging house (min pad size, min pad pitch, max number of rows for wire bonding ...)" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": null, 203 | "id": "9", 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "import gdsfactory as gf\n", 208 | "import pandas as pd\n", 209 | "\n", 210 | "\n", 211 | "@gf.cell\n", 212 | "def spiral_gc(**kwargs):\n", 213 | " \"\"\"Returns spiral with Grating Couplers.\"\"\"\n", 214 | " c = gf.components.spiral(**kwargs)\n", 215 | " c = gf.routing.add_fiber_array(c)\n", 216 | " c.info[\"doe\"] = \"spirals_sc\" # strip Cband spirals\n", 217 | " c.info[\"measurement\"] = \"optical_spectrum\"\n", 218 | " c.info[\"measurement_parameters\"] = \"\"\n", 219 | " c.info[\"analysis\"] = \"[power_envelope]\"\n", 220 | " c.info[\"analysis_parameters\"] = \"[]\"\n", 221 | " c.info[\"ports_optical\"] = 4\n", 222 | " c.info[\"ports_electrical\"] = 0\n", 223 | " c.info.update(kwargs)\n", 224 | " return c\n", 225 | "\n", 226 | "\n", 227 | "@gf.cell\n", 228 | "def mzm_gc(length_x=10, **kwargs) -> gf.Component:\n", 229 | " \"\"\"Returns a MZI with Grating Couplers.\n", 230 | "\n", 231 | " Args:\n", 232 | " length_x: length of the MZI.\n", 233 | " kwargs: additional settings.\n", 234 | " \"\"\"\n", 235 | " c = gf.components.mzi2x2_2x2_phase_shifter(\n", 236 | " length_x=length_x, auto_rename_ports=False, **kwargs\n", 237 | " )\n", 238 | " c = gf.routing.add_pads_top(c, port_names=[\"top_l_e1\", \"top_r_e3\"])\n", 239 | " c = gf.routing.add_fiber_array(c)\n", 240 | " c.info[\"doe\"] = \"mzm\"\n", 241 | " c.info[\"measurement\"] = \"optical_spectrum\"\n", 242 | " c.info[\"analysis\"] = \"[fsr]\"\n", 243 | " c.info[\"analysis_parameters\"] = \"[]\"\n", 244 | " c.info[\"measurement_parameters\"] = \"\"\n", 245 | " c.info[\"ports_electrical\"] = 2\n", 246 | " c.info[\"ports_optical\"] = 6\n", 247 | " c.info[\"length_x\"] = length_x\n", 248 | " c.info.update(kwargs)\n", 249 | " return c\n", 250 | "\n", 251 | "\n", 252 | "def sample_reticle(grid: bool = False) -> gf.Component:\n", 253 | " \"\"\"Returns MZI with TE grating couplers.\"\"\"\n", 254 | "\n", 255 | " mzis = [mzm_gc(length_x=lengths) for lengths in [100, 200, 300]]\n", 256 | " spirals = [spiral_gc(length=length) for length in [0, 100, 200]]\n", 257 | " rings = []\n", 258 | " for length_x in [10, 20, 30]:\n", 259 | " ring = gf.components.ring_single_heater(length_x=length_x)\n", 260 | " c = gf.c.add_fiber_array_optical_south_electrical_north(\n", 261 | " component=ring,\n", 262 | " electrical_port_names=[\"l_e2\", \"r_e2\"],\n", 263 | " pad=gf.c.pad,\n", 264 | " grating_coupler=gf.c.grating_coupler_te,\n", 265 | " cross_section_metal=\"metal3\",\n", 266 | " )\n", 267 | " c.name = f\"ring_{length_x}\"\n", 268 | " c.info[\"doe\"] = \"ring_length_x\"\n", 269 | " c.info[\"measurement\"] = \"optical_spectrum\"\n", 270 | " c.info[\"measurement_parameters\"] = \"\"\n", 271 | " c.info[\"ports_electrical\"] = 2\n", 272 | " c.info[\"ports_optical\"] = 4\n", 273 | " c.info[\"analysis\"] = \"[fsr]\"\n", 274 | " c.info[\"analysis_parameters\"] = \"[]\"\n", 275 | " c.info[\"length_x\"] = length_x\n", 276 | " rings.append(c)\n", 277 | "\n", 278 | " copies = 3 # number of copies of each component\n", 279 | " components = mzis * copies + rings * copies + spirals * copies\n", 280 | " if grid:\n", 281 | " return gf.grid(components)\n", 282 | " c = gf.pack(components)\n", 283 | " if len(c) > 1:\n", 284 | " c = gf.pack(c)[0]\n", 285 | " return c[0]\n", 286 | "\n", 287 | "\n", 288 | "c = sample_reticle()\n", 289 | "c.show()\n", 290 | "c" 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "execution_count": null, 296 | "id": "10", 297 | "metadata": {}, 298 | "outputs": [], 299 | "source": [ 300 | "gf.labels.write_test_manifest(c, csvpath=\"sample_reticle.csv\")\n", 301 | "df = pd.read_csv(\"sample_reticle.csv\")\n", 302 | "df" 303 | ] 304 | } 305 | ], 306 | "metadata": { 307 | "jupytext": { 308 | "cell_metadata_filter": "vscode,-all", 309 | "custom_cell_magics": "kql", 310 | "encoding": "# -*- coding: utf-8 -*-", 311 | "notebook_metadata_filter": "-all" 312 | }, 313 | "kernelspec": { 314 | "display_name": "docode-ofVJGyO7", 315 | "language": "python", 316 | "name": "python3" 317 | }, 318 | "language_info": { 319 | "codemirror_mode": { 320 | "name": "ipython", 321 | "version": 3 322 | }, 323 | "file_extension": ".py", 324 | "mimetype": "text/x-python", 325 | "name": "python", 326 | "nbconvert_exporter": "python", 327 | "pygments_lexer": "ipython3", 328 | "version": "3.11.9" 329 | } 330 | }, 331 | "nbformat": 4, 332 | "nbformat_minor": 5 333 | } 334 | -------------------------------------------------------------------------------- /notebooks/30_mzi.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# MZI filter\n", 9 | "\n", 10 | "In this example we will go over a [Mach–Zehnder interferometer](https://en.wikipedia.org/wiki/Mach%E2%80%93Zehnder_interferometer) filter design.\n" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "1", 16 | "metadata": {}, 17 | "source": [ 18 | "## Calculations" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "id": "2", 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import gdsfactory as gf\n", 29 | "import numpy as np" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "id": "3", 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "def mzi(\n", 40 | " wl: np.ndarray,\n", 41 | " neff: float | None,\n", 42 | " neff1: float | None = None,\n", 43 | " neff2: float | None = None,\n", 44 | " delta_length: float | None = None,\n", 45 | " length1: float | None = 0,\n", 46 | " length2: float | None = None,\n", 47 | ") -> np.ndarray:\n", 48 | " \"\"\"Returns Frequency Domain Response of an MZI interferometer in linear units.\n", 49 | "\n", 50 | " Args:\n", 51 | " wl: wavelength in um.\n", 52 | " neff: effective index.\n", 53 | " neff1: effective index branch 1.\n", 54 | " neff2: effective index branch 2.\n", 55 | " delta_length: length difference L2-L1.\n", 56 | " length1: length of branch 1.\n", 57 | " length2: length of branch 2.\n", 58 | " \"\"\"\n", 59 | " k_0 = 2 * np.pi / wl\n", 60 | " length2 = length2 or length1 + delta_length\n", 61 | " delta_length = delta_length or np.abs(length2 - length1)\n", 62 | " neff1 = neff1 or neff\n", 63 | " neff2 = neff2 or neff\n", 64 | "\n", 65 | " E_out = 0.5 * (\n", 66 | " np.exp(1j * k_0 * neff1 * (length1 + delta_length))\n", 67 | " + np.exp(1j * k_0 * neff2 * length1)\n", 68 | " )\n", 69 | " return np.abs(E_out) ** 2\n", 70 | "\n", 71 | "\n", 72 | "if __name__ == \"__main__\":\n", 73 | " import gplugins.tidy3d as gt\n", 74 | " import matplotlib.pyplot as plt\n", 75 | "\n", 76 | " nm = 1e-3\n", 77 | " strip = gt.modes.Waveguide(\n", 78 | " wavelength=1.55,\n", 79 | " core_width=500 * nm,\n", 80 | " core_thickness=220 * nm,\n", 81 | " slab_thickness=0.0,\n", 82 | " core_material=\"si\",\n", 83 | " clad_material=\"sio2\",\n", 84 | " )\n", 85 | "\n", 86 | " neff = 2.46 # Effective index of the waveguides\n", 87 | " wl0 = 1.55 # [μm] the wavelength at which neff and ng are defined\n", 88 | " wl = np.linspace(1.5, 1.6, 1000) # [μm] Wavelengths to sweep over\n", 89 | " ngs = [4.182551, 4.169563, 4.172917]\n", 90 | " thicknesses = [210, 220, 230]\n", 91 | "\n", 92 | " length = 4e3\n", 93 | " dn = np.pi / length\n", 94 | "\n", 95 | " polyfit_TE1550SOI_220nm = np.array(\n", 96 | " [\n", 97 | " 1.02478963e-09,\n", 98 | " -8.65556534e-08,\n", 99 | " 3.32415694e-06,\n", 100 | " -7.68408985e-05,\n", 101 | " 1.19282177e-03,\n", 102 | " -1.31366332e-02,\n", 103 | " 1.05721429e-01,\n", 104 | " -6.31057637e-01,\n", 105 | " 2.80689677e00,\n", 106 | " -9.26867694e00,\n", 107 | " 2.24535191e01,\n", 108 | " -3.90664800e01,\n", 109 | " 4.71899278e01,\n", 110 | " -3.74726005e01,\n", 111 | " 1.77381560e01,\n", 112 | " -1.12666286e00,\n", 113 | " ]\n", 114 | " )\n", 115 | "\n", 116 | " def neff_w(w):\n", 117 | " return np.poly1d(polyfit_TE1550SOI_220nm)(w)\n", 118 | "\n", 119 | " w0 = 450 * nm\n", 120 | " dn1 = neff_w(w0 + 1 * nm / 2) - neff_w(w0 - 1 * nm / 2)\n", 121 | " dn5 = neff_w(w0 + 5 * nm / 2) - neff_w(w0 - 5 * nm / 2)\n", 122 | " dn10 = neff_w(w0 + 10 * nm / 2) - neff_w(w0 - 10 * nm / 2)\n", 123 | "\n", 124 | " pi_length1 = np.pi / dn1\n", 125 | " pi_length5 = np.pi / dn5\n", 126 | " pi_length10 = np.pi / dn10\n", 127 | "\n", 128 | " print(f\"pi_length = {pi_length1:.0f}um for 1nm width variation\")\n", 129 | " print(f\"pi_length = {pi_length5:.0f}um for 5nm width variation\")\n", 130 | " print(f\"pi_length = {pi_length10:.0f}um for 10nm width variation\")\n", 131 | "\n", 132 | " dn = dn1\n", 133 | " p = mzi(wl, neff=neff, neff1=neff + dn, neff2=neff + dn, delta_length=10)\n", 134 | " plt.plot(wl, p)\n", 135 | " plt.title(\"MZI\")\n", 136 | " plt.xlabel(\"wavelength (um)\")\n", 137 | " plt.ylabel(\"Power Transmission\")\n", 138 | " plt.grid()\n", 139 | " plt.legend()\n", 140 | " plt.show()" 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "id": "4", 146 | "metadata": {}, 147 | "source": [ 148 | "## Mode solver\n", 149 | "\n", 150 | "For waveguides you can compute the EM modes." 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": null, 156 | "id": "5", 157 | "metadata": {}, 158 | "outputs": [], 159 | "source": [ 160 | "import gplugins.tidy3d as gt\n", 161 | "import matplotlib.pyplot as plt\n", 162 | "import numpy as np\n", 163 | "\n", 164 | "nm = 1e-3\n", 165 | "strip = gt.modes.Waveguide(\n", 166 | " wavelength=1.55,\n", 167 | " core_width=0.5,\n", 168 | " core_thickness=0.22,\n", 169 | " slab_thickness=0.0,\n", 170 | " core_material=\"si\",\n", 171 | " clad_material=\"sio2\",\n", 172 | " group_index_step=10 * nm,\n", 173 | ")\n", 174 | "strip.plot_field(field_name=\"Ex\", mode_index=0) # TE" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": null, 180 | "id": "6", 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [ 184 | "ng = strip.n_group[0]\n", 185 | "ng" 186 | ] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "id": "7", 191 | "metadata": {}, 192 | "source": [ 193 | "## FDTD\n", 194 | "\n", 195 | "Lets compute the Sparameters of a 1x2 power splitter using [tidy3D](https://docs.flexcompute.com/projects/tidy3d/en/latest/), which is a fast GPU based FDTD commercial solver.\n", 196 | "\n", 197 | "To run, you need to [create an account](https://simulation.cloud/) and add credits. The number of credits that each simulation takes depends on the simulation computation time.\n", 198 | "\n", 199 | "![cloud_model](https://i.imgur.com/5VTCPLR.png)" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "id": "8", 206 | "metadata": { 207 | "lines_to_next_cell": 2 208 | }, 209 | "outputs": [], 210 | "source": [ 211 | "import gdsfactory.components as pdk\n", 212 | "import gplugins as sim\n", 213 | "import gplugins.tidy3d as gt" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": null, 219 | "id": "9", 220 | "metadata": {}, 221 | "outputs": [], 222 | "source": [ 223 | "c = pdk.mmi1x2()\n", 224 | "c.plot()" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": null, 230 | "id": "10", 231 | "metadata": {}, 232 | "outputs": [], 233 | "source": [ 234 | "sp = gt.write_sparameters(c, filepath=\"./mmi1x2.npz\")" 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": null, 240 | "id": "11", 241 | "metadata": {}, 242 | "outputs": [], 243 | "source": [ 244 | "sim.plot.plot_sparameters(sp)" 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": null, 250 | "id": "12", 251 | "metadata": {}, 252 | "outputs": [], 253 | "source": [ 254 | "sim.plot.plot_loss1x2(sp)" 255 | ] 256 | }, 257 | { 258 | "attachments": {}, 259 | "cell_type": "markdown", 260 | "id": "13", 261 | "metadata": {}, 262 | "source": [ 263 | "## Circuit simulation\n", 264 | "\n", 265 | "For the simulations you need to build Sparameters models for your components using FDTD or other methods.\n", 266 | "\n", 267 | "![demo](https://i.imgur.com/RSOTDIN.png)\n", 268 | "\n", 269 | "Sparameters are common in RF and photonic simulation.\n", 270 | "\n", 271 | "We are going to simulate a MZI interferometer circuit.\n", 272 | "For that we need to simulate each of the component Sparameters in tidy3d and then SAX Sparameter circuit solver to solve the Sparameters for the circuit.\n", 273 | "We will be using SAX which is an open source circuit simulator." 274 | ] 275 | }, 276 | { 277 | "cell_type": "code", 278 | "execution_count": null, 279 | "id": "14", 280 | "metadata": {}, 281 | "outputs": [], 282 | "source": [ 283 | "mzi10 = gf.components.mzi(splitter=c, delta_length=10)\n", 284 | "mzi10.plot()" 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": null, 290 | "id": "15", 291 | "metadata": {}, 292 | "outputs": [], 293 | "source": [ 294 | "mzi10.plot_netlist()" 295 | ] 296 | }, 297 | { 298 | "cell_type": "code", 299 | "execution_count": null, 300 | "id": "16", 301 | "metadata": {}, 302 | "outputs": [], 303 | "source": [ 304 | "import gdsfactory as gf\n", 305 | "import gplugins.sax as gsax\n", 306 | "import jax.numpy as jnp\n", 307 | "import matplotlib.pyplot as plt\n", 308 | "import numpy as np\n", 309 | "import sax" 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": null, 315 | "id": "17", 316 | "metadata": {}, 317 | "outputs": [], 318 | "source": [ 319 | "def straight(wl=1.5, length=10.0, neff=2.4) -> sax.SDict:\n", 320 | " return sax.reciprocal({(\"o1\", \"o2\"): jnp.exp(2j * jnp.pi * neff * length / wl)})\n", 321 | "\n", 322 | "\n", 323 | "def bend_euler(wl=1.5, length=20.0):\n", 324 | " \"\"\"Assumes a reduced transmission for the euler bend compared to a straight\"\"\"\n", 325 | " return {k: 0.99 * v for k, v in straight(wl=wl, length=length).items()}" 326 | ] 327 | }, 328 | { 329 | "cell_type": "code", 330 | "execution_count": null, 331 | "id": "18", 332 | "metadata": {}, 333 | "outputs": [], 334 | "source": [ 335 | "mmi1x2 = gsax.read.model_from_npz(sp)\n", 336 | "models = {\n", 337 | " \"bend_euler\": bend_euler,\n", 338 | " \"mmi1x2\": mmi1x2,\n", 339 | " \"straight\": straight,\n", 340 | "}" 341 | ] 342 | }, 343 | { 344 | "cell_type": "code", 345 | "execution_count": null, 346 | "id": "19", 347 | "metadata": {}, 348 | "outputs": [], 349 | "source": [ 350 | "netlist = mzi10.get_netlist()\n", 351 | "circuit, _ = sax.circuit(netlist=netlist, models=models)" 352 | ] 353 | }, 354 | { 355 | "cell_type": "code", 356 | "execution_count": null, 357 | "id": "20", 358 | "metadata": {}, 359 | "outputs": [], 360 | "source": [ 361 | "wl = np.linspace(1.5, 1.6)\n", 362 | "S = circuit(wl=wl)\n", 363 | "plt.figure(figsize=(14, 4))\n", 364 | "plt.title(\"MZI\")\n", 365 | "plt.plot(1e3 * wl, 10 * np.log10(jnp.abs(S[\"o1\", \"o2\"]) ** 2))\n", 366 | "plt.xlabel(\"λ [nm]\")\n", 367 | "plt.ylabel(\"T\")\n", 368 | "plt.grid(True)\n", 369 | "plt.show()" 370 | ] 371 | }, 372 | { 373 | "cell_type": "code", 374 | "execution_count": null, 375 | "id": "21", 376 | "metadata": {}, 377 | "outputs": [], 378 | "source": [ 379 | "mzi20 = gf.components.mzi(splitter=c, delta_length=20)\n", 380 | "mzi20.plot()" 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": null, 386 | "id": "22", 387 | "metadata": {}, 388 | "outputs": [], 389 | "source": [ 390 | "netlist = mzi20.get_netlist()\n", 391 | "circuit, _ = sax.circuit(netlist=netlist, models=models)" 392 | ] 393 | }, 394 | { 395 | "cell_type": "code", 396 | "execution_count": null, 397 | "id": "23", 398 | "metadata": {}, 399 | "outputs": [], 400 | "source": [ 401 | "wl = np.linspace(1.5, 1.6)\n", 402 | "S = circuit(wl=wl)\n", 403 | "plt.figure(figsize=(14, 4))\n", 404 | "plt.title(\"MZI\")\n", 405 | "plt.plot(1e3 * wl, 10 * np.log10(jnp.abs(S[\"o1\", \"o2\"]) ** 2))\n", 406 | "plt.xlabel(\"λ [nm]\")\n", 407 | "plt.ylabel(\"T\")\n", 408 | "plt.grid(True)\n", 409 | "plt.show()" 410 | ] 411 | } 412 | ], 413 | "metadata": { 414 | "jupytext": { 415 | "cell_metadata_filter": "-all", 416 | "custom_cell_magics": "kql", 417 | "encoding": "# -*- coding: utf-8 -*-", 418 | "notebook_metadata_filter": "-all" 419 | }, 420 | "kernelspec": { 421 | "display_name": "Python 3 (ipykernel)", 422 | "language": "python", 423 | "name": "python3" 424 | }, 425 | "language_info": { 426 | "codemirror_mode": { 427 | "name": "ipython", 428 | "version": 3 429 | }, 430 | "file_extension": ".py", 431 | "mimetype": "text/x-python", 432 | "name": "python", 433 | "nbconvert_exporter": "python", 434 | "pygments_lexer": "ipython3", 435 | "version": "3.11.8" 436 | } 437 | }, 438 | "nbformat": 4, 439 | "nbformat_minor": 5 440 | } 441 | -------------------------------------------------------------------------------- /notebooks/21_modesolver_fdfd.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Tidy3D mode solver\n", 9 | "\n", 10 | "Tidy3D includes an open-source FDFD mode solver for simulating waveguide modes.\n", 11 | "\n", 12 | "## Waveguides\n", 13 | "\n", 14 | "Guided electromagnetic modes are those with an effective index larger than that of the waveguide cladding.\n", 15 | "\n", 16 | "In the example below, we have a silicon waveguide (n = 3.4) surrounded by a SiO₂ cladding (n = 1.44). For a waveguide with dimensions of 220 nm in height and 450 nm in width, the effective index is calculated to be 2.466." 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "id": "1", 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "import gdsfactory as gf\n", 27 | "import gplugins.tidy3d as gt\n", 28 | "import matplotlib.pyplot as plt\n", 29 | "import numpy as np\n", 30 | "from gdsfactory.generic_tech import get_generic_pdk\n", 31 | "\n", 32 | "gf.config.rich_output()\n", 33 | "PDK = get_generic_pdk()\n", 34 | "PDK.activate()\n", 35 | "\n", 36 | "nm = 1e-3" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "id": "2", 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "strip = gt.modes.Waveguide(\n", 47 | " wavelength=1.55,\n", 48 | " core_width=0.5,\n", 49 | " core_thickness=0.22,\n", 50 | " slab_thickness=0.0,\n", 51 | " core_material=\"si\",\n", 52 | " clad_material=\"sio2\",\n", 53 | ")\n", 54 | "strip.plot_index()" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "id": "3", 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "strip.plot_grid()" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "id": "4", 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "strip.plot_field(field_name=\"Ex\", mode_index=0) # TE" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "id": "5", 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "strip.plot_field(field_name=\"Ex\", mode_index=0, value=\"dB\") # TE" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "id": "6", 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "strip.plot_field(field_name=\"Ey\", mode_index=1) # TM" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "id": "7", 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "strip.n_eff" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "id": "8", 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "rib = gt.modes.Waveguide(\n", 115 | " wavelength=1.55,\n", 116 | " core_width=0.5,\n", 117 | " core_thickness=0.22,\n", 118 | " slab_thickness=0.15,\n", 119 | " core_material=\"si\",\n", 120 | " clad_material=\"sio2\",\n", 121 | ")\n", 122 | "rib.plot_index()\n", 123 | "rib.n_eff" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": null, 129 | "id": "9", 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [ 133 | "rib.plot_field(field_name=\"Ex\", mode_index=0) # TE" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "id": "10", 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "nitride = gt.modes.Waveguide(\n", 144 | " wavelength=1.55,\n", 145 | " core_width=1.0,\n", 146 | " core_thickness=400 * nm,\n", 147 | " slab_thickness=0.0,\n", 148 | " core_material=\"sin\",\n", 149 | " clad_material=\"sio2\",\n", 150 | ")\n", 151 | "nitride.plot_index()\n", 152 | "nitride.n_eff" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": null, 158 | "id": "11", 159 | "metadata": {}, 160 | "outputs": [], 161 | "source": [ 162 | "nitride.plot_field(field_name=\"Ex\", mode_index=0) # TE" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "id": "12", 168 | "metadata": {}, 169 | "source": [ 170 | "## Sweep width\n", 171 | "\n", 172 | "You can sweep the waveguide width and compute the modes.\n", 173 | "\n", 174 | "By increasing the waveguide width, the waveguide supports many more TE and TM modes. Where TE modes have a dominant Ex field and TM modes have larger Ey fields.\n", 175 | "\n", 176 | "Notice that waveguides wider than 0.450 um support more than one TE mode. Therefore the maximum width for single mode operation is 0.450 um.\n" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "id": "13", 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [ 186 | "strip = gt.modes.Waveguide(\n", 187 | " wavelength=1.55,\n", 188 | " core_width=1.0,\n", 189 | " slab_thickness=0.0,\n", 190 | " core_material=\"si\",\n", 191 | " clad_material=\"sio2\",\n", 192 | " core_thickness=220 * nm,\n", 193 | " num_modes=4,\n", 194 | ")\n", 195 | "w = np.linspace(400 * nm, 1000 * nm, 7)\n", 196 | "n_eff = gt.modes.sweep_n_eff(strip, core_width=w)\n", 197 | "fraction_te = gt.modes.sweep_fraction_te(strip, core_width=w)\n", 198 | "\n", 199 | "for i in range(4):\n", 200 | " plt.plot(w, n_eff.sel(mode_index=i).real, c=\"k\")\n", 201 | " plt.scatter(\n", 202 | " w, n_eff.sel(mode_index=i).real, c=fraction_te.sel(mode_index=i), vmin=0, vmax=1\n", 203 | " )\n", 204 | "plt.axhline(y=1.44, color=\"k\", ls=\"--\")\n", 205 | "plt.colorbar().set_label(\"TE fraction\")\n", 206 | "plt.xlabel(\"Width of waveguide µm\")\n", 207 | "plt.ylabel(\"Effective refractive index\")\n", 208 | "plt.title(\"Effective index sweep\")" 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "id": "14", 214 | "metadata": {}, 215 | "source": [ 216 | "**Exercises**\n", 217 | "\n", 218 | "- What is the maximum width to support a single TE mode at 1310 nm?\n", 219 | "- For a Silicon Nitride (n=2) 400nm thick waveguide surrounded by SiO2 (n=1.44), what is the maximum width to support a single TE mode at 1550 nm?\n", 220 | "- For two 500x220nm Silicon waveguides surrounded by SiO2, what is the coupling length (100% coupling) for 200 nm gap?\n" 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "id": "15", 226 | "metadata": {}, 227 | "source": [ 228 | "## Group index\n", 229 | "\n", 230 | "You can also compute the group index for a waveguide." 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": null, 236 | "id": "16", 237 | "metadata": {}, 238 | "outputs": [], 239 | "source": [ 240 | "nm = 1e-3\n", 241 | "\n", 242 | "strip = gt.modes.Waveguide(\n", 243 | " wavelength=1.55,\n", 244 | " core_width=500 * nm,\n", 245 | " slab_thickness=0.0,\n", 246 | " core_material=\"si\",\n", 247 | " clad_material=\"sio2\",\n", 248 | " core_thickness=220 * nm,\n", 249 | " num_modes=4,\n", 250 | " group_index_step=10 * nm,\n", 251 | ")\n", 252 | "print(strip.n_group)" 253 | ] 254 | }, 255 | { 256 | "cell_type": "markdown", 257 | "id": "17", 258 | "metadata": {}, 259 | "source": [ 260 | "## Bend modes\n", 261 | "\n", 262 | "You can compute bend modes specifying the bend radius." 263 | ] 264 | }, 265 | { 266 | "cell_type": "code", 267 | "execution_count": null, 268 | "id": "18", 269 | "metadata": {}, 270 | "outputs": [], 271 | "source": [ 272 | "strip_bend = gt.modes.Waveguide(\n", 273 | " wavelength=1.55,\n", 274 | " core_width=500 * nm,\n", 275 | " core_thickness=220 * nm,\n", 276 | " slab_thickness=0.0,\n", 277 | " bend_radius=4,\n", 278 | " core_material=\"si\",\n", 279 | " clad_material=\"sio2\",\n", 280 | ")\n", 281 | "strip_bend.plot_field(field_name=\"Ex\", mode_index=0) # TE" 282 | ] 283 | }, 284 | { 285 | "cell_type": "markdown", 286 | "id": "19", 287 | "metadata": {}, 288 | "source": [ 289 | "## Bend loss\n", 290 | "\n", 291 | "You can also compute the losses coming from the mode mismatch from the bend into a straight waveguide.\n", 292 | "To compute the bend loss due to mode mismatch you can calculate the mode overlap of the straight mode and the bent mode.\n", 293 | "Because there are two mode mismatch interfaces the total loss due to mode mismatch will be squared (from bend to straight and from straight to bend).\n", 294 | "\n", 295 | "![](https://i.imgur.com/M1Yysdr.png)\n", 296 | "\n", 297 | "[from paper](https://ieeexplore.ieee.org/ielaam/50/8720127/8684870-aam.pdf)" 298 | ] 299 | }, 300 | { 301 | "cell_type": "code", 302 | "execution_count": null, 303 | "id": "20", 304 | "metadata": { 305 | "lines_to_next_cell": 2 306 | }, 307 | "outputs": [], 308 | "source": [ 309 | "radii = np.arange(4, 7)\n", 310 | "bend = gt.modes.Waveguide(\n", 311 | " wavelength=1.55,\n", 312 | " core_width=500 * nm,\n", 313 | " core_thickness=220 * nm,\n", 314 | " core_material=\"si\",\n", 315 | " clad_material=\"sio2\",\n", 316 | " num_modes=1,\n", 317 | " bend_radius=radii.min(),\n", 318 | ")\n", 319 | "mismatch = gt.modes.sweep_bend_mismatch(bend, radii)\n", 320 | "\n", 321 | "plt.plot(radii, 10 * np.log10(mismatch))\n", 322 | "plt.title(\"Strip waveguide bend\")\n", 323 | "plt.xlabel(\"Radius (μm)\")\n", 324 | "plt.ylabel(\"Mismatch (dB)\")" 325 | ] 326 | }, 327 | { 328 | "cell_type": "code", 329 | "execution_count": null, 330 | "id": "21", 331 | "metadata": {}, 332 | "outputs": [], 333 | "source": [ 334 | "dB_cm = 2 # dB/cm\n", 335 | "length = 2 * np.pi * radii * 1e-6\n", 336 | "propagation_loss = dB_cm * length * 1e2\n", 337 | "print(propagation_loss)\n", 338 | "\n", 339 | "plt.title(\"Bend90 loss for TE polarization\")\n", 340 | "plt.plot(radii, -10 * np.log10(mismatch), \".\", label=\"mode loss\")\n", 341 | "plt.plot(radii, propagation_loss, \".\", label=\"propagation loss\")\n", 342 | "plt.xlabel(\"bend radius (um)\")\n", 343 | "plt.ylabel(\"Loss (dB)\")\n", 344 | "plt.legend()" 345 | ] 346 | }, 347 | { 348 | "cell_type": "code", 349 | "execution_count": null, 350 | "id": "22", 351 | "metadata": {}, 352 | "outputs": [], 353 | "source": [ 354 | "rib = gt.modes.Waveguide(\n", 355 | " wavelength=1.55,\n", 356 | " core_width=1000 * nm,\n", 357 | " core_thickness=220 * nm,\n", 358 | " slab_thickness=110 * nm,\n", 359 | " bend_radius=15,\n", 360 | " core_material=\"si\",\n", 361 | " clad_material=\"sio2\",\n", 362 | ")\n", 363 | "rib.plot_field(field_name=\"Ex\", mode_index=0) # TE" 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": null, 369 | "id": "23", 370 | "metadata": {}, 371 | "outputs": [], 372 | "source": [ 373 | "nitride_bend = gt.modes.Waveguide(\n", 374 | " wavelength=1.55,\n", 375 | " core_width=1000 * nm,\n", 376 | " core_thickness=400 * nm,\n", 377 | " slab_thickness=0.0,\n", 378 | " bend_radius=30,\n", 379 | " core_material=\"sin\",\n", 380 | " clad_material=\"sio2\",\n", 381 | ")\n", 382 | "nitride_bend.plot_field(field_name=\"Ex\", mode_index=0, value=\"abs\") # TE" 383 | ] 384 | }, 385 | { 386 | "cell_type": "code", 387 | "execution_count": null, 388 | "id": "24", 389 | "metadata": { 390 | "lines_to_next_cell": 2 391 | }, 392 | "outputs": [], 393 | "source": [ 394 | "radii = np.array([30, 35, 40])\n", 395 | "bend = gt.modes.Waveguide(\n", 396 | " wavelength=1.55,\n", 397 | " core_width=1000 * nm,\n", 398 | " core_thickness=400 * nm,\n", 399 | " core_material=\"sin\",\n", 400 | " clad_material=\"sio2\",\n", 401 | " num_modes=1,\n", 402 | " bend_radius=radii.min(),\n", 403 | ")\n", 404 | "mismatch = gt.modes.sweep_bend_mismatch(bend, radii)" 405 | ] 406 | }, 407 | { 408 | "cell_type": "code", 409 | "execution_count": null, 410 | "id": "25", 411 | "metadata": {}, 412 | "outputs": [], 413 | "source": [ 414 | "dB_cm = 2 # dB/cm\n", 415 | "length = 2 * np.pi * radii * 1e-6\n", 416 | "propagation_loss = dB_cm * length * 1e2\n", 417 | "print(propagation_loss)\n", 418 | "\n", 419 | "plt.title(\"Bend90 loss for TE polarization\")\n", 420 | "plt.plot(radii, -10 * np.log10(mismatch), \".\", label=\"mode loss\")\n", 421 | "plt.plot(radii, propagation_loss, \".\", label=\"propagation loss\")\n", 422 | "plt.xlabel(\"bend radius (um)\")\n", 423 | "plt.ylabel(\"Loss (dB)\")\n", 424 | "plt.legend()" 425 | ] 426 | }, 427 | { 428 | "cell_type": "markdown", 429 | "id": "26", 430 | "metadata": {}, 431 | "source": [ 432 | "**Exercises**\n", 433 | "\n", 434 | "- For a 500nm wide 220nm thick Silicon waveguide surrounded by SiO2, what is the minimum bend radius to have less than 0.04dB loss for TE polarization at 1550nm?\n", 435 | "- For a 500nm wide 220nm thick Silicon waveguide surrounded by SiO2, what is the minimum bend radius to have 99% power transmission for TM polarization at 1550nm?" 436 | ] 437 | }, 438 | { 439 | "cell_type": "markdown", 440 | "id": "27", 441 | "metadata": {}, 442 | "source": [ 443 | "## Waveguide coupler\n", 444 | "\n", 445 | "You can also compute the modes of a waveguide coupler.\n", 446 | "\n", 447 | "```\n", 448 | " ore_width[0] core_width[1]\n", 449 | " <-------> <------->\n", 450 | " _______ _______ _\n", 451 | " | | | | |\n", 452 | " | | | |\n", 453 | " | |_____| | | core_thickness\n", 454 | " |slab_thickness |\n", 455 | " |_____________________| |_\n", 456 | " <----->\n", 457 | " gap\n", 458 | "\n", 459 | "\n", 460 | "```" 461 | ] 462 | }, 463 | { 464 | "cell_type": "code", 465 | "execution_count": null, 466 | "id": "28", 467 | "metadata": {}, 468 | "outputs": [], 469 | "source": [ 470 | "c = gt.modes.WaveguideCoupler(\n", 471 | " wavelength=1.55,\n", 472 | " core_width=(500 * nm, 500 * nm),\n", 473 | " gap=200 * nm,\n", 474 | " core_thickness=220 * nm,\n", 475 | " slab_thickness=100 * nm,\n", 476 | " core_material=\"si\",\n", 477 | " clad_material=\"sio2\",\n", 478 | ")\n", 479 | "c.plot_index()" 480 | ] 481 | }, 482 | { 483 | "cell_type": "code", 484 | "execution_count": null, 485 | "id": "29", 486 | "metadata": {}, 487 | "outputs": [], 488 | "source": [ 489 | "c.plot_field(field_name=\"Ex\", mode_index=0) # TE" 490 | ] 491 | }, 492 | { 493 | "cell_type": "code", 494 | "execution_count": null, 495 | "id": "30", 496 | "metadata": {}, 497 | "outputs": [], 498 | "source": [ 499 | "c.plot_field(field_name=\"Ex\", mode_index=1) # TE" 500 | ] 501 | }, 502 | { 503 | "cell_type": "code", 504 | "execution_count": null, 505 | "id": "31", 506 | "metadata": {}, 507 | "outputs": [], 508 | "source": [ 509 | "coupler = gt.modes.WaveguideCoupler(\n", 510 | " wavelength=1.55,\n", 511 | " core_width=(450 * nm, 450 * nm),\n", 512 | " core_thickness=220 * nm,\n", 513 | " core_material=\"si\",\n", 514 | " clad_material=\"sio2\",\n", 515 | " num_modes=4,\n", 516 | " gap=0.1,\n", 517 | ")\n", 518 | "\n", 519 | "print(\"\\nCoupler:\", coupler)\n", 520 | "print(\"Effective indices:\", coupler.n_eff)\n", 521 | "print(\"Mode areas:\", coupler.mode_area)\n", 522 | "print(\"Coupling length:\", coupler.coupling_length())\n", 523 | "\n", 524 | "gaps = np.linspace(0.05, 0.15, 11)\n", 525 | "lengths = gt.modes.sweep_coupling_length(coupler, gaps)\n", 526 | "\n", 527 | "_, ax = plt.subplots(1, 1)\n", 528 | "ax.plot(gaps, lengths)\n", 529 | "ax.set(xlabel=\"Gap (μm)\", ylabel=\"Coupling length (μm)\")\n", 530 | "ax.legend([\"TE\", \"TM\"])\n", 531 | "ax.grid()" 532 | ] 533 | } 534 | ], 535 | "metadata": { 536 | "jupytext": { 537 | "cell_metadata_filter": "-all", 538 | "custom_cell_magics": "kql", 539 | "encoding": "# -*- coding: utf-8 -*-", 540 | "notebook_metadata_filter": "-all" 541 | }, 542 | "kernelspec": { 543 | "display_name": "Python 3 (ipykernel)", 544 | "language": "python", 545 | "name": "python3" 546 | }, 547 | "language_info": { 548 | "codemirror_mode": { 549 | "name": "ipython", 550 | "version": 3 551 | }, 552 | "file_extension": ".py", 553 | "mimetype": "text/x-python", 554 | "name": "python", 555 | "nbconvert_exporter": "python", 556 | "pygments_lexer": "ipython3", 557 | "version": "3.11.5" 558 | } 559 | }, 560 | "nbformat": 4, 561 | "nbformat_minor": 5 562 | } 563 | -------------------------------------------------------------------------------- /notebooks/10_layout_full.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0", 6 | "metadata": {}, 7 | "source": [ 8 | "# Layout tutorial\n", 9 | "\n", 10 | "A `Component` is like an empty canvas, where you can add polygons, references to other Components and ports (to connect to other components)\n", 11 | "\n", 12 | "![](https://i.imgur.com/oeuKGsc.png)\n", 13 | "\n", 14 | "In gdsfactory **all dimensions** are in **microns**" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "id": "1", 20 | "metadata": {}, 21 | "source": [ 22 | "## Polygons\n", 23 | "\n", 24 | "You can add polygons to different layers. By default all units in gdsfactory are in um." 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "id": "2", 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "import gdsfactory as gf\n", 35 | "\n", 36 | "c = (\n", 37 | " gf.Component()\n", 38 | ") # Create a blank component (essentially an empty GDS cell with some special features)\n", 39 | "p1 = c.add_polygon([(-8, -6), (6, 8), (7, 17), (9, 5)], layer=(1, 0))\n", 40 | "c.write_gds(\"demo.gds\") # write it to a GDS file. You can open it in klayout.\n", 41 | "c.show() # show it in klayout\n", 42 | "c.plot() # plot it in jupyter notebook" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "id": "3", 48 | "metadata": {}, 49 | "source": [ 50 | "**Exercise** :\n", 51 | "\n", 52 | "Make a component similar to the one above that has a second polygon in layer (2, 0)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": null, 58 | "id": "4", 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "c = gf.Component()\n", 63 | "\n", 64 | "# Create some new geometry from the functions available in the geometry library\n", 65 | "t = gf.components.text(\"Hello!\")\n", 66 | "r = gf.components.rectangle(size=(5, 10), layer=(2, 0))\n", 67 | "\n", 68 | "# Add references to the new geometry to c, our blank component\n", 69 | "text1 = c.add_ref(t) # Add the text we created as a reference\n", 70 | "# Using the << operator (identical to add_ref()), add the same geometry a second time\n", 71 | "text2 = c << t\n", 72 | "r = c << r # Add the rectangle we created\n", 73 | "\n", 74 | "# Now that the geometry has been added to \"c\", we can move everything around:\n", 75 | "text1.movey(25)\n", 76 | "text2.move([5, 30])\n", 77 | "text2.rotate(45)\n", 78 | "r.movex(-15)\n", 79 | "c" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "id": "5", 86 | "metadata": { 87 | "lines_to_next_cell": 2 88 | }, 89 | "outputs": [], 90 | "source": [ 91 | "c = gf.Component()\n", 92 | "p1 = gf.kdb.DPolygon([(-8, -6), (6, 8), (7, 17), (9, 5)]) # DPolygons are in um\n", 93 | "p2 = p1.sized(2)\n", 94 | "\n", 95 | "c.add_polygon(p1, layer=(1, 0))\n", 96 | "c.add_polygon(p2, layer=(2, 0))\n", 97 | "c.plot()" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "id": "6", 103 | "metadata": {}, 104 | "source": [ 105 | "For operating boolean operations Klayout Regions are very useful.\n", 106 | "\n", 107 | "Notice that Many Klayout objects are in Database Units (DBU) which usually is 1nm, therefore we need to convert a DPolygon (in um) to DBU." 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": null, 113 | "id": "7", 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [ 117 | "c = gf.Component()\n", 118 | "p1 = gf.kdb.DPolygon([(-8, -6), (6, 8), (7, 17), (9, 5)])\n", 119 | "r1 = gf.kdb.Region(p1.to_itype(gf.kcl.dbu)) # convert from um to DBU\n", 120 | "r2 = r1.sized(2000) # in DBU\n", 121 | "r3 = r2 - r1\n", 122 | "\n", 123 | "c.add_polygon(r3, layer=(2, 0))\n", 124 | "c.plot()" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "id": "8", 131 | "metadata": {}, 132 | "outputs": [], 133 | "source": [ 134 | "c = gf.Component()\n", 135 | "p1 = [(-8, -6), (6, 8), (7, 17), (9, 5)]\n", 136 | "s1 = c.add_polygon(p1, layer=(1, 0))\n", 137 | "r1 = gf.kdb.Region(s1.polygon)\n", 138 | "r2 = r1.sized(2000) # in DBU, 1 DBU = 1 nm, size it by 2000 nm = 2um\n", 139 | "r3 = r2 - r1\n", 140 | "c.add_polygon(r3, layer=(2, 0))\n", 141 | "c.plot()" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "id": "9", 147 | "metadata": { 148 | "lines_to_next_cell": 2 149 | }, 150 | "source": [ 151 | "## Connect **ports**\n", 152 | "\n", 153 | "Components can have a \"Port\" that allows you to connect ComponentReferences together like legos.\n", 154 | "\n", 155 | "You can write a simple function to make a rectangular straight, assign ports to the ends, and then connect those rectangles together.\n", 156 | "\n", 157 | "Notice that `connect` transform each reference but things won't remain connected if you move any of the references afterwards.\n" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "id": "10", 164 | "metadata": {}, 165 | "outputs": [], 166 | "source": [ 167 | "def straight(length=10, width=1, layer=(1, 0)):\n", 168 | " c = gf.Component()\n", 169 | " c.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=layer)\n", 170 | " c.add_port(\n", 171 | " name=\"o1\", center=[0, width / 2], width=width, orientation=180, layer=layer\n", 172 | " )\n", 173 | " c.add_port(\n", 174 | " name=\"o2\", center=[length, width / 2], width=width, orientation=0, layer=layer\n", 175 | " )\n", 176 | " return c\n", 177 | "\n", 178 | "\n", 179 | "c = gf.Component()\n", 180 | "\n", 181 | "wg1 = c << straight(length=6, width=2.5, layer=(1, 0))\n", 182 | "wg2 = c << straight(length=6, width=2.5, layer=(2, 0))\n", 183 | "wg3 = c << straight(length=15, width=2.5, layer=(3, 0))\n", 184 | "wg2.movey(10)\n", 185 | "wg2.rotate(10)\n", 186 | "\n", 187 | "wg3.movey(20)\n", 188 | "wg3.rotate(15)\n", 189 | "\n", 190 | "c.plot()" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": null, 196 | "id": "11", 197 | "metadata": {}, 198 | "outputs": [], 199 | "source": [ 200 | "wg2.movex(10)" 201 | ] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "id": "12", 206 | "metadata": {}, 207 | "source": [ 208 | "Now we can connect everything together using the ports:\n", 209 | "\n", 210 | "Each straight has two ports: 'o1' and 'o2', respectively on the East and West sides of the rectangular straight component. These are arbitrary\n", 211 | "names defined in our straight() function above" 212 | ] 213 | }, 214 | { 215 | "cell_type": "code", 216 | "execution_count": null, 217 | "id": "13", 218 | "metadata": {}, 219 | "outputs": [], 220 | "source": [ 221 | "# Let's keep wg1 in place on the bottom, and connect the other straights to it.\n", 222 | "# To do that, on wg2 we'll grab the \"o1\" port and connect it to the \"o2\" on wg1:\n", 223 | "wg2.connect(\"o1\", wg1.ports[\"o2\"], allow_layer_mismatch=True)\n", 224 | "\n", 225 | "# Next, on wg3 let's grab the \"o1\" port and connect it to the \"o2\" on wg2:\n", 226 | "wg3.connect(\"o1\", wg2.ports[\"o2\"], allow_layer_mismatch=True)\n", 227 | "\n", 228 | "c.plot()" 229 | ] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "id": "14", 234 | "metadata": {}, 235 | "source": [ 236 | "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", 237 | "(i.e. eastmost and westmost ports)" 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": null, 243 | "id": "15", 244 | "metadata": {}, 245 | "outputs": [], 246 | "source": [ 247 | "c.add_port(\"o1\", port=wg1.ports[\"o1\"])\n", 248 | "c.add_port(\"o2\", port=wg3.ports[\"o2\"])\n", 249 | "c.plot()" 250 | ] 251 | }, 252 | { 253 | "cell_type": "markdown", 254 | "id": "16", 255 | "metadata": {}, 256 | "source": [ 257 | "You can show the ports by adding port pins with triangular shape or using the show_ports plugin\n", 258 | "\n", 259 | "![](https://i.imgur.com/Y3CuM30.png)" 260 | ] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "execution_count": null, 265 | "id": "17", 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [ 269 | "c.draw_ports()\n", 270 | "c.plot()" 271 | ] 272 | }, 273 | { 274 | "cell_type": "markdown", 275 | "id": "18", 276 | "metadata": {}, 277 | "source": [ 278 | "Also you can visualize ports in klayout as the ports are stored in the GDS cell metadata." 279 | ] 280 | }, 281 | { 282 | "cell_type": "markdown", 283 | "id": "19", 284 | "metadata": {}, 285 | "source": [ 286 | "## Move and rotate references\n", 287 | "\n", 288 | "You can move, rotate, and reflect references to Components.\n", 289 | "\n", 290 | "There are two main types of movement:\n", 291 | "\n", 292 | "1. Using Integer DataBaseUnits (DBU) (default), in most foundries, 1 DBU = 1nm\n", 293 | "2. Using Decimals Floats. Where 1.0 represents 1.0um" 294 | ] 295 | }, 296 | { 297 | "cell_type": "code", 298 | "execution_count": null, 299 | "id": "20", 300 | "metadata": {}, 301 | "outputs": [], 302 | "source": [ 303 | "c = gf.Component()\n", 304 | "\n", 305 | "wg1 = c << straight(length=1, layer=(1, 0))\n", 306 | "wg2 = c << straight(length=2, layer=(2, 0))\n", 307 | "wg3 = c << straight(length=3, layer=(3, 0))\n", 308 | "\n", 309 | "# Shift the second straight we created over by dx = 2, dy = 2 um. D stands for Decimal\n", 310 | "wg2.move([2.0, 2.0])\n", 311 | "\n", 312 | "# Then, move again the third straight by 3um\n", 313 | "wg3.movex(3) # equivalent to wg3.dmove(3)\n", 314 | "\n", 315 | "c.plot()" 316 | ] 317 | }, 318 | { 319 | "cell_type": "markdown", 320 | "id": "21", 321 | "metadata": {}, 322 | "source": [ 323 | "You can also define the positions relative to other references." 324 | ] 325 | }, 326 | { 327 | "cell_type": "code", 328 | "execution_count": null, 329 | "id": "22", 330 | "metadata": {}, 331 | "outputs": [], 332 | "source": [ 333 | "c = gf.Component()\n", 334 | "\n", 335 | "wg1 = c << straight(length=1, layer=(1, 0))\n", 336 | "wg2 = c << straight(length=2, layer=(2, 0))\n", 337 | "wg3 = c << straight(length=3, layer=(3, 0))\n", 338 | "\n", 339 | "# Shift the second straight we created over so that the xmin matches wg1.xmax\n", 340 | "wg2.xmin = wg1.xmax\n", 341 | "\n", 342 | "# Then, leave a 1um gap with on the last straight\n", 343 | "wg3.xmin = wg2.xmax + 1\n", 344 | "\n", 345 | "c.plot()" 346 | ] 347 | }, 348 | { 349 | "cell_type": "markdown", 350 | "id": "23", 351 | "metadata": {}, 352 | "source": [ 353 | "## Ports\n", 354 | "\n", 355 | "Your straights wg1/wg2/wg3 are references to other waveguide components.\n", 356 | "\n", 357 | "If you want to add ports to the new Component `c` you can use `add_port`, where you can create a new port or use a reference to an existing port from the underlying reference." 358 | ] 359 | }, 360 | { 361 | "cell_type": "markdown", 362 | "id": "24", 363 | "metadata": {}, 364 | "source": [ 365 | "You can access the ports of a Component or ComponentReference" 366 | ] 367 | }, 368 | { 369 | "cell_type": "code", 370 | "execution_count": null, 371 | "id": "25", 372 | "metadata": {}, 373 | "outputs": [], 374 | "source": [ 375 | "import gdsfactory as gf\n", 376 | "\n", 377 | "\n", 378 | "def straight(length=10, width=1, layer=(1, 0)):\n", 379 | " c = gf.Component()\n", 380 | " c.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=layer)\n", 381 | " c.add_port(\n", 382 | " name=\"o1\", center=[0, width / 2], width=width, orientation=180, layer=layer\n", 383 | " )\n", 384 | " c.add_port(\n", 385 | " name=\"o2\", center=[length, width / 2], width=width, orientation=0, layer=layer\n", 386 | " )\n", 387 | " return c" 388 | ] 389 | }, 390 | { 391 | "cell_type": "code", 392 | "execution_count": null, 393 | "id": "26", 394 | "metadata": {}, 395 | "outputs": [], 396 | "source": [ 397 | "s = straight(length=10)\n", 398 | "s.ports" 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": null, 404 | "id": "27", 405 | "metadata": {}, 406 | "outputs": [], 407 | "source": [ 408 | "s.pprint_ports()" 409 | ] 410 | }, 411 | { 412 | "cell_type": "markdown", 413 | "id": "28", 414 | "metadata": {}, 415 | "source": [ 416 | "## References\n", 417 | "\n", 418 | "Now that your component `c` is a multi-straight component, you can add references to that component in a new blank Component `c2`, then add two references and shift one to see the movement." 419 | ] 420 | }, 421 | { 422 | "cell_type": "code", 423 | "execution_count": null, 424 | "id": "29", 425 | "metadata": {}, 426 | "outputs": [], 427 | "source": [ 428 | "c2 = gf.Component()\n", 429 | "wg1 = straight(length=10)\n", 430 | "wg2 = straight(length=10, layer=(2, 0))\n", 431 | "mwg1_ref = c2.add_ref(wg1)\n", 432 | "mwg2_ref = c2.add_ref(wg2)\n", 433 | "mwg2_ref.dmovex(10)\n", 434 | "c2.plot()" 435 | ] 436 | }, 437 | { 438 | "cell_type": "code", 439 | "execution_count": null, 440 | "id": "30", 441 | "metadata": {}, 442 | "outputs": [], 443 | "source": [ 444 | "# Like before, let's connect mwg1 and mwg2 together\n", 445 | "mwg1_ref.connect(port=\"o2\", other=mwg2_ref.ports[\"o1\"], allow_layer_mismatch=True)\n", 446 | "c2.plot()" 447 | ] 448 | }, 449 | { 450 | "cell_type": "markdown", 451 | "id": "31", 452 | "metadata": {}, 453 | "source": [ 454 | "## Labels\n", 455 | "\n", 456 | "You can add abstract GDS labels to annotate your Components, in order to record information\n", 457 | "directly into the final GDS file without putting any extra geometry onto any layer\n", 458 | "This label will display in a GDS viewer, but will not be rendered or printed\n", 459 | "like the polygons created by `gf.components.text()`." 460 | ] 461 | }, 462 | { 463 | "cell_type": "code", 464 | "execution_count": null, 465 | "id": "32", 466 | "metadata": {}, 467 | "outputs": [], 468 | "source": [ 469 | "c2.add_label(text=\"First label\", position=mwg1_ref.dcenter)\n", 470 | "c2.add_label(text=\"Second label\", position=mwg2_ref.dcenter)\n", 471 | "\n", 472 | "# labels are useful for recording information\n", 473 | "c2.add_label(\n", 474 | " text=f\"The x size of this\\nlayout is {c2.dxsize}\",\n", 475 | " position=(c2.dx, c2.dy),\n", 476 | " layer=(10, 0),\n", 477 | ")\n", 478 | "c2.plot()" 479 | ] 480 | }, 481 | { 482 | "cell_type": "markdown", 483 | "id": "33", 484 | "metadata": {}, 485 | "source": [ 486 | "Another simple example" 487 | ] 488 | }, 489 | { 490 | "cell_type": "code", 491 | "execution_count": null, 492 | "id": "34", 493 | "metadata": {}, 494 | "outputs": [], 495 | "source": [ 496 | "c = gf.Component()\n", 497 | "r = c << gf.components.rectangle(size=(1, 1))\n", 498 | "r.dx = 0\n", 499 | "r.dy = 0\n", 500 | "c.add_label(\n", 501 | " text=\"Demo label\",\n", 502 | " position=(0, 0),\n", 503 | " layer=(1, 0),\n", 504 | ")\n", 505 | "c.plot()" 506 | ] 507 | }, 508 | { 509 | "cell_type": "markdown", 510 | "id": "35", 511 | "metadata": {}, 512 | "source": [ 513 | "## Boolean shapes\n", 514 | "\n", 515 | "If you want to subtract one shape from another, merge two shapes, or\n", 516 | "perform an XOR on them, you can do that with the `boolean()` function.\n", 517 | "\n", 518 | "\n", 519 | "The ``operation`` argument should be {not, and, or, xor, 'A-B', 'B-A', 'A+B'}.\n", 520 | "Note that 'A+B' is equivalent to 'or', 'A-B' is equivalent to 'not', and\n", 521 | "'B-A' is equivalent to 'not' with the operands switched" 522 | ] 523 | }, 524 | { 525 | "cell_type": "code", 526 | "execution_count": null, 527 | "id": "36", 528 | "metadata": {}, 529 | "outputs": [], 530 | "source": [ 531 | "c = gf.Component()\n", 532 | "e1 = c.add_ref(gf.components.ellipse(layer=(2, 0)))\n", 533 | "e2 = c.add_ref(gf.components.ellipse(radii=(10, 6), layer=(2, 0))).dmovex(2)\n", 534 | "e3 = c.add_ref(gf.components.ellipse(radii=(10, 4), layer=(2, 0))).dmovex(5)\n", 535 | "c.plot()" 536 | ] 537 | }, 538 | { 539 | "cell_type": "code", 540 | "execution_count": null, 541 | "id": "37", 542 | "metadata": {}, 543 | "outputs": [], 544 | "source": [ 545 | "c2 = gf.boolean(A=e2, B=e1, operation=\"not\", layer=(2, 0))\n", 546 | "c2.plot()" 547 | ] 548 | }, 549 | { 550 | "cell_type": "markdown", 551 | "id": "38", 552 | "metadata": {}, 553 | "source": [ 554 | "## Move Reference by port" 555 | ] 556 | }, 557 | { 558 | "cell_type": "code", 559 | "execution_count": null, 560 | "id": "39", 561 | "metadata": {}, 562 | "outputs": [], 563 | "source": [ 564 | "c = gf.Component()\n", 565 | "mmi = c.add_ref(gf.components.mmi1x2())\n", 566 | "bend = c.add_ref(gf.components.bend_circular(layer=(1, 0)))\n", 567 | "c.plot()" 568 | ] 569 | }, 570 | { 571 | "cell_type": "code", 572 | "execution_count": null, 573 | "id": "40", 574 | "metadata": {}, 575 | "outputs": [], 576 | "source": [ 577 | "bend.connect(\"o1\", mmi.ports[\"o2\"]) # connects follow Source -> Destination syntax\n", 578 | "c.plot()" 579 | ] 580 | }, 581 | { 582 | "cell_type": "markdown", 583 | "id": "41", 584 | "metadata": {}, 585 | "source": [ 586 | "## Mirror reference\n", 587 | "\n", 588 | "By default the mirror works along the y-axis." 589 | ] 590 | }, 591 | { 592 | "cell_type": "code", 593 | "execution_count": null, 594 | "id": "42", 595 | "metadata": {}, 596 | "outputs": [], 597 | "source": [ 598 | "c = gf.Component()\n", 599 | "mmi = c.add_ref(gf.components.mmi1x2())\n", 600 | "bend = c.add_ref(gf.components.bend_circular())\n", 601 | "bend.connect(\n", 602 | " \"o1\", mmi.ports[\"o2\"], mirror=True\n", 603 | ") # connects follow Source -> Destination syntax\n", 604 | "c.plot()" 605 | ] 606 | }, 607 | { 608 | "cell_type": "markdown", 609 | "id": "43", 610 | "metadata": {}, 611 | "source": [ 612 | "## Write\n", 613 | "\n", 614 | "You can write your Component to:\n", 615 | "\n", 616 | "- GDS file (Graphical Database System) or OASIS for chips.\n", 617 | "- gerber for PCB.\n", 618 | "- STL for 3d printing." 619 | ] 620 | }, 621 | { 622 | "cell_type": "code", 623 | "execution_count": null, 624 | "id": "44", 625 | "metadata": {}, 626 | "outputs": [], 627 | "source": [ 628 | "import gdsfactory as gf\n", 629 | "\n", 630 | "c = gf.components.cross()\n", 631 | "c.write_gds(\"demo.gds\")\n", 632 | "c.plot()" 633 | ] 634 | }, 635 | { 636 | "cell_type": "markdown", 637 | "id": "45", 638 | "metadata": {}, 639 | "source": [ 640 | "You can see the GDS file in Klayout viewer.\n", 641 | "\n", 642 | "Sometimes you also want to save the GDS together with metadata (settings, port names, widths, locations ...) in YAML" 643 | ] 644 | }, 645 | { 646 | "cell_type": "code", 647 | "execution_count": null, 648 | "id": "46", 649 | "metadata": {}, 650 | "outputs": [], 651 | "source": [ 652 | "c.write_gds(\"demo.gds\", with_metadata=True)" 653 | ] 654 | }, 655 | { 656 | "cell_type": "markdown", 657 | "id": "47", 658 | "metadata": {}, 659 | "source": [ 660 | "OASIS is a newer format that can store CAD files and that reduces the size." 661 | ] 662 | }, 663 | { 664 | "cell_type": "code", 665 | "execution_count": null, 666 | "id": "48", 667 | "metadata": {}, 668 | "outputs": [], 669 | "source": [ 670 | "c.write(\"demo.oas\")" 671 | ] 672 | }, 673 | { 674 | "cell_type": "markdown", 675 | "id": "49", 676 | "metadata": {}, 677 | "source": [ 678 | "You can also save it as STL for 3D printing or for some simulator, thanks to the LayerStack" 679 | ] 680 | }, 681 | { 682 | "cell_type": "code", 683 | "execution_count": null, 684 | "id": "50", 685 | "metadata": {}, 686 | "outputs": [], 687 | "source": [ 688 | "gf.export.to_stl(c, \"demo.stl\")" 689 | ] 690 | }, 691 | { 692 | "cell_type": "code", 693 | "execution_count": null, 694 | "id": "51", 695 | "metadata": {}, 696 | "outputs": [], 697 | "source": [ 698 | "scene = c.to_3d()\n", 699 | "scene.show()" 700 | ] 701 | }, 702 | { 703 | "cell_type": "code", 704 | "execution_count": null, 705 | "id": "52", 706 | "metadata": {}, 707 | "outputs": [], 708 | "source": [] 709 | } 710 | ], 711 | "metadata": { 712 | "jupytext": { 713 | "cell_metadata_filter": "-all", 714 | "custom_cell_magics": "kql" 715 | }, 716 | "kernelspec": { 717 | "display_name": "base", 718 | "language": "python", 719 | "name": "python3" 720 | }, 721 | "language_info": { 722 | "codemirror_mode": { 723 | "name": "ipython", 724 | "version": 3 725 | }, 726 | "file_extension": ".py", 727 | "mimetype": "text/x-python", 728 | "name": "python", 729 | "nbconvert_exporter": "python", 730 | "pygments_lexer": "ipython3", 731 | "version": "3.11.8" 732 | } 733 | }, 734 | "nbformat": 4, 735 | "nbformat_minor": 5 736 | } 737 | --------------------------------------------------------------------------------