|
| 1 | +{ |
| 2 | + "cells": [ |
| 3 | + { |
| 4 | + "cell_type": "markdown", |
| 5 | + "metadata": {}, |
| 6 | + "source": [ |
| 7 | + "# Metalens challenge\n", |
| 8 | + "\n", |
| 9 | + "The metalens challenge entails designing a one-dimensional metalens that focuses blue, green, and red light (450 nm, 550 nm, and 650 nm) to the same point in space. This problem was studied in \"[Validation and characterization of algorithms and software for photonics inverse design](https://opg.optica.org/josab/ViewMedia.cfm?uri=josab-41-2-A161)\" by Chen et al.; the associated [photonics-opt-testbed repo](https://github.com/NanoComp/photonics-opt-testbed/tree/main/RGB_metalens) contains several example designs." |
| 10 | + ] |
| 11 | + }, |
| 12 | + { |
| 13 | + "cell_type": "markdown", |
| 14 | + "metadata": {}, |
| 15 | + "source": [ |
| 16 | + "## Simulating an existing design\n", |
| 17 | + "\n", |
| 18 | + "We'll begin by loading, visualizing, and simulating a design from the photonics-opt-testbed repo." |
| 19 | + ] |
| 20 | + }, |
| 21 | + { |
| 22 | + "cell_type": "code", |
| 23 | + "execution_count": null, |
| 24 | + "metadata": {}, |
| 25 | + "outputs": [], |
| 26 | + "source": [ |
| 27 | + "import matplotlib.pyplot as plt\n", |
| 28 | + "import numpy as onp\n", |
| 29 | + "\n", |
| 30 | + "design = onp.genfromtxt(\n", |
| 31 | + " \"../../reference_designs/metalens/Ex/Rasmus70nm.csv\",\n", |
| 32 | + " delimiter=\",\",\n", |
| 33 | + ")\n", |
| 34 | + "\n", |
| 35 | + "# Flip the design, so that row `0` corresponds to the top, and `-1` the bottom.\n", |
| 36 | + "design = design[:, ::-1]\n", |
| 37 | + "\n", |
| 38 | + "# The `Rasmus70nm` design has grid spacing of 10 nm; some other designs use 20 nm.\n", |
| 39 | + "grid_spacing = 0.010\n", |
| 40 | + "\n", |
| 41 | + "plt.figure(figsize=(8, 2))\n", |
| 42 | + "ax = plt.subplot(111)\n", |
| 43 | + "ax.imshow(design.T, cmap=\"gray\")\n", |
| 44 | + "ax.set_xticks([])\n", |
| 45 | + "_ = ax.set_yticks([])" |
| 46 | + ] |
| 47 | + }, |
| 48 | + { |
| 49 | + "cell_type": "markdown", |
| 50 | + "metadata": {}, |
| 51 | + "source": [ |
| 52 | + "As in other examples, we will use the `metalens` challenge to simulate this design. However, we need to configure the challenge so that the design region size and grid spacing precisely match the reference. All of these physical characteristics are stored in a `MetalensSpec` object. Modify the defaults to match our design, pad `design` so that it has the shape required by the challenge, and then create the challenge object." |
| 53 | + ] |
| 54 | + }, |
| 55 | + { |
| 56 | + "cell_type": "code", |
| 57 | + "execution_count": null, |
| 58 | + "metadata": {}, |
| 59 | + "outputs": [], |
| 60 | + "source": [ |
| 61 | + "import dataclasses\n", |
| 62 | + "from invrs_gym import challenges\n", |
| 63 | + "from invrs_gym.challenges.metalens import challenge as metalens_challenge\n", |
| 64 | + "\n", |
| 65 | + "spec = dataclasses.replace(\n", |
| 66 | + " metalens_challenge.METALENS_SPEC,\n", |
| 67 | + " width_lens=design.shape[0] * grid_spacing,\n", |
| 68 | + " width_pml=0.5,\n", |
| 69 | + " thickness_lens=design.shape[1] * grid_spacing,\n", |
| 70 | + " grid_spacing=grid_spacing,\n", |
| 71 | + ")\n", |
| 72 | + "\n", |
| 73 | + "# Pad design so that it extends to the edge of the simulation domain.\n", |
| 74 | + "padding = int(onp.around(spec.width / spec.grid_spacing)) - design.shape[0]\n", |
| 75 | + "padded_design = onp.pad(design, ((padding // 2, padding // 2), (0, 0)), mode=\"edge\")\n", |
| 76 | + "\n", |
| 77 | + "challenge = challenges.metalens(spec=spec)" |
| 78 | + ] |
| 79 | + }, |
| 80 | + { |
| 81 | + "cell_type": "markdown", |
| 82 | + "metadata": {}, |
| 83 | + "source": [ |
| 84 | + "To simulate our metalens design, we need to create a `totypes.types.Density2DArray` object that has this design as its `array` attribute. We obtain dummy parameters and then overwrite them with our design." |
| 85 | + ] |
| 86 | + }, |
| 87 | + { |
| 88 | + "cell_type": "code", |
| 89 | + "execution_count": null, |
| 90 | + "metadata": {}, |
| 91 | + "outputs": [], |
| 92 | + "source": [ |
| 93 | + "import jax\n", |
| 94 | + "\n", |
| 95 | + "dummy_params = challenge.component.init(jax.random.PRNGKey(0))\n", |
| 96 | + "params = dataclasses.replace(dummy_params, array=padded_design)" |
| 97 | + ] |
| 98 | + }, |
| 99 | + { |
| 100 | + "cell_type": "markdown", |
| 101 | + "metadata": {}, |
| 102 | + "source": [ |
| 103 | + "We are now ready to simulate the metalens, using the `component.response` method. By default, this will not compute the fields passing through the metalens (for improved performance), but we will do so here for visualization purposes." |
| 104 | + ] |
| 105 | + }, |
| 106 | + { |
| 107 | + "cell_type": "code", |
| 108 | + "execution_count": null, |
| 109 | + "metadata": {}, |
| 110 | + "outputs": [], |
| 111 | + "source": [ |
| 112 | + "response, aux = challenge.component.response(params, compute_fields=True)" |
| 113 | + ] |
| 114 | + }, |
| 115 | + { |
| 116 | + "cell_type": "markdown", |
| 117 | + "metadata": {}, |
| 118 | + "source": [ |
| 119 | + "The response includes the intensity at the focus for each of the three wavelengths." |
| 120 | + ] |
| 121 | + }, |
| 122 | + { |
| 123 | + "cell_type": "code", |
| 124 | + "execution_count": null, |
| 125 | + "metadata": {}, |
| 126 | + "outputs": [], |
| 127 | + "source": [ |
| 128 | + "for wvl, enhancement in zip(response.wavelength, response.enhancement_ex):\n", |
| 129 | + " print(f\"Intensity for wavelength={wvl:.3f} is {enhancement:.2f}\")" |
| 130 | + ] |
| 131 | + }, |
| 132 | + { |
| 133 | + "cell_type": "markdown", |
| 134 | + "metadata": {}, |
| 135 | + "source": [ |
| 136 | + "These values are close to those reported in the [photonics-opt-testbed](https://github.com/NanoComp/photonics-opt-testbed/tree/main/RGB_metalens) repo, indicating that our simulation is well-converged. Next, let's visualize the fields. Since we have specified `compute_fields=True`, in the fields are included in the `aux` dictionary." |
| 137 | + ] |
| 138 | + }, |
| 139 | + { |
| 140 | + "cell_type": "code", |
| 141 | + "execution_count": null, |
| 142 | + "metadata": {}, |
| 143 | + "outputs": [], |
| 144 | + "source": [ |
| 145 | + "from skimage import measure\n", |
| 146 | + "\n", |
| 147 | + "ex, ey, ez = aux[\"efield\"]\n", |
| 148 | + "x, _, z = aux[\"field_coordinates\"]\n", |
| 149 | + "xplot, zplot = onp.meshgrid(x[:, 0], z, indexing=\"ij\")\n", |
| 150 | + "\n", |
| 151 | + "abs_field = onp.sqrt(onp.abs(ex) ** 2 + onp.abs(ey) ** 2 + onp.abs(ez) ** 2)\n", |
| 152 | + "\n", |
| 153 | + "plt.figure(figsize=(8, 9))\n", |
| 154 | + "for i, color in enumerate([\"b\", \"g\", \"r\"]):\n", |
| 155 | + " cmap = plt.cm.colors.LinearSegmentedColormap.from_list(\"b\", [\"w\", color], N=256)\n", |
| 156 | + "\n", |
| 157 | + " ax = plt.subplot(3, 1, i + 1)\n", |
| 158 | + " im = ax.pcolormesh(xplot, zplot, abs_field[i, :, 0, :, 0], cmap=cmap)\n", |
| 159 | + " maxval = onp.amax(abs_field[i, :, 0, :])\n", |
| 160 | + " im.set_clim([0, maxval])\n", |
| 161 | + " ax.axis(\"equal\")\n", |
| 162 | + "\n", |
| 163 | + " contours = measure.find_contours(onp.asarray(params.array))\n", |
| 164 | + " for c in contours:\n", |
| 165 | + " x = c[:, 0] * spec.grid_spacing\n", |
| 166 | + " z = c[:, 1] * spec.grid_spacing + spec.focus_offset + spec.thickness_ambient\n", |
| 167 | + " ax.plot(x, z, \"k\")\n", |
| 168 | + "\n", |
| 169 | + " ax.set_xlim([onp.amin(xplot), onp.amax(xplot)])\n", |
| 170 | + " ax.set_ylim([onp.amax(zplot), onp.amin(zplot)])\n", |
| 171 | + " ax.axis(False)\n", |
| 172 | + "\n", |
| 173 | + " ax.set_title(\n", |
| 174 | + " f\"wavelength={response.wavelength[i]:.3f}: intensity={response.enhancement_ex[i]:.2f}\"\n", |
| 175 | + " )" |
| 176 | + ] |
| 177 | + }, |
| 178 | + { |
| 179 | + "cell_type": "markdown", |
| 180 | + "metadata": {}, |
| 181 | + "source": [ |
| 182 | + "# Metalens optimization\n", |
| 183 | + "\n", |
| 184 | + "To optimize a metalens, you may follow the recipe in the `ceviche_challenge` and `metagrating_challenge` notebooks." |
| 185 | + ] |
| 186 | + } |
| 187 | + ], |
| 188 | + "metadata": { |
| 189 | + "kernelspec": { |
| 190 | + "display_name": "invrs", |
| 191 | + "language": "python", |
| 192 | + "name": "python3" |
| 193 | + }, |
| 194 | + "language_info": { |
| 195 | + "codemirror_mode": { |
| 196 | + "name": "ipython", |
| 197 | + "version": 3 |
| 198 | + }, |
| 199 | + "file_extension": ".py", |
| 200 | + "mimetype": "text/x-python", |
| 201 | + "name": "python", |
| 202 | + "nbconvert_exporter": "python", |
| 203 | + "pygments_lexer": "ipython3", |
| 204 | + "version": "3.10.12" |
| 205 | + } |
| 206 | + }, |
| 207 | + "nbformat": 4, |
| 208 | + "nbformat_minor": 2 |
| 209 | +} |
0 commit comments