Diffractive splitter

Diffractive splitter#

The diffractive splitter challenge entails designing a metasurface that evenly splits a normally-incident plane wave into a 7x7 array of beams. Light is incident from the ambient, with the substrate and the metasurface pattern being silicon oxide. The operating wavelength is 732.8 nm, and the unit cell pitch is 7.2 microns, corresponding to diffraction angles of ±15 degrees. The challenge is based on “Design and rigorous analysis of a non-paraxial diffractive beamsplitter” slide deck retrieved from the LightTrans web site.

Simulating an existing design#

We’ll begin by loading, visualizing, and simulating several designs from the invrs-gym paper.

import matplotlib.pyplot as plt
import numpy as onp
from skimage import measure
from totypes import json_utils


def load_design(file):
    path = f"../../../reference_designs/diffractive_splitter/{file}"
    with open(path, "r") as f:
        serialized = f.read()
    return json_utils.pytree_from_json(serialized)

files = [
    "240413_mfschubert_40b6a186a2ad09fe4a4a9e8fbce60f5d38e39bf11f17102c0005612d8eb6df7b.json",
    "240413_mfschubert_ba86cdee2ff56eb6602b30bc0a87c995d6d4924cdd06fee0e949a9da3590d789.json",
    "240413_mfschubert_8d45bcf673d167db063065dab04b82a527a1e39e816e984c4ff3b4597ddc525a.json",
]

designs = [load_design(file) for file in files]

plt.figure(figsize=(8, 4))
for i, params in enumerate(designs):
    density = params["density"].array
    ax = plt.subplot(1, 3, i + 1)
    im = ax.imshow(1 - density, cmap="gray")
    im.set_clim([-2, 1])
    contours = measure.find_contours(density)
    for c in contours:
        plt.plot(c[:, 1], c[:, 0], "k", lw=1)
    ax.set_xticks([])
    ax.set_yticks([])
../../_images/f3577e8adb8f0f776bc013bfdc1c848d66fa44838b3132ce5117938b432c0934.png

While several challenges involve only the design of two-dimensional patterns (with a Density2DArray being the optimization variable), the diffractive splitter degrees of freedom include both the metasurface pattern and several film thicknesses, in the form of a BoundedArray.

for key, value in designs[0].items():
    print(f"Variable {key}: {type(value)}")
Variable density: <class 'totypes.types.Density2DArray'>
Variable thickness_cap: <class 'totypes.types.BoundedArray'>
Variable thickness_grating: <class 'totypes.types.BoundedArray'>
Variable thickness_spacer: <class 'totypes.types.BoundedArray'>

Instantiate the challenge and simulate a design using the component.response method.

from invrs_gym.challenges.diffract import splitter_challenge

challenge = splitter_challenge.diffractive_splitter()

response, aux = challenge.component.response(params=designs[0])

Now let’s plot the diffraction efficiency for each order. We use the extract_orders_for_splitting function, and get the efficiency for a 9x9 array of beams (even though this design is for a 7x7 splitter). This will let us see how the diffraction efficiency drops off for orders beyond those targeted by the design.

plt.figure(figsize=(4, 3))

splitting = splitter_challenge.extract_orders_for_splitting(
    response.transmission_efficiency,
    response.expansion,
    splitting=(9, 9),
    polarization="TM",
)

ax = plt.subplot(111)
im = plt.imshow(splitting * 100, cmap="coolwarm")
ax.set_xticks(onp.arange(9))
ax.set_yticks(onp.arange(9))
ax.set_xticklabels(range(-4, 5))
ax.set_yticklabels(range(-4, 5))
plt.colorbar(im)
im.set_clim([0, onp.amax(splitting * 100)])
ax.set_title("device1\nDiffraction efficiency (%)")
_ = ax.set_ylim(ax.get_ylim()[::-1])
../../_images/5961136f7e3ed00da5d3542142b8145851cb778068f1bcb4d0bccbf556304e94.png

The device appears to have relatively good uniformity and efficiency. This can be quantified musing metrics compute using the challenge metrics method.

print("Solution 1 challenge metrics:")
for key, value in challenge.metrics(response, params=params, aux=aux).items():
    print(f"    {key} = {value:.4f}")
Solution 1 challenge metrics:
    binarization_degree = 1.0000
    total_efficiency = 0.6472
    average_efficiency = 0.0132
    min_efficiency = 0.0124
    zeroth_order_efficiency = 0.0132
    zeroth_order_error = -0.0026
    uniformity_error = 0.0602
    uniformity_error_without_zeroth_order = 0.0602

Let’s take a look at the remaining devices, which have larger feature sizes. We expect lower performance since the ability to exploit small features should contribute to high performance.

plt.figure(figsize=(8, 3))
for i, params in enumerate(designs[1:]):
    response, aux = challenge.component.response(params)

    splitting = splitter_challenge.extract_orders_for_splitting(
        response.transmission_efficiency,
        response.expansion,
        splitting=(9, 9),
        polarization="TM",
    )

    ax = plt.subplot(1, 2, i + 1)
    im = plt.imshow(splitting * 100, cmap="coolwarm")
    ax.set_xticks(onp.arange(9))
    ax.set_yticks(onp.arange(9))
    ax.set_xticklabels(range(-4, 5))
    ax.set_yticklabels(range(-4, 5))
    plt.colorbar(im)
    im.set_clim([0, onp.amax(splitting * 100)])
    ax.set_title(f"Solution {i + 2}\nDiffraction efficiency (%)")
    ax.set_ylim(ax.get_ylim()[::-1])
../../_images/3781f11cfa9e2401bf20f0ee1e38b70c982bbb9d7f7c31dc08cbe872e714e70f.png

Observe that the symmetry in the layout of the third design results in symmetry in the diffraction efficiencies.