├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── build.cmd ├── docs ├── Makefile ├── make.bat ├── nohup.out └── source │ ├── build_meep_python_parallel.sh │ ├── component-documentation.rst │ ├── components │ ├── dbr.rst │ ├── dc.rst │ ├── gratingcouplers.rst │ ├── imgs │ │ ├── ac.png │ │ ├── ac.svg │ │ ├── ac2.png │ │ ├── ac2.svg │ │ ├── ac_v2.png │ │ ├── ac_v2.svg │ │ ├── bbend.png │ │ ├── bbend.svg │ │ ├── bondpad.png │ │ ├── contradc.png │ │ ├── contradc.svg │ │ ├── contradc_zoom.png │ │ ├── contradc_zoom.svg │ │ ├── cross.png │ │ ├── dbr.png │ │ ├── dbr.svg │ │ ├── dc.png │ │ ├── dc.svg │ │ ├── dc_matrix.png │ │ ├── ebend.png │ │ ├── ebend.svg │ │ ├── ebend1.png │ │ ├── ebend2.png │ │ ├── esbend.png │ │ ├── esbend.svg │ │ ├── fc.png │ │ ├── fc.svg │ │ ├── gratingcoupler.png │ │ ├── gratingcoupler.svg │ │ ├── gratingcouplerfocusing.png │ │ ├── gratingcouplerstraight.png │ │ ├── mmi1x2 (dkita-ThinkPad-P50's conflicted copy 2019-01-09).svg │ │ ├── mmi1x2.png │ │ ├── mmi1x2.svg │ │ ├── mmi1x2_v2.png │ │ ├── mmi1x2_v2.svg │ │ ├── mmi2x2.png │ │ ├── mmi2x2.svg │ │ ├── mzi.png │ │ ├── sbend.png │ │ ├── sbend.svg │ │ ├── spiral.png │ │ ├── spiral.svg │ │ ├── stripslotconverter.png │ │ ├── stripslotconverter.svg │ │ ├── stripslotmmiconverter.png │ │ ├── stripslotmmiconverter.svg │ │ ├── stripslotyconverter.png │ │ ├── stripslotyconverter.svg │ │ ├── swgcontradc.png │ │ ├── swgcontradc.svg │ │ ├── swgcontradc_zoom.png │ │ ├── swgcontradc_zoom.svg │ │ ├── taper.png │ │ ├── taper.svg │ │ ├── target.png │ │ ├── waveguide_zoom.png │ │ ├── waveguide_zoom.svg │ │ ├── waveguides.png │ │ ├── wrapped_disks.png │ │ ├── wrapped_rings.png │ │ └── ysplitter.png │ ├── markers.rst │ ├── metalroutes.rst │ ├── mmis.rst │ ├── modeconverters.rst │ ├── mzi.rst │ ├── resonators.rst │ ├── spirals.rst │ ├── splitters.rst │ └── waveguides.rst │ ├── conf.py │ ├── imgs │ ├── mask_template.png │ ├── mcts-ez-topview.t124.png │ ├── mcts-ez-topview.t147.png │ ├── mcts-ez-topview.t178.png │ ├── meep-sim-res20-dc.png │ ├── mode_Efields.png │ ├── mode_Hfields.png │ ├── topview-mcts.png │ ├── tutorial1.png │ ├── tutorial2.png │ └── tutorial2_layers.png │ ├── index.rst │ ├── installation.rst │ ├── license.rst │ ├── picsim-documentation.rst │ ├── toolkit-documentation.rst │ ├── tutorial.rst │ ├── tutorial1.py │ ├── tutorial2.py │ └── tutorial3.py ├── picwriter ├── __init__.py ├── components │ ├── __init__.py │ ├── adiabaticcoupler.py │ ├── alignmentmarker.py │ ├── bbend.py │ ├── contradc.py │ ├── dbr.py │ ├── directionalcoupler.py │ ├── disk.py │ ├── ebend.py │ ├── electrical.py │ ├── fullcoupler.py │ ├── gratingcoupler.py │ ├── mmi1x2.py │ ├── mmi2x2.py │ ├── mzi.py │ ├── ring.py │ ├── sbend.py │ ├── spiral.py │ ├── stripslotconverter.py │ ├── stripslotmmiconverter.py │ ├── stripslotyconverter.py │ ├── swgcontradc.py │ ├── taper.py │ ├── via.py │ ├── waveguide.py │ ├── ysplitter.py │ └── zerolengthcavity.py ├── examples │ ├── mask_template.gds │ ├── mask_template.py │ ├── tutorial1.py │ └── tutorial2.py ├── mcm.py ├── mcts.py ├── picsim.py ├── tests │ ├── __init__.py │ └── test_picwriter.py └── toolkit.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled python modules. 2 | *.pyc 3 | 4 | # Setuptools distribution folder. 5 | /dist/ 6 | 7 | # Python egg metadata, regenerated from source files by setuptools. 8 | /*.egg-info 9 | 10 | # Build files 11 | /build/ 12 | /docs/build/ 13 | 14 | # Old component files 15 | /picwriter/components/old_files/ 16 | 17 | # Deprecated components 18 | /picwriter/components/spiral_old.py 19 | 20 | # Intermediate image files for documentation 21 | /docs/source/imgs/*.svg 22 | 23 | # GDSPY <-> MEEP/MPB conversion 24 | /picwriter/*.h5 25 | /picwriter/*.out 26 | /picwriter/*.ctl 27 | /picwriter/*.vtk 28 | /picwriter/test.py 29 | /picwriter/test-out/ 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.5-dev" # 3.5 development branch 7 | - "3.6" 8 | - "3.6-dev" # 3.6 development branch 9 | # command to install dependencies 10 | install: 11 | - pip install -r requirements.txt 12 | # command to run tests 13 | script: 14 | - nosetests 15 | notifications: 16 | email: false 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Derek Kita 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 | Linux/OS: [![Build Status](https://travis-ci.org/DerekK88/PICwriter.svg?branch=master)](https://travis-ci.org/DerekK88/PICwriter) 2 | Windows: [![Build status](https://ci.appveyor.com/api/projects/status/f9q96u9na63hy3ce?svg=true)](https://ci.appveyor.com/project/DerekK88/PICwriter) 3 | Documentation: [![Documentation Status](https://readthedocs.org/projects/picwriter/badge/?version=latest)](http://picwriter.readthedocs.io/en/latest/?badge=latest) 4 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) 5 | 6 | # UPDATE: This project is no longer actively maintained. 7 | It should still work, but please consider using more recent, stable projects like [gdsfactory](https://gdsfactory.github.io/gdsfactory/index.html) for your photonics layout needs! 8 | 9 | # PICwriter README 10 | Picwriter (Photonic-Integrated-Circuit Writer) is a [Python](https://www.python.org/) module, built above the [gdspy](https://github.com/heitzmann/gdspy) module, aimed at simplifying the process of designing complex masks for photonic integrated circuits through a prebuilt library of easy-to-implement PCells. Supported blocks currently include: 11 | * waveguides (strip, slot, and sub-wavelength grating) 12 | * grating couplers (straight and focusing) 13 | * tapers 14 | * strip to slot waveguide mode converters 15 | * directional couplers 16 | * adiabatic couplers 17 | * spiral structures (auto-generated from specified waveguide length) 18 | * 1x2 multi-mode interferometers 19 | * 2x2 multi-mode interferometers 20 | * ring & disk resonators with automatic bus waveguide wrapping 21 | * distributed bragg reflectors 22 | * Mach-Zehnder interferometers 23 | * alignment markers (for photolithography and ebeam lithography) 24 | * metal routes 25 | * bond pads 26 | 27 | Stay tuned, more components are coming soon! In the meantime, check out the documentation for this project at [picwriter.readthedocs.io](http://picwriter.readthedocs.io). 28 | 29 | ## Features 30 | The ultimate goal of this module is to reduce the time required to generate photonic integrated circuit mask designs, by extending the functionality of the gdspy library. 31 | * High-level specification of common building blocks for photonic-integrated circuits 32 | * Fabrication specific masks. Specify the photoresist type (`'+'` or `'-'`) and fabrication type (such as `'ETCH'`) and PICwriter will generate the appropriate mask files for electron-beam or photolithography. 33 | 34 | ## Installation 35 | 36 | ### Dependencies: 37 | With a working version of python, all dependencies should be automatically installed through the instructions below. 38 | 39 | * [Python](http://www.python.org/) (tested with versions 2.7, 3.4, 3.5, 3.6 for Linux/OS, tested with versions 2.7, 3.4, 3.5, 3.6, 2.7-x64, 3.4-x64, 3.5-x64, 3.6-x64 for Windows.) 40 | * [gdspy](https://github.com/heitzmann/gdspy) (tested with versions 2.7, 3.4, 3.5, and 3.6) 41 | * [Numpy](http://numpy.scipy.org/) 42 | * [SciPy](https://www.scipy.org/) 43 | * [Python-future](http://python-future.org/) (only for Python 2) 44 | 45 | ### Linux / OS X 46 | Both options should automatically install all dependencies (like gdspy, numpy, etc.). 47 | 48 | Option 1: using [pip](https://docs.python.org/3/installing/): 49 | 50 | ```sh 51 | pip install picwriter 52 | ``` 53 | 54 | Option 2: download the source from [github](https://github.com/DerekK88/picwriter) and build/install with: 55 | 56 | ```sh 57 | python setup.py install 58 | ``` 59 | 60 | ### Windows 61 | 62 | The best way of obtaining the library is by installing the prebuilt binaries. 63 | 64 | * First, go to the [gdspy appveyor project page](https://ci.appveyor.com/project/heitzmann/gdspy), then click the python environment that matches your python version and processor type. For example, if you have a 64-bit processor with Python version 3.5 (you can check by running `python --version` in a command prompt) then you would click 'PYTHON=C:\Python35-x64'. Then, click the **Artifacts** tab and download the corresponding `dist\gdspy-1.X.X.X.whl` wheel file. 65 | * Open up a command prompt (type `cmd` in the search bar), navigate to your downloads, then install via: 66 | 67 | ```sh 68 | pip install dist\gdspy-1.X.X.X.whl 69 | ``` 70 | 71 | * Next, install the PICwriter library by following the same procedure as before at the [picwriter appveyor page](https://ci.appveyor.com/project/DerekK88/picwriter) to install the corresponding prebuilt picwriter `.whl` file. 72 | * In a command prompt, navigate to your downloads and install with pip: 73 | 74 | ```sh 75 | pip install dist\picwriter-1.X.X.X.whl 76 | ``` 77 | 78 | Building from source is also possible. For installing gdspy, an appropriate build environment is required for compilation of the C extension modules. 79 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | 3 | matrix: 4 | 5 | # For Python versions available on Appveyor, see 6 | # http://www.appveyor.com/docs/installed-software#python 7 | # The list here is complete (excluding Python 2.6, which 8 | # isn't covered by this document) at the time of writing. 9 | 10 | - PYTHON: "C:\\Python27" 11 | - PYTHON: "C:\\Python34" 12 | - PYTHON: "C:\\Python35" 13 | - PYTHON: "C:\\Python36" 14 | - PYTHON: "C:\\Python27-x64" 15 | - PYTHON: "C:\\Python34-x64" 16 | DISTUTILS_USE_SDK: "1" 17 | - PYTHON: "C:\\Python35-x64" 18 | - PYTHON: "C:\\Python36-x64" 19 | 20 | install: 21 | # We need wheel installed to build wheels 22 | - "build.cmd %PYTHON%\\python.exe -m pip install gdspy" 23 | - "%PYTHON%\\python.exe -m pip install -r requirements.txt" 24 | - "%PYTHON%\\python.exe -m pip install wheel" 25 | 26 | build: off 27 | 28 | test_script: 29 | # Put your test command here. 30 | # If you don't need to build C extensions on 64-bit Python 3.3 or 3.4, 31 | # you can remove "build.cmd" from the front of the command, as it's 32 | # only needed to support those cases. 33 | # Note that you must use the environment variable %PYTHON% to refer to 34 | # the interpreter you're using - Appveyor does not do anything special 35 | # to put the Python version you want to use on PATH. 36 | - "build.cmd %PYTHON%\\python.exe setup.py test" 37 | 38 | after_test: 39 | # This step builds your wheels. 40 | # Again, you only need build.cmd if you're building C extensions for 41 | # 64-bit Python 3.3/3.4. And you need to use %PYTHON% to get the correct 42 | # interpreter 43 | - "build.cmd %PYTHON%\\python.exe setup.py bdist_wheel" 44 | 45 | artifacts: 46 | # bdist_wheel puts your built wheel in the dist directory 47 | - path: dist\* 48 | 49 | deploy: 50 | provider: GitHub 51 | tag: $(appveyor_repo_tag_name) 52 | description: Release $(appveyor_repo_tag_name) 53 | auth_token: 54 | secure: a/Pq9koPPpBIMd/voA8OisDMF2F+X37E7klugJoaAlAFa1nzn8wPq5hRqB/Wdr4g 55 | artifact: /picwriter.*\.whl/ 56 | prerelease: false 57 | draft: true 58 | on: 59 | appveyor_repo_tag: true 60 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | 2 | @echo off 3 | :: To build extensions for 64 bit Python 3, we need to configure environment 4 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: 5 | :: MS Windows SDK for Windows 7 and .NET Framework 4 6 | :: 7 | :: More details at: 8 | :: https://github.com/cython/cython/wiki/CythonExtensionsOnWindows 9 | 10 | IF "%DISTUTILS_USE_SDK%"=="1" ( 11 | ECHO Configuring environment to build with MSVC on a 64bit architecture 12 | ECHO Using Windows SDK 7.1 13 | "C:\Program Files\Microsoft SDKs\Windows\v7.1\Setup\WindowsSdkVer.exe" -q -version:v7.1 14 | CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 /release 15 | SET MSSdk=1 16 | REM Need the following to allow tox to see the SDK compiler 17 | SET TOX_TESTENV_PASSENV=DISTUTILS_USE_SDK MSSdk INCLUDE LIB 18 | ) ELSE ( 19 | ECHO Using default MSVC build environment 20 | ) 21 | 22 | CALL %* 23 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = picwriter 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | set SPHINXPROJ=picwriter 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/nohup.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/nohup.out -------------------------------------------------------------------------------- /docs/source/build_meep_python_parallel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | RPATH_FLAGS="-Wl,-rpath,/usr/local/lib:/usr/lib/x86_64-linux-gnu/hdf5/openmpi" 6 | MY_LDFLAGS="-L/usr/local/lib -L/usr/lib/x86_64-linux-gnu/hdf5/openmpi ${RPATH_FLAGS}" 7 | MY_CPPFLAGS="-I/usr/local/include -I/usr/include/hdf5/openmpi" 8 | 9 | sudo apt-get update 10 | sudo apt-get -y install \ 11 | libblas-dev \ 12 | liblapack-dev \ 13 | libgmp-dev \ 14 | swig \ 15 | libgsl-dev \ 16 | autoconf \ 17 | pkg-config \ 18 | libpng16-dev \ 19 | git \ 20 | guile-2.0-dev \ 21 | libfftw3-dev \ 22 | libpython3.5-dev \ 23 | python3-numpy \ 24 | python3-pip 25 | 26 | mkdir -p ~/install 27 | 28 | cd ~/install 29 | git clone https://github.com/stevengj/harminv.git 30 | cd harminv/ 31 | sh autogen.sh --enable-shared 32 | make && sudo make install 33 | 34 | cd ~/install 35 | git clone https://github.com/stevengj/libctl.git 36 | cd libctl/ 37 | sh autogen.sh --enable-shared 38 | make && sudo make install 39 | 40 | cd ~/install 41 | wget --no-check-certificate https://www.open-mpi.org/software/ompi/v2.0/downloads/openmpi-2.0.4.tar.gz 42 | tar xvzf openmpi-2.0.4.tar.gz 43 | cd openmpi-2.0.4/ 44 | ./configure --with-pic 45 | make && make check && sudo make install 46 | 47 | export CC=mpicc 48 | export CXX=mpic++ 49 | 50 | cd ~/install 51 | wget --no-check-certificate https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-1.10/hdf5-1.10.1/src/hdf5-1.10.1.tar.gz 52 | tar xvzf hdf5-1.10.1.tar.gz 53 | cd hdf5-1.10.1/ 54 | ./configure --prefix=/usr/local CC=mpicc CXX=mpic++ --with-pic --enable-parallel --enable-shared --disable-static 55 | make && sudo make install 56 | 57 | cd ~/install 58 | git clone https://github.com/stevengj/h5utils.git 59 | cd h5utils/ 60 | sh autogen.sh CC=mpicc CXX=mpic++ LDFLAGS="${MY_LDFLAGS}" CPPFLAGS="${MY_CPPFLAGS}" 61 | make && sudo make install 62 | 63 | cd ~/install 64 | git clone https://github.com/stevengj/mpb.git 65 | cd mpb/ 66 | sh autogen.sh --enable-shared CC=mpicc CXX=mpic++ LDFLAGS="${MY_LDFLAGS}" CPPFLAGS="${MY_CPPFLAGS}" 67 | make && sudo make install 68 | 69 | sudo pip install --upgrade pip 70 | pip install --no-cache-dir mpi4py 71 | export HDF5_MPI="ON" 72 | pip install --no-binary=h5py h5py 73 | 74 | cd ~/install 75 | git clone https://github.com/stevengj/meep.git 76 | cd meep/ 77 | sh autogen.sh --enable-shared --with-mpi PYTHON=/path/to/anaconda2/bin/python \ 78 | CC=mpicc CXX=mpic++ LDFLAGS="${MY_LDFLAGS}" CPPFLAGS="${MY_CPPFLAGS}" 79 | make && sudo make install 80 | -------------------------------------------------------------------------------- /docs/source/component-documentation.rst: -------------------------------------------------------------------------------- 1 | Component Documentation 2 | *********************** 3 | 4 | Below is the set of supported components that come standard in the picwriter library: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | components/waveguides.rst 10 | components/modeconverters.rst 11 | components/gratingcouplers.rst 12 | components/spirals.rst 13 | components/dc.rst 14 | components/mmis.rst 15 | components/splitters.rst 16 | components/resonators.rst 17 | components/dbr.rst 18 | components/markers.rst 19 | components/metalroutes.rst 20 | .. 21 | removed this line => components/mzi.rst 22 | 23 | components/markers.rst 24 | components/metalroutes.rst 25 | -------------------------------------------------------------------------------- /docs/source/components/dbr.rst: -------------------------------------------------------------------------------- 1 | Distributed Bragg Reflectors 2 | ++++++++++++++++++++++++++++ 3 | 4 | .. automodule:: picwriter.components 5 | :members: DBR 6 | 7 | .. image:: imgs/dbr.png 8 | -------------------------------------------------------------------------------- /docs/source/components/dc.rst: -------------------------------------------------------------------------------- 1 | Waveguide Couplers 2 | +++++++++++++++++++ 3 | 4 | ------------------------------ 5 | Directional Coupler 6 | ------------------------------ 7 | 8 | .. automodule:: picwriter.components 9 | :members: DirectionalCoupler 10 | 11 | .. image:: imgs/dc.png 12 | 13 | ------------------------------ 14 | Adiabatic 3dB Coupler 15 | ------------------------------ 16 | 17 | .. automodule:: picwriter.components 18 | :members: AdiabaticCoupler 19 | 20 | .. image:: imgs/ac_v2.png 21 | 22 | ------------------------------ 23 | Adiabatic Full Coupler 24 | ------------------------------ 25 | 26 | .. automodule:: picwriter.components 27 | :members: FullCoupler 28 | 29 | .. image:: imgs/fc.png 30 | 31 | ------------------------------------------------------------ 32 | Sub-wavelength Grating Assisted Contra-Directional Coupler 33 | ------------------------------------------------------------ 34 | 35 | For more details on the principles and operation behind this type of component, please see https://doi.org/10.1364/OE.25.025310. This component uses one regular waveguide (at top) and one sub-wavelength grating (at bottom) to selectively reflect a certain spectral band to the `input_bot` port. Apodization of the top waveguide is also supported. 36 | 37 | .. automodule:: picwriter.components 38 | :members: SWGContraDirectionalCoupler 39 | 40 | .. image:: imgs/swgcontradc.png 41 | .. image:: imgs/swgcontradc_zoom.png 42 | 43 | ------------------------------ 44 | Example Usage 45 | ------------------------------ 46 | 47 | .. image:: imgs/dc_matrix.png 48 | 49 | The directional coupler matrix shown above is generated by:: 50 | 51 | top = gdspy.Cell("top") 52 | wgt = WaveguideTemplate(bend_radius=100, resist='+') 53 | 54 | wg1=Waveguide([(0,0), (100,0)], wgt) 55 | tk.add(top, wg1) 56 | 57 | dc1 = DirectionalCoupler(wgt, 10.0, 0.5, angle=np.pi/6.0, parity=1, **wg1.portlist["output"]) 58 | dc2 = DirectionalCoupler(wgt, 10.0, 0.5, angle=np.pi/6.0, parity=-1, **dc1.portlist["output_top"]) 59 | dc3 = DirectionalCoupler(wgt, 10.0, 0.5, angle=np.pi/6.0, parity=1, **dc1.portlist["output_bot"]) 60 | dc4 = DirectionalCoupler(wgt, 10.0, 0.5, angle=np.pi/6.0, parity=1, **dc2.portlist["output_bot"]) 61 | dc5 = DirectionalCoupler(wgt, 10.0, 0.5, angle=np.pi/6.0, parity=-1, **dc2.portlist["output_top"]) 62 | dc6 = DirectionalCoupler(wgt, 10.0, 0.5, angle=np.pi/6.0, parity=1, **dc3.portlist["output_bot"]) 63 | tk.add(top, dc1) 64 | tk.add(top, dc2) 65 | tk.add(top, dc3) 66 | tk.add(top, dc4) 67 | tk.add(top, dc5) 68 | tk.add(top, dc6) 69 | -------------------------------------------------------------------------------- /docs/source/components/gratingcouplers.rst: -------------------------------------------------------------------------------- 1 | Grating Couplers 2 | ++++++++++++++++ 3 | 4 | Grating Couplers 5 | ------------------------- 6 | 7 | .. automodule:: picwriter.components 8 | :members: GratingCoupler 9 | 10 | .. image:: imgs/gratingcoupler.png 11 | 12 | Straight Grating Couplers 13 | ------------------------- 14 | 15 | .. automodule:: picwriter.components 16 | :members: GratingCouplerStraight 17 | 18 | .. image:: imgs/gratingcouplerstraight.png 19 | 20 | Focusing Grating Couplers 21 | ------------------------- 22 | 23 | .. automodule:: picwriter.components 24 | :members: GratingCouplerFocusing 25 | 26 | .. image:: imgs/gratingcouplerfocusing.png 27 | -------------------------------------------------------------------------------- /docs/source/components/imgs/ac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/ac.png -------------------------------------------------------------------------------- /docs/source/components/imgs/ac2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/ac2.png -------------------------------------------------------------------------------- /docs/source/components/imgs/ac_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/ac_v2.png -------------------------------------------------------------------------------- /docs/source/components/imgs/bbend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/bbend.png -------------------------------------------------------------------------------- /docs/source/components/imgs/bondpad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/bondpad.png -------------------------------------------------------------------------------- /docs/source/components/imgs/contradc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/contradc.png -------------------------------------------------------------------------------- /docs/source/components/imgs/contradc_zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/contradc_zoom.png -------------------------------------------------------------------------------- /docs/source/components/imgs/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/cross.png -------------------------------------------------------------------------------- /docs/source/components/imgs/dbr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/dbr.png -------------------------------------------------------------------------------- /docs/source/components/imgs/dc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/dc.png -------------------------------------------------------------------------------- /docs/source/components/imgs/dc_matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/dc_matrix.png -------------------------------------------------------------------------------- /docs/source/components/imgs/ebend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/ebend.png -------------------------------------------------------------------------------- /docs/source/components/imgs/ebend1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/ebend1.png -------------------------------------------------------------------------------- /docs/source/components/imgs/ebend2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/ebend2.png -------------------------------------------------------------------------------- /docs/source/components/imgs/esbend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/esbend.png -------------------------------------------------------------------------------- /docs/source/components/imgs/fc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/fc.png -------------------------------------------------------------------------------- /docs/source/components/imgs/gratingcoupler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/gratingcoupler.png -------------------------------------------------------------------------------- /docs/source/components/imgs/gratingcouplerfocusing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/gratingcouplerfocusing.png -------------------------------------------------------------------------------- /docs/source/components/imgs/gratingcouplerstraight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/gratingcouplerstraight.png -------------------------------------------------------------------------------- /docs/source/components/imgs/mmi1x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/mmi1x2.png -------------------------------------------------------------------------------- /docs/source/components/imgs/mmi1x2_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/mmi1x2_v2.png -------------------------------------------------------------------------------- /docs/source/components/imgs/mmi2x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/mmi2x2.png -------------------------------------------------------------------------------- /docs/source/components/imgs/mzi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/mzi.png -------------------------------------------------------------------------------- /docs/source/components/imgs/sbend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/sbend.png -------------------------------------------------------------------------------- /docs/source/components/imgs/spiral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/spiral.png -------------------------------------------------------------------------------- /docs/source/components/imgs/stripslotconverter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/stripslotconverter.png -------------------------------------------------------------------------------- /docs/source/components/imgs/stripslotmmiconverter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/stripslotmmiconverter.png -------------------------------------------------------------------------------- /docs/source/components/imgs/stripslotyconverter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/stripslotyconverter.png -------------------------------------------------------------------------------- /docs/source/components/imgs/swgcontradc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/swgcontradc.png -------------------------------------------------------------------------------- /docs/source/components/imgs/swgcontradc_zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/swgcontradc_zoom.png -------------------------------------------------------------------------------- /docs/source/components/imgs/taper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/taper.png -------------------------------------------------------------------------------- /docs/source/components/imgs/target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/target.png -------------------------------------------------------------------------------- /docs/source/components/imgs/waveguide_zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/waveguide_zoom.png -------------------------------------------------------------------------------- /docs/source/components/imgs/waveguides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/waveguides.png -------------------------------------------------------------------------------- /docs/source/components/imgs/wrapped_disks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/wrapped_disks.png -------------------------------------------------------------------------------- /docs/source/components/imgs/wrapped_rings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/wrapped_rings.png -------------------------------------------------------------------------------- /docs/source/components/imgs/ysplitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/components/imgs/ysplitter.png -------------------------------------------------------------------------------- /docs/source/components/markers.rst: -------------------------------------------------------------------------------- 1 | Alignment Markers 2 | ++++++++++++++++++ 3 | 4 | Alignment Cross 5 | --------------- 6 | 7 | .. automodule:: picwriter.components 8 | :members: AlignmentCross 9 | 10 | .. image:: imgs/cross.png 11 | 12 | Alignment Target 13 | ----------------- 14 | 15 | .. automodule:: picwriter.components 16 | :members: AlignmentTarget 17 | 18 | .. image:: imgs/target.png 19 | -------------------------------------------------------------------------------- /docs/source/components/metalroutes.rst: -------------------------------------------------------------------------------- 1 | Metal Templates, Routes, & Bondpads 2 | ++++++++++++++++++++++++++++++++++++ 3 | 4 | Metal Template 5 | ------------------ 6 | 7 | .. automodule:: picwriter.components 8 | :members: MetalTemplate 9 | 10 | Metal Routes 11 | ------------ 12 | 13 | .. automodule:: picwriter.components 14 | :members: MetalRoute 15 | 16 | Bondpads 17 | --------- 18 | 19 | .. automodule:: picwriter.components 20 | :members: Bondpad 21 | 22 | .. image:: imgs/bondpad.png 23 | 24 | The above was generated with:: 25 | 26 | top = gdspy.Cell("top") 27 | mt = MetalTemplate(bend_radius=0, resist='+', fab="ETCH") 28 | 29 | mt1=MetalRoute([(0,0), (0,250), (100,250), (100,500), (400,500)], mt) 30 | bp1 = Bondpad(mt, **mt1.portlist["output"]) 31 | bp2 = Bondpad(mt, **mt1.portlist["input"]) 32 | tk.add(top, bp1) 33 | tk.add(top, bp2) 34 | tk.add(top, mt1) 35 | -------------------------------------------------------------------------------- /docs/source/components/mmis.rst: -------------------------------------------------------------------------------- 1 | Multi-Mode Interferometers (MMI's) 2 | ++++++++++++++++++++++++++++++++++ 3 | 4 | 1x2 MMI 5 | ------- 6 | 7 | .. automodule:: picwriter.components 8 | :members: MMI1x2 9 | 10 | .. image:: imgs/mmi1x2_v2.png 11 | 12 | 13 | 2x2 MMI 14 | ------- 15 | 16 | .. automodule:: picwriter.components 17 | :members: MMI2x2 18 | 19 | .. image:: imgs/mmi2x2.png 20 | -------------------------------------------------------------------------------- /docs/source/components/modeconverters.rst: -------------------------------------------------------------------------------- 1 | Mode converters 2 | ++++++++++++++++ 3 | 4 | ---------------------------- 5 | Linear tapers 6 | ---------------------------- 7 | 8 | Below is a standard taper class that can be used to linearly taper the waveguide width (such as for inverse tapers commonly used for fiber-to-chip coupling). 9 | 10 | .. automodule:: picwriter.components 11 | :members: Taper 12 | 13 | .. image:: imgs/taper.png 14 | 15 | ------------------------------ 16 | Strip-to-slot mode converters 17 | ------------------------------ 18 | 19 | .. automodule:: picwriter.components 20 | :members: StripSlotConverter 21 | 22 | .. image:: imgs/stripslotconverter.png 23 | 24 | .. automodule:: picwriter.components 25 | :members: StripSlotMMIConverter 26 | 27 | .. image:: imgs/stripslotmmiconverter.png 28 | 29 | .. automodule:: picwriter.components 30 | :members: StripSlotYConverter 31 | 32 | .. image:: imgs/stripslotyconverter.png 33 | -------------------------------------------------------------------------------- /docs/source/components/mzi.rst: -------------------------------------------------------------------------------- 1 | Mach-Zehnder Interferometers 2 | ++++++++++++++++++++++++++++++++++ 3 | 4 | Regular Mach-Zehnder 5 | -------------------- 6 | 7 | .. automodule:: picwriter.components 8 | :members: MachZehnder 9 | 10 | Mach-Zehnder Switches 11 | ---------------------- 12 | Same as the type above, but instead places a 1x2 MMI for the input, and a 2x2 MMI at the output. Tuning of the respective arms allows for optical switching between the two output paths. 13 | 14 | .. automodule:: picwriter.components 15 | :members: MachZehnderSwitch1x2, MachZehnderSwitchDC1x2, MachZehnderSwitchDC2x2 16 | 17 | .. image:: imgs/mzi.png 18 | 19 | The above image of an unbalanced Mach-Zehnder with heaters for thermo-optic phase modulation is easily generated from the following code:: 20 | 21 | top = gdspy.Cell("top") 22 | wgt = WaveguideTemplate(bend_radius=50, wg_width=1.0, resist='+') 23 | htr_mt = MetalTemplate(width=25, clad_width=25, bend_radius=wgt.bend_radius, resist='+', fab="ETCH", metal_layer=13, metal_datatype=0, clad_layer=14, clad_datatype=0) 24 | mt = MetalTemplate(width=25, clad_width=25, resist='+', fab="ETCH", metal_layer=11, metal_datatype=0, clad_layer=12, clad_datatype=0) 25 | 26 | wg_in = Waveguide([(0,0), (300,0)], wgt) 27 | tk.add(top, wg_in) 28 | mzi = MachZehnder(wgt, MMIlength=50, MMIwidth=10, MMItaper_width=2.0, MMIwg_sep=3, arm1=0, arm2=100, heater=True, heater_length=400, mt=htr_mt, **wg_in.portlist["output"]) 29 | tk.add(top, mzi) 30 | wg_out = Waveguide([mzi.portlist["output"]["port"], (mzi.portlist["output"]["port"][0]+300, mzi.portlist["output"]["port"][1])], wgt) 31 | tk.add(top, wg_out) 32 | 33 | mt1=MetalRoute([mzi.portlist["heater_top_in"]["port"], 34 | (mzi.portlist["heater_top_in"]["port"][0]-150, mzi.portlist["heater_top_in"]["port"][1]), 35 | (mzi.portlist["heater_top_in"]["port"][0]-150, mzi.portlist["heater_top_in"]["port"][1]+200)], mt) 36 | mt2=MetalRoute([mzi.portlist["heater_top_out"]["port"], 37 | (mzi.portlist["heater_top_out"]["port"][0]+150, mzi.portlist["heater_top_out"]["port"][1]), 38 | (mzi.portlist["heater_top_out"]["port"][0]+150, mzi.portlist["heater_top_out"]["port"][1]+200)], mt) 39 | mt3=MetalRoute([mzi.portlist["heater_bot_in"]["port"], 40 | (mzi.portlist["heater_bot_in"]["port"][0]-150, mzi.portlist["heater_bot_in"]["port"][1]), 41 | (mzi.portlist["heater_bot_in"]["port"][0]-150, mzi.portlist["heater_bot_in"]["port"][1]-200)], mt) 42 | mt4=MetalRoute([mzi.portlist["heater_bot_out"]["port"], 43 | (mzi.portlist["heater_bot_out"]["port"][0]+150, mzi.portlist["heater_bot_out"]["port"][1]), 44 | (mzi.portlist["heater_bot_out"]["port"][0]+150, mzi.portlist["heater_bot_out"]["port"][1]-200)], mt) 45 | tk.add(top, mt1) 46 | tk.add(top, mt2) 47 | tk.add(top, mt3) 48 | tk.add(top, mt4) 49 | tk.add(top, Bondpad(mt, **mt1.portlist["output"])) 50 | tk.add(top, Bondpad(mt, **mt2.portlist["output"])) 51 | tk.add(top, Bondpad(mt, **mt3.portlist["output"])) 52 | tk.add(top, Bondpad(mt, **mt4.portlist["output"])) 53 | -------------------------------------------------------------------------------- /docs/source/components/resonators.rst: -------------------------------------------------------------------------------- 1 | Cavities and Resonators 2 | +++++++++++++++++++++++ 3 | 4 | Ring Resonators 5 | --------------- 6 | 7 | .. automodule:: picwriter.components 8 | :members: Ring 9 | 10 | .. image:: imgs/wrapped_rings.png 11 | 12 | The code for generating the above three rings is:: 13 | 14 | top = gdspy.Cell("top") 15 | wgt = WaveguideTemplate(bend_radius=50, resist='+') 16 | 17 | wg1=Waveguide([(0,0), (100,0)], wgt) 18 | tk.add(top, wg1) 19 | 20 | r1 = Ring(wgt, 60.0, 1.0, wrap_angle=np.pi/2., parity=1, **wg1.portlist["output"]) 21 | 22 | wg2=Waveguide([r1.portlist["output"]["port"], (r1.portlist["output"]["port"][0]+100, r1.portlist["output"]["port"][1])], wgt) 23 | tk.add(top, wg2) 24 | 25 | r2 = Ring(wgt, 50.0, 0.8, wrap_angle=np.pi, parity=-1, **wg2.portlist["output"]) 26 | 27 | wg3=Waveguide([r2.portlist["output"]["port"], (r2.portlist["output"]["port"][0]+100, r2.portlist["output"]["port"][1])], wgt) 28 | tk.add(top, wg3) 29 | 30 | r3 = Ring(wgt, 40.0, 0.6, parity=1, **wg3.portlist["output"]) 31 | 32 | wg4=Waveguide([r3.portlist["output"]["port"], (r3.portlist["output"]["port"][0]+100, r3.portlist["output"]["port"][1])], wgt) 33 | tk.add(top, wg4) 34 | 35 | tk.add(top, r1) 36 | tk.add(top, r2) 37 | tk.add(top, r3) 38 | 39 | gdspy.LayoutViewer() 40 | 41 | Disk Resonators 42 | --------------- 43 | 44 | .. automodule:: picwriter.components 45 | :members: Disk 46 | 47 | .. image:: imgs/wrapped_disks.png 48 | 49 | The code for generating the above three disks is:: 50 | 51 | top = gdspy.Cell("top") 52 | wgt = WaveguideTemplate(bend_radius=50, resist='+') 53 | 54 | wg1=Waveguide([(0,0), (100,0)], wgt) 55 | tk.add(top, wg1) 56 | 57 | r1 = Disk(wgt, 60.0, 1.0, wrap_angle=np.pi/2., parity=1, **wg1.portlist["output"]) 58 | 59 | wg2=Waveguide([r1.portlist["output"]["port"], (r1.portlist["output"]["port"][0]+100, r1.portlist["output"]["port"][1])], wgt) 60 | tk.add(top, wg2) 61 | 62 | r2 = Disk(wgt, 50.0, 0.8, wrap_angle=np.pi, parity=-1, **wg2.portlist["output"]) 63 | 64 | wg3=Waveguide([r2.portlist["output"]["port"], (r2.portlist["output"]["port"][0]+100, r2.portlist["output"]["port"][1])], wgt) 65 | tk.add(top, wg3) 66 | 67 | r3 = Disk(wgt, 40.0, 0.6, parity=1, **wg3.portlist["output"]) 68 | 69 | wg4=Waveguide([r3.portlist["output"]["port"], (r3.portlist["output"]["port"][0]+100, r3.portlist["output"]["port"][1])], wgt) 70 | tk.add(top, wg4) 71 | 72 | tk.add(top, r1) 73 | tk.add(top, r2) 74 | tk.add(top, r3) 75 | 76 | gdspy.LayoutViewer() 77 | -------------------------------------------------------------------------------- /docs/source/components/spirals.rst: -------------------------------------------------------------------------------- 1 | Spirals 2 | +++++++ 3 | 4 | .. automodule:: picwriter.components 5 | :members: Spiral 6 | 7 | .. image:: imgs/spiral.png 8 | -------------------------------------------------------------------------------- /docs/source/components/splitters.rst: -------------------------------------------------------------------------------- 1 | Power Splitters and Combiners 2 | ++++++++++++++++++++++++++++++++++ 3 | 4 | Y Splitter 5 | ----------- 6 | 7 | .. automodule:: picwriter.components 8 | :members: SplineYSplitter 9 | 10 | .. image:: imgs/ysplitter.png -------------------------------------------------------------------------------- /docs/source/components/waveguides.rst: -------------------------------------------------------------------------------- 1 | Waveguides & Waveguide Templates 2 | ++++++++++++++++++++++++++++++++ 3 | 4 | Waveguide Template 5 | -------------------- 6 | 7 | Waveguide template objects are used to define a standard set of parameters (width, cladding, layers, etc.) that is passed to waveguide routes and PICwriter components. 8 | 9 | .. automodule:: picwriter.components 10 | :members: WaveguideTemplate 11 | 12 | .. image:: imgs/waveguides.png 13 | .. image:: imgs/waveguide_zoom.png 14 | :width: 60% 15 | 16 | Example usage: 17 | 18 | To generate a strip waveguide template with 1.0um width and 25.0um bending radius:: 19 | 20 | wgt= WaveguideTemplate(wg_type='strip', wg_width=1.0, bend_radius=25) 21 | 22 | To generate a slot waveguide with 1.0um width, 25.0um bending radius, and a 0.3um slot in the center:: 23 | 24 | wgt = WaveguideTemplate(wg_type='slot', wg_width=1.0, bend_radius=25.0, slot=0.3) 25 | 26 | To generate a sub-wavelength grating waveguide with 1.0um width, 25.0um bending radius, 50% duty-cycle, and a 1um period:: 27 | 28 | wgt= WaveguideTemplate(wg_type='swg', wg_width=1.0, bend_radius=25, duty_cycle=0.50, period=1.0) 29 | 30 | 31 | Waveguides 32 | ------------ 33 | 34 | Waveguide objects are fully defined by a WaveguideTemplate object as well as a list of (x,y) points that determine where the waveguide is routed. 35 | 36 | .. automodule:: picwriter.components 37 | :members: Waveguide 38 | 39 | Example usage to generate a waveguide with waypoints:: 40 | 41 | top = gdspy.Cell('top') 42 | wgt= WaveguideTemplate(wg_type='strip', wg_width=1.0, bend_radius=25) 43 | waypoints = [(0, 0), (200, 0), (250, 100), (400, 100)] 44 | wg = Waveguide(waypoints, wgt) 45 | tk.add(top, wg) 46 | gdspy.LayoutViewer() 47 | 48 | Bends 49 | ++++++++++++++++++++++++++++++++ 50 | 51 | Euler Bends 52 | ------------ 53 | 54 | .. automodule:: picwriter.components 55 | :members: EBend 56 | 57 | .. image:: imgs/ebend.png 58 | 59 | Euler S-Bends 60 | --------------- 61 | 62 | .. automodule:: picwriter.components 63 | :members: EulerSBend 64 | 65 | .. image:: imgs/esbend.png 66 | 67 | Sinusoidal S-bends 68 | ------------------- 69 | 70 | .. automodule:: picwriter.components 71 | :members: SBend 72 | 73 | .. image:: imgs/sbend.png 74 | 75 | Bezier curves 76 | ------------------ 77 | 78 | .. automodule:: picwriter.components 79 | :members: BBend 80 | 81 | .. image:: imgs/bbend.png -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # picwriter documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Feb 8 18:08:33 2018. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | # import os 20 | # import sys 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | # needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.mathjax", "sphinx.ext.githubpages"] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ["ntemplates"] 37 | 38 | # The suffix(es) of source filenames. 39 | # You can specify multiple suffix as a list of string: 40 | # 41 | # source_suffix = ['.rst', '.md'] 42 | source_suffix = ".rst" 43 | 44 | # The master toctree document. 45 | master_doc = "index" 46 | 47 | # General information about the project. 48 | project = u"picwriter" 49 | copyright = u"2018, Derek Kita" 50 | author = u"Derek Kita" 51 | 52 | # The version info for the project you're documenting, acts as replacement for 53 | # |version| and |release|, also used in various other places throughout the 54 | # built documents. 55 | # 56 | # The short X.Y version. 57 | version = u"0.0.1" 58 | # The full version, including alpha/beta/rc tags. 59 | release = u"0.0.1" 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | language = None 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | # This patterns also effect to html_static_path and html_extra_path 71 | exclude_patterns = [] 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | pygments_style = "sphinx" 75 | 76 | # If true, `todo` and `todoList` produce output, else they produce nothing. 77 | todo_include_todos = False 78 | 79 | 80 | # -- Options for HTML output ---------------------------------------------- 81 | 82 | # The theme to use for HTML and HTML Help pages. See the documentation for 83 | # a list of builtin themes. 84 | # 85 | html_theme = "sphinx_rtd_theme" 86 | 87 | # Theme options are theme-specific and customize the look and feel of a theme 88 | # further. For a list of options available for each theme, see the 89 | # documentation. 90 | # 91 | # html_theme_options = {} 92 | 93 | # Add any paths that contain custom static files (such as style sheets) here, 94 | # relative to this directory. They are copied after the builtin static files, 95 | # so a file named "default.css" will overwrite the builtin "default.css". 96 | html_static_path = ["nstatic"] 97 | 98 | # Custom sidebar templates, must be a dictionary that maps document names 99 | # to template names. 100 | # 101 | # This is required for the alabaster theme 102 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 103 | html_sidebars = { 104 | "**": [ 105 | "about.html", 106 | "navigation.html", 107 | "relations.html", # needs 'show_related': True theme option to display 108 | "searchbox.html", 109 | "donate.html", 110 | ] 111 | } 112 | 113 | 114 | # -- Options for HTMLHelp output ------------------------------------------ 115 | 116 | # Output file base name for HTML help builder. 117 | htmlhelp_basename = "picwriterdoc" 118 | 119 | 120 | # -- Options for LaTeX output --------------------------------------------- 121 | 122 | latex_elements = { 123 | # The paper size ('letterpaper' or 'a4paper'). 124 | # 125 | # 'papersize': 'letterpaper', 126 | # The font size ('10pt', '11pt' or '12pt'). 127 | # 128 | # 'pointsize': '10pt', 129 | # Additional stuff for the LaTeX preamble. 130 | # 131 | # 'preamble': '', 132 | # Latex figure (float) alignment 133 | # 134 | # 'figure_align': 'htbp', 135 | } 136 | 137 | # Grouping the document tree into LaTeX files. List of tuples 138 | # (source start file, target name, title, 139 | # author, documentclass [howto, manual, or own class]). 140 | latex_documents = [ 141 | (master_doc, "picwriter.tex", u"picwriter Documentation", u"Derek Kita", "manual") 142 | ] 143 | 144 | 145 | # -- Options for manual page output --------------------------------------- 146 | 147 | # One entry per manual page. List of tuples 148 | # (source start file, name, description, authors, manual section). 149 | man_pages = [(master_doc, "picwriter", u"picwriter Documentation", [author], 1)] 150 | 151 | 152 | # -- Options for Texinfo output ------------------------------------------- 153 | 154 | # Grouping the document tree into Texinfo files. List of tuples 155 | # (source start file, target name, title, author, 156 | # dir menu entry, description, category) 157 | texinfo_documents = [ 158 | ( 159 | master_doc, 160 | "picwriter", 161 | u"picwriter Documentation", 162 | author, 163 | "picwriter", 164 | "One line description of project.", 165 | "Miscellaneous", 166 | ) 167 | ] 168 | -------------------------------------------------------------------------------- /docs/source/imgs/mask_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/imgs/mask_template.png -------------------------------------------------------------------------------- /docs/source/imgs/mcts-ez-topview.t124.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/imgs/mcts-ez-topview.t124.png -------------------------------------------------------------------------------- /docs/source/imgs/mcts-ez-topview.t147.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/imgs/mcts-ez-topview.t147.png -------------------------------------------------------------------------------- /docs/source/imgs/mcts-ez-topview.t178.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/imgs/mcts-ez-topview.t178.png -------------------------------------------------------------------------------- /docs/source/imgs/meep-sim-res20-dc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/imgs/meep-sim-res20-dc.png -------------------------------------------------------------------------------- /docs/source/imgs/mode_Efields.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/imgs/mode_Efields.png -------------------------------------------------------------------------------- /docs/source/imgs/mode_Hfields.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/imgs/mode_Hfields.png -------------------------------------------------------------------------------- /docs/source/imgs/topview-mcts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/imgs/topview-mcts.png -------------------------------------------------------------------------------- /docs/source/imgs/tutorial1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/imgs/tutorial1.png -------------------------------------------------------------------------------- /docs/source/imgs/tutorial2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/imgs/tutorial2.png -------------------------------------------------------------------------------- /docs/source/imgs/tutorial2_layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/docs/source/imgs/tutorial2_layers.png -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | PICwriter Documentation 2 | ======================= 3 | 4 | Picwriter (Photonic-Integrated-Circuit Writer) is a free Python module, built above the gdspy module, aimed at simplifying the process of designing complex GDSII masks for photonic integrated circuits through a prebuilt library of easy-to-implement PCells. Supported blocks include waveguides, straight grating couplers, focusing grating couplers, tapers, directional couplers, multi-mode interferometers (MMI's), resonators, spiral structures, and more that are coming soon! 5 | 6 | Features 7 | -------- 8 | 9 | The ultimate goal of this module is to reduce the time required to generate photonic integrated circuit mask designs, by extending the functionality of the gdspy library. 10 | 11 | * High-level specification of common building blocks for photonic-integrated circuits 12 | * Easily snap photonic components together using portlist syntax and waypoint routing of waveguides and metal traces 13 | * PICwriter will automatically detect if you are adding a cell to the mask which is identical to one added before, so you don't have to worry about referencing existing cells. 14 | * Waveguide bends and curves automatically compute the number of vertices per polygon to minimize grid errors. 15 | * (coming soon!) simple templates for writing your own custom PCells to be used with PICwriter 16 | 17 | Contribute 18 | ---------- 19 | 20 | This project got started because I wanted to make it easier to generate simple lithography masks for in-house fabrication of PICs at MIT. If you find this project useful, or have ideas for new components to add to the library, please feel free to contribute to the project! 21 | 22 | - Issue Tracker: `github.com/DerekK88/PICwriter/issues `_ 23 | - Source Code: `github.com/DerekK88/PICwriter `_ 24 | 25 | Guide 26 | ^^^^^ 27 | .. toctree:: 28 | :maxdepth: 2 29 | 30 | installation.rst 31 | tutorial.rst 32 | component-documentation.rst 33 | picsim-documentation.rst 34 | toolkit-documentation.rst 35 | license.rst 36 | 37 | Indices and tables 38 | ================== 39 | 40 | * :ref:`genindex` 41 | * :ref:`search` 42 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | PICwriter is tested on python versions 2.7, 3.4, 3.5, and 3.6 on Linux, OS X, and Windows. Please check `here `_ for the current build status (if building from source). 5 | 6 | Requirements 7 | ------------ 8 | 9 | A working version of python is required for using the PICwriter library. You can go to `python.org `_ to download python (or check if it's installed on your computer by running `python -\\-version` in a command prompt or terminal. I personally recommend downloading `Anaconda `_ since it includes several nice scientific libraries, the conda package manager, Spyder IDE, and other niceties. 10 | 11 | Installation (Linux / OS X) 12 | --------------------------- 13 | 14 | (**Option 1 -- preferred**) Install PICwriter by first downloading the source code `here `_. and then in the picwriter directory run:: 15 | 16 | python setup.py install 17 | 18 | (**Option 2:**) Install PICwriter by running:: 19 | 20 | pip install picwriter 21 | 22 | Installation (Windows) 23 | ---------------------- 24 | 25 | The best way of obtaining the library is by installing the prebuilt binaries. 26 | 27 | * First, go to the `gdspy appveyor project page `_, then click the python environment that matches your python version and processor type. For example, if you have a 64-bit processor with Python version 3.5 (you can check by running `python --version` in a command prompt) then you would click 'PYTHON=C:\Python35-x64'. Then, click the **Artifacts** tab and download the corresponding `dist\gdspy-1.X.X.X.whl` wheel file. 28 | * Open up a command prompt (type `cmd` in the search bar), navigate to your downloads, then install the appropriate `.whl` file via:: 29 | 30 | pip install gdspy-1.X.X.X.whl 31 | 32 | * Next, install the PICwriter library by following the same procedure as before at the `picwriter appveyor page `_ to install the corresponding prebuilt picwriter `.whl` file. 33 | * In a command prompt, navigate to your downloads and install the appropriate `.whl` file with pip:: 34 | 35 | pip install picwriter-1.X.X.X.whl 36 | 37 | Building from source is also possible. For installing gdspy, an appropriate build environment is required for compilation of the C extension modules. 38 | 39 | Simulations with PICwriter 40 | -------------------------- 41 | 42 | In order to use the built-in PICwriter simulation functions, it is necessary to download and build `MEEP `_ and `MPB `_ (the free and open-source software packages for electromagnetic simulation that PICwriter calls). Currently, this functionality is only tested on Ubuntu 16.04, but should work fine for any UNIX environment. For the FDTD functionality, it is necessary for MEEP and MPB to be built from source (so that they can be linked together for importing eigenmode sources). Below is a downloadable script that I use to install and build MEEP/MPB from source: 43 | :download:`build_meep_python_parallel.sh `. 44 | 45 | Please see the section :doc:`/picsim-documentation` for information and tutorials for simulating PICwriter components. 46 | 47 | Getting Started 48 | --------------- 49 | 50 | You can check that PICwriter and gdspy are properly installed by running:: 51 | 52 | import gdspy 53 | import picwriter 54 | 55 | in a python file or python prompt. To get a feel for using the PICwriter library, checkout the Tutorial page. 56 | -------------------------------------------------------------------------------- /docs/source/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | MIT License 5 | 6 | Copyright (c) 2018 Derek Kita 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /docs/source/picsim-documentation.rst: -------------------------------------------------------------------------------- 1 | Simulations with PICwriter 2 | ************************************ 3 | 4 | The 3-dimensional geometry of all PICwriter components (a scalar dielectric function) can be exported to an `HDF5 `_ file format for simulation using various open-source and commercial electromagnetic simulation software. Convenient functions are also provided for automatically computing the transmission/reflection spectra of PICwriter components with arbitrary numbers of ports (specified by their portlist) using freely available finite-difference time-domain (FDTD) software (`MEEP `_). The pymeep python package must be installed to use the functions available below. 5 | 6 | In order to specify the vertical profile in the vertical (out-of-plane) direction, the user must first specify a **`MaterialStack`** object that is responsible for mapping each mask layer/datatype to a vertical stack of materials. 7 | 8 | Defining 3D geometries using PICwriter 9 | --------------------------------------- 10 | 11 | The mapping of GDSII layers/datatypes to vertical dielectric profiles is done by creating a *MaterialStack* object and adding *VStacks* for each layer/datatype to be considered. An example of creating a MaterialStack is given below:: 12 | 13 | import picwriter.picsim as ps 14 | 15 | epsSiO2 = 1.444**2 16 | epsSi = 3.55**2 17 | etch_stack = [(epsSiO2, 1.89), (epsSi, 0.07), (epsSiO2, 2.04)] 18 | mstack = ps.MaterialStack(vsize=4.0, default_stack=etch_stack, name="Si waveguide") 19 | waveguide_stack = [(epsSiO2, 1.89), (epsSi, 0.22), (epsSiO2, 1.89)] 20 | clad_stack = [(epsSiO2, 1.89), (epsSi, 0.05), (epsSiO2, 2.06)] 21 | mstack.addVStack(layer=1, datatype=0, stack=waveguide_stack) 22 | mstack.addVStack(layer=2, datatype=0, stack=clad_stack) 23 | 24 | First, we import the picsim library. Next, we specified the dielectric constant for the two materials considered (silicon and silicon dioxide at a wavelength of 1550 nm). Then, we create a VStack list (`etch_stack`), that is in the format [(dielectric1, thickness1), (dielectric2, thickness2), ...]. With this we can create the MaterialStack object, with `etch_stack` the default vertical stack in the domain. Next, we create a `waveguide_stack` VStack list and associate it with the (1,0) GDSII layer using the `addVStack` call. 25 | 26 | Quickly computing mode profiles 27 | --------------------------------- 28 | 29 | With a properly defined material stack, PICsim makes it easy to quickly view the mode profile corresponding to your layout's WaveguideTemplate with the `compute_mode()` function:: 30 | 31 | import picwriter.components as pc 32 | 33 | wgt = pc.WaveguideTemplate(bend_radius=15, wg_width=0.5, clad_width=3.0, 34 | wg_layer=1, wg_datatype=0, clad_layer=2, clad_datatype=0) 35 | 36 | ps.compute_mode(wgt, mstack, res=128, wavelength=1.55, sx=3.0, sy=3.0, 37 | plot_mode_number=1, polarization="TE") 38 | 39 | Which produces plots of the corresponding electric fields: 40 | 41 | .. image:: imgs/mode_Efields.png 42 | :width: 1000px 43 | :align: center 44 | 45 | and magnetic fields: 46 | 47 | .. image:: imgs/mode_Hfields.png 48 | :width: 1000px 49 | :align: center 50 | 51 | 52 | Computing the transmission/reflection spectra 53 | ----------------------------------------------- 54 | 55 | Likewise, we can build a PICwriter component in the normal way and directly launch a MEEP simulation. Below we build a DirectionalCoupler object and give it 2 um of waveguide at all the inputs/outputs (this will be useful when we simulate with MEEP later):: 56 | 57 | import picwriter.toolkit as tk 58 | from picwriter.components import * 59 | import numpy as np 60 | import gdspy 61 | 62 | top = gdspy.Cell("top") 63 | wgt = WaveguideTemplate(bend_radius=15, wg_width=0.5, clad_width=3.0, 64 | wg_layer=1, wg_datatype=0, clad_layer=2, clad_datatype=0) 65 | 66 | simulated_component = gdspy.Cell('sc') 67 | 68 | dc = DirectionalCoupler(wgt, 3.5, 0.2, angle=np.pi/16.0, 69 | parity=1, direction='EAST', port=(0,0)) 70 | tk.add(simulated_component, dc) 71 | 72 | x0,y0 = dc.portlist['input_top']['port'] 73 | x1,y1 = dc.portlist['input_bot']['port'] 74 | x2,y2 = dc.portlist['output_top']['port'] 75 | x3,y3 = dc.portlist['output_bot']['port'] 76 | 77 | PML_wg1 = Waveguide([(x0,y0), (x0-2,y0)], wgt) 78 | PML_wg2 = Waveguide([(x1,y1), (x1-2,y1)], wgt) 79 | PML_wg3 = Waveguide([(x2,y2), (x2+2,y2)], wgt) 80 | PML_wg4 = Waveguide([(x3,y3), (x3+2,y3)], wgt) 81 | 82 | tk.add(simulated_component, PML_wg1) 83 | tk.add(simulated_component, PML_wg2) 84 | tk.add(simulated_component, PML_wg3) 85 | tk.add(simulated_component, PML_wg4) 86 | 87 | The gdspy Cell object `simulated_component` now contains four short waveguides and a DirectionalCoupler object. In order to launch a MEEP simulation and compute transmission/reflection spectra, we need to tell PICwriter what `ports` we want to monitor the flux through:: 88 | 89 | ports = [dc.portlist['input_top'], 90 | dc.portlist['input_bot'], 91 | dc.portlist['output_top'], 92 | dc.portlist['output_bot']] 93 | 94 | The first port specified in the list above will be the inport where MEEP will place an EigenmodeSource. The last step is calling `compute_transmission_spectra` with the simulated component, MaterialStack, ports, and some additional information about the simulation:: 95 | 96 | ps.compute_transmission_spectra(simulated_component, mstack, wgt, ports, port_vcenter=0, 97 | port_height=1.5*0.22, port_width=1.5*wgt.wg_width, dpml=0.5, 98 | res=20, wl_center=1.55, wl_span=0.6, fields=True, 99 | norm=True, parallel=False) 100 | 101 | In the above *port_vcenter* specifies the center of the port in the vertical direction, *port_height* and *port_width* are the cross-sectional size of the power flux planes, *res* is the resolution (in pixels/um), *wl_center* and *wl_span* specify the center wavelength and wavelength span of the input pulse (in um), *fields* = True tells MEEP to output images of the electric field profile every few timesteps, *norm* = True tells MEEP to first perform a normalization calculation (straight waveguide) using the *wgt* WaveguideTemplate parameters. *parallel* specifies if the simulation should be run using multiple processor cores (requires MEEP/MPB to be built using parallel libaries), and *n_p* then specifies the number of cores to run on. 102 | 103 | *NOTE*: This function requires MEEP and MPB to be compiled (from source) together, so that MEEP can call MPB to input an EigenmodeSource at the first port location. 104 | 105 | The resulting structure that is simulated and several field images are shown below: 106 | 107 | .. image:: imgs/topview-mcts.png 108 | :width: 1000px 109 | :align: center 110 | .. image:: imgs/mcts-ez-topview.t124.png 111 | :width: 1000px 112 | :align: center 113 | .. image:: imgs/mcts-ez-topview.t147.png 114 | :width: 1000px 115 | :align: center 116 | .. image:: imgs/mcts-ez-topview.t178.png 117 | :width: 1000px 118 | :align: center 119 | 120 | The `compute_transmission_spectra()` function will also compute and plot the appropriately normalized transmission/reflection spectra, saving the .png image in the working directory. The raw power flux data is also saved in .out and .dat files in the working directory. 121 | 122 | .. image:: imgs/meep-sim-res20-dc.png 123 | :width: 800px 124 | :align: center 125 | 126 | PICsim Documentation 127 | -------------------- 128 | 129 | .. automodule:: picwriter.picsim 130 | :members: 131 | -------------------------------------------------------------------------------- /docs/source/toolkit-documentation.rst: -------------------------------------------------------------------------------- 1 | Toolkit Documentation 2 | ********************* 3 | 4 | .. automodule:: picwriter.toolkit 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/tutorial1.py: -------------------------------------------------------------------------------- 1 | import gdspy 2 | from picwriter import toolkit as tk 3 | import picwriter.components as pc 4 | 5 | top = gdspy.Cell("top") 6 | 7 | top.add(gdspy.Rectangle((0, 0), (1000, 1000), layer=100, datatype=0)) 8 | 9 | wgt = pc.WaveguideTemplate( 10 | wg_width=0.45, 11 | clad_width=10.0, 12 | bend_radius=100, 13 | resist="+", 14 | fab="ETCH", 15 | wg_layer=1, 16 | wg_datatype=0, 17 | clad_layer=2, 18 | clad_datatype=0, 19 | ) 20 | wg = pc.Waveguide( 21 | [(25, 25), (975, 25), (975, 500), (25, 500), (25, 975), (975, 975)], wgt 22 | ) 23 | 24 | tk.add(top, wg) 25 | 26 | tk.build_mask(top, wgt, final_layer=3, final_datatype=0) 27 | 28 | gdspy.LayoutViewer() 29 | gdspy.write_gds("tutorial.gds", unit=1.0e-6, precision=1.0e-9) 30 | -------------------------------------------------------------------------------- /docs/source/tutorial2.py: -------------------------------------------------------------------------------- 1 | import gdspy 2 | from picwriter import toolkit as tk 3 | import picwriter.components as pc 4 | 5 | top = gdspy.Cell("top") 6 | wgt = pc.WaveguideTemplate( 7 | wg_width=0.45, 8 | clad_width=10.0, 9 | bend_radius=60, 10 | resist="+", 11 | fab="ETCH", 12 | wg_layer=1, 13 | wg_datatype=0, 14 | clad_layer=2, 15 | clad_datatype=0, 16 | ) 17 | 18 | gc1 = pc.GratingCouplerFocusing( 19 | wgt, 20 | focus_distance=20.0, 21 | width=20, 22 | length=40, 23 | period=1.0, 24 | dutycycle=0.7, 25 | port=(100, 0), 26 | direction="WEST", 27 | ) 28 | tk.add(top, gc1) 29 | 30 | wg1 = pc.Waveguide([gc1.portlist["output"]["port"], (200, 0)], wgt) 31 | tk.add(top, wg1) 32 | 33 | mmi1 = pc.MMI1x2( 34 | wgt, length=50, width=10, taper_width=2.0, wg_sep=3, **wg1.portlist["output"] 35 | ) 36 | tk.add(top, mmi1) 37 | 38 | mmi2 = pc.MMI1x2( 39 | wgt, 40 | length=50, 41 | width=10, 42 | taper_width=2.0, 43 | wg_sep=3, 44 | port=(1750, 0), 45 | direction="WEST", 46 | ) 47 | tk.add(top, mmi2) 48 | 49 | (xtop, ytop) = mmi1.portlist["output_top"]["port"] 50 | wg2 = pc.Waveguide( 51 | [ 52 | (xtop, ytop), 53 | (xtop + 100, ytop), 54 | (xtop + 100, ytop + 200), 55 | (xtop + 200, ytop + 200), 56 | ], 57 | wgt, 58 | ) 59 | tk.add(top, wg2) 60 | 61 | sp = pc.Spiral(wgt, 800.0, 8000.0, parity=-1, **wg2.portlist["output"]) 62 | tk.add(top, sp) 63 | 64 | (xtop_out, ytop_out) = sp.portlist["output"]["port"] 65 | (xmmi_top, ymmi_top) = mmi2.portlist["output_bot"]["port"] 66 | wg_spiral_out = pc.Waveguide( 67 | [ 68 | (xtop_out, ytop_out), 69 | (xmmi_top - 100, ytop_out), 70 | (xmmi_top - 100, ytop_out - 200), 71 | (xmmi_top, ytop_out - 200), 72 | ], 73 | wgt, 74 | ) 75 | tk.add(top, wg_spiral_out) 76 | 77 | (xbot, ybot) = mmi1.portlist["output_bot"]["port"] 78 | wg3 = pc.Waveguide( 79 | [ 80 | (xbot, ybot), 81 | (xbot + 100, ybot), 82 | (xbot + 100, ybot - 200), 83 | (xmmi_top - 100, ybot - 200), 84 | (xmmi_top - 100, ybot), 85 | (xmmi_top, ybot), 86 | ], 87 | wgt, 88 | ) 89 | tk.add(top, wg3) 90 | 91 | gc2 = pc.GratingCouplerFocusing( 92 | wgt, 93 | focus_distance=20.0, 94 | width=20, 95 | length=40, 96 | period=1.0, 97 | dutycycle=0.7, 98 | port=(mmi2.portlist["input"]["port"][0] + 100, mmi2.portlist["input"]["port"][1]), 99 | direction="EAST", 100 | ) 101 | tk.add(top, gc2) 102 | 103 | wg_gc2 = pc.Waveguide( 104 | [mmi2.portlist["input"]["port"], gc2.portlist["output"]["port"]], wgt 105 | ) 106 | tk.add(top, wg_gc2) 107 | 108 | tk.build_mask(top, wgt, final_layer=3, final_datatype=0) 109 | 110 | gdspy.LayoutViewer(cells=top) 111 | gdspy.write_gds("tutorial2.gds", unit=1.0e-6, precision=1.0e-9) 112 | -------------------------------------------------------------------------------- /docs/source/tutorial3.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import gdspy 3 | from picwriter import toolkit as tk 4 | import picwriter.components as pc 5 | 6 | X_SIZE, Y_SIZE = 15000, 15000 7 | exclusion_region = 2000.0 # region where no devices are to be fabricated 8 | x0, y0 = X_SIZE / 2.0, Y_SIZE / 2.0 # define origin of the die 9 | step = 100.0 # standard spacing between components 10 | 11 | top = gdspy.Cell("top") 12 | 13 | wgt = pc.WaveguideTemplate( 14 | wg_width=0.45, 15 | clad_width=10.0, 16 | bend_radius=100, 17 | resist="+", 18 | fab="ETCH", 19 | wg_layer=1, 20 | wg_datatype=0, 21 | clad_layer=2, 22 | clad_datatype=0, 23 | ) 24 | 25 | """ Add a die outline, with exclusion, from gdspy geometries found at 26 | http://gdspy.readthedocs.io/en/latest/""" 27 | top.add(gdspy.Rectangle((0, 0), (X_SIZE, Y_SIZE), layer=6, datatype=0)) 28 | top.add( 29 | gdspy.Rectangle( 30 | (0, Y_SIZE - exclusion_region), (X_SIZE, Y_SIZE), layer=7, datatype=0 31 | ) 32 | ) 33 | top.add(gdspy.Rectangle((0, 0), (X_SIZE, exclusion_region), layer=7, datatype=0)) 34 | top.add(gdspy.Rectangle((0, 0), (exclusion_region, Y_SIZE), layer=7, datatype=0)) 35 | top.add( 36 | gdspy.Rectangle( 37 | (X_SIZE - exclusion_region, 0), (X_SIZE, Y_SIZE), layer=7, datatype=0 38 | ) 39 | ) 40 | 41 | """ Add some components from the PICwriter library """ 42 | spiral_unit = gdspy.Cell("spiral_unit") 43 | sp1 = pc.Spiral( 44 | wgt, 1000.0, 10000, parity=1, port=(500.0 + exclusion_region + 4 * step, y0) 45 | ) 46 | tk.add(spiral_unit, sp1) 47 | 48 | wg1 = pc.Waveguide( 49 | [sp1.portlist["input"]["port"], (sp1.portlist["input"]["port"][0], 4000.0)], wgt 50 | ) 51 | wg2 = pc.Waveguide( 52 | [ 53 | sp1.portlist["output"]["port"], 54 | (sp1.portlist["output"]["port"][0], Y_SIZE - 4000.0), 55 | ], 56 | wgt, 57 | ) 58 | tk.add(spiral_unit, wg1) 59 | tk.add(spiral_unit, wg2) 60 | 61 | tp_bot = pc.Taper(wgt, length=100.0, end_width=0.1, **wg1.portlist["output"]) 62 | tk.add(spiral_unit, tp_bot) 63 | 64 | gc_top = pc.GratingCouplerFocusing( 65 | wgt, 66 | focus_distance=20, 67 | width=20, 68 | length=40, 69 | period=0.7, 70 | dutycycle=0.4, 71 | wavelength=1.55, 72 | sin_theta=np.sin(np.pi * 8 / 180), 73 | **wg2.portlist["output"] 74 | ) 75 | tk.add(spiral_unit, gc_top) 76 | 77 | for i in range(9): 78 | top.add(gdspy.CellReference(spiral_unit, (i * 1100.0, 0))) 79 | 80 | tk.build_mask(top, wgt, final_layer=3, final_datatype=0) 81 | 82 | gdspy.LayoutViewer(cells=top) 83 | gdspy.write_gds("mask_template.gds", unit=1.0e-6, precision=1.0e-9) 84 | -------------------------------------------------------------------------------- /picwriter/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | from .toolkit import ( 5 | add, 6 | get_angle, 7 | get_exact_angle, 8 | dist, 9 | get_direction, 10 | flip_direction, 11 | translate_point, 12 | get_turn, 13 | ) 14 | from .toolkit import get_keys, get_trace_length 15 | 16 | """ 17 | ================================================================================================================================ 18 | Copyright 2019 Derek M. Kita 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 21 | (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 22 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 23 | so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 30 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | ================================================================================================================================ 32 | """ 33 | -------------------------------------------------------------------------------- /picwriter/components/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | from .waveguide import WaveguideTemplate, Waveguide 5 | from .electrical import MetalTemplate, MetalRoute, Bondpad 6 | from .taper import Taper 7 | from .gratingcoupler import ( 8 | GratingCoupler, 9 | GratingCouplerStraight, 10 | GratingCouplerFocusing, 11 | ) 12 | from .spiral import Spiral 13 | from .mmi1x2 import MMI1x2 14 | from .mmi2x2 import MMI2x2 15 | from .ring import Ring 16 | from .disk import Disk 17 | from .alignmentmarker import AlignmentCross, AlignmentTarget 18 | from .mzi import ( 19 | MachZehnder, 20 | MachZehnderSwitch1x2, 21 | MachZehnderSwitchDC1x2, 22 | MachZehnderSwitchDC2x2, 23 | ) 24 | from .dbr import DBR 25 | from .directionalcoupler import DirectionalCoupler 26 | from .contradc import ContraDirectionalCoupler 27 | from .swgcontradc import SWGContraDirectionalCoupler 28 | from .stripslotconverter import StripSlotConverter 29 | from .stripslotyconverter import StripSlotYConverter 30 | from .stripslotmmiconverter import StripSlotMMIConverter 31 | from .adiabaticcoupler import AdiabaticCoupler 32 | from .fullcoupler import FullCoupler 33 | from .zerolengthcavity import ZeroLengthCavity 34 | from .sbend import SBend 35 | from .ebend import EBend, EulerSBend 36 | from .bbend import BBend 37 | from .ysplitter import SplineYSplitter 38 | from .via import Via 39 | -------------------------------------------------------------------------------- /picwriter/components/adiabaticcoupler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | from picwriter.components.waveguide import Waveguide 8 | from picwriter.components.bbend import BBend 9 | from picwriter.components.taper import Taper 10 | 11 | 12 | class AdiabaticCoupler(tk.Component): 13 | """Adiabatic Coupler Cell class. Design based on asymmetric adiabatic 3dB coupler designs, such as those from https://doi.org/10.1364/CLEO.2010.CThAA2, https://doi.org/10.1364/CLEO_SI.2017.SF1I.5, and https://doi.org/10.1364/CLEO_SI.2018.STh4B.4. Uses Bezier curves for the input, with poles set to half of the x-length of the S-bend. 14 | 15 | In this design, Region I is the first half of the input S-bend waveguide where the input waveguides widths taper by +dw and -dw, Region II is the second half of the S-bend waveguide with constant, unbalanced widths, Region III is the region where the two asymmetric waveguides gradually come together, Region IV is the coupling region where the waveguides taper back to the original width at a fixed distance from one another, and Region IV is the output S-bend waveguide. 16 | 17 | Args: 18 | * **wgt** (WaveguideTemplate): WaveguideTemplate object 19 | * **length1** (float): Length of the region that gradually brings the two assymetric waveguides together. In this region the waveguide widths gradually change to be different by `dw`. 20 | * **length2** (float): Length of the coupling region, where the asymmetric waveguides gradually become the same width. 21 | * **length3** (float): Length of the output region where the two waveguides separate. 22 | * **wg_sep** (float): Distance between the two waveguides, center-to-center, in the coupling region (Region 2). 23 | * **input_wg_sep** (float): Separation of the two waveguides at the input, center-to-center. 24 | * **output_wg_sep** (float): Separation of the two waveguides at the output, center-to-center. 25 | * **dw** (float): Change in waveguide width. In Region 1, the top arm tapers to the waveguide width+dw/2.0, bottom taper to width-dw/2.0. 26 | 27 | Keyword Args: 28 | * **port** (tuple): Cartesian coordinate of the input port (top left). Defaults to (0,0). 29 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians). Defaults to 'EAST'. 30 | 31 | Members: 32 | * **portlist** (dict): Dictionary with the relevant port information 33 | 34 | Portlist format: 35 | * portlist['input_top'] = {'port': (x1,y1), 'direction': 'dir1'} 36 | * portlist['input_bot'] = {'port': (x2,y2), 'direction': 'dir1'} 37 | * portlist['output_top'] = {'port': (x3, y3), 'direction': 'dir3'} 38 | * portlist['output_bot'] = {'port': (x4, y4), 'direction': 'dir4'} 39 | 40 | Where in the above (x1,y1) is the same as the input 'port', (x3, y3), and (x4, y4) are the two output port locations. Directions 'dir1', 'dir2', etc. are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 41 | 'Direction' points *towards* the waveguide that will connect to it. 42 | 43 | """ 44 | 45 | def __init__( 46 | self, 47 | wgt, 48 | length1, 49 | length2, 50 | length3, 51 | wg_sep, 52 | input_wg_sep, 53 | output_wg_sep, 54 | dw, 55 | port=(0, 0), 56 | direction="EAST", 57 | ): 58 | tk.Component.__init__(self, "AdiabaticCoupler", locals()) 59 | 60 | self.portlist = {} 61 | 62 | self.port = port 63 | self.direction = direction 64 | 65 | self.length1 = length1 66 | self.length2 = length2 67 | self.length3 = length3 68 | self.wg_sep = wg_sep 69 | self.input_wg_sep = input_wg_sep 70 | self.output_wg_sep = output_wg_sep 71 | self.dw = dw 72 | self.wgt = wgt 73 | self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 74 | self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype} 75 | 76 | self.yc = -input_wg_sep / 2.0 77 | 78 | self.portlist_input_top = (0, 0) 79 | self.portlist_input_bot = (0, -input_wg_sep) 80 | self.portlist_output_top = ( 81 | length1 + length2 + length3, 82 | self.yc + self.output_wg_sep / 2.0, 83 | ) 84 | self.portlist_output_bot = ( 85 | length1 + length2 + length3, 86 | self.yc - self.output_wg_sep / 2.0, 87 | ) 88 | 89 | self.__build_cell() 90 | self.__build_ports() 91 | 92 | """ Translate & rotate the ports corresponding to this specific component object 93 | """ 94 | self._auto_transform_() 95 | 96 | def __build_cell(self): 97 | # Sequentially build all the geometric shapes using gdspy path functions 98 | # for waveguide, then add it to the Cell 99 | 100 | """ Add the Region 1 S-bend waveguides with Bezier curves """ 101 | poles = [ 102 | (0, 0), 103 | (self.length1 / 2.0, 0), 104 | (self.length1 / 2.0, self.yc + self.wg_sep / 2.0), 105 | (self.length1, self.yc + self.wg_sep / 2.0), 106 | ] 107 | input_top_bezier = BBend( 108 | self.wgt, poles, end_width=self.wgt.wg_width + self.dw / 2.0 109 | ) 110 | self.add(input_top_bezier) 111 | 112 | poles = [ 113 | (0, -self.input_wg_sep), 114 | (self.length1 / 2.0, -self.input_wg_sep), 115 | (self.length1 / 2.0, self.yc - self.wg_sep / 2.0), 116 | (self.length1, self.yc - self.wg_sep / 2.0), 117 | ] 118 | input_bot_bezier = BBend( 119 | self.wgt, poles, end_width=self.wgt.wg_width - self.dw / 2.0 120 | ) 121 | self.add(input_bot_bezier) 122 | 123 | """ Add the Region 2 tapered waveguide part """ 124 | taper_top = Taper( 125 | self.wgt, 126 | self.length2, 127 | end_width=self.wgt.wg_width, 128 | start_width=self.wgt.wg_width + self.dw / 2.0, 129 | **input_top_bezier.portlist["output"] 130 | ) 131 | self.add(taper_top) 132 | taper_bot = Taper( 133 | self.wgt, 134 | self.length2, 135 | end_width=self.wgt.wg_width, 136 | start_width=self.wgt.wg_width - self.dw / 2.0, 137 | **input_bot_bezier.portlist["output"] 138 | ) 139 | self.add(taper_bot) 140 | 141 | """ Add the Region 3 S-bend output waveguides with Bezier curves """ 142 | poles = [ 143 | (self.length1 + self.length2, self.yc + self.wg_sep / 2.0), 144 | ( 145 | self.length1 + self.length2 + self.length3 / 2.0, 146 | self.yc + self.wg_sep / 2.0, 147 | ), 148 | ( 149 | self.length1 + self.length2 + self.length3 / 2.0, 150 | self.yc + self.output_wg_sep / 2.0, 151 | ), 152 | ( 153 | self.length1 + self.length2 + self.length3, 154 | self.yc + self.output_wg_sep / 2.0, 155 | ), 156 | ] 157 | output_top_bezier = BBend(self.wgt, poles) 158 | self.add(output_top_bezier) 159 | 160 | poles = [ 161 | (self.length1 + self.length2, self.yc - self.wg_sep / 2.0), 162 | ( 163 | self.length1 + self.length2 + self.length3 / 2.0, 164 | self.yc - self.wg_sep / 2.0, 165 | ), 166 | ( 167 | self.length1 + self.length2 + self.length3 / 2.0, 168 | self.yc - self.output_wg_sep / 2.0, 169 | ), 170 | ( 171 | self.length1 + self.length2 + self.length3, 172 | self.yc - self.output_wg_sep / 2.0, 173 | ), 174 | ] 175 | output_bot_bezier = BBend(self.wgt, poles) 176 | self.add(output_bot_bezier) 177 | 178 | def __build_ports(self): 179 | # Portlist format: 180 | # example: example: {'port':(x_position, y_position), 'direction': 'NORTH'} 181 | self.portlist["input_top"] = { 182 | "port": self.portlist_input_top, 183 | "direction": "WEST", 184 | } 185 | self.portlist["input_bot"] = { 186 | "port": self.portlist_input_bot, 187 | "direction": "WEST", 188 | } 189 | self.portlist["output_top"] = { 190 | "port": self.portlist_output_top, 191 | "direction": "EAST", 192 | } 193 | self.portlist["output_bot"] = { 194 | "port": self.portlist_output_bot, 195 | "direction": "EAST", 196 | } 197 | 198 | 199 | if __name__ == "__main__": 200 | from . import * 201 | from picwriter.components.waveguide import WaveguideTemplate 202 | 203 | top = gdspy.Cell("top") 204 | wgt = WaveguideTemplate(wg_width=0.5, bend_radius=100, resist="+") 205 | 206 | wg1 = Waveguide([(0, 0), (0.1, 0)], wgt) 207 | tk.add(top, wg1) 208 | 209 | ac = AdiabaticCoupler( 210 | wgt, 211 | length1=30.0, 212 | length2=50.0, 213 | length3=20.0, 214 | wg_sep=1.0, 215 | input_wg_sep=3.0, 216 | output_wg_sep=3.0, 217 | dw=0.1, 218 | **wg1.portlist["output"] 219 | ) 220 | tk.add(top, ac) 221 | 222 | ac2 = AdiabaticCoupler( 223 | wgt, 224 | length1=20.0, 225 | length2=50.0, 226 | length3=30.0, 227 | wg_sep=1.0, 228 | input_wg_sep=3.0, 229 | output_wg_sep=3.0, 230 | dw=0.1, 231 | **ac.portlist["output_bot"] 232 | ) 233 | tk.add(top, ac2) 234 | 235 | for p in ac.portlist.keys(): 236 | print(str(p) + ": " + str(ac.portlist[p]["port"])) 237 | 238 | gdspy.LayoutViewer() 239 | gdspy.write_gds("ac.gds", unit=1.0e-6, precision=1.0e-9) 240 | -------------------------------------------------------------------------------- /picwriter/components/alignmentmarker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | 8 | 9 | class AlignmentCross(tk.Component): 10 | """Cross Cell class, used for alignment 11 | 12 | Args: 13 | * **cross_length** (float): Length of each arm of the cross. 14 | * **cross_width** (float): Width of the cross arm 15 | * **center** (tuple): Coordinate (x1, y1) of the center of the cross 16 | 17 | Keyword Args: 18 | * **small_cross_width** (float): If given, sets the width of the small cross in the center of the big cross. Defaults to 1/4 the value of cross_width 19 | * **layer** (int): Layer to place the marker on. Defaults to 1 20 | * **datatype** (int): Datatype to place the marker on. Defaults to 0 21 | 22 | """ 23 | 24 | def __init__( 25 | self, 26 | cross_length, 27 | cross_width, 28 | small_cross_width=None, 29 | center=(0, 0), 30 | layer=1, 31 | datatype=0, 32 | ): 33 | tk.Component.__init__(self, "AlignmentCross", locals()) 34 | 35 | self.cross_length = cross_length 36 | self.cross_width = cross_width 37 | self.small_cross_width = ( 38 | cross_width / 4.0 if small_cross_width == None else small_cross_width 39 | ) 40 | self.layer = layer 41 | self.datatype = datatype 42 | 43 | self.port = center 44 | self.direction = "EAST" 45 | 46 | self.portlist = {} 47 | self.__build_cell() 48 | self.__build_ports() 49 | 50 | """ Translate & rotate the ports corresponding to this specific component object 51 | """ 52 | self._auto_transform_() 53 | 54 | def __build_cell(self): 55 | # Sequentially build all the geometric shapes, then add it to the Cell 56 | x0, y0 = (0, 0) 57 | 58 | # Add big cross arms 59 | self.add( 60 | gdspy.Rectangle( 61 | (x0 - self.cross_length, y0 - self.cross_width / 2.0), 62 | (x0 - self.cross_width / 2.0, y0 + self.cross_width / 2.0), 63 | layer=self.layer, 64 | datatype=self.datatype, 65 | ) 66 | ) 67 | self.add( 68 | gdspy.Rectangle( 69 | (x0 + self.cross_length, y0 - self.cross_width / 2.0), 70 | (x0 + self.cross_width / 2.0, y0 + self.cross_width / 2.0), 71 | layer=self.layer, 72 | datatype=self.datatype, 73 | ) 74 | ) 75 | self.add( 76 | gdspy.Rectangle( 77 | (x0 - self.cross_width / 2.0, y0 - self.cross_length), 78 | (x0 + self.cross_width / 2.0, y0 - self.cross_width / 2.0), 79 | layer=self.layer, 80 | datatype=self.datatype, 81 | ) 82 | ) 83 | self.add( 84 | gdspy.Rectangle( 85 | (x0 - self.cross_width / 2.0, y0 + self.cross_length), 86 | (x0 + self.cross_width / 2.0, y0 + self.cross_width / 2.0), 87 | layer=self.layer, 88 | datatype=self.datatype, 89 | ) 90 | ) 91 | 92 | # Add little cross arms 93 | self.add( 94 | gdspy.Rectangle( 95 | (x0 - self.cross_width / 2.0, y0 - self.small_cross_width / 2.0), 96 | (x0 + self.cross_width / 2.0, y0 + self.small_cross_width / 2.0), 97 | layer=self.layer, 98 | datatype=self.datatype, 99 | ) 100 | ) 101 | self.add( 102 | gdspy.Rectangle( 103 | (x0 - self.small_cross_width / 2.0, y0 - self.cross_width / 2.0), 104 | (x0 + self.small_cross_width / 2.0, y0 + self.cross_width / 2.0), 105 | layer=self.layer, 106 | datatype=self.datatype, 107 | ) 108 | ) 109 | 110 | def __build_ports(self): 111 | self.portlist["center"] = {"port": (0, 0), "direction": "WEST"} 112 | 113 | 114 | class AlignmentTarget(tk.Component): 115 | """Standard Target Cell class, used for alignment. Set of concentric circles 116 | 117 | Args: 118 | * **diameter** (float): Total diameter of the target marker 119 | * **ring_width** (float): Width of each ring 120 | 121 | Keyword Args: 122 | * **num_rings** (float): Number of concentric rings in the target. Defaults to 10 123 | * **center** (tuple): Coordinate (x1, y1) of the center of the cross. Defaults to (0,0) 124 | * **layer** (int): Layer to place the marker on. Defaults to 1 125 | * **datatype** (int): Datatype to place the marker on. Defaults to 0 126 | 127 | """ 128 | 129 | def __init__( 130 | self, diameter, ring_width, num_rings=10, center=(0, 0), layer=1, datatype=0 131 | ): 132 | tk.Component.__init__(self, "AlignmentTarget", locals()) 133 | 134 | self.diameter = diameter 135 | self.ring_width = ring_width 136 | self.num_rings = num_rings 137 | self.layer = layer 138 | self.datatype = datatype 139 | 140 | self.port = center 141 | self.direction = "EAST" 142 | self.portlist = {} 143 | 144 | self.build_cell() 145 | self.__build_ports() 146 | 147 | """ Translate & rotate the ports corresponding to this specific component object 148 | """ 149 | self._auto_transform_() 150 | 151 | def build_cell(self): 152 | # Sequentially build all the geometric shapes, then add it to the Cell 153 | x0, y0 = (0, 0) 154 | spacing = self.diameter / (4.0 * self.num_rings) 155 | for i in range(self.num_rings): 156 | self.add( 157 | gdspy.Round( 158 | (x0, y0), 159 | 2 * (i + 1) * spacing, 160 | 2 * (i + 1) * spacing - self.ring_width, 161 | layer=self.layer, 162 | datatype=self.datatype, 163 | number_of_points=0.1, 164 | ) 165 | ) 166 | 167 | def __build_ports(self): 168 | self.portlist["center"] = {"port": (0, 0), "direction": "WEST"} 169 | 170 | 171 | if __name__ == "__main__": 172 | from . import * 173 | 174 | top = gdspy.Cell("top") 175 | # mark1 = AlignmentCross(500, 1, center=(0,0)) 176 | # mark2 = AlignmentCross(500, 1, center=(100,1000)) 177 | mark1 = AlignmentTarget(200, 3, num_rings=10) 178 | mark2 = AlignmentTarget(200, 3, num_rings=11, center=(200, 300)) 179 | tk.add(top, mark1) 180 | tk.add(top, mark2) 181 | 182 | gdspy.LayoutViewer() 183 | # gdspy.write_gds('target.gds', unit=1.0e-6, precision=1.0e-9) 184 | -------------------------------------------------------------------------------- /picwriter/components/bbend.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | import math 8 | 9 | 10 | class BBend(tk.Component): 11 | """Bezier Cell class. Creates a Bezier waveguide bend that can be used in waveguide routing. The number of points is computed based on the waveguide template grid resolution to automatically minimize grid errors. 12 | 13 | See https://en.wikipedia.org/wiki/Bezier_curve for more information. 14 | 15 | Args: 16 | * **wgt** (WaveguideTemplate): WaveguideTemplate object 17 | * **poles** (list): List of (x,y) pole coordinates used for routing the Bezier curve 18 | 19 | Keyword Args: 20 | * **start_width** (float): If a value is provided, overrides the initial waveguide width (otherwise the width is taken from the WaveguideTemplate object). Currently only works for strip waveguides. 21 | * **end_width** (float): If a value is provided, overrides the final waveguide width (otherwise the width is taken from the WaveguideTemplate object). Currently only works for strip waveguides. 22 | * **port** (tuple): Cartesian coordinate of the input port. Defaults to (0,0). 23 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) 24 | 25 | Members: 26 | * **portlist** (dict): Dictionary with the relevant port information 27 | 28 | Portlist format: 29 | * portlist['input'] = {'port': (x1,y1), 'direction': 'dir1'} 30 | * portlist['output'] = {'port': (x2, y2), 'direction': 'dir2'} 31 | 32 | Where in the above (x1,y1) is the same as the 'port' input, (x2, y2) is the end of the taper, and 'dir1', 'dir2' are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 33 | 'Direction' points *towards* the waveguide that will connect to it. 34 | 35 | """ 36 | 37 | def __init__(self, wgt, poles, start_width=None, end_width=None): 38 | tk.Component.__init__(self, "BBend", locals()) 39 | 40 | self.portlist = {} 41 | self.port = (0, 0) 42 | 43 | if start_width != None: 44 | self.start_width = start_width 45 | else: 46 | self.start_width = wgt.wg_width 47 | if end_width != None: 48 | self.end_width = end_width 49 | else: 50 | self.end_width = wgt.wg_width 51 | 52 | self.input_port = (poles[0][0], poles[0][1]) 53 | self.output_port = (poles[-1][0], poles[-1][1]) 54 | 55 | self.poles = poles 56 | 57 | self.input_direction = tk.get_exact_angle(poles[1], poles[0]) 58 | self.output_direction = tk.get_exact_angle(poles[-2], poles[-1]) 59 | 60 | self.wgt = wgt 61 | self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 62 | self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype} 63 | 64 | self.__build_cell() 65 | self.__build_ports() 66 | 67 | """ Translate & rotate the ports corresponding to this specific component object 68 | """ 69 | 70 | # self._auto_transform_() 71 | 72 | def _bezier_function(self, t): 73 | # input (t) goes from 0->1 74 | # Returns an (x,y) tuple 75 | n = len(self.poles) - 1 76 | x, y = 0, 0 77 | for i in range(n + 1): 78 | coeff = math.factorial(n) / (math.factorial(i) * math.factorial(n - i)) 79 | x += coeff * ((1 - t) ** (n - i)) * (t ** i) * self.poles[i][0] 80 | y += coeff * ((1 - t) ** (n - i)) * (t ** i) * self.poles[i][1] 81 | return (x, y) 82 | 83 | def __build_cell(self): 84 | # Sequentially build all the geometric shapes using gdspy path functions 85 | # for waveguide, then add it to the Cell 86 | 87 | # Add waveguide s-bend 88 | wg = gdspy.Path(self.start_width, (0, 0)) 89 | wg.parametric( 90 | self._bezier_function, 91 | final_width=self.end_width, 92 | tolerance=self.wgt.grid / 2.0, 93 | max_points=199, 94 | **self.wg_spec 95 | ) 96 | self.add(wg) 97 | 98 | # Add cladding s-bend 99 | for i in range(len(self.wgt.waveguide_stack) - 1): 100 | cur_width = self.wgt.waveguide_stack[i + 1][0] 101 | cur_spec = { 102 | "layer": self.wgt.waveguide_stack[i + 1][1][0], 103 | "datatype": self.wgt.waveguide_stack[i + 1][1][1], 104 | } 105 | 106 | clad = gdspy.Path(cur_width, (0, 0)) 107 | clad.parametric( 108 | self._bezier_function, 109 | tolerance=self.wgt.grid / 2.0, 110 | max_points=199, 111 | **cur_spec 112 | ) 113 | self.add(clad) 114 | 115 | def __build_ports(self): 116 | # Portlist format: 117 | # example: example: {'port':(x_position, y_position), 'direction': 'NORTH'} 118 | self.portlist["input"] = { 119 | "port": self.input_port, 120 | "direction": self.input_direction, 121 | } 122 | self.portlist["output"] = { 123 | "port": self.output_port, 124 | "direction": self.output_direction, 125 | } 126 | 127 | 128 | if __name__ == "__main__": 129 | from . import * 130 | 131 | top = gdspy.Cell("top") 132 | wgt = WaveguideTemplate(bend_radius=50, resist="+") 133 | 134 | wg1 = Waveguide([(0, 0), (25, 0)], wgt) 135 | tk.add(top, wg1) 136 | 137 | bb1 = BBend(wgt, [(25, 0), (125, 0), (125, 100), (225, 100)]) 138 | tk.add(top, bb1) 139 | 140 | x, y = bb1.portlist["output"]["port"] 141 | wg2 = Waveguide([(x, y), (x + 25, y)], wgt) 142 | tk.add(top, wg2) 143 | 144 | gdspy.LayoutViewer(cells=top, depth=3) 145 | # gdspy.write_gds('bbend.gds', unit=1.0e-6, precision=1.0e-9) 146 | -------------------------------------------------------------------------------- /picwriter/components/dbr.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | 8 | 9 | class DBR(tk.Component): 10 | """Distributed Bragg Reflector Cell class. Tapers the input waveguide to a periodic waveguide structure with varying width (1-D photonic crystal). 11 | 12 | Args: 13 | * **wgt** (WaveguideTemplate): WaveguideTemplate object 14 | * **length** (float): Length of the DBR region. 15 | * **period** (float): Period of the repeated unit. 16 | * **dc** (float): Duty cycle of the repeated unit (must be a float between 0 and 1.0). 17 | * **w_phc** (float): Width of the thin section of the waveguide. w_phc = 0 corresponds to disconnected periodic blocks. 18 | 19 | Keyword Args: 20 | * **taper_length** (float): Length of the taper between the input/output waveguide and the DBR region. Defaults to 20.0. 21 | * **fins** (boolean): If `True`, adds fins to the input/output waveguides. In this case a different template for the component must be specified. This feature is useful when performing electron-beam lithography and using different beam currents for fine features (helps to reduce stitching errors). Defaults to `False` 22 | * **fin_size** ((x,y) Tuple): Specifies the x- and y-size of the `fins`. Defaults to 200 nm x 50 nm 23 | * **dbr_wgt** (WaveguideTemplate): If `fins` above is True, a WaveguideTemplate (dbr_wgt) must be specified. This defines the layertype / datatype of the DBR (which will be separate from the input/output waveguides). Defaults to `None` 24 | * **port** (tuple): Cartesian coordinate of the input port. Defaults to (0,0). 25 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) 26 | 27 | Members: 28 | * **portlist** (dict): Dictionary with the relevant port information 29 | 30 | Portlist format: 31 | * portlist['input'] = {'port': (x1,y1), 'direction': 'dir1'} 32 | * portlist['output'] = {'port': (x2, y2), 'direction': 'dir2'} 33 | 34 | Where in the above (x1,y1) is the same as the 'port' input, (x2, y2) is the end of the DBR, and 'dir1', 'dir2' are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 35 | 'Direction' points *towards* the waveguide that will connect to it. 36 | 37 | """ 38 | 39 | def __init__( 40 | self, 41 | wgt, 42 | length, 43 | period, 44 | dc, 45 | w_phc, 46 | taper_length=20.0, 47 | fins=False, 48 | fin_size=(0.2, 0.05), 49 | dbr_wgt=None, 50 | port=(0, 0), 51 | direction="EAST", 52 | ): 53 | tk.Component.__init__(self, "DBR", locals()) 54 | 55 | self.portlist = {} 56 | 57 | self.port = port 58 | self.direction = direction 59 | self.length = length 60 | self.taper_length = taper_length 61 | self.period = period 62 | self.dc = dc 63 | self.w_phc = w_phc 64 | self.fins = fins 65 | self.fin_size = fin_size 66 | 67 | if fins: 68 | self.wgt = dbr_wgt 69 | self.side_wgt = wgt 70 | self.wg_spec = {"layer": dbr_wgt.wg_layer, "datatype": dbr_wgt.wg_datatype} 71 | self.clad_spec = { 72 | "layer": dbr_wgt.clad_layer, 73 | "datatype": dbr_wgt.clad_datatype, 74 | } 75 | self.fin_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 76 | if dbr_wgt is None: 77 | raise ValueError( 78 | "Warning! A waveguide template for the DBR (dbr_wgt) must be specified." 79 | ) 80 | else: 81 | self.wgt = wgt 82 | self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 83 | self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype} 84 | 85 | """ Make sure the photonic crystal waveguide width is smaller than the waveguide width """ 86 | if self.w_phc > self.wgt.wg_width: 87 | raise ValueError( 88 | "Warning! The w_phc parameter must be smaller than the waveguide template wg_width." 89 | ) 90 | 91 | self.__build_cell() 92 | self.__build_ports() 93 | 94 | """ Translate & rotate the ports corresponding to this specific component object 95 | """ 96 | self._auto_transform_() 97 | 98 | def __build_cell(self): 99 | # Sequentially build all the geometric shapes using gdspy path functions 100 | # for waveguide, then add it to the Cell 101 | # Add waveguide tapers leading to DBR region 102 | taper = gdspy.Path(self.wgt.wg_width, (0, 0)) 103 | taper.segment( 104 | self.taper_length, direction=0.0, final_width=self.w_phc, **self.wg_spec 105 | ) 106 | taper.segment(self.length, **self.wg_spec) 107 | taper.segment(self.taper_length, final_width=self.wgt.wg_width, **self.wg_spec) 108 | # Cladding for DBR region 109 | clad = gdspy.Path(2 * self.wgt.clad_width + self.wgt.wg_width, (0, 0)) 110 | clad.segment( 111 | self.length + 2 * self.taper_length, direction=0.0, **self.clad_spec 112 | ) 113 | 114 | self.add(taper) 115 | self.add(clad) 116 | 117 | """ Now add the periodic PhC components """ 118 | num_blocks = (2 * self.taper_length + self.length) // self.period 119 | blockx = self.period * self.dc 120 | startx = ( 121 | self.taper_length 122 | + self.length / 2.0 123 | - (num_blocks - 1) * self.period / 2.0 124 | - blockx / 2.0 125 | ) 126 | y0 = 0 127 | block_list = [] 128 | for i in range(int(num_blocks)): 129 | x = startx + i * self.period 130 | block_list.append( 131 | gdspy.Rectangle( 132 | (x, y0 - self.wgt.wg_width / 2.0), 133 | (x + blockx, y0 + self.wgt.wg_width / 2.0), 134 | **self.wg_spec 135 | ) 136 | ) 137 | 138 | """ And add the 'fins' if self.fins==True """ 139 | if self.fins: 140 | num_fins = self.wgt.wg_width // (2 * self.fin_size[1]) 141 | x0, y0 = ( 142 | 0, 143 | -num_fins * (2 * self.fin_size[1]) / 2.0 + self.fin_size[1] / 2.0, 144 | ) 145 | xend = 2 * self.taper_length + self.length 146 | for i in range(int(num_fins)): 147 | y = y0 + i * 2 * self.fin_size[1] 148 | block_list.append( 149 | gdspy.Rectangle( 150 | (x0, y), 151 | (x0 + self.fin_size[0], y + self.fin_size[1]), 152 | **self.fin_spec 153 | ) 154 | ) 155 | block_list.append( 156 | gdspy.Rectangle( 157 | (xend - self.fin_size[0], y), 158 | (xend, y + self.fin_size[1]), 159 | **self.fin_spec 160 | ) 161 | ) 162 | 163 | for block in block_list: 164 | self.add(block) 165 | 166 | def __build_ports(self): 167 | # Portlist format: 168 | # example: example: {'port':(x_position, y_position), 'direction': 'NORTH'} 169 | self.portlist["input"] = {"port": (0, 0), "direction": "WEST"} 170 | self.portlist["output"] = { 171 | "port": (self.length + 2 * self.taper_length, 0), 172 | "direction": "EAST", 173 | } 174 | 175 | 176 | if __name__ == "__main__": 177 | from . import * 178 | 179 | top = gdspy.Cell("top") 180 | wgt = WaveguideTemplate(bend_radius=50, resist="+") 181 | dbr_wgt = WaveguideTemplate(bend_radius=50, resist="+", wg_layer=3, wg_datatype=0) 182 | 183 | wg1 = Waveguide([(0, 0), (100, 0)], wgt) 184 | tk.add(top, wg1) 185 | 186 | dbr1 = DBR( 187 | wgt, 10.0, 0.85, 0.5, 0.4, fins=True, dbr_wgt=dbr_wgt, **wg1.portlist["output"] 188 | ) 189 | tk.add(top, dbr1) 190 | 191 | (x1, y1) = dbr1.portlist["output"]["port"] 192 | wg2 = Waveguide([(x1, y1), (x1 + 100, y1), (x1 + 100, y1 + 100)], wgt) 193 | tk.add(top, wg2) 194 | 195 | dbr2 = DBR( 196 | wgt, 10.0, 0.85, 0.5, 0.6, fins=True, dbr_wgt=dbr_wgt, **wg2.portlist["output"] 197 | ) 198 | tk.add(top, dbr2) 199 | 200 | (x2, y2) = dbr2.portlist["output"]["port"] 201 | wg3 = Waveguide( 202 | [(x2, y2), (x2, y2 + 100.0), (x2 + 100, y2 + 200), (x2 + 100, y2 + 300)], wgt 203 | ) 204 | tk.add(top, wg3) 205 | 206 | gdspy.LayoutViewer() 207 | # gdspy.write_gds('dbr.gds', unit=1.0e-6, precision=1.0e-9) 208 | -------------------------------------------------------------------------------- /picwriter/components/directionalcoupler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | from picwriter.components.waveguide import Waveguide 8 | 9 | 10 | class DirectionalCoupler(tk.Component): 11 | """Directional Coupler Cell class. 12 | 13 | Args: 14 | * **wgt** (WaveguideTemplate): WaveguideTemplate object 15 | * **length** (float): Length of the coupling region. 16 | * **gap** (float): Distance between the two waveguides. 17 | 18 | Keyword Args: 19 | * **angle** (float): Angle in radians (between 0 and pi/2) at which the waveguide bends towards the coupling region. Default=pi/6. 20 | * **parity** (integer -1 or 1): If -1, mirror-flips the structure so that the input port is actually the *bottom* port. Default = 1. 21 | * **port** (tuple): Cartesian coordinate of the input port (AT TOP if parity=1, AT BOTTOM if parity=-1). Defaults to (0,0). 22 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians). Defaults to 'EAST'. 23 | 24 | Members: 25 | * **portlist** (dict): Dictionary with the relevant port information 26 | 27 | Portlist format: 28 | * portlist['input_top'] = {'port': (x1,y1), 'direction': 'dir1'} 29 | * portlist['input_bot'] = {'port': (x2,y2), 'direction': 'dir1'} 30 | * portlist['output_top'] = {'port': (x3, y3), 'direction': 'dir3'} 31 | * portlist['output_bot'] = {'port': (x4, y4), 'direction': 'dir4'} 32 | 33 | Where in the above (x1,y1) (or (x2,y2) if parity=-1) is the same as the input 'port', (x3, y3), and (x4, y4) are the two output port locations. Directions 'dir1', 'dir2', etc. are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 34 | 'Direction' points *towards* the waveguide that will connect to it. 35 | 36 | """ 37 | 38 | def __init__( 39 | self, 40 | wgt, 41 | length, 42 | gap, 43 | angle=np.pi / 6.0, 44 | parity=1, 45 | port=(0, 0), 46 | direction="EAST", 47 | ): 48 | tk.Component.__init__(self, "DirectionalCoupler", locals()) 49 | 50 | self.portlist = {} 51 | 52 | self.port = port 53 | self.direction = direction 54 | 55 | if angle > np.pi / 2.0 or angle < 0: 56 | raise ValueError( 57 | "Warning! Improper angle specified (" 58 | + str(angle) 59 | + "). Must be between 0 and pi/2.0." 60 | ) 61 | self.angle = angle 62 | if parity != 1 and parity != -1: 63 | raise ValueError( 64 | "Warning! Parity input *must* be 1 or -1. Received parity=" 65 | + str(parity) 66 | + " instead." 67 | ) 68 | self.parity = parity 69 | self.length = length 70 | self.gap = gap 71 | self.wgt = wgt 72 | self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 73 | self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype} 74 | 75 | self.__build_cell() 76 | self.__build_ports() 77 | 78 | """ Translate & rotate the ports corresponding to this specific component object 79 | """ 80 | self._auto_transform_() 81 | 82 | def __build_cell(self): 83 | # Sequentially build all the geometric shapes using gdspy path functions 84 | # for waveguide, then add it to the Cell 85 | 86 | x0, y0 = 0, 0 # shift to port location after rotation later 87 | 88 | # X-distance of horizontal waveguide 89 | dlx = abs(self.wgt.bend_radius * np.tan((self.angle) / 2.0)) 90 | padding = 0.01 # Add extra 10nm to allow room for curves 91 | angle_x_dist = 2.0 * (dlx + padding) * np.cos(self.angle) 92 | angle_y_dist = 2.0 * (dlx + padding) * np.sin(self.angle) * self.parity 93 | tracelist_top = [ 94 | (x0, y0), 95 | (x0 + dlx + padding, y0), 96 | (x0 + dlx + padding + angle_x_dist, y0 - angle_y_dist), 97 | (x0 + 3 * dlx + padding + angle_x_dist + self.length, y0 - angle_y_dist), 98 | (x0 + 3 * dlx + padding + 2 * angle_x_dist + self.length, y0), 99 | (x0 + 4 * dlx + 2 * padding + 2 * angle_x_dist + self.length, y0), 100 | ] 101 | wg_top = Waveguide(tracelist_top, self.wgt) 102 | 103 | y_bot_start = ( 104 | y0 - (2 * abs(angle_y_dist) + self.gap + self.wgt.wg_width) * self.parity 105 | ) 106 | tracelist_bot = [ 107 | (x0, y_bot_start), 108 | (x0 + dlx + padding, y_bot_start), 109 | (x0 + dlx + padding + angle_x_dist, y_bot_start + angle_y_dist), 110 | ( 111 | x0 + 3 * dlx + padding + angle_x_dist + self.length, 112 | y_bot_start + angle_y_dist, 113 | ), 114 | (x0 + 3 * dlx + padding + 2 * angle_x_dist + self.length, y_bot_start), 115 | (x0 + 4 * dlx + 2 * padding + 2 * angle_x_dist + self.length, y_bot_start), 116 | ] 117 | wg_bot = Waveguide(tracelist_bot, self.wgt) 118 | 119 | distx = 4 * dlx + 2 * angle_x_dist + self.length 120 | disty = (2 * abs(angle_y_dist) + self.gap + self.wgt.wg_width) * self.parity 121 | 122 | self.add(wg_top) 123 | self.add(wg_bot) 124 | self.portlist_input = (0, 0) 125 | self.portlist_output_straight = (distx, 0.0) 126 | self.portlist_output_cross = (distx, -disty) 127 | self.portlist_input_cross = (0.0, -disty) 128 | 129 | def __build_ports(self): 130 | # Portlist format: 131 | # example: example: {'port':(x_position, y_position), 'direction': 'NORTH'} 132 | if self.parity == 1: 133 | self.portlist["input_top"] = { 134 | "port": self.portlist_input, 135 | "direction": "WEST", 136 | } 137 | self.portlist["input_bot"] = { 138 | "port": self.portlist_input_cross, 139 | "direction": "WEST", 140 | } 141 | self.portlist["output_top"] = { 142 | "port": self.portlist_output_straight, 143 | "direction": "EAST", 144 | } 145 | self.portlist["output_bot"] = { 146 | "port": self.portlist_output_cross, 147 | "direction": "EAST", 148 | } 149 | elif self.parity == -1: 150 | self.portlist["input_top"] = { 151 | "port": self.portlist_input_cross, 152 | "direction": "WEST", 153 | } 154 | self.portlist["input_bot"] = { 155 | "port": self.portlist_input, 156 | "direction": "WEST", 157 | } 158 | self.portlist["output_top"] = { 159 | "port": self.portlist_output_cross, 160 | "direction": "EAST", 161 | } 162 | self.portlist["output_bot"] = { 163 | "port": self.portlist_output_straight, 164 | "direction": "EAST", 165 | } 166 | 167 | 168 | if __name__ == "__main__": 169 | from . import * 170 | from picwriter.components.waveguide import WaveguideTemplate 171 | 172 | top = gdspy.Cell("top") 173 | wgt = WaveguideTemplate(bend_radius=100, resist="+") 174 | 175 | wg1 = Waveguide([(0, 0), (100, 0)], wgt) 176 | tk.add(top, wg1) 177 | 178 | # dc = DirectionalCoupler(wgt, 20.0, 0.5, angle=np.pi/12.0, parity=1, **wg1.portlist["output"]) 179 | # tk.add(top, dc) 180 | 181 | dc1 = DirectionalCoupler( 182 | wgt, 10.0, 0.5, angle=np.pi / 6.0, parity=1, **wg1.portlist["output"] 183 | ) 184 | dc2 = DirectionalCoupler( 185 | wgt, 10.0, 0.5, angle=np.pi / 6.0, parity=-1, **dc1.portlist["output_top"] 186 | ) 187 | dc3 = DirectionalCoupler( 188 | wgt, 10.0, 0.5, angle=np.pi / 6.0, parity=1, **dc1.portlist["output_bot"] 189 | ) 190 | dc4 = DirectionalCoupler( 191 | wgt, 10.0, 0.5, angle=np.pi / 6.0, parity=1, **dc2.portlist["output_bot"] 192 | ) 193 | dc5 = DirectionalCoupler( 194 | wgt, 10.0, 0.5, angle=np.pi / 6.0, parity=-1, **dc2.portlist["output_top"] 195 | ) 196 | dc6 = DirectionalCoupler( 197 | wgt, 10.0, 0.5, angle=np.pi / 6.0, parity=1, **dc3.portlist["output_bot"] 198 | ) 199 | tk.add(top, dc1) 200 | tk.add(top, dc2) 201 | tk.add(top, dc3) 202 | tk.add(top, dc4) 203 | tk.add(top, dc5) 204 | tk.add(top, dc6) 205 | 206 | print(top.area()) 207 | 208 | gdspy.LayoutViewer() 209 | # gdspy.write_gds('dc2.gds', unit=1.0e-6, precision=1.0e-9) 210 | -------------------------------------------------------------------------------- /picwriter/components/fullcoupler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | from picwriter.components.waveguide import Waveguide 8 | 9 | 10 | class FullCoupler(tk.Component): 11 | """Adiabatic Full Cell class. Design based on asymmetric adiabatic full coupler designs, such as the one reported in 'Integrated Optic Adiabatic Devices on Silicon' by Y. Shani, et al (IEEE Journal of Quantum Electronics, Vol. 27, No. 3 March 1991). 12 | 13 | In this design, Region I is the first half of the input S-bend waveguide where the input waveguides widths taper by +dw and -dw, Region II is the second half of the S-bend waveguide with constant, unbalanced widths, Region III is the coupling region where the waveguides from unbalanced widths to balanced widths to reverse polarity unbalanced widths, Region IV is the fixed width waveguide that curves away from the coupling region, and Region V is the final curve where the waveguides taper back to the regular width specified in the waveguide template. 14 | 15 | Args: 16 | * **wgt** (WaveguideTemplate): WaveguideTemplate object 17 | * **length** (float): Length of the coupling region. 18 | * **gap** (float): Distance between the two waveguides. 19 | * **dw** (float): Change in waveguide width. Top arm tapers to the waveguide width - dw, bottom taper to width - dw. 20 | 21 | Keyword Args: 22 | * **angle** (float): Angle in radians (between 0 and pi/2) at which the waveguide bends towards the coupling region. Default=pi/6. 23 | * **parity** (integer -1 or 1): If -1, mirror-flips the structure so that the input port is actually the *bottom* port. Default = 1. 24 | * **port** (tuple): Cartesian coordinate of the input port (AT TOP if parity=1, AT BOTTOM if parity=-1). Defaults to (0,0). 25 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians). Defaults to 'EAST'. 26 | 27 | Members: 28 | * **portlist** (dict): Dictionary with the relevant port information 29 | 30 | Portlist format: 31 | * portlist['input_top'] = {'port': (x1,y1), 'direction': 'dir1'} 32 | * portlist['input_bot'] = {'port': (x2,y2), 'direction': 'dir1'} 33 | * portlist['output_top'] = {'port': (x3, y3), 'direction': 'dir3'} 34 | * portlist['output_bot'] = {'port': (x4, y4), 'direction': 'dir4'} 35 | 36 | Where in the above (x1,y1) (or (x2,y2) if parity=-1) is the same as the input 'port', (x3, y3), and (x4, y4) are the two output port locations. Directions 'dir1', 'dir2', etc. are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 37 | 'Direction' points *towards* the waveguide that will connect to it. 38 | 39 | """ 40 | 41 | def __init__( 42 | self, 43 | wgt, 44 | length, 45 | gap, 46 | dw, 47 | angle=np.pi / 6.0, 48 | parity=1, 49 | port=(0, 0), 50 | direction="EAST", 51 | ): 52 | tk.Component.__init__(self, "FullCoupler", locals()) 53 | 54 | self.portlist = {} 55 | 56 | self.port = port 57 | self.direction = direction 58 | 59 | if angle > np.pi / 2.0 or angle < 0: 60 | raise ValueError( 61 | "Warning! Improper angle specified (" 62 | + str(angle) 63 | + "). Must be between 0 and pi/2.0." 64 | ) 65 | self.angle = angle 66 | if parity != 1 and parity != -1: 67 | raise ValueError( 68 | "Warning! Parity input *must* be 1 or -1. Received parity=" 69 | + str(parity) 70 | + " instead." 71 | ) 72 | self.parity = parity 73 | self.length = length 74 | self.gap = gap 75 | self.dw = dw 76 | self.wgt = wgt 77 | self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 78 | self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype} 79 | 80 | self.__build_cell() 81 | self.__build_ports() 82 | 83 | """ Translate & rotate the ports corresponding to this specific component object 84 | """ 85 | self._auto_transform_() 86 | 87 | def __build_cell(self): 88 | # Sequentially build all the geometric shapes using gdspy path functions 89 | # for waveguide, then add it to the Cell 90 | p = self.parity 91 | angle_x_dist = 2 * self.wgt.bend_radius * np.sin(self.angle) 92 | 93 | angle_y_dist = 2 * self.wgt.bend_radius * (1 - np.cos(self.angle)) 94 | distx = 2 * angle_x_dist + self.length 95 | disty = p * (2 * abs(angle_y_dist) + self.gap + self.wgt.wg_width) 96 | 97 | x0, y0 = 0, 0 # shift to port location after rotation later 98 | 99 | """ Build the adiabatic DC from gdspy Path derivatives """ 100 | """ First the top waveguide """ 101 | wg_top = gdspy.Path(self.wgt.wg_width, (x0, y0)) 102 | wg_top.turn( 103 | self.wgt.bend_radius, 104 | -p * self.angle, 105 | number_of_points=self.wgt.get_num_points_wg(self.angle), 106 | final_width=self.wgt.wg_width + self.dw, 107 | **self.wg_spec 108 | ) 109 | wg_top.turn( 110 | self.wgt.bend_radius, 111 | p * self.angle, 112 | number_of_points=self.wgt.get_num_points_wg(self.angle), 113 | **self.wg_spec 114 | ) 115 | wg_top.segment( 116 | self.length, final_width=self.wgt.wg_width - self.dw, **self.wg_spec 117 | ) 118 | wg_top.turn( 119 | self.wgt.bend_radius, 120 | p * self.angle, 121 | number_of_points=self.wgt.get_num_points_wg(self.angle), 122 | **self.wg_spec 123 | ) 124 | wg_top.turn( 125 | self.wgt.bend_radius, 126 | -p * self.angle, 127 | number_of_points=self.wgt.get_num_points_wg(self.angle), 128 | final_width=self.wgt.wg_width, 129 | **self.wg_spec 130 | ) 131 | 132 | wg_top_clad = gdspy.Path(2 * self.wgt.clad_width + self.wgt.wg_width, (x0, y0)) 133 | wg_top_clad.turn( 134 | self.wgt.bend_radius, 135 | -p * self.angle, 136 | number_of_points=self.wgt.get_num_points_wg(self.angle), 137 | **self.clad_spec 138 | ) 139 | wg_top_clad.turn( 140 | self.wgt.bend_radius, 141 | p * self.angle, 142 | number_of_points=self.wgt.get_num_points_wg(self.angle), 143 | final_width=self.wgt.wg_width + 2 * self.wgt.clad_width, 144 | **self.clad_spec 145 | ) 146 | wg_top_clad.segment(self.length, **self.clad_spec) 147 | wg_top_clad.turn( 148 | self.wgt.bend_radius, 149 | p * self.angle, 150 | number_of_points=self.wgt.get_num_points_wg(self.angle), 151 | final_width=self.wgt.wg_width + 2 * self.wgt.clad_width, 152 | **self.clad_spec 153 | ) 154 | wg_top_clad.turn( 155 | self.wgt.bend_radius, 156 | -p * self.angle, 157 | number_of_points=self.wgt.get_num_points_wg(self.angle), 158 | **self.clad_spec 159 | ) 160 | 161 | """ Next, the bottom waveguide """ 162 | x1, y1 = 0, -disty 163 | wg_bot = gdspy.Path(self.wgt.wg_width, (x1, y1)) 164 | wg_bot.turn( 165 | self.wgt.bend_radius, 166 | +p * self.angle, 167 | number_of_points=self.wgt.get_num_points_wg(self.angle), 168 | final_width=self.wgt.wg_width - self.dw, 169 | **self.wg_spec 170 | ) 171 | wg_bot.turn( 172 | self.wgt.bend_radius, 173 | -p * self.angle, 174 | number_of_points=self.wgt.get_num_points_wg(self.angle), 175 | **self.wg_spec 176 | ) 177 | wg_bot.segment( 178 | self.length, final_width=self.wgt.wg_width + self.dw, **self.wg_spec 179 | ) 180 | wg_bot.turn( 181 | self.wgt.bend_radius, 182 | -p * self.angle, 183 | number_of_points=self.wgt.get_num_points_wg(self.angle), 184 | **self.wg_spec 185 | ) 186 | wg_bot.turn( 187 | self.wgt.bend_radius, 188 | +p * self.angle, 189 | number_of_points=self.wgt.get_num_points_wg(self.angle), 190 | final_width=self.wgt.wg_width, 191 | **self.wg_spec 192 | ) 193 | 194 | wg_bot_clad = gdspy.Path(2 * self.wgt.clad_width + self.wgt.wg_width, (x1, y1)) 195 | wg_bot_clad.turn( 196 | self.wgt.bend_radius, 197 | +p * self.angle, 198 | number_of_points=self.wgt.get_num_points_wg(self.angle), 199 | **self.clad_spec 200 | ) 201 | wg_bot_clad.turn( 202 | self.wgt.bend_radius, 203 | -p * self.angle, 204 | number_of_points=self.wgt.get_num_points_wg(self.angle), 205 | final_width=self.wgt.wg_width + 2 * self.wgt.clad_width, 206 | **self.clad_spec 207 | ) 208 | wg_bot_clad.segment(self.length, **self.clad_spec) 209 | wg_bot_clad.turn( 210 | self.wgt.bend_radius, 211 | -p * self.angle, 212 | number_of_points=self.wgt.get_num_points_wg(self.angle), 213 | final_width=self.wgt.wg_width + 2 * self.wgt.clad_width, 214 | **self.clad_spec 215 | ) 216 | wg_bot_clad.turn( 217 | self.wgt.bend_radius, 218 | +p * self.angle, 219 | number_of_points=self.wgt.get_num_points_wg(self.angle), 220 | **self.clad_spec 221 | ) 222 | 223 | self.portlist_input_straight = (0, 0) 224 | self.portlist_output_straight = (distx, 0) 225 | self.portlist_output_cross = (distx, -disty) 226 | self.portlist_input_cross = (0, -disty) 227 | 228 | self.add(wg_top) 229 | self.add(wg_bot) 230 | self.add(wg_top_clad) 231 | self.add(wg_bot_clad) 232 | 233 | def __build_ports(self): 234 | # Portlist format: 235 | # example: example: {'port':(x_position, y_position), 'direction': 'NORTH'} 236 | if self.parity == 1: 237 | self.portlist["input_top"] = { 238 | "port": self.portlist_input_straight, 239 | "direction": "WEST", 240 | } 241 | self.portlist["input_bot"] = { 242 | "port": self.portlist_input_cross, 243 | "direction": "WEST", 244 | } 245 | self.portlist["output_top"] = { 246 | "port": self.portlist_output_straight, 247 | "direction": "EAST", 248 | } 249 | self.portlist["output_bot"] = { 250 | "port": self.portlist_output_cross, 251 | "direction": "EAST", 252 | } 253 | elif self.parity == -1: 254 | self.portlist["input_top"] = { 255 | "port": self.portlist_input_cross, 256 | "direction": "WEST", 257 | } 258 | self.portlist["input_bot"] = { 259 | "port": self.portlist_input_straight, 260 | "direction": "WEST", 261 | } 262 | self.portlist["output_top"] = { 263 | "port": self.portlist_output_cross, 264 | "direction": "EAST", 265 | } 266 | self.portlist["output_bot"] = { 267 | "port": self.portlist_output_straight, 268 | "direction": "EAST", 269 | } 270 | 271 | 272 | if __name__ == "__main__": 273 | from . import * 274 | from picwriter.components.waveguide import WaveguideTemplate 275 | 276 | top = gdspy.Cell("top") 277 | wgt = WaveguideTemplate(wg_width=2.0, bend_radius=100, resist="+") 278 | 279 | wg1 = Waveguide([(0, 0), (100, 40)], wgt) 280 | tk.add(top, wg1) 281 | 282 | fc = FullCoupler( 283 | wgt, 40.0, 0.5, 1.0, angle=np.pi / 12.0, parity=1, **wg1.portlist["output"] 284 | ) 285 | tk.add(top, fc) 286 | for p in fc.portlist.keys(): 287 | print(str(p) + ": " + str(fc.portlist[p]["port"])) 288 | 289 | gdspy.LayoutViewer() 290 | # gdspy.write_gds('fc.gds', unit=1.0e-6, precision=1.0e-9) 291 | -------------------------------------------------------------------------------- /picwriter/components/mmi1x2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | import numpy as np 4 | import gdspy 5 | import picwriter.toolkit as tk 6 | from picwriter.components.ebend import EulerSBend 7 | from picwriter.components.taper import Taper 8 | 9 | 10 | class MMI1x2(tk.Component): 11 | """1x2 multi-mode interfereomter (MMI) Cell class. 12 | 13 | Args: 14 | * **wgt** (WaveguideTemplate): WaveguideTemplate object 15 | * **length** (float): Length of the MMI region (along direction of propagation) 16 | * **width** (float): Width of the MMI region (perpendicular to direction of propagation) 17 | 18 | Keyword Args: 19 | * **wg_sep** (float): Separation between waveguides on the 2-port side (defaults to width/3.0). Defaults to None (width/3.0). 20 | * **taper_width** (float): Ending width of the taper region (default = wg_width from wg_template). Defaults to None (waveguide width). 21 | * **taper_length** (float): Length of the input taper leading up to the MMI (single-port side). Defaults to None (no input taper, port right against the MMI region). 22 | * **output_length** (float): Length (along x-direction) of the output bends, made with Euler S-Bends. Defaults to None (no output bend, ports right up againt the MMI region). 23 | * **output_wg_sep** (float): Distance (along y-direction) between the two output bends, made with Euler S-Bends. Defaults to None (no output bend, ports right up againt the MMI region). 24 | * **output_width** (float): Starting width of the output waveguide. Defaults to None (no change from regular wg_width). 25 | * **port** (tuple): Cartesian coordinate of the input port. Defaults to (0,0). 26 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) 27 | 28 | Members: 29 | * **portlist** (dict): Dictionary with the relevant port information 30 | 31 | Portlist format: 32 | * portlist['input'] = {'port': (x1,y1), 'direction': 'dir1'} 33 | * portlist['output_top'] = {'port': (x2, y2), 'direction': 'dir2'} 34 | * portlist['output_bot'] = {'port': (x3, y3), 'direction': 'dir3'} 35 | 36 | Where in the above (x1,y1) is the input port, (x2, y2) is the top output port, (x3, y3) is the bottom output port, and 'dir1', 'dir2', 'dir3' are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 37 | 'Direction' points *towards* the waveguide that will connect to it. 38 | 39 | """ 40 | 41 | def __init__( 42 | self, 43 | wgt, 44 | length, 45 | width, 46 | wg_sep=None, 47 | taper_width=None, 48 | taper_length=None, 49 | output_length=None, 50 | output_wg_sep=None, 51 | output_width=None, 52 | port=(0, 0), 53 | direction="EAST", 54 | ): 55 | tk.Component.__init__(self, "MMI1x2", locals()) 56 | 57 | self.port = port 58 | self.direction = direction 59 | self.portlist = {} 60 | 61 | self.wgt = wgt 62 | self.length = length 63 | self.width = width 64 | 65 | self.totlength = length 66 | 67 | if (output_length != None) and (output_wg_sep != None): 68 | self.output_length = output_length 69 | self.output_wg_sep = output_wg_sep 70 | self.output_width = wgt.wg_width if output_width == None else output_width 71 | self.draw_outputs = True 72 | self.totlength += self.output_length 73 | elif (output_length == None) and (output_wg_sep == None): 74 | self.draw_outputs = False 75 | self.output_wg_sep = wg_sep 76 | else: 77 | raise ValueError( 78 | "Warning! One of the two output values was None, and the other was provided. Both must be provided *OR* omitted." 79 | ) 80 | 81 | if (taper_width != None) and (taper_length != None): 82 | self.taper_width = taper_width 83 | self.taper_length = taper_length 84 | self.draw_input = True 85 | self.totlength += taper_length 86 | elif (taper_width == None) and (taper_length == None): 87 | self.draw_input = False 88 | else: 89 | raise ValueError( 90 | "Warning! One of the two input values was None, and the other was provided. Both must be provided *OR* omitted." 91 | ) 92 | 93 | self.wg_sep = width / 3.0 if wg_sep == None else wg_sep 94 | 95 | self.resist = wgt.resist 96 | self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 97 | self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype} 98 | 99 | self.input_port = (0, 0) 100 | self.output_port_top = (self.totlength, self.output_wg_sep / 2.0) 101 | self.output_port_bot = (self.totlength, -self.output_wg_sep / 2.0) 102 | 103 | self.__type_check_values() 104 | self.__build_cell() 105 | self.__build_ports() 106 | 107 | """ Translate & rotate the ports corresponding to this specific component object 108 | """ 109 | self._auto_transform_() 110 | 111 | def __type_check_values(self): 112 | # Check that the values for the MMI1x2 are all valid 113 | 114 | if self.wg_sep > (self.width - self.taper_width): 115 | raise ValueError( 116 | "Warning! Waveguide separation is larger than the " 117 | "max value (width - taper_width)" 118 | ) 119 | if self.wg_sep < self.taper_width: 120 | raise ValueError( 121 | "Warning! Waveguide separation is smaller than the " 122 | "minimum value (taper_width)" 123 | ) 124 | if self.draw_outputs: 125 | if self.output_length < (self.output_wg_sep - self.wg_sep) / 2.0: 126 | raise ValueError( 127 | "Warning! The output length must be greater than half the output wg separation" 128 | ) 129 | 130 | def __build_cell(self): 131 | # Sequentially build all the geometric shapes using gdspy path functions 132 | # then add it to the Cell 133 | 134 | x, y = (0, 0) 135 | 136 | """ Add the input taper """ 137 | if self.draw_input: 138 | tp = Taper( 139 | self.wgt, 140 | self.taper_length, 141 | self.taper_width, 142 | port=(x, y), 143 | direction="EAST", 144 | ) 145 | self.add(tp) 146 | x, y = tp.portlist["output"]["port"] 147 | 148 | # 149 | # path1 = gdspy.Path(self.wgt.wg_width, (0,0)) 150 | # path1.segment(self.taper_length, direction='+x', final_width=self.taper_width, **self.wg_spec) 151 | # 152 | """ Add the MMI region """ 153 | mmi = gdspy.Path(self.width, (x, y)) 154 | mmi.segment(self.length, direction="+x", **self.wg_spec) 155 | self.add(mmi) 156 | 157 | clad_pts = [ 158 | (0.0, -self.wgt.wg_width / 2.0 - self.wgt.clad_width), 159 | (self.taper_length, -self.width / 2.0 - self.wgt.clad_width), 160 | (self.taper_length + self.length, -self.width / 2.0 - self.wgt.clad_width), 161 | ( 162 | 2 * self.taper_length + self.length, 163 | -self.wg_sep / 2.0 - self.wgt.wg_width / 2.0 - self.wgt.clad_width, 164 | ), 165 | ( 166 | 2 * self.taper_length + self.length, 167 | self.wg_sep / 2.0 + self.wgt.wg_width / 2.0 + self.wgt.clad_width, 168 | ), 169 | (self.taper_length + self.length, self.width / 2.0 + self.wgt.clad_width), 170 | (self.taper_length, self.width / 2.0 + self.wgt.clad_width), 171 | (0.0, self.wgt.wg_width / 2.0 + self.wgt.clad_width), 172 | ] 173 | clad = gdspy.Polygon(clad_pts, **self.clad_spec) 174 | self.add(clad) 175 | 176 | (x, y) = (x + self.length, y) 177 | 178 | """ Add the output tapers """ 179 | if self.draw_outputs: 180 | dy = (self.output_wg_sep - self.wg_sep) / 2.0 181 | esb_top = EulerSBend( 182 | self.wgt, 183 | self.output_length, 184 | dy, 185 | self.output_width, 186 | end_width=self.wgt.wg_width, 187 | port=(x, y + self.wg_sep / 2.0), 188 | ) 189 | self.add(esb_top) 190 | 191 | esb_bot = EulerSBend( 192 | self.wgt, 193 | self.output_length, 194 | -dy, 195 | self.output_width, 196 | end_width=self.wgt.wg_width, 197 | port=(x, y - self.wg_sep / 2.0), 198 | ) 199 | self.add(esb_bot) 200 | 201 | # path3 = gdspy.Path(self.taper_width, (path2.x, path2.y+self.wg_sep/2.0)) 202 | # path3.turn(self.wgt.bend_radius, self.angle, number_of_points=self.wgt.get_num_points_wg(self.angle), final_width=self.wgt.wg_width, **self.wg_spec) 203 | # path3.turn(self.wgt.bend_radius, -self.angle, number_of_points=self.wgt.get_num_points_wg(self.angle), **self.wg_spec) 204 | # 205 | # path4 = gdspy.Path(self.taper_width, (path2.x, path2.y-self.wg_sep/2.0)) 206 | # path4.turn(self.wgt.bend_radius, -self.angle, number_of_points=self.wgt.get_num_points_wg(self.angle), final_width=self.wgt.wg_width, **self.wg_spec) 207 | # path4.turn(self.wgt.bend_radius, self.angle, number_of_points=self.wgt.get_num_points_wg(self.angle), **self.wg_spec) 208 | 209 | # clad_path3 = gdspy.Path(self.taper_width+2*self.wgt.clad_width, (path2.x, path2.y+self.wg_sep/2.0)) 210 | # clad_path3.turn(self.wgt.bend_radius, self.angle, number_of_points=self.wgt.get_num_points_wg(self.angle), final_width=self.wgt.wg_width+2*self.wgt.clad_width, **self.clad_spec) 211 | # clad_path3.turn(self.wgt.bend_radius, -self.angle, number_of_points=self.wgt.get_num_points_wg(self.angle), **self.clad_spec) 212 | # 213 | # clad_path4 = gdspy.Path(self.taper_width+2*self.wgt.clad_width, (path2.x, path2.y-self.wg_sep/2.0)) 214 | # clad_path4.turn(self.wgt.bend_radius, -self.angle, number_of_points=self.wgt.get_num_points_wg(self.angle), final_width=self.wgt.wg_width+2*self.wgt.clad_width, **self.clad_spec) 215 | # clad_path4.turn(self.wgt.bend_radius, +self.angle, number_of_points=self.wgt.get_num_points_wg(self.angle), **self.clad_spec) 216 | 217 | # self.add(path1) 218 | # self.add(path2) 219 | # self.add(path3) 220 | # self.add(path4) 221 | # self.add(clad_path3) 222 | # self.add(clad_path4) 223 | # self.add(clad) 224 | 225 | def __build_ports(self): 226 | # Portlist format: 227 | # example: {'port':(x_position, y_position), 'direction': 'NORTH'} 228 | 229 | self.portlist["input"] = {"port": self.input_port, "direction": "WEST"} 230 | self.portlist["output_top"] = { 231 | "port": self.output_port_top, 232 | "direction": "EAST", 233 | } 234 | self.portlist["output_bot"] = { 235 | "port": self.output_port_bot, 236 | "direction": "EAST", 237 | } 238 | 239 | 240 | if __name__ == "__main__": 241 | from . import * 242 | 243 | top = gdspy.Cell("top") 244 | wgt = WaveguideTemplate(bend_radius=50, wg_width=1.0, resist="+") 245 | 246 | # wg1=Waveguide([(0, 0), (25, 0)], wgt) 247 | # tk.add(top, wg1) 248 | 249 | mmi = MMI1x2( 250 | wgt, 251 | length=20, 252 | width=7, 253 | taper_length=10.0, 254 | taper_width=2.0, 255 | wg_sep=7 / 2, 256 | output_wg_sep=10.0, 257 | output_length=20.0, 258 | output_width=2.0, 259 | ) 260 | # mmi2 = MMI1x2(wgt, length=50, width=10, taper_length=10.0, taper_width=2.0, wg_sep=3, output_wg_sep=20.0, output_length=40.0, output_width=2.5, **mmi.portlist["output_top"]) 261 | # mmi3 = MMI1x2(wgt, length=50, width=10, taper_length=10.0, taper_width=2.0, wg_sep=3, output_wg_sep=20.0, output_length=40.0, output_width=2.5, **mmi.portlist["output_bot"]) 262 | # mmi = MMI1x2(wgt, length=50, width=10, taper_width=2.0, wg_sep=4.0, port=(0,0), direction='EAST') 263 | mmi.addto(top) 264 | # mmi2.addto(top) 265 | # mmi3.addto(top) 266 | # tk.add(top, mmi) 267 | # tk.add(top, mmi2) 268 | # tk.add(top, mmi3) 269 | 270 | gdspy.LayoutViewer() 271 | # gdspy.write_gds('mmi1x2.gds', unit=1.0e-6, precision=1.0e-9) 272 | -------------------------------------------------------------------------------- /picwriter/components/mmi2x2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | import numpy as np 4 | import gdspy 5 | import picwriter.toolkit as tk 6 | 7 | 8 | class MMI2x2(tk.Component): 9 | """2x2 multi-mode interferometer (MMI) Cell class. Two input ports, two output ports. 10 | 11 | Args: 12 | * **wgt** (WaveguideTemplate): WaveguideTemplate object 13 | * **length** (float): Length of the MMI region (along direction of propagation) 14 | * **width** (float): Width of the MMI region (perpendicular to direction of propagation) 15 | 16 | Keyword Args: 17 | * **angle** (float): Angle in radians (between 0 and pi/2) at which the waveguide bends towards the coupling region. Default=pi/6. Note: it is possible to generate a MMI with straight tapered outputs (not curved) by setting angle=0 and then connecting a straight Taper object to the desired MMI ports. 18 | * **taper_width** (float): Maximum width of the taper region (default = wg_width from wg_template) 19 | * **wg_sep** (float): Separation between waveguides on the 2-port side (defaults to width/3.0) 20 | * **port** (tuple): Cartesian coordinate of the **top** input port 21 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) 22 | 23 | Members: 24 | * **portlist** (dict): Dictionary with the relevant port information 25 | 26 | Portlist format: 27 | * portlist['input_top'] = {'port': (x1,y1), 'direction': 'dir1'} 28 | * portlist['input_bot'] = {'port': (x2, y2), 'direction': 'dir2'} 29 | * portlist['output_top'] = {'port': (x3, y3), 'direction': 'dir3'} 30 | * portlist['output_bot'] = {'port': (x4, y4), 'direction': 'dir4'} 31 | 32 | Where in the above (x1,y1) is the input port, (x2, y2) is the top output port, (x3, y3) is the bottom output port, and 'dir1', 'dir2', 'dir3' are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 33 | 'Direction' points *towards* the waveguide that will connect to it. 34 | 35 | """ 36 | 37 | def __init__( 38 | self, 39 | wgt, 40 | length, 41 | width, 42 | angle=np.pi / 6.0, 43 | taper_width=None, 44 | wg_sep=None, 45 | port=(0, 0), 46 | direction="EAST", 47 | ): 48 | tk.Component.__init__(self, "MMI2x2", locals()) 49 | 50 | self.portlist = {} 51 | 52 | self.wgt = wgt 53 | self.length = length 54 | self.width = width 55 | if angle > np.pi / 2.0 or angle < 0: 56 | raise ValueError( 57 | "Warning! Improper angle specified (" 58 | + str(angle) 59 | + "). Must be between 0 and pi/2.0." 60 | ) 61 | self.angle = angle 62 | self.taper_width = wgt.wg_width if taper_width == None else taper_width 63 | self.wg_sep = width / 3.0 if wg_sep == None else wg_sep 64 | 65 | self.port = port 66 | self.direction = direction 67 | self.resist = wgt.resist 68 | self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 69 | self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype} 70 | 71 | self.type_check_values() 72 | self.__build_cell() 73 | self.__build_ports() 74 | 75 | """ Translate & rotate the ports corresponding to this specific component object 76 | """ 77 | self._auto_transform_() 78 | 79 | def type_check_values(self): 80 | # Check that the values for the MMI1x2 are all valid 81 | 82 | if self.wg_sep > (self.width - self.taper_width): 83 | raise ValueError( 84 | "Warning! Waveguide separation is larger than the " 85 | "max value (width - taper_width)" 86 | ) 87 | if self.wg_sep < self.taper_width: 88 | raise ValueError( 89 | "Warning! Waveguide separation is smaller than the " 90 | "minimum value (taper_width)" 91 | ) 92 | 93 | def __build_cell(self): 94 | # Sequentially build all the geometric shapes using gdspy path functions 95 | # then add it to the Cell 96 | 97 | angle_x_dist = 2 * self.wgt.bend_radius * np.sin(self.angle) 98 | angle_y_dist = 2 * self.wgt.bend_radius * (1 - np.cos(self.angle)) 99 | 100 | """ Waveguide paths """ 101 | # Top input 102 | path1 = gdspy.Path(self.wgt.wg_width, (0, 0)) 103 | path1.turn( 104 | self.wgt.bend_radius, 105 | -self.angle, 106 | number_of_points=self.wgt.get_num_points_wg(self.angle), 107 | **self.wg_spec 108 | ) 109 | path1.turn( 110 | self.wgt.bend_radius, 111 | self.angle, 112 | number_of_points=self.wgt.get_num_points_wg(self.angle), 113 | final_width=self.taper_width, 114 | **self.wg_spec 115 | ) 116 | # Bottom input 117 | path2 = gdspy.Path(self.wgt.wg_width, (0, -self.wg_sep - 2 * angle_y_dist)) 118 | path2.turn( 119 | self.wgt.bend_radius, 120 | self.angle, 121 | number_of_points=self.wgt.get_num_points_wg(self.angle), 122 | **self.wg_spec 123 | ) 124 | path2.turn( 125 | self.wgt.bend_radius, 126 | -self.angle, 127 | number_of_points=self.wgt.get_num_points_wg(self.angle), 128 | final_width=self.taper_width, 129 | **self.wg_spec 130 | ) 131 | # MMI body 132 | path3 = gdspy.Path( 133 | self.width, (angle_x_dist, -self.wg_sep / 2.0 - angle_y_dist) 134 | ) 135 | path3.segment(self.length, direction="+x", **self.wg_spec) 136 | # Top output 137 | path4 = gdspy.Path(self.taper_width, (path1.x + self.length, path1.y)) 138 | path4.turn( 139 | self.wgt.bend_radius, 140 | self.angle, 141 | number_of_points=self.wgt.get_num_points_wg(self.angle), 142 | final_width=self.wgt.wg_width, 143 | **self.wg_spec 144 | ) 145 | path4.turn( 146 | self.wgt.bend_radius, 147 | -self.angle, 148 | number_of_points=self.wgt.get_num_points_wg(self.angle), 149 | **self.wg_spec 150 | ) 151 | # Bottom output 152 | path5 = gdspy.Path(self.taper_width, (path2.x + self.length, path2.y)) 153 | path5.turn( 154 | self.wgt.bend_radius, 155 | -self.angle, 156 | number_of_points=self.wgt.get_num_points_wg(self.angle), 157 | final_width=self.wgt.wg_width, 158 | **self.wg_spec 159 | ) 160 | path5.turn( 161 | self.wgt.bend_radius, 162 | self.angle, 163 | number_of_points=self.wgt.get_num_points_wg(self.angle), 164 | **self.wg_spec 165 | ) 166 | 167 | """ Now, generate the cladding paths """ 168 | # Top input 169 | clad_path1 = gdspy.Path(self.wgt.wg_width + 2 * self.wgt.clad_width, (0, 0)) 170 | clad_path1.turn( 171 | self.wgt.bend_radius, 172 | -self.angle, 173 | number_of_points=self.wgt.get_num_points_wg(self.angle), 174 | **self.clad_spec 175 | ) 176 | clad_path1.turn( 177 | self.wgt.bend_radius, 178 | self.angle, 179 | number_of_points=self.wgt.get_num_points_wg(self.angle), 180 | final_width=self.taper_width + 2 * self.wgt.clad_width, 181 | **self.clad_spec 182 | ) 183 | # Bottom input 184 | clad_path2 = gdspy.Path( 185 | self.wgt.wg_width + 2 * self.wgt.clad_width, 186 | (0, -self.wg_sep - 2 * angle_y_dist), 187 | ) 188 | clad_path2.turn( 189 | self.wgt.bend_radius, 190 | self.angle, 191 | number_of_points=self.wgt.get_num_points_wg(self.angle), 192 | **self.clad_spec 193 | ) 194 | clad_path2.turn( 195 | self.wgt.bend_radius, 196 | -self.angle, 197 | number_of_points=self.wgt.get_num_points_wg(self.angle), 198 | final_width=self.taper_width + 2 * self.wgt.clad_width, 199 | **self.clad_spec 200 | ) 201 | # MMI body 202 | c_start_width = 2 * self.wgt.clad_width + 2 * self.taper_width + self.wg_sep 203 | clad_path3 = gdspy.Path( 204 | c_start_width, 205 | (angle_x_dist - self.wgt.clad_width, -self.wg_sep / 2.0 - angle_y_dist), 206 | ) 207 | clad_path3.segment( 208 | self.wgt.clad_width, 209 | final_width=self.width + 2 * self.wgt.clad_width, 210 | direction="+x", 211 | **self.clad_spec 212 | ) 213 | clad_path3.segment(self.length, direction="+x", **self.clad_spec) 214 | clad_path3.segment( 215 | self.wgt.clad_width, 216 | final_width=c_start_width, 217 | direction="+x", 218 | **self.clad_spec 219 | ) 220 | # Top output 221 | clad_path4 = gdspy.Path( 222 | self.taper_width + 2 * self.wgt.clad_width, 223 | (clad_path1.x + self.length, clad_path1.y), 224 | ) 225 | clad_path4.turn( 226 | self.wgt.bend_radius, 227 | self.angle, 228 | number_of_points=self.wgt.get_num_points_wg(self.angle), 229 | final_width=self.wgt.wg_width + 2 * self.wgt.clad_width, 230 | **self.clad_spec 231 | ) 232 | clad_path4.turn( 233 | self.wgt.bend_radius, 234 | -self.angle, 235 | number_of_points=self.wgt.get_num_points_wg(self.angle), 236 | **self.clad_spec 237 | ) 238 | # Bottom output 239 | clad_path5 = gdspy.Path( 240 | self.taper_width + 2 * self.wgt.clad_width, 241 | (clad_path2.x + self.length, clad_path2.y), 242 | ) 243 | clad_path5.turn( 244 | self.wgt.bend_radius, 245 | -self.angle, 246 | number_of_points=self.wgt.get_num_points_wg(self.angle), 247 | final_width=self.wgt.wg_width + 2 * self.wgt.clad_width, 248 | **self.clad_spec 249 | ) 250 | clad_path5.turn( 251 | self.wgt.bend_radius, 252 | self.angle, 253 | number_of_points=self.wgt.get_num_points_wg(self.angle), 254 | **self.clad_spec 255 | ) 256 | 257 | self.input_port_top = (0.0, 0.0) 258 | self.input_port_bot = (0.0, -self.wg_sep - 2 * angle_y_dist) 259 | self.output_port_top = (2 * angle_x_dist + self.length, 0.0) 260 | self.output_port_bot = ( 261 | 2 * angle_x_dist + self.length, 262 | -self.wg_sep - 2 * angle_y_dist, 263 | ) 264 | 265 | self.add(path1) 266 | self.add(path2) 267 | self.add(path3) 268 | self.add(path4) 269 | self.add(path5) 270 | self.add(clad_path1) 271 | self.add(clad_path2) 272 | self.add(clad_path3) 273 | self.add(clad_path4) 274 | self.add(clad_path5) 275 | 276 | def __build_ports(self): 277 | # Portlist format: 278 | # example: {'port':(x_position, y_position), 'direction': 'NORTH'} 279 | self.portlist["input_top"] = {"port": self.input_port_top, "direction": "WEST"} 280 | self.portlist["input_bot"] = {"port": self.input_port_bot, "direction": "WEST"} 281 | self.portlist["output_top"] = { 282 | "port": self.output_port_top, 283 | "direction": "EAST", 284 | } 285 | self.portlist["output_bot"] = { 286 | "port": self.output_port_bot, 287 | "direction": "EAST", 288 | } 289 | 290 | 291 | if __name__ == "__main__": 292 | from . import * 293 | 294 | top = gdspy.Cell("top") 295 | wgt = WaveguideTemplate(bend_radius=50, wg_width=1.0, resist="+") 296 | 297 | wg1 = Waveguide([(0, 0), (0, -100)], wgt) 298 | tk.add(top, wg1) 299 | 300 | mmi1 = MMI2x2( 301 | wgt, length=50, width=10, taper_width=2.0, wg_sep=3.0, **wg1.portlist["output"] 302 | ) 303 | mmi2 = MMI2x2( 304 | wgt, 305 | length=50, 306 | width=10, 307 | taper_width=2.0, 308 | wg_sep=3.0, 309 | **mmi1.portlist["output_top"] 310 | ) 311 | tk.add(top, mmi1) 312 | tk.add(top, mmi2) 313 | 314 | gdspy.LayoutViewer() 315 | # # gdspy.write_gds('mmi2x2.gds', unit=1.0e-6, precision=1.0e-9) 316 | -------------------------------------------------------------------------------- /picwriter/components/sbend.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | 8 | 9 | class SBend(tk.Component): 10 | """Sinusoidal S-shaped Bend Cell class. Creates a sinusoidal waveguide bend that can be used in waveguide routing. The number of points is computed based on the waveguide template grid resolution to automatically minimize grid errors. 11 | 12 | Args: 13 | * **wgt** (WaveguideTemplate): WaveguideTemplate object 14 | * **length** (float): Length of the S-bend 15 | * **height** (float): Height of the S-bend 16 | 17 | Keyword Args: 18 | * **port** (tuple): Cartesian coordinate of the input port. Defaults to (0,0). 19 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) 20 | 21 | Members: 22 | * **portlist** (dict): Dictionary with the relevant port information 23 | 24 | Portlist format: 25 | * portlist['input'] = {'port': (x1,y1), 'direction': 'dir1'} 26 | * portlist['output'] = {'port': (x2, y2), 'direction': 'dir2'} 27 | 28 | Where in the above (x1,y1) is the same as the 'port' input, (x2, y2) is the end of the taper, and 'dir1', 'dir2' are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 29 | 'Direction' points *towards* the waveguide that will connect to it. 30 | 31 | """ 32 | 33 | def __init__(self, wgt, length, height, port=(0, 0), direction="EAST"): 34 | tk.Component.__init__(self, "SBend", locals()) 35 | 36 | self.port = port 37 | self.portlist = {} 38 | self.direction = direction 39 | 40 | self.input_port = (0, 0) 41 | self.output_port = (length, height) 42 | 43 | self.wgt = wgt 44 | self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 45 | self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype} 46 | 47 | self.__build_cell() 48 | self.__build_ports() 49 | 50 | """ Translate & rotate the ports corresponding to this specific component object 51 | """ 52 | self._auto_transform_() 53 | 54 | def __sine_function(self, t): 55 | # input (t) goes from 0->1 56 | # Returns an (x,y) tuple 57 | return ( 58 | self.output_port[0] * t, 59 | 0.5 * self.output_port[1] * np.sin(np.pi * t - 0.5 * np.pi) 60 | + 0.5 * self.output_port[1], 61 | ) 62 | 63 | def __build_cell(self): 64 | # Sequentially build all the geometric shapes using gdspy path functions 65 | # for waveguide, then add it to the Cell 66 | 67 | # Add waveguide s-bend 68 | wg = gdspy.Path(self.wgt.wg_width, (0, 0)) 69 | wg.parametric( 70 | self.__sine_function, 71 | tolerance=self.wgt.grid / 2.0, 72 | max_points=199, 73 | **self.wg_spec 74 | ) 75 | self.add(wg) 76 | 77 | # Add cladding s-bend 78 | for i in range(len(self.wgt.waveguide_stack) - 1): 79 | cur_width = self.wgt.waveguide_stack[i + 1][0] 80 | cur_spec = { 81 | "layer": self.wgt.waveguide_stack[i + 1][1][0], 82 | "datatype": self.wgt.waveguide_stack[i + 1][1][1], 83 | } 84 | 85 | clad = gdspy.Path(cur_width, (0, 0)) 86 | clad.parametric( 87 | self.__sine_function, 88 | tolerance=self.wgt.grid / 2.0, 89 | max_points=199, 90 | **cur_spec 91 | ) 92 | self.add(clad) 93 | 94 | def __build_ports(self): 95 | # Portlist format: 96 | # example: example: {'port':(x_position, y_position), 'direction': 'NORTH'} 97 | self.portlist["input"] = {"port": self.input_port, "direction": "WEST"} 98 | self.portlist["output"] = {"port": self.output_port, "direction": "EAST"} 99 | 100 | 101 | if __name__ == "__main__": 102 | from . import * 103 | 104 | top = gdspy.Cell("top") 105 | wgt = WaveguideTemplate(bend_radius=50, resist="+") 106 | 107 | wg1 = Waveguide([(0, 0), (100, 0)], wgt) 108 | tk.add(top, wg1) 109 | 110 | sb1 = SBend(wgt, 200.0, 100.0, **wg1.portlist["output"]) 111 | tk.add(top, sb1) 112 | 113 | x, y = sb1.portlist["output"]["port"] 114 | wg2 = Waveguide([(x, y), (x + 100, y)], wgt) 115 | tk.add(top, wg2) 116 | 117 | gdspy.LayoutViewer(cells=top, depth=3) 118 | # gdspy.write_gds('sbend.gds', unit=1.0e-6, precision=1.0e-9) 119 | -------------------------------------------------------------------------------- /picwriter/components/stripslotconverter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | 8 | 9 | class StripSlotConverter(tk.Component): 10 | """Strip-to-Slot Side Converter Cell class. Adiabatically transforms a strip to a slot waveguide mode, with two sections. Section 1 introduces a narrow waveguide alongside the input strip waveguide and gradually lowers the gap between the strip waveguide and narrow side waveguide. Section 2 gradually converts the widths of the two waveguides until they are equal to the slot rail widths. 11 | 12 | Args: 13 | * **wgt_input** (WaveguideTemplate): WaveguideTemplate object for the input waveguide (should be either of type `strip` or `slot`). 14 | * **wgt_output** (WaveguideTemplate): WaveguideTemplate object for the output waveguide (should be either of type `strip` or `slot`, opposite of the input type). 15 | * **length1** (float): Length of section 1 that gradually changes the distance between the two waveguides. 16 | * **length2** (float): Length of section 2 that gradually changes the widths of the two waveguides until equal to the slot waveguide rail widths. 17 | * **start_rail_width** (float): Width of the narrow waveguide appearing next to the strip waveguide. 18 | * **end_strip_width** (float): Width of the strip waveguide at the end of `length1` and before `length2` 19 | * **d** (float): Distance between the outer edge of the strip waveguide and the start of the slot waveguide rail. 20 | 21 | Keyword Args: 22 | * **input_strip** (Boolean): If `True`, sets the input port to be the strip waveguide side. If `False`, slot waveguide is on the input. Defaults to `None`, in which case the input port waveguide template is used to choose. 23 | * **port** (tuple): Cartesian coordinate of the input port. Defaults to (0,0). 24 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) 25 | 26 | Members: 27 | * **portlist** (dict): Dictionary with the relevant port information 28 | 29 | Portlist format: 30 | * portlist['input'] = {'port': (x1,y1), 'direction': 'dir1'} 31 | * portlist['output'] = {'port': (x2, y2), 'direction': 'dir2'} 32 | 33 | Where in the above (x1,y1) is the same as the 'port' input, (x2, y2) is the end of the taper, and 'dir1', 'dir2' are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 34 | 'Direction' points *towards* the waveguide that will connect to it. 35 | 36 | Note: The waveguide and cladding layer/datatype are taken from the `wgt_slot` by default. 37 | 38 | """ 39 | 40 | def __init__( 41 | self, 42 | wgt_input, 43 | wgt_output, 44 | length1, 45 | length2, 46 | start_rail_width, 47 | end_strip_width, 48 | d, 49 | input_strip=None, 50 | port=(0, 0), 51 | direction="EAST", 52 | ): 53 | 54 | tk.Component.__init__(self, "StripSlotConverter", locals()) 55 | 56 | self.portlist = {} 57 | 58 | if (not isinstance(input_strip, bool)) and (input_strip != None): 59 | raise ValueError( 60 | "Invalid input provided for `input_strip`. Please specify a boolean." 61 | ) 62 | if input_strip == None: 63 | # Auto-detect based on wgt_input 64 | self.input_strip = ( 65 | wgt_input.wg_type == "strip" or wgt_input.wg_type == "swg" 66 | ) 67 | else: 68 | # User-override 69 | self.input_strip = input_strip 70 | 71 | if self.input_strip: 72 | self.wgt_strip = wgt_input 73 | self.wgt_slot = wgt_output 74 | else: 75 | self.wgt_strip = wgt_output 76 | self.wgt_slot = wgt_input 77 | 78 | self.wg_spec = { 79 | "layer": wgt_output.wg_layer, 80 | "datatype": wgt_output.wg_datatype, 81 | } 82 | self.clad_spec = { 83 | "layer": wgt_output.clad_layer, 84 | "datatype": wgt_output.clad_datatype, 85 | } 86 | 87 | self.length1 = length1 88 | self.length2 = length2 89 | self.d = d 90 | self.start_rail_width = start_rail_width 91 | self.end_strip_width = end_strip_width 92 | 93 | self.port = port 94 | self.direction = direction 95 | 96 | self.__build_cell() 97 | self.__build_ports() 98 | 99 | """ Translate & rotate the ports corresponding to this specific component object 100 | """ 101 | self._auto_transform_() 102 | 103 | def __build_cell(self): 104 | # Sequentially build all the geometric shapes using polygons 105 | 106 | # Add strip waveguide taper for region 1 107 | x0, y0 = (0, 0) 108 | 109 | pts = [ 110 | (x0, y0 - self.wgt_strip.wg_width / 2.0), 111 | (x0, y0 + self.wgt_strip.wg_width / 2.0), 112 | ( 113 | x0 + self.length1, 114 | y0 - self.wgt_strip.wg_width / 2.0 + self.end_strip_width, 115 | ), 116 | (x0 + self.length1, y0 - self.wgt_strip.wg_width / 2.0), 117 | ] 118 | strip1 = gdspy.Polygon( 119 | pts, layer=self.wgt_strip.wg_layer, datatype=self.wgt_strip.wg_datatype 120 | ) 121 | 122 | # Add the thin side waveguide for region 1 123 | pts = [ 124 | (x0, y0 + self.wgt_strip.wg_width / 2.0 + self.d), 125 | (x0, y0 + self.wgt_strip.wg_width / 2.0 + self.d + self.start_rail_width), 126 | ( 127 | x0 + self.length1, 128 | y0 129 | - self.wgt_strip.wg_width / 2.0 130 | + self.end_strip_width 131 | + self.wgt_slot.slot 132 | + self.start_rail_width, 133 | ), 134 | ( 135 | x0 + self.length1, 136 | y0 137 | - self.wgt_strip.wg_width / 2.0 138 | + self.end_strip_width 139 | + self.wgt_slot.slot, 140 | ), 141 | ] 142 | thin_strip = gdspy.Polygon( 143 | pts, layer=self.wgt_strip.wg_layer, datatype=self.wgt_strip.wg_datatype 144 | ) 145 | 146 | # Add the bottom rail for region 2 147 | pts = [ 148 | ( 149 | x0 + self.length1, 150 | y0 - self.wgt_strip.wg_width / 2.0 + self.end_strip_width, 151 | ), 152 | (x0 + self.length1, y0 - self.wgt_strip.wg_width / 2.0), 153 | (x0 + self.length1 + self.length2, y0 - self.wgt_slot.wg_width / 2.0), 154 | ( 155 | x0 + self.length1 + self.length2, 156 | y0 - self.wgt_slot.wg_width / 2.0 + self.wgt_slot.rail, 157 | ), 158 | ] 159 | rail1 = gdspy.Polygon( 160 | pts, layer=self.wgt_strip.wg_layer, datatype=self.wgt_strip.wg_datatype 161 | ) 162 | 163 | # Add the top rail for region 2 164 | pts = [ 165 | ( 166 | x0 + self.length1, 167 | y0 168 | - self.wgt_strip.wg_width / 2.0 169 | + self.end_strip_width 170 | + self.wgt_slot.slot 171 | + self.start_rail_width, 172 | ), 173 | ( 174 | x0 + self.length1, 175 | y0 176 | - self.wgt_strip.wg_width / 2.0 177 | + self.end_strip_width 178 | + self.wgt_slot.slot, 179 | ), 180 | ( 181 | x0 + self.length1 + self.length2, 182 | y0 + self.wgt_slot.wg_width / 2.0 - self.wgt_slot.rail, 183 | ), 184 | (x0 + self.length1 + self.length2, y0 + self.wgt_slot.wg_width / 2.0), 185 | ] 186 | rail2 = gdspy.Polygon( 187 | pts, layer=self.wgt_strip.wg_layer, datatype=self.wgt_strip.wg_datatype 188 | ) 189 | 190 | # Add a cladding polygon 191 | pts = [ 192 | (x0, y0 + self.wgt_strip.clad_width + self.wgt_strip.wg_width / 2.0), 193 | ( 194 | x0 + self.length1 + self.length2, 195 | y0 + self.wgt_slot.clad_width + self.wgt_slot.wg_width / 2.0, 196 | ), 197 | ( 198 | x0 + self.length1 + self.length2, 199 | y0 - self.wgt_slot.clad_width - self.wgt_slot.wg_width / 2.0, 200 | ), 201 | (x0, y0 - self.wgt_strip.clad_width - self.wgt_strip.wg_width / 2.0), 202 | ] 203 | clad = gdspy.Polygon( 204 | pts, layer=self.wgt_strip.clad_layer, datatype=self.wgt_strip.clad_datatype 205 | ) 206 | 207 | self.add(strip1) 208 | self.add(thin_strip) 209 | self.add(rail1) 210 | self.add(rail2) 211 | self.add(clad) 212 | 213 | def __build_ports(self): 214 | # Portlist format: 215 | # example: example: {'port':(x_position, y_position), 'direction': 'NORTH'} 216 | self.portlist["input"] = {"port": (0, 0), "direction": "WEST"} 217 | self.portlist["output"] = { 218 | "port": (self.length1 + self.length2, 0), 219 | "direction": "EAST", 220 | } 221 | 222 | 223 | if __name__ == "__main__": 224 | from . import * 225 | 226 | top = gdspy.Cell("top") 227 | wgt_strip = WaveguideTemplate(bend_radius=50, wg_type="strip", wg_width=0.7) 228 | wgt_slot = WaveguideTemplate(bend_radius=50, wg_type="slot", wg_width=0.7, slot=0.2) 229 | 230 | wg1 = Waveguide([(0, 0), (100, 0)], wgt_strip) 231 | tk.add(top, wg1) 232 | 233 | ssc = StripSlotConverter( 234 | wgt_strip, 235 | wgt_slot, 236 | length1=15.0, 237 | length2=15.0, 238 | start_rail_width=0.1, 239 | end_strip_width=0.4, 240 | d=1.0, 241 | **wg1.portlist["output"] 242 | ) 243 | tk.add(top, ssc) 244 | 245 | (x1, y1) = ssc.portlist["output"]["port"] 246 | wg2 = Waveguide([(x1, y1), (x1 + 100, y1)], wgt_slot) 247 | tk.add(top, wg2) 248 | 249 | gdspy.LayoutViewer(cells=top) 250 | # gdspy.write_gds('StripSlotConverter.gds', unit=1.0e-6, precision=1.0e-9) 251 | -------------------------------------------------------------------------------- /picwriter/components/stripslotmmiconverter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | 8 | 9 | class StripSlotMMIConverter(tk.Component): 10 | """Strip-to-Slot MMI Converter Cell class. For more information on this specific type of strip to slot mode converter, please see the original papers at https://doi.org/10.1364/OL.39.005665 and https://doi.org/10.1364/OE.24.007347. 11 | 12 | Args: 13 | * **wgt_input** (WaveguideTemplate): WaveguideTemplate object for the input waveguide (should be either of type `strip` or `slot`). 14 | * **wgt_output** (WaveguideTemplate): WaveguideTemplate object for the output waveguide (should be either of type `strip` or `slot`, opposite of the input type). 15 | * **w_mmi** (float): Width of the MMI region. 16 | * **l_mmi** (float): Length of the MMI region. 17 | * **length** (float): Length of the entire mode converter (MMI region + tapered region on slot waveguide side). 18 | 19 | Keyword Args: 20 | * **input_strip** (Boolean): If `True`, sets the input port to be the strip waveguide side. If `False`, slot waveguide is on the input. Defaults to `None`, in which case the input port waveguide template is used to choose. 21 | * **port** (tuple): Cartesian coordinate of the input port. Defaults to (0,0). 22 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) 23 | 24 | Members: 25 | * **portlist** (dict): Dictionary with the relevant port information 26 | 27 | Portlist format: 28 | * portlist['input'] = {'port': (x1,y1), 'direction': 'dir1'} 29 | * portlist['output'] = {'port': (x2, y2), 'direction': 'dir2'} 30 | 31 | Where in the above (x1,y1) is the same as the 'port' input, (x2, y2) is the end of the taper, and 'dir1', 'dir2' are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 32 | 'Direction' points *towards* the waveguide that will connect to it. 33 | 34 | Note: The waveguide and cladding layer/datatype are taken from the `wgt_slot` by default. 35 | 36 | """ 37 | 38 | def __init__( 39 | self, 40 | wgt_input, 41 | wgt_output, 42 | w_mmi, 43 | l_mmi, 44 | length, 45 | input_strip=None, 46 | port=(0, 0), 47 | direction="EAST", 48 | ): 49 | tk.Component.__init__(self, "StripSlotMMIConverter", locals()) 50 | 51 | self.portlist = {} 52 | 53 | if (not isinstance(input_strip, bool)) and (input_strip != None): 54 | raise ValueError( 55 | "Invalid input provided for `input_strip`. Please specify a boolean." 56 | ) 57 | if input_strip == None: 58 | # Auto-detect based on wgt_input 59 | self.input_strip = ( 60 | wgt_input.wg_type == "strip" or wgt_input.wg_type == "swg" 61 | ) 62 | else: 63 | # User-override 64 | self.input_strip = input_strip 65 | 66 | if self.input_strip: 67 | self.wgt_strip = wgt_input 68 | self.wgt_slot = wgt_output 69 | else: 70 | self.wgt_strip = wgt_output 71 | self.wgt_slot = wgt_input 72 | self.wg_spec = { 73 | "layer": wgt_output.wg_layer, 74 | "datatype": wgt_output.wg_datatype, 75 | } 76 | self.clad_spec = { 77 | "layer": wgt_output.clad_layer, 78 | "datatype": wgt_output.clad_datatype, 79 | } 80 | 81 | self.length = length 82 | self.w_mmi = w_mmi 83 | self.l_mmi = l_mmi 84 | 85 | self.port = port 86 | self.direction = direction 87 | 88 | self.__build_cell() 89 | self.__build_ports() 90 | 91 | """ Translate & rotate the ports corresponding to this specific component object 92 | """ 93 | self._auto_transform_() 94 | 95 | def __build_cell(self): 96 | # Sequentially build all the geometric shapes using gdspy path functions 97 | # for waveguide, then add it to the Cell 98 | 99 | # Add MMI region 100 | path_mmi = gdspy.Path(self.w_mmi, (0, 0)) 101 | path_mmi.segment(self.l_mmi, direction=0, **self.wg_spec) 102 | 103 | print("path_mmi_coords = " + str((path_mmi.x, path_mmi.y))) 104 | 105 | # Add slot tapered region 106 | path_taper = gdspy.Path( 107 | (self.w_mmi - self.wgt_slot.slot) / 2.0, 108 | initial_point=(path_mmi.x, path_mmi.y), 109 | number_of_paths=2, 110 | distance=(self.w_mmi + self.wgt_slot.slot) / 2.0, 111 | ) 112 | path_taper.segment( 113 | self.length - self.l_mmi, 114 | final_width=self.wgt_slot.rail, 115 | final_distance=self.wgt_slot.rail_dist, 116 | direction=0, 117 | **self.wg_spec 118 | ) 119 | 120 | # Cladding for waveguide taper 121 | path_clad = gdspy.Path( 122 | 2 * self.wgt_strip.clad_width + self.wgt_strip.wg_width, (0, 0) 123 | ) 124 | path_clad.segment( 125 | self.length, 126 | final_width=2 * self.wgt_slot.clad_width + self.wgt_slot.wg_width, 127 | direction=0, 128 | **self.clad_spec 129 | ) 130 | 131 | if not self.input_strip: 132 | center_pt = (self.length / 2.0, 0) 133 | path_mmi.rotate(np.pi, center_pt) 134 | path_taper.rotate(np.pi, center_pt) 135 | path_clad.rotate(np.pi, center_pt) 136 | 137 | self.add(path_mmi) 138 | self.add(path_taper) 139 | self.add(path_clad) 140 | 141 | def __build_ports(self): 142 | # Portlist format: 143 | # example: example: {'port':(x_position, y_position), 'direction': 'NORTH'} 144 | self.portlist["input"] = {"port": (0, 0), "direction": "WEST"} 145 | self.portlist["output"] = {"port": (self.length, 0), "direction": "EAST"} 146 | 147 | 148 | if __name__ == "__main__": 149 | from . import * 150 | 151 | top = gdspy.Cell("top") 152 | wgt_slot = WaveguideTemplate(bend_radius=50, wg_type="strip", wg_width=0.7) 153 | wgt_strip = WaveguideTemplate( 154 | bend_radius=50, wg_type="slot", wg_width=0.7, slot=0.2 155 | ) 156 | 157 | wg1 = Waveguide([(0, 0), (100, 30)], wgt_strip) 158 | tk.add(top, wg1) 159 | 160 | ycoup = StripSlotMMIConverter( 161 | wgt_strip, wgt_slot, 2.5, 6.0, 20.0, **wg1.portlist["output"] 162 | ) 163 | tk.add(top, ycoup) 164 | 165 | (x1, y1) = ycoup.portlist["output"]["port"] 166 | wg2 = Waveguide([(x1, y1), (x1 + 100, y1)], wgt_slot) 167 | tk.add(top, wg2) 168 | 169 | gdspy.LayoutViewer(cells=top) 170 | # gdspy.write_gds('StripSlotMMIConverter.gds', unit=1.0e-6, precision=1.0e-9) 171 | -------------------------------------------------------------------------------- /picwriter/components/stripslotyconverter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | 8 | 9 | class StripSlotYConverter(tk.Component): 10 | """Strip-to-Slot Y Converter Cell class. For more information on this specific type of strip to slot mode converter, please see the original paper at https://doi.org/10.1364/OL.34.001498. 11 | 12 | Args: 13 | * **wgt_input** (WaveguideTemplate): WaveguideTemplate object for the input waveguide (should be either of type `strip` or `slot`). 14 | * **wgt_output** (WaveguideTemplate): WaveguideTemplate object for the output waveguide (should be either of type `strip` or `slot`, opposite of the input type). 15 | * **length** (float): Length of the tapered region. 16 | * **d** (float): Distance between the outer edge of the strip waveguide and the start of the slot waveguide rail. 17 | 18 | Keyword Args: 19 | * **end_strip_width** (float): End width of the strip waveguide (at the narrow tip). Defaults to 0. 20 | * **end_slot_width** (float): End width of the slot waveguide (at the narrow tip). Defaults to 0. 21 | * **input_strip** (Boolean): If `True`, sets the input port to be the strip waveguide side. If `False`, slot waveguide is on the input. Defaults to `None`, in which case the input port waveguide template is used to choose. 22 | * **port** (tuple): Cartesian coordinate of the input port. Defaults to (0,0). 23 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) 24 | 25 | Members: 26 | * **portlist** (dict): Dictionary with the relevant port information 27 | 28 | Portlist format: 29 | * portlist['input'] = {'port': (x1,y1), 'direction': 'dir1'} 30 | * portlist['output'] = {'port': (x2, y2), 'direction': 'dir2'} 31 | 32 | Where in the above (x1,y1) is the same as the 'port' input, (x2, y2) is the end of the taper, and 'dir1', 'dir2' are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 33 | 'Direction' points *towards* the waveguide that will connect to it. 34 | 35 | Note: The waveguide and cladding layer/datatype are taken from the `wgt_slot` by default. 36 | 37 | """ 38 | 39 | def __init__( 40 | self, 41 | wgt_input, 42 | wgt_output, 43 | length, 44 | d, 45 | end_strip_width=0, 46 | end_slot_width=0, 47 | input_strip=None, 48 | port=(0, 0), 49 | direction="EAST", 50 | ): 51 | tk.Component.__init__(self, "StripSlotYConverter", locals()) 52 | 53 | self.portlist = {} 54 | 55 | if (not isinstance(input_strip, bool)) and (input_strip != None): 56 | raise ValueError( 57 | "Invalid input provided for `input_strip`. Please specify a boolean." 58 | ) 59 | if input_strip == None: 60 | # Auto-detect based on wgt_input 61 | self.input_strip = ( 62 | wgt_input.wg_type == "strip" or wgt_input.wg_type == "swg" 63 | ) 64 | else: 65 | # User-override 66 | self.input_strip = input_strip 67 | 68 | if self.input_strip: 69 | self.wgt_strip = wgt_input 70 | self.wgt_slot = wgt_output 71 | else: 72 | self.wgt_strip = wgt_output 73 | self.wgt_slot = wgt_input 74 | self.wg_spec = { 75 | "layer": wgt_output.wg_layer, 76 | "datatype": wgt_output.wg_datatype, 77 | } 78 | self.clad_spec = { 79 | "layer": wgt_output.clad_layer, 80 | "datatype": wgt_output.clad_datatype, 81 | } 82 | 83 | self.length = length 84 | self.d = d 85 | self.end_strip_width = end_strip_width 86 | self.end_slot_width = end_slot_width 87 | 88 | self.port = port 89 | self.direction = direction 90 | 91 | self.__build_cell() 92 | self.__build_ports() 93 | 94 | """ Translate & rotate the ports corresponding to this specific component object 95 | """ 96 | self._auto_transform_() 97 | 98 | def __build_cell(self): 99 | # Sequentially build all the geometric shapes using gdspy path functions 100 | # for waveguide, then add it to the Cell 101 | 102 | # Add strip waveguide taper 103 | path_strip = gdspy.Path(self.wgt_strip.wg_width, (0, 0)) 104 | path_strip.segment( 105 | self.length, final_width=self.end_strip_width, direction=0, **self.wg_spec 106 | ) 107 | 108 | # Add slot waveguide taper 109 | path_slot = gdspy.Path( 110 | self.wgt_slot.rail, 111 | (self.length, 0), 112 | number_of_paths=2, 113 | distance=self.wgt_slot.rail_dist, 114 | ) 115 | path_slot.segment( 116 | self.length, 117 | final_width=self.end_slot_width, 118 | final_distance=(self.wgt_strip.wg_width + 2 * self.d + self.end_slot_width), 119 | direction=np.pi, 120 | **self.wg_spec 121 | ) 122 | 123 | # Cladding for waveguide taper 124 | path_clad = gdspy.Path( 125 | 2 * self.wgt_strip.clad_width + self.wgt_strip.wg_width, (0, 0) 126 | ) 127 | path_clad.segment( 128 | self.length, 129 | final_width=2 * self.wgt_slot.clad_width + self.wgt_slot.wg_width, 130 | direction=0, 131 | **self.clad_spec 132 | ) 133 | 134 | if not self.input_strip: 135 | center_pt = (self.length / 2.0, 0) 136 | path_strip.rotate(np.pi, center_pt) 137 | path_slot.rotate(np.pi, center_pt) 138 | path_clad.rotate(np.pi, center_pt) 139 | 140 | self.add(path_strip) 141 | self.add(path_slot) 142 | self.add(path_clad) 143 | 144 | def __build_ports(self): 145 | # Portlist format: 146 | # example: example: {'port':(x_position, y_position), 'direction': 'NORTH'} 147 | self.portlist["input"] = {"port": (0, 0), "direction": "WEST"} 148 | self.portlist["output"] = {"port": (self.length, 0), "direction": "EAST"} 149 | 150 | 151 | if __name__ == "__main__": 152 | from . import * 153 | 154 | top = gdspy.Cell("top") 155 | wgt_strip = WaveguideTemplate(bend_radius=50, wg_type="strip", wg_width=0.7) 156 | wgt_slot = WaveguideTemplate(bend_radius=50, wg_type="slot", wg_width=0.7, slot=0.2) 157 | 158 | wg1 = Waveguide([(0, 0), (100, 100)], wgt_strip) 159 | tk.add(top, wg1) 160 | 161 | ycoup = StripSlotYConverter( 162 | wgt_strip, wgt_slot, 10.0, 0.2, end_slot_width=0.1, **wg1.portlist["output"] 163 | ) 164 | tk.add(top, ycoup) 165 | 166 | (x1, y1) = ycoup.portlist["output"]["port"] 167 | wg2 = Waveguide([(x1, y1), (x1 + 100, y1 + 100)], wgt_slot) 168 | tk.add(top, wg2) 169 | 170 | gdspy.LayoutViewer(cells=top) 171 | # gdspy.write_gds('stripslotyconverter.gds', unit=1.0e-6, precision=1.0e-9) 172 | -------------------------------------------------------------------------------- /picwriter/components/taper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | 8 | 9 | class Taper(tk.Component): 10 | """Taper Cell class. 11 | 12 | Args: 13 | * **wgt** (WaveguideTemplate): WaveguideTemplate object 14 | * **length** (float): Length of the taper 15 | * **end_width** (float): Final width of the taper (initial width received from WaveguieTemplate) 16 | 17 | Keyword Args: 18 | * **start_width** (float): Beginning width of the taper. Defaults to the waveguide width provided by the WaveguideTemplate object. 19 | * **end_clad_width** (float): Clad width at the end of the taper. Defaults to the regular clad width. 20 | * **extra_clad_length** (float): Extra cladding beyond the end of the taper. Defaults to 0. 21 | * **port** (tuple): Cartesian coordinate of the input port. Defaults to (0,0). 22 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians). 23 | 24 | Members: 25 | * **portlist** (dict): Dictionary with the relevant port information 26 | 27 | Portlist format: 28 | * portlist['input'] = {'port': (x1,y1), 'direction': 'dir1'} 29 | * portlist['output'] = {'port': (x2, y2), 'direction': 'dir2'} 30 | 31 | Where in the above (x1,y1) is the same as the 'port' input, (x2, y2) is the end of the taper, and 'dir1', 'dir2' are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 32 | 'Direction' points *towards* the waveguide that will connect to it. 33 | 34 | """ 35 | 36 | def __init__( 37 | self, 38 | wgt, 39 | length, 40 | end_width, 41 | start_width=None, 42 | end_clad_width=None, 43 | extra_clad_length=0, 44 | port=(0, 0), 45 | direction="EAST", 46 | ): 47 | tk.Component.__init__(self, "Taper", locals()) 48 | 49 | self.portlist = {} 50 | 51 | self.port = port 52 | self.direction = direction 53 | 54 | self.length = length 55 | self.end_width = end_width 56 | self.start_width = wgt.wg_width if start_width == None else start_width 57 | self.end_clad_width = ( 58 | wgt.clad_width if end_clad_width == None else end_clad_width 59 | ) 60 | self.extra_clad_length = extra_clad_length 61 | self.wgt = wgt 62 | self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 63 | self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype} 64 | 65 | self.__build_cell() 66 | self.__build_ports() 67 | 68 | """ Translate & rotate the ports corresponding to this specific component object 69 | """ 70 | self._auto_transform_() 71 | 72 | def __build_cell(self): 73 | # Sequentially build all the geometric shapes using gdspy path functions 74 | # for waveguide, then add it to the Cell 75 | 76 | # Add waveguide taper 77 | path = gdspy.Path(self.start_width, (0, 0)) 78 | path.segment( 79 | self.length, direction=0.0, final_width=self.end_width, **self.wg_spec 80 | ) 81 | # Cladding for waveguide taper 82 | path2 = gdspy.Path(2 * self.wgt.clad_width + self.wgt.wg_width, (0, 0)) 83 | path2.segment( 84 | self.length, 85 | direction=0.0, 86 | final_width=2 * self.end_clad_width + self.end_width, 87 | **self.clad_spec 88 | ) 89 | path2.segment(self.extra_clad_length, **self.clad_spec) 90 | 91 | self.add(path) 92 | self.add(path2) 93 | 94 | def __build_ports(self): 95 | # Portlist format: 96 | # example: example: {'port':(x_position, y_position), 'direction': 'NORTH'} 97 | self.portlist["input"] = {"port": (0, 0), "direction": "WEST"} 98 | self.portlist["output"] = {"port": (self.length, 0), "direction": "EAST"} 99 | 100 | 101 | if __name__ == "__main__": 102 | from . import * 103 | 104 | top = gdspy.Cell("top") 105 | wgt = WaveguideTemplate(bend_radius=50, resist="+") 106 | 107 | wg1 = Waveguide([(0, 0), (100, 40)], wgt) 108 | tk.add(top, wg1) 109 | 110 | tp1 = Taper(wgt, 100.0, 0.3, end_clad_width=50, **wg1.portlist["input"]) 111 | tp2 = Taper(wgt, 100.0, 0.5, end_clad_width=15, **wg1.portlist["output"]) 112 | tk.add(top, tp1) 113 | tk.add(top, tp2) 114 | 115 | gdspy.LayoutViewer() 116 | # gdspy.write_gds('taper.gds', unit=1.0e-6, precision=1.0e-9) 117 | -------------------------------------------------------------------------------- /picwriter/components/via.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | 8 | class Via(tk.Component): 9 | """ Inter-metal Via Cell class. Creates a square via with top and bottom contacts. 10 | 11 | Args: 12 | * **mt_bot** (MetalTemplate): The metal template of the lower metal layer. Overrides bot_layer. Defaults to None. Does not create cladding geometry. 13 | * **mt_top** (MetalTemplate): The metal template of the upper metal layer. Overrides top_layer. Defaults to None. Does not create cladding geometry. 14 | * **bot_layer** (int): (if mt_bot is not used) Layer used for the bottom metal. No clad is included and the datatype defaults to 0. 15 | * **top_layer** (int): (if mt_top is not used) Layer used for the top metal. No clad is included and the datatype defaults to 0. 16 | * **via_layer** (int): Layer used to define the via etch. 17 | * **size** (float): Size of the via (edge length). 18 | 19 | Keyword Args: 20 | * **top_bias** (float): Amount to bias the top contact pad, around the via dimension. Defaults to 1 micron. If the top metal trace width is wider than size+2*top_bias, top_bias is increased to match the top connection size with the top trace size. 21 | * **bot_bias** (float): Amount to bias the bottom contact pad, around the via dimension. Defaults to 1 micron. If the bottom metal trace width is wider than size+2*bot_bias, bot_bias is increased to match the bottom connection size with the bottom trace size. 22 | * **port**: The center of the via structure. 23 | Members: 24 | * **portlist** (dict): Dictionary with the relevant port information. Due to the 90 degree rotation symmetry, "direction" has no meaning for this class. 25 | 26 | Portlist format: 27 | * portlist['input'] = {'port': (x1,y1), 'direction': 'dir1'} 28 | 29 | 30 | To-do: 31 | * Add options for having grid arrays of vias 32 | """ 33 | def __init__(self, 34 | mt_bot=None, 35 | mt_top=None, 36 | via_layer=12, 37 | bot_layer=11, 38 | top_layer=13, 39 | size=5, 40 | top_bias=1, 41 | bot_bias=1, 42 | port=(0,0), 43 | direction='EAST'): 44 | tk.Component.__init__(self, "Via", locals()) 45 | 46 | self.portlist = {} 47 | 48 | self.port = port 49 | self.direction = direction 50 | 51 | self.mt_bot=mt_bot 52 | self.mt_top=mt_top 53 | self.via_layer=via_layer 54 | self.bot_layer=bot_layer 55 | self.top_layer=top_layer 56 | self.size=size 57 | self.top_bias=top_bias 58 | self.bot_bias=bot_bias 59 | 60 | self.__build_cell() 61 | self.__build_ports() 62 | self._auto_transform_() 63 | 64 | 65 | def __build_cell(self): 66 | 67 | # Build simple shapes using specified parameters or the metal template settings. 68 | if self.mt_bot is None: 69 | bot_layer = self.bot_layer 70 | bot_dtype = 0 71 | bot_bias = self.bot_bias 72 | else: 73 | bot_layer = self.mt_bot.metal_layer 74 | bot_dtype = self.mt_bot.metal_datatype 75 | bot_bias = np.max([self.bot_bias, (self.mt_bot.width - self.size)/2.0]) 76 | 77 | if self.mt_top is None: 78 | top_layer = self.top_layer 79 | top_dtype = 0 80 | top_bias = self.top_bias 81 | else: 82 | top_layer = self.mt_top.metal_layer 83 | top_dtype = self.mt_top.metal_datatype 84 | top_bias = np.max([self.top_bias, (self.mt_top.width - self.size)/2.0]) 85 | 86 | top_edge = self.size + 2*top_bias 87 | bot_edge = self.size + 2*bot_bias 88 | 89 | bot_pad = gdspy.Rectangle( (-bot_edge/2,-bot_edge/2), (bot_edge/2,bot_edge/2), 90 | layer=bot_layer, datatype=bot_dtype ) 91 | top_pad = gdspy.Rectangle( (-top_edge/2,-top_edge/2), (top_edge/2,top_edge/2), 92 | layer=top_layer, datatype=top_dtype) 93 | via = gdspy.Rectangle( (-self.size/2,-self.size/2), (self.size/2,self.size/2), 94 | layer=self.via_layer, datatype=0) 95 | 96 | 97 | components =[bot_pad, top_pad, via] 98 | """ Add all the components """ 99 | for c in components: 100 | self.add( c ) 101 | 102 | 103 | def __build_ports(self): 104 | self.portlist["center"] = {'port':(0,0), 105 | 'direction':self.direction} 106 | -------------------------------------------------------------------------------- /picwriter/components/ysplitter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | import numpy as np 4 | import scipy.interpolate 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | from picwriter.components.ebend import EulerSBend 8 | from picwriter.components.taper import Taper 9 | 10 | 11 | class SplineYSplitter(tk.Component): 12 | """1x2 Spline based Y Splitter Cell class. 13 | Based on Zhang et al. (2013) A compact and low loss Y-junction for submicron silicon waveguide https://doi.org/10.1364/OE.21.001310 14 | 15 | Args: 16 | * **wgt** (WaveguideTemplate): WaveguideTemplate object 17 | * **length** (float): Length of the splitter region (along direction of propagation) 18 | * **widths** (array of floats): Widths of the Spline Curve Splitter region (perpendicular to direction of propagation). Width values are evenly spaced along the length of the splitter. 19 | 20 | Keyword Args: 21 | * **wg_sep** (float): Separation between waveguides on the 2-port side (defaults to be flush with the last width in the splitter region). Defaults to None. 22 | * **taper_width** (float): Ending width of the taper region (default = wg_width from wg_template). Defaults to None (waveguide width). 23 | * **taper_length** (float): Length of the input taper leading up to the Y-splitter (single-port side). Defaults to None (no input taper, port right against the splitter region). 24 | * **output_length** (float): Length (along x-direction) of the output bends, made with Euler S-Bends. Defaults to None (no output bend, ports right up againt the splitter region). 25 | * **output_wg_sep** (float): Distance (along y-direction) between the two output bends, made with Euler S-Bends. Defaults to None (no output bend, ports right up againt the splitter region). 26 | * **output_width** (float): Starting width of the output waveguide. Defaults to None (no change from regular wg_width). 27 | * **port** (tuple): Cartesian coordinate of the input port. Defaults to (0,0). 28 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) 29 | 30 | Members: 31 | * **portlist** (dict): Dictionary with the relevant port information 32 | 33 | Portlist format: 34 | * portlist['input'] = {'port': (x1,y1), 'direction': 'dir1'} 35 | * portlist['output_top'] = {'port': (x2, y2), 'direction': 'dir2'} 36 | * portlist['output_bot'] = {'port': (x3, y3), 'direction': 'dir3'} 37 | 38 | Where in the above (x1,y1) is the input port, (x2, y2) is the top output port, (x3, y3) is the bottom output port, and 'dir1', 'dir2', 'dir3' are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 39 | 'Direction' points *towards* the waveguide that will connect to it. 40 | 41 | """ 42 | 43 | def __init__( 44 | self, 45 | wgt, 46 | length, 47 | widths, 48 | wg_sep=None, 49 | taper_width=None, 50 | taper_length=None, 51 | output_length=None, 52 | output_wg_sep=None, 53 | output_width=None, 54 | port=(0, 0), 55 | direction="EAST", 56 | ): 57 | tk.Component.__init__(self, "YSpline1x2", locals()) 58 | 59 | self.port = port 60 | self.direction = direction 61 | self.portlist = {} 62 | 63 | self.wgt = wgt 64 | self.length = length 65 | self.widths = widths 66 | 67 | self.totlength = length 68 | 69 | if (output_length != None) and (output_wg_sep != None): 70 | self.output_length = output_length 71 | self.output_wg_sep = output_wg_sep 72 | self.output_width = wgt.wg_width if output_width == None else output_width 73 | self.draw_outputs = True 74 | self.totlength += self.output_length 75 | elif (output_length == None) and (output_wg_sep == None): 76 | self.draw_outputs = False 77 | self.output_wg_sep = wg_sep 78 | else: 79 | raise ValueError( 80 | "Warning! One of the two output values was None, and the other was provided. Both must be provided *OR* omitted." 81 | ) 82 | 83 | if (taper_width != None) and (taper_length != None): 84 | self.taper_width = taper_width 85 | self.taper_length = taper_length 86 | self.draw_input = True 87 | self.totlength += taper_length 88 | elif (taper_width == None) and (taper_length == None): 89 | self.draw_input = False 90 | else: 91 | raise ValueError( 92 | "Warning! One of the two input values was None, and the other was provided. Both must be provided *OR* omitted." 93 | ) 94 | 95 | self.wg_sep = widths[-1] - self.output_width if wg_sep == None else wg_sep 96 | 97 | self.resist = wgt.resist 98 | self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 99 | self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype} 100 | 101 | self.input_port = (0, 0) 102 | self.output_port_top = (self.totlength, self.output_wg_sep / 2.0) 103 | self.output_port_bot = (self.totlength, -self.output_wg_sep / 2.0) 104 | 105 | self.__type_check_values() 106 | self.__build_cell() 107 | self.__build_ports() 108 | 109 | """ Translate & rotate the ports corresponding to this specific component object 110 | """ 111 | self._auto_transform_() 112 | 113 | def __type_check_values(self): 114 | # Check that the values for the Y Splitter 1x2 are all valid 115 | 116 | if self.wg_sep > (self.widths[-1] - self.output_width): 117 | raise ValueError( 118 | "Warning! Waveguide separation is larger than the " 119 | "max value (width - taper_width)" 120 | ) 121 | if self.draw_input and self.wg_sep < self.taper_width: 122 | raise ValueError( 123 | "Warning! Waveguide separation is smaller than the " 124 | "minimum value (taper_width)" 125 | ) 126 | if self.draw_outputs: 127 | if self.output_length < (self.output_wg_sep - self.wg_sep) / 2.0: 128 | raise ValueError( 129 | "Warning! The output length must be greater than half the output wg separation" 130 | ) 131 | 132 | def __build_cell(self): 133 | # Sequentially build all the geometric shapes using gdspy path functions 134 | # then add it to the Cell 135 | 136 | x, y = (0, 0) 137 | 138 | """ Add the input taper """ 139 | if self.draw_input: 140 | tp = Taper( 141 | self.wgt, 142 | self.taper_length, 143 | self.taper_width, 144 | port=(x, y), 145 | direction="EAST", 146 | ) 147 | tp.addto(self) 148 | x, y = tp.portlist["output"]["port"] 149 | 150 | """ Add the Coupler region """ 151 | x_widths = np.linspace(0, self.length, len(self.widths)) 152 | x_positions = np.linspace(0, self.length, int(self.length // 0.0025)) 153 | spl = scipy.interpolate.CubicSpline( 154 | x_widths, self.widths, bc_type="clamped" 155 | ) # Spline mode still unclear. 156 | y_positions = spl(x_positions) 157 | 158 | coupler_pts = np.concatenate( 159 | ( 160 | [x_positions, y_positions / 2], 161 | [x_positions[::-1], -y_positions[::-1] / 2], 162 | ), 163 | axis=1, 164 | ).T 165 | coupler_region = gdspy.Polygon(coupler_pts, **self.wg_spec) 166 | self.add(coupler_region) 167 | 168 | (x, y) = (x + self.length, y) 169 | 170 | clad_region = gdspy.Polygon( 171 | [ 172 | (x_positions[0], y_positions[0] / 2.0 + self.wgt.clad_width), 173 | (x_positions[-1], y_positions[-1] / 2.0 + self.wgt.clad_width), 174 | (x_positions[-1], -y_positions[-1] / 2.0 - self.wgt.clad_width), 175 | (x_positions[0], -y_positions[0] / 2.0 - self.wgt.clad_width), 176 | ], 177 | **self.clad_spec 178 | ) 179 | self.add(clad_region) 180 | 181 | """ Add the output tapers """ 182 | if self.draw_outputs: 183 | dy = (self.output_wg_sep - self.wg_sep) / 2.0 184 | esb_top = EulerSBend( 185 | self.wgt, 186 | self.output_length, 187 | dy, 188 | self.output_width, 189 | end_width=self.wgt.wg_width, 190 | port=(x, y + self.wg_sep / 2.0), 191 | ) 192 | esb_top.addto(self) 193 | 194 | esb_bot = EulerSBend( 195 | self.wgt, 196 | self.output_length, 197 | -dy, 198 | self.output_width, 199 | end_width=self.wgt.wg_width, 200 | port=(x, y - self.wg_sep / 2.0), 201 | ) 202 | esb_bot.addto(self) 203 | 204 | def __build_ports(self): 205 | # Portlist format: 206 | # example: {'port':(x_position, y_position), 'direction': 'NORTH'} 207 | 208 | self.portlist["input"] = {"port": self.input_port, "direction": "WEST"} 209 | self.portlist["output_top"] = { 210 | "port": self.output_port_top, 211 | "direction": "EAST", 212 | } 213 | self.portlist["output_bot"] = { 214 | "port": self.output_port_bot, 215 | "direction": "EAST", 216 | } 217 | 218 | 219 | if __name__ == "__main__": 220 | # from . import * 221 | import picwriter.components as pc 222 | 223 | top = gdspy.Cell("top") 224 | wgt = pc.WaveguideTemplate(bend_radius=50, wg_width=0.5, resist="+") 225 | 226 | # Values from Publication 227 | spline_widths = [0.5, 0.5, 0.6, 0.7, 0.9, 1.26, 1.4, 1.4, 1.4, 1.4, 1.31, 1.2, 1.2] 228 | ysplitter = SplineYSplitter( 229 | wgt, 230 | length=2, 231 | widths=spline_widths, 232 | taper_width=None, 233 | taper_length=None, 234 | output_length=10, 235 | output_wg_sep=5, 236 | output_width=0.5, 237 | port=(0, 0), 238 | direction="EAST", 239 | ) 240 | wg1 = pc.Waveguide([(-10, 0), ysplitter.portlist["input"]["port"]], wgt) 241 | ysplitter.addto(top) 242 | wg1.addto(top) 243 | gdspy.LayoutViewer() 244 | -------------------------------------------------------------------------------- /picwriter/components/zerolengthcavity.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | import numpy as np 5 | import gdspy 6 | import picwriter.toolkit as tk 7 | from picwriter.components.waveguide import Waveguide 8 | 9 | 10 | class ZeroLengthCavity(tk.Component): 11 | """Zero-Length Cavity Cell class. 12 | 13 | Args: 14 | * **wgt** (WaveguideTemplate): WaveguideTemplate object 15 | * **num_holes** (float): Number of holes in the mirror. 16 | * **period** (float): Period of the repeated unit. 17 | * **radius** (float): Radius of the holes of the mirror. 18 | * **radius_taper** (float): Radius of the smallest hole of the taper. Defaults to radius/2. 19 | * **gap** (float): Gap between the cavity and the waveguide. 20 | * **wgt_beam_length** (float): Extra length of nanobeam that is simple waveguide. 21 | 22 | Keyword Args: 23 | * **num_taper_holes** (float): Number of holes in the taper region between the mirror and the waveguide. Defaults to 4. 24 | * **taper_type** (string): Determines the radius of the taper holes. 'ratio' corresponds to a constant radius/period ratio. 'FF' corresponds to a linearly decreasing fill factor (hole area/unit cell area). Defaults to 'FF'. 25 | * **port** (tuple): Cartesian coordinate of the input port. Defaults to (0,0). 26 | * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) 27 | 28 | Members: 29 | * **portlist** (dict): Dictionary with the relevant port information 30 | 31 | Portlist format: 32 | * portlist['input'] = {'port': (x1,y1), 'direction': 'dir1'} 33 | * portlist['output'] = {'port': (x2, y2), 'direction': 'dir2'} 34 | 35 | Where in the above (x1,y1) is the same as the 'port' input, (x2, y2) is the end of the DBR, and 'dir1', 'dir2' are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*. 36 | 'Direction' points *towards* the waveguide that will connect to it. 37 | 38 | """ 39 | 40 | def __init__( 41 | self, 42 | wgt, 43 | num_holes, 44 | period, 45 | radius, 46 | radius_taper, 47 | gap, 48 | wgt_beam_length, 49 | num_taper_holes=4, 50 | taper_type="FF", 51 | port=(0, 0), 52 | direction="EAST", 53 | ): 54 | tk.Component.__init__(self, "ZeroLengthCavity", locals()) 55 | 56 | self.portlist = {} 57 | 58 | self.port = port 59 | self.direction = direction 60 | 61 | self.num_holes = num_holes 62 | self.num_taper_holes = num_taper_holes 63 | self.radius = radius 64 | self.period = period 65 | self.radius_taper = radius_taper 66 | self.taper_type = taper_type 67 | self.gap = gap 68 | self.wgt_beam_length = wgt_beam_length 69 | 70 | self.wgt = wgt 71 | self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype} 72 | self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype} 73 | 74 | if taper_type == "FF": 75 | self.taper_length = num_taper_holes * period 76 | self.total_length = ( 77 | num_holes * period + 2 * self.taper_length + 2 * self.wgt_beam_length 78 | ) 79 | elif taper_type == "ratio": 80 | self.taper_length = ( 81 | period 82 | / radius 83 | * ( 84 | radius * num_taper_holes 85 | + (num_taper_holes + 1) * (radius_taper - radius) / 2 86 | ) 87 | ) 88 | self.total_length = ( 89 | num_holes * period + 2 * self.taper_length + 2 * self.wgt_beam_length 90 | ) 91 | 92 | self.__build_cell() 93 | self.__build_ports() 94 | """ Translate & rotate the ports corresponding to this specific component object 95 | """ 96 | self._auto_transform_() 97 | 98 | def __build_cell(self): 99 | # Sequentially build all the geometric shapes using gdspy path functions 100 | # for waveguide, then add it to the Cell 101 | 102 | # Add bus waveguide, and cladding 103 | bus = gdspy.Path(self.wgt.wg_width, (0, 0)) 104 | bus.segment(self.total_length, direction=0, **self.wg_spec) 105 | # Cladding for bus waveguide 106 | bus_clad = gdspy.Path(2 * self.wgt.clad_width + self.wgt.wg_width, (0, 0)) 107 | bus_clad.segment(self.total_length, direction=0, **self.clad_spec) 108 | 109 | self.add(bus) 110 | self.add(bus_clad) 111 | 112 | # Add nanobeam waveguide 113 | beam_x = 0 114 | beam_y = self.gap + 2 * self.wgt.wg_width 115 | nanobeam = gdspy.Path(self.wgt.wg_width, (beam_x, beam_y)) 116 | nanobeam.segment(self.total_length, direction=0, **self.wg_spec) 117 | # Cladding for nanobeam 118 | nanobeam_clad = gdspy.Path( 119 | 2 * self.wgt.clad_width + self.wgt.wg_width, (beam_x, beam_y) 120 | ) 121 | nanobeam_clad.segment(self.total_length, direction=0, **self.clad_spec) 122 | 123 | """ Add the mirror holes """ 124 | startx = beam_x + self.wgt_beam_length + self.taper_length + self.period / 2 125 | starty = beam_y 126 | hole_list = [] 127 | for i in range(int(self.num_holes)): 128 | x = startx + i * self.period 129 | y = starty 130 | hole_list.append(gdspy.Round((x, y), self.radius)) 131 | 132 | """ Add the taper holes """ 133 | if self.taper_type == "FF": 134 | startx_in = beam_x + self.wgt_beam_length + self.period / 2 135 | startx_out = ( 136 | beam_x + self.total_length - self.wgt_beam_length - self.period / 2 137 | ) 138 | starty_in = beam_y 139 | starty_out = beam_y 140 | taper_list_in = [] 141 | taper_list_out = [] 142 | for i in range(int(self.num_taper_holes)): 143 | fill_factor = np.pi * np.square( 144 | self.radius_taper / self.period 145 | ) + i * np.pi * ( 146 | np.square(self.radius / self.period) 147 | - np.square(self.radius_taper / self.period) 148 | ) / ( 149 | self.num_taper_holes - 1 150 | ) 151 | taper_radii = np.sqrt(fill_factor / np.pi) * self.period 152 | x_in = startx_in + i * self.period 153 | y_in = starty_in 154 | taper_list_in.append(gdspy.Round((x_in, y_in), taper_radii)) 155 | x_out = startx_out - i * self.period 156 | y_out = starty_out 157 | taper_list_out.append(gdspy.Round((x_out, y_out), taper_radii)) 158 | elif self.taper_type == "ratio": 159 | startx_in = beam_x + self.wgt_beam_length 160 | startx_out = beam_x + (self.total_length - self.wgt_beam_length) 161 | starty_in = beam_y 162 | starty_out = beam_y 163 | taper_list_in = [] 164 | taper_list_out = [] 165 | for i in range(int(self.num_taper_holes)): 166 | ratio = self.period / self.radius 167 | taper_radii = self.radius_taper + i * ( 168 | self.radius - self.radius_taper 169 | ) / (self.num_taper_holes - 1) 170 | taper_period = taper_radii * ratio 171 | x_in = startx_in + i * taper_period 172 | y_in = starty_in 173 | taper_list_in.append(gdspy.Round((x_in, y_in), taper_radii)) 174 | x_out = startx_out - i * taper_period 175 | y_out = starty_out 176 | taper_list_out.append(gdspy.Round((x_out, y_out), taper_radii)) 177 | 178 | for hole in hole_list: 179 | nanobeam = gdspy.fast_boolean(nanobeam, hole, "xor") 180 | for hole_in in taper_list_in: 181 | nanobeam = gdspy.fast_boolean(nanobeam, hole_in, "xor") 182 | for hole_out in taper_list_out: 183 | nanobeam = gdspy.fast_boolean(nanobeam, hole_out, "xor") 184 | 185 | self.add(nanobeam) 186 | self.add(nanobeam_clad) 187 | 188 | def __build_ports(self): 189 | # Portlist format: 190 | # example: example: {'port':(x_position, y_position), 'direction': 'NORTH'} 191 | self.portlist["input"] = {"port": (0, 0), "direction": "WEST"} 192 | self.portlist["output"] = {"port": (self.total_length, 0), "direction": "EAST"} 193 | 194 | 195 | if __name__ == "__main__": 196 | from picwriter.components import * 197 | 198 | top = gdspy.Cell("top") 199 | wgt = WaveguideTemplate(wg_width=1.0, clad_width=10.0, bend_radius=50, resist="+") 200 | 201 | wg1 = Waveguide([(0, 0), (100, 0)], wgt) 202 | tk.add(top, wg1) 203 | 204 | zlc1 = ZeroLengthCavity(wgt, 8, 0.4, 0.1, 0.08, 0.05, 1, **wg1.portlist["output"]) 205 | tk.add(top, zlc1) 206 | 207 | (x1, y1) = zlc1.portlist["output"]["port"] 208 | wg2 = Waveguide([(x1, y1), (x1 + 100, y1), (x1 + 100, y1 + 100)], wgt) 209 | tk.add(top, wg2) 210 | 211 | zlc2 = ZeroLengthCavity( 212 | wgt, 213 | 20, 214 | 0.4, 215 | 0.1, 216 | 0.08, 217 | 0.05, 218 | 1, 219 | 6, 220 | taper_type="ratio", 221 | **wg2.portlist["output"] 222 | ) 223 | tk.add(top, zlc2) 224 | 225 | (x2, y2) = zlc2.portlist["output"]["port"] 226 | wg3 = Waveguide( 227 | [(x2, y2), (x2, y2 + 100.0), (x2 + 100, y2 + 200), (x2 + 100, y2 + 300)], wgt 228 | ) 229 | tk.add(top, wg3) 230 | 231 | gdspy.LayoutViewer() 232 | # gdspy.write_gds('zlc.gds', unit=1.0e-6, precision=1.0e-9) 233 | -------------------------------------------------------------------------------- /picwriter/examples/mask_template.gds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerekK44/PICwriter/f6d9825c9994a6825f8ff1b6257bc8ab60eb9864/picwriter/examples/mask_template.gds -------------------------------------------------------------------------------- /picwriter/examples/mask_template.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @author: DerekK88 4 | """ 5 | import numpy as np 6 | import gdspy 7 | from picwriter import toolkit as tk 8 | from picwriter.components import * 9 | 10 | X_SIZE, Y_SIZE = 15000, 15000 11 | exclusion_region = 2000.0 # region where no devices are to be fabricated 12 | x0, y0 = X_SIZE / 2.0, Y_SIZE / 2.0 # define origin of the die 13 | step = 100.0 # standard spacing between components 14 | 15 | """ Top level Cell that contains everything """ 16 | top = gdspy.Cell("top") 17 | 18 | wgt = WaveguideTemplate( 19 | wg_width=0.45, 20 | clad_width=10.0, 21 | bend_radius=100, 22 | resist="+", 23 | fab="ETCH", 24 | wg_layer=1, 25 | wg_datatype=0, 26 | clad_layer=2, 27 | clad_datatype=0, 28 | ) 29 | 30 | """ Add a die outline, with exclusion, from gdspy geometries found at 31 | http://gdspy.readthedocs.io/en/latest/""" 32 | top.add(gdspy.Rectangle((0, 0), (X_SIZE, Y_SIZE), layer=6, datatype=0)) 33 | top.add( 34 | gdspy.Rectangle( 35 | (0, Y_SIZE - exclusion_region), (X_SIZE, Y_SIZE), layer=7, datatype=0 36 | ) 37 | ) 38 | top.add(gdspy.Rectangle((0, 0), (X_SIZE, exclusion_region), layer=7, datatype=0)) 39 | top.add(gdspy.Rectangle((0, 0), (exclusion_region, Y_SIZE), layer=7, datatype=0)) 40 | top.add( 41 | gdspy.Rectangle( 42 | (X_SIZE - exclusion_region, 0), (X_SIZE, Y_SIZE), layer=7, datatype=0 43 | ) 44 | ) 45 | 46 | """ Add some components from the PICwriter library """ 47 | spiral_unit = gdspy.Cell("spiral_unit") 48 | sp1 = Spiral( 49 | wgt, 1000.0, 1000.0, 10000, parity=1, port=(500.0 + exclusion_region + 4 * step, y0) 50 | ) 51 | tk.add(spiral_unit, sp1) 52 | 53 | wg1 = Waveguide( 54 | [sp1.portlist["input"]["port"], (sp1.portlist["input"]["port"][0], 4000.0)], wgt 55 | ) 56 | wg2 = Waveguide( 57 | [ 58 | sp1.portlist["output"]["port"], 59 | (sp1.portlist["output"]["port"][0], Y_SIZE - 4000.0), 60 | ], 61 | wgt, 62 | ) 63 | tk.add(spiral_unit, wg1) 64 | tk.add(spiral_unit, wg2) 65 | 66 | tp_bot = Taper(wgt, length=100.0, end_width=0.1, **wg1.portlist["output"]) 67 | tk.add(spiral_unit, tp_bot) 68 | 69 | gc_top = GratingCouplerFocusing( 70 | wgt, 71 | focus_distance=20, 72 | width=20, 73 | length=40, 74 | period=0.7, 75 | dutycycle=0.4, 76 | wavelength=1.55, 77 | sin_theta=np.sin(np.pi * 8 / 180), 78 | **wg2.portlist["output"] 79 | ) 80 | tk.add(spiral_unit, gc_top) 81 | 82 | for i in range(9): 83 | top.add(gdspy.CellReference(spiral_unit, (i * 1100.0, 0))) 84 | 85 | tk.build_mask(top, wgt, final_layer=3, final_datatype=0) 86 | 87 | gdspy.LayoutViewer() 88 | gdspy.write_gds("mask_template.gds", unit=1.0e-6, precision=1.0e-9) 89 | -------------------------------------------------------------------------------- /picwriter/examples/tutorial1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @author: DerekK88 4 | """ 5 | import gdspy 6 | from picwriter import toolkit as tk 7 | from picwriter.components import * 8 | 9 | top = gdspy.Cell("top") 10 | wgt = WaveguideTemplate( 11 | wg_width=0.45, 12 | clad_width=10.0, 13 | bend_radius=100, 14 | resist="+", 15 | fab="ETCH", 16 | wg_layer=1, 17 | wg_datatype=0, 18 | clad_layer=2, 19 | clad_datatype=0, 20 | ) 21 | 22 | top.add(gdspy.Rectangle((0, 0), (1000, 1000), layer=100, datatype=0)) 23 | wg = Waveguide([(25, 25), (975, 25), (975, 500), (25, 500), (25, 975), (975, 975)], wgt) 24 | tk.add(top, wg) 25 | 26 | tk.build_mask(top, wgt, final_layer=3, final_datatype=0) 27 | 28 | gdspy.LayoutViewer() 29 | gdspy.write_gds("tutorial1.gds", unit=1.0e-6, precision=1.0e-9) 30 | -------------------------------------------------------------------------------- /picwriter/examples/tutorial2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @author: DerekK88 4 | """ 5 | import gdspy 6 | from picwriter import toolkit as tk 7 | from picwriter.components import * 8 | 9 | top = gdspy.Cell("top") 10 | wgt = WaveguideTemplate( 11 | wg_width=0.45, 12 | clad_width=10.0, 13 | bend_radius=100, 14 | resist="+", 15 | fab="ETCH", 16 | wg_layer=1, 17 | wg_datatype=0, 18 | clad_layer=2, 19 | clad_datatype=0, 20 | ) 21 | 22 | gc1 = GratingCouplerFocusing( 23 | wgt, 24 | focus_distance=20.0, 25 | width=20, 26 | length=40, 27 | period=1.0, 28 | dutycycle=0.7, 29 | port=(100, 0), 30 | direction="WEST", 31 | ) 32 | tk.add(top, gc1) 33 | 34 | wg1 = Waveguide([gc1.portlist["output"]["port"], (200, 0)], wgt) 35 | tk.add(top, wg1) 36 | 37 | mmi1 = MMI1x2( 38 | wgt, length=50, width=10, taper_width=2.0, wg_sep=3, **wg1.portlist["output"] 39 | ) 40 | tk.add(top, mmi1) 41 | 42 | mmi2 = MMI1x2( 43 | wgt, 44 | length=50, 45 | width=10, 46 | taper_width=2.0, 47 | wg_sep=3, 48 | port=(1750, 0), 49 | direction="WEST", 50 | ) 51 | tk.add(top, mmi2) 52 | 53 | (xtop, ytop) = mmi1.portlist["output_top"]["port"] 54 | wg2 = Waveguide( 55 | [ 56 | (xtop, ytop), 57 | (xtop + 100, ytop), 58 | (xtop + 100, ytop + 200), 59 | (xtop + 200, ytop + 200), 60 | ], 61 | wgt, 62 | ) 63 | tk.add(top, wg2) 64 | 65 | sp = Spiral(wgt, 600.0, 1000.0, 8000.0, parity=-1, **wg2.portlist["output"]) 66 | tk.add(top, sp) 67 | 68 | (xtop_out, ytop_out) = sp.portlist["output"]["port"] 69 | (xmmi_top, ymmi_top) = mmi2.portlist["output_bot"]["port"] 70 | wg_spiral_out = Waveguide( 71 | [ 72 | (xtop_out, ytop_out), 73 | (xmmi_top - 100, ytop_out), 74 | (xmmi_top - 100, ytop_out - 200), 75 | (xmmi_top, ytop_out - 200), 76 | ], 77 | wgt, 78 | ) 79 | tk.add(top, wg_spiral_out) 80 | 81 | (xbot, ybot) = mmi1.portlist["output_bot"]["port"] 82 | wg3 = Waveguide( 83 | [ 84 | (xbot, ybot), 85 | (xbot + 100, ybot), 86 | (xbot + 100, ybot - 200), 87 | (xmmi_top - 100, ybot - 200), 88 | (xmmi_top - 100, ybot), 89 | (xmmi_top, ybot), 90 | ], 91 | wgt, 92 | ) 93 | tk.add(top, wg3) 94 | 95 | gc2 = GratingCouplerFocusing( 96 | wgt, 97 | focus_distance=20.0, 98 | width=20, 99 | length=40, 100 | period=1.0, 101 | dutycycle=0.7, 102 | port=(mmi2.portlist["input"]["port"][0] + 100, mmi2.portlist["input"]["port"][1]), 103 | direction="EAST", 104 | ) 105 | tk.add(top, gc2) 106 | 107 | wg_gc2 = Waveguide( 108 | [mmi2.portlist["input"]["port"], gc2.portlist["output"]["port"]], wgt 109 | ) 110 | tk.add(top, wg_gc2) 111 | 112 | tk.build_mask(top, wgt, final_layer=3, final_datatype=0) 113 | 114 | gdspy.LayoutViewer() 115 | gdspy.write_gds("tutorial2.gds", unit=1.0e-6, precision=1.0e-9) 116 | -------------------------------------------------------------------------------- /picwriter/tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @author: DerekK88 5 | """ 6 | from __future__ import absolute_import, division, print_function, unicode_literals 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | setuptools 2 | numpy 3 | scipy 4 | future; python_version < "3" 5 | gdspy 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @author: DerekK88 5 | """ 6 | 7 | from setuptools import setup 8 | 9 | 10 | def readme(): 11 | with open("README.md") as f: 12 | return f.read() 13 | 14 | 15 | setup( 16 | name="picwriter", 17 | version="0.5", 18 | description="Mask generation tool", 19 | long_description=readme(), 20 | url="http://github.com/DerekK88/picwriter", 21 | author="Derek Kita", 22 | license="MIT", 23 | classifiers=[ 24 | # How mature is this project? Common values are 25 | "Development Status :: 3 - Alpha", 26 | # Indicate who your project is intended for 27 | "Intended Audience :: Science/Research", 28 | "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", 29 | # Pick your license as you wish (should match "license" above) 30 | "License :: OSI Approved :: MIT License", 31 | # Specify the Python versions you support here. In particular, ensure 32 | # that you indicate whether you support Python 2, Python 3 or both. 33 | "Programming Language :: Python :: 2", 34 | "Programming Language :: Python :: 2.7", 35 | "Programming Language :: Python :: 3", 36 | "Programming Language :: Python :: 3.4", 37 | "Programming Language :: Python :: 3.5", 38 | "Programming Language :: Python :: 3.6", 39 | ], 40 | keywords="mask writing library", 41 | packages=["picwriter", "picwriter.components"], 42 | install_requires=["gdspy", "numpy"], 43 | test_suite="nose.collector", 44 | tests_require=["nose"], 45 | zip_safe=False, 46 | ) 47 | --------------------------------------------------------------------------------