diff --git a/doc/docs/Python_Tutorials/BidirectionalCoupler.md b/doc/docs/Python_Tutorials/BidirectionalCoupler.md new file mode 100644 index 000000000..eeef2a29c --- /dev/null +++ b/doc/docs/Python_Tutorials/BidirectionalCoupler.md @@ -0,0 +1,275 @@ +--- +# Bidirectional coupler +--- + +In this example we study a bidirectional coupler designed to split +light arriving from an incoming waveguide into two beams of +roughly equal flux departing through two outgoing waveguides. + +This example is modeled on the 3 dB coupler example included +in the extremely useful open-source +[SiEPIC](https://github.com/lukasc-ubc/SiEPIC_EBeam_PDK) +PDK and library for silicon photonics. + +--- +## GDSII file +--- + +The geometry we will study is described by the GDSII file +[`coupler.gds`](coupler.gds), which is a modified version of the file +[`3dB_coupler_TM.gds`](https://github.com/lukasc-ubc/SiEPIC_EBeam_PDK/blob/master/Documentation/Splitter_BDC%20(Broadband%20Directional%20Coupler)/TM_3dB_BDC_splitter/GDS/3dB_coupler_TM.gds) +included in the SiEPIC distribution. + +![coupler.png](coupler.png) + +More specifically, we +used the excellent open-source GDSII editing tool [KLayout](https://www.klayout.de/) +to make the following changes: + ++ We translated the geometry so that it is centered at the origin of the XY plane. + ++ We dragged each of the four vertical edges at the extreme left and right of the geometry +outward to define straight waveguide sections for the incoming and outgoing ports. + ++ Whereas the original file defined the entire coupler to exist on a single +GDSII layer (layer 31), here we put the lower branch of the coupler on a +separate layer (layer 32). This makes it easy to vary the separation between +the branches in the python script that drives our calculation (see below). + ++ We defined 4 *port* regions, each defined on a different GDSII layer and consisting +of a single GDSII polygon (actually just a zero-thickness *path*) +spanning the width of a waveguide section. These will be used to specify +[`volume`s](https://meep.readthedocs.io/en/latest/Python_User_Interface/#volume) +for defining +[flux regions](https://meep.readthedocs.io/en/latest/Python_User_Interface/#fluxregion), +over which we will ask meep to measure power flux, +or [mode monitors](https://meep.readthedocs.io/en/latest/Python_Tutorials/Mode_Decomposition) +over which to measure eigenmode-expansion coefficients. +Similarly, on a separate layer we include a region defining an +[eigenmode source](`https://meep.readthedocs.io/en/latest/Python_User_Interface/#eigenmodesource) +describing power entering the coupler at Port 1. + +The last step is not strictly necessary, since we could just define regions +for sources and flux/mode-monitors by hand by explicitly specifying coordinates +in our meep script. But it's convenient to have the specification of these +regions defined in the same GDSII geometry as the actual structure. + +--- +## Python script +--- + +A full python script for studying the performance of the coupler +is [`coupler.py`](coupler.py). Each run of this script computes +the power outflow from each port (assuming unit power input +to port 1), over a range of frequencies, for a single value +of the branch-branch separation. The script may be run by saying e.g. + +```bash + % mpirun -np 12 python coupler.py [--gdsIIFile coupler.gds] [--separation 0.3] [--res 21] [--3D] +``` + +where the options are + ++ `--gdsIIFile` specifies the GDSII file (`coupler.gds` by default) + ++ `--separation` defines the minimum separation distance between the coupler branches; the default is 0.3 &um; + ++ `--res` sets the meep resolution + ++ `--3D` specifies a 3D calculation in which the waveguides are 220-nm-thick + silicon on an oxide layer. If this flag is absent (the default), + the calculation is 2D, describing silicon waveguides of infinite + thickness in the *z* direction. + +Here's a sample run: +```bash + % mpirun -np 12 python coupler.py --separation 0.1 +``` + +This produces the file `coupler2D.s0.1.r21.0.out`: + +![coupler_data.png](coupler_data.png) + +Here's the full python script: + +```python +import os +import unittest +import numpy as np +import meep as mp +import argparse + +################################################## +# global variables +################################################## + +# hard-coded info about the GDSII file +gdsII_file = 'coupler.gds' +CELL_LAYER = 0 +PORT1_LAYER = 1 +PORT2_LAYER = 2 +PORT3_LAYER = 3 +PORT4_LAYER = 4 +SOURCE_LAYER = 5 +UPPER_BRANCH_LAYER = 31 +LOWER_BRANCH_LAYER = 32 + +file_separation = 0.3 # separation between coupler branches in GDSII file +separation = 0.3 # user's requested separation + +oxide_thickness = 1.0 # thicknesses in Z-direction (only for 3D calculation) +silicon_thickness = 0.22 +air_thickness = 0.78 + +eps_oxide = 2.25 +eps_silicon = 12 + +# other computational parameters +dpml=1 +resolution=21 +three_d = False; + +# frequency range +lmin = 1.50 +lcen = 1.55 +lmax = 1.60 + +fmin = 1.0/lmax +fcen = 1.0/lcen +fmax = 1.0/lmin +df_source = fmax-fmin +mode_num = 1 + +df_flux = df_source +nfreq = 50 + +################################################## +# utility routine to extract center and size of a +# meep volume (as opposed to a pymeep Volume). +# there is surely a more natural way to do this... +################################################## +def get_center_and_size(v): + rmin=v.get_min_corner() + rmax=v.get_max_corner() + v3rmin = mp.Vector3(rmin.x(), rmin.y(), rmin.z()) + v3rmax = mp.Vector3(rmax.x(), rmax.y(), rmax.z()) + if v.dim + + + + + + + + + image/svg+xml + + + + + + + + + + + Port 1 + + + Port 2 + + + Port 3 + + + Port 4 + Source + + + diff --git a/doc/docs/Python_Tutorials/coupler_data.png b/doc/docs/Python_Tutorials/coupler_data.png new file mode 100644 index 000000000..d84898a97 Binary files /dev/null and b/doc/docs/Python_Tutorials/coupler_data.png differ diff --git a/libmeepgeom/GDSIIgeom.cpp b/libmeepgeom/GDSIIgeom.cpp index 6300ff587..8b6f36596 100644 --- a/libmeepgeom/GDSIIgeom.cpp +++ b/libmeepgeom/GDSIIgeom.cpp @@ -189,8 +189,7 @@ geometric_object get_GDSII_prism(material_type material, const char *GDSIIFile, meep::volume get_GDSII_volume(const char *GDSIIFile, const char *Text, int Layer, double zmin, double zmax) { dVec polygon = get_polygon(GDSIIFile, Text, Layer); - meep::ndim di = ((zmin==0.0 && zmax==0.0) ? meep::D2 : meep::D3); -di = meep::D2; + meep::ndim di = ( ( ((float)zmin)==0.0 && ((float)zmax)==0.0) ? meep::D2 : meep::D3 ); meep::vec max_corner=meep::zero_vec(di), min_corner=meep::zero_vec(di); get_polygon_bounding_box(polygon, max_corner, min_corner); max_corner.set_direction(meep::Z, zmax); diff --git a/mkdocs.yml b/mkdocs.yml index d5e632167..9f0758c19 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -63,6 +63,7 @@ pages: - Tutorial/Optical Forces: 'Python_Tutorials/Optical_Forces.md' - Tutorial/Frequency-Domain Solver: 'Python_Tutorials/Frequency_Domain_Solver.md' - Tutorial/Mode Decomposition: 'Python_Tutorials/Mode_Decomposition.md' + - Tutorial/BidirectionalCoupler: 'Python_Tutorials/BidirectionalCoupler.md' - Scheme Interface: - User Interface: 'Scheme_User_Interface.md' - Tutorial/Basics: 'Scheme_Tutorials/Basics.md'