{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cell-00",
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "# Importing GDS files\n",
    "\n",
    "In Tidy3D, complex structures can be defined or imported from GDSII files using [Photonforge](https://www.flexcompute.com/photonforge/), Flexcompute's photonic design automation tool. In this tutorial, we will illustrate how to use Photonforge to read a previously saved GDS file and import the structures into a simulation. For a tutorial on how to generate the GDS file used in this example, please refer to [this](https://www.flexcompute.com/tidy3d/examples/notebooks/GDSCreation/) notebook.\n",
    "\n",
    "<img src=\"img/splitter.png\" alt=\"Schematic of the directional coupler\" width=\"400\"/>\n",
    "\n",
    "Note that this tutorial requires Photonforge, so grab it with `pip install photonforge` before running the tutorial or uncomment the cell line below.\n",
    "\n",
    "We also provide a comprehensive list of other tutorials such as [how to define boundary conditions](https://www.flexcompute.com/tidy3d/examples/notebooks/BoundaryConditions/), [how to compute the S-matrix of a device](https://www.flexcompute.com/tidy3d/examples/notebooks/SMatrix/), [how to interact with tidy3d's web API](https://www.flexcompute.com/tidy3d/examples/notebooks/WebAPI/), and [how to define self-intersecting polygons](https://www.flexcompute.com/tidy3d/examples/notebooks/SelfIntersectingPolyslab/).\n",
    "\n",
    "If you are new to the finite-difference time-domain (FDTD) method, we highly recommend going through our [FDTD101](https://www.flexcompute.com/fdtd101/) tutorials."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "cell-01",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# install photonforge\n",
    "# !pip install photonforge\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import photonforge as pf\n",
    "import tidy3d as td"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-02",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Loading a GDS file with Photonforge\n",
    "\n",
    "To load the geometry from a GDSII file, we use `pf.load_layout`, which reads the file and returns a dictionary of `Component` objects. We then use `pf.find_top_level` to automatically identify the top-level cell — the one that is not referenced as a sub-cell by any other cell in the file:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "cell-03",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "gds_path = \"misc/coupler.gds\"\n",
    "\n",
    "# Load all components from the GDS file\n",
    "components = pf.load_layout(gds_path)\n",
    "\n",
    "# Identify the top-level component (not referenced by any other cell)\n",
    "top_level = pf.find_top_level(*components.values())[0]\n",
    "\n",
    "print(\"Top-level component:\", top_level.name)\n",
    "print(\"Available cells:\", list(components.keys()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-04",
   "metadata": {},
   "source": [
    "### Inspecting Available Layers\n",
    "\n",
    "`get_structures()` extracts all 2D structures from the component (including flattened sub-cell references), grouped by `(layer, datatype)` pairs. This tells us which layers are present and how many structures each contains:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "cell-05",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "structures_dict = top_level.get_structures()\n",
    "\n",
    "for (layer, dtype), polys in structures_dict.items():\n",
    "    print(f\"Layer ({layer}, {dtype}): {len(polys)} polygon(s)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-06",
   "metadata": {},
   "source": [
    "We need to map each `(layer, datatype)` pair to a Tidy3D medium and vertical extent (`slab_bounds`). In this coupler layout, layer `(0, 0)` is the $\\text{SiO}_2$ substrate extending from z = -4 µm to z = 0, and layer `(1, 0)` contains the Si waveguide arms from z = 0 to z = `wg_height`.\n",
    "\n",
    "We can also define fabrication-related parameters. We consider the waveguide sidewalls to be slanted by `sidewall_angle` — positive values model the typical fabrication scenario where the base of the waveguide is wider than the top. A small `dilation` accounts for proximity effects."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "cell-07",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define waveguide height and fabrication parameters\n",
    "wg_height = 0.22\n",
    "dilation = 0.02\n",
    "sidewall_angle = np.deg2rad(10)\n",
    "\n",
    "# Define materials\n",
    "wg_n = 3.48  # Si refractive index\n",
    "sub_n = 1.45  # SiO2 refractive index\n",
    "medium_wg = td.Medium(permittivity=wg_n**2)\n",
    "medium_sub = td.Medium(permittivity=sub_n**2)\n",
    "\n",
    "# Map (layer, datatype) -> medium and extrusion parameters\n",
    "layer_map = {\n",
    "    (0, 0): dict(medium=medium_sub, slab_bounds=(-4.0, 0.0), sidewall_angle=0.0, dilation=0.0),\n",
    "    (1, 0): dict(\n",
    "        medium=medium_wg,\n",
    "        slab_bounds=(0.0, wg_height),\n",
    "        sidewall_angle=sidewall_angle,\n",
    "        dilation=dilation,\n",
    "    ),\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-08",
   "metadata": {},
   "source": [
    "### Set up Geometries\n",
    "\n",
    "Each polygon from `get_structures()` exposes a `to_polygon().vertices` array that maps directly to `td.PolySlab`. We iterate over the layer map, skip any layers not in our map, and build a list of `td.Structure` objects. We also collect the arm geometries separately so we can compute the simulation bounding box from them later.\n",
    "\n",
    "A positive `sidewall_angle` on the waveguide arms models a tapered cross-section — the base (z = 0) is wider than the top (z = `wg_height`) by `dilation` on each side, which is typical of real fabrication."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "cell-09",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "arm_geos = []\n",
    "structures = []\n",
    "\n",
    "for (layer, dtype), polys in structures_dict.items():\n",
    "    if (layer, dtype) not in layer_map:\n",
    "        continue\n",
    "    params = layer_map[(layer, dtype)]\n",
    "    for poly in polys:\n",
    "        geo = td.PolySlab(\n",
    "            vertices=poly.to_polygon().vertices,\n",
    "            axis=2,\n",
    "            slab_bounds=params[\"slab_bounds\"],\n",
    "            sidewall_angle=params[\"sidewall_angle\"],\n",
    "            dilation=params[\"dilation\"],\n",
    "        )\n",
    "        structures.append(td.Structure(geometry=geo, medium=params[\"medium\"]))\n",
    "        if (layer, dtype) == (1, 0):  # collect arm geometries for bounding box\n",
    "            arm_geos.append(geo)\n",
    "\n",
    "# Group arm geometries to compute simulation bounding box\n",
    "arms_geo = td.GeometryGroup(geometries=arm_geos)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-10",
   "metadata": {},
   "source": [
    "Let's plot the base and the top of the coupler waveguide arms to make sure it looks ok. The base of the device should be larger than the top due to a positive `sidewall_angle`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "cell-11",
   "metadata": {},
   "outputs": [],
   "source": [
    "f, ax = plt.subplots(2, 1, figsize=(15, 6), tight_layout=True)\n",
    "arms_geo.plot(z=0.0, ax=ax[0])\n",
    "arms_geo.plot(z=wg_height, ax=ax[1])\n",
    "\n",
    "ax[0].set_ylim(-5, 5)\n",
    "_ = ax[1].set_ylim(-5, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-14",
   "metadata": {},
   "source": [
    "### Set up Simulation\n",
    "\n",
    "Now let's set up the rest of the Simulation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "cell-15",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Spacing between waveguides and PML\n",
    "pml_spacing = 1.0\n",
    "\n",
    "# Simulation size\n",
    "sim_size = list(arms_geo.bounding_box.size)\n",
    "sim_size[0] -= 4 * pml_spacing\n",
    "sim_size[1] += 2 * pml_spacing\n",
    "sim_size[2] = wg_height + 2 * pml_spacing\n",
    "\n",
    "sim_center = list(arms_geo.bounding_box.center)\n",
    "sim_center[2] = 0\n",
    "\n",
    "# grid size in each direction\n",
    "dl = 0.020\n",
    "\n",
    "### Initialize and visualize simulation ###\n",
    "sim = td.Simulation(\n",
    "    size=sim_size,\n",
    "    center=sim_center,\n",
    "    grid_spec=td.GridSpec.uniform(dl=dl),\n",
    "    structures=structures,\n",
    "    run_time=2e-12,\n",
    "    boundary_spec=td.BoundarySpec.all_sides(boundary=td.PML()),\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-16",
   "metadata": {},
   "source": [
    "### Plot Simulation Geometry\n",
    "\n",
    "Let's take a look at the simulation all together with the PolySlabs added. Here the angle of the sidewall deviating from the vertical direction is 30 degree."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "cell-17",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(17, 5))\n",
    "\n",
    "sim.plot(z=wg_height / 2, lw=1, edgecolor=\"k\", ax=ax1)\n",
    "sim.plot(x=0.1, lw=1, edgecolor=\"k\", ax=ax2)\n",
    "\n",
    "ax2.set_xlim([-3, 3])\n",
    "_ = ax2.set_ylim([-1, 1])"
   ]
  }
 ],
 "metadata": {
  "description": "This notebook demonstrates how to import geometries from a gds file to Tidy3D for FDTD simulations.",
  "feature_imag": "",
  "feature_image": "N/A",
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "keywords": "gds, import, Tidy3D, FDTD",
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.7"
  },
  "title": "GDS File Import in Tidy3D | Flexcompute"
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
