Defining common photonic crystal structures#

This notebook contains grab-and-go functions of popular periodic structures commonly used in photonic crystals and other photonic devices: square/hexagonal arrays of cylinders, slabs with square/hexagonal arrays of holes, rectangular grating, L and H cavities, wood pile, and FCC/BCC crystals. Users can directly copy these pre-defined functions to your own script and use them directly in building your simulations. More importantly, users can learn the workflow from these examples and build your own periodic Structures using the same principles.

For photonic crystal related case studies, please see the band structure calculation, the optimized L3 cavity, and the waveguide polarization filter.

For how to define common photonic integrated circuit components, please see here.

[1]:
import tidy3d as td
import numpy as np

Square Array of Cylinders#

The function square_cylinder_array returns a square array of cylinders with tunable radius, spacing in both x and y directions, height, and array dimensions.

[2]:
def square_cylinder_array(
    x0,
    y0,
    z0,
    R,
    hole_spacing_x,
    hole_spacing_y,
    n_x,
    n_y,
    height,
    medium,
    reference_plane="bottom",
    sidewall_angle=0,
    axis=2,
):
    # parameters
    # ------------------------------------------------------------
    # x0: x coordinate of center of the array (um)
    # y0: y coordinate of center of the array (um)
    # z0: z coordinate of center of the array (um)
    # R: radius of the circular holes (um)
    # hole_spacing_x: distance between centers of holes in x direction (um)
    # hole_spacing_y: distance between centers of holes in y direction (um)
    # n_x: number of holes in x direction
    # n_y: number of holes in y direction
    # height: height of array
    # medium: medium of the cylinders
    # reference_plane
    # sidewall_angle: angle slant of cylinders
    # axis

    cylinder_group = []

    start_x, start_y = x0 + hole_spacing_x * (1 - n_x) / 2, y0 + hole_spacing_y * (1 - n_y) / 2
    for i in range(0, n_x):
        for j in range(0, n_y):
            c = td.Cylinder(
                axis=axis,
                sidewall_angle=sidewall_angle,
                reference_plane=reference_plane,
                radius=R,
                center=(start_x + i * hole_spacing_x, start_y + j * hole_spacing_y, z0),
                length=height,
            )
            cylinder_group.append(c)

    structure = td.Structure(geometry=td.GeometryGroup(geometries=cylinder_group), medium=medium)

    return structure

To demonstrate an example, we use the square_cylinder_array function and put it into an empty simulation for visualization purposes. By using the plot_3d method of the Simulation object, we can visualize the defined structures in 3D.

[3]:
s = square_cylinder_array(0, 0, 0, 0.3, 0.7, 0.8, 8, 10, 0.4, td.Medium(permittivity=3.48**2))

sim = td.Simulation(
    size=[10, 10, 3],
    grid_spec=td.GridSpec.auto(wavelength=1.55),
    structures=[s],
    run_time=1,
)

sim.plot_3d()

Hexagonal Array of Cylinders#

The function hex_cylinder_array returns a hexagonal array of cylinders with tunable radius, spacing in both x and y directions, height, and array dimensions.

[4]:
def hex_cylinder_array(
    x0,
    y0,
    z0,
    R,
    hole_spacing_x,
    hole_spacing_y,
    n_x,
    n_y,
    height,
    medium,
    reference_plane="bottom",
    sidewall_angle=0,
    axis=2,
):
    # parameters
    # ------------------------------------------------------------
    # x0: x coordinate of center of the array (um)
    # y0: y coordinate of center of the array (um)
    # z0: z coordinate of center of the array (um)
    # R: radius of the circular holes (um)
    # hole_spacing_x: distance between centers of holes in x direction (um)
    # hole_spacing_y: distance between centers of holes in y direction (um)
    # n_x: number of holes in x direction
    # n_y: number of holes in y direction
    # height: height of array
    # medium: medium of the holes
    # reference_plane
    # sidewall_angle: angle slant of cylinders. Add compensation for the box geometry if != 0?
    # axis

    x_slab_length, y_slab_length = hole_spacing_x * (n_x + 0.5), hole_spacing_y * n_y
    start_x, start_y = (
        x0 - x_slab_length / 2 + hole_spacing_x / 2,
        y0 - y_slab_length / 2 + hole_spacing_y / 2,
    )

    cylinders = []

    for i in range(0, n_x):
        for j in range(0, n_y):
            c = td.Cylinder(
                axis=axis,
                sidewall_angle=sidewall_angle,
                reference_plane=reference_plane,
                radius=R,
                center=(
                    start_x + (i + (j % 2) * 0.5) * hole_spacing_x,
                    start_y + j * hole_spacing_y,
                    z0,
                ),
                length=height,
            )
            cylinders.append(c)

    structure = td.Structure(geometry=td.GeometryGroup(geometries=cylinders), medium=medium)

    return structure
[5]:
s = hex_cylinder_array(0, 0, 0, 0.3, 0.7, 0.8, 8, 10, 0.4, td.Medium(permittivity=3.48**2))

sim = td.Simulation(
    size=[10, 10, 3],
    grid_spec=td.GridSpec.auto(wavelength=1.55),
    structures=[s],
    run_time=1,
)

sim.plot_3d()

Square Array of Holes#

The function slab_square_array returns a slab with a square array of holes of separately-defined medium. The slab has a tunable hole radius, spacing in both x and y directions, height, and array dimensions.

[6]:
def slab_square_array(
    x0,
    y0,
    z0,
    R,
    hole_spacing_x,
    hole_spacing_y,
    n_x,
    n_y,
    height,
    hole_medium,
    slab_medium,
    reference_plane="bottom",
    sidewall_angle=0,
    axis=2,
):
    # parameters
    # ------------------------------------------------------------
    # x0: x coordinate of center of the array (um)
    # y0: y coordinate of center of the array (um)
    # z0: z coordinate of center of the array (um)
    # R: radius of the circular holes (um)
    # hole_spacing_x: distance between centers of holes in x direction (um)
    # hole_spacing_y: distance between centers of holes in y direction (um)
    # n_x: number of holes in x direction
    # n_y: number of holes in y direction
    # height: height of array
    # hole_medium: medium of the holes
    # slab_medium: medium of the slab
    # reference_plane
    # sidewall_angle: angle slant of cylinders. Add compensation for the box geometry if != 0?
    # axis

    start_x, start_y = x0 - hole_spacing_x * (n_x - 1) / 2, y0 - hole_spacing_y * (n_y - 1) / 2

    box = td.Box(center=(x0, y0, z0), size=(hole_spacing_x * n_x, hole_spacing_y * n_y, height))
    structures = [td.Structure(geometry=box, medium=slab_medium)]
    cylinders = []

    for i in range(0, n_x):
        for j in range(0, n_y):
            c = td.Cylinder(
                axis=axis,
                sidewall_angle=sidewall_angle,
                reference_plane=reference_plane,
                radius=R,
                center=(start_x + i * hole_spacing_x, start_y + j * hole_spacing_y, z0),
                length=height,
            )
            cylinders.append(c)

    cylinders_structure = td.Structure(
        geometry=td.GeometryGroup(geometries=cylinders), medium=hole_medium
    )

    structures.append(cylinders_structure)

    return structures
[7]:
s = slab_square_array(
    0,
    0,
    0,
    0.3,
    0.7,
    0.8,
    8,
    10,
    0.4,
    td.Medium(permittivity=3.48**2),
    td.Medium(permittivity=1.003**2),
)

sim = td.Simulation(
    size=[10, 10, 10],
    grid_spec=td.GridSpec.auto(wavelength=1.55),
    structures=s,
    run_time=1,
)

sim.plot_3d()

Hexagonal Array of Holes#

The function slab_hex_array returns a slab with a square array of holes with separately-defined medium. The slab has a tunable hole radius, spacing in both x and y directions, height, and array dimensions.

[8]:
def slab_hex_array(
    x0,
    y0,
    z0,
    R,
    hole_spacing_x,
    hole_spacing_y,
    n_x,
    n_y,
    height,
    hole_medium,
    slab_medium,
    reference_plane="bottom",
    sidewall_angle=0,
    axis=2,
):
    # parameters
    # ------------------------------------------------------------
    # x0: x coordinate of center of the array (um)
    # y0: y coordinate of center of the array (um)
    # z0: z coordinate of center of the array (um)
    # R: radius of the circular holes (um)
    # hole_spacing_x: distance between centers of holes in x direction (um)
    # hole_spacing_y: distance between centers of holes in y direction (um)
    # n_x: number of holes in x direction
    # n_y: number of holes in y direction
    # height: height of array
    # hole_medium: medium of the holes
    # slab_medium: medium of the slab
    # reference_plane
    # sidewall_angle: angle slant of cylinders. Add compensation for the box geometry if != 0?
    # axis

    # define geometry for slab
    x_slab_length, y_slab_length = hole_spacing_x * (n_x + 0.5), hole_spacing_y * n_y
    start_x, start_y = (
        x0 - x_slab_length / 2 + hole_spacing_x / 2,
        y0 - y_slab_length / 2 + hole_spacing_y / 2,
    )
    box = td.Box(center=(x0, y0, z0), size=(x_slab_length, y_slab_length, height))

    structures = [td.Structure(geometry=box, medium=slab_medium)]

    cylinders = []
    for i in range(0, n_x):
        for j in range(0, n_y):
            c = td.Cylinder(
                axis=axis,
                sidewall_angle=sidewall_angle,
                reference_plane=reference_plane,
                radius=R,
                center=(
                    start_x + (i + (j % 2) * 0.5) * hole_spacing_x,
                    start_y + j * hole_spacing_y,
                    z0,
                ),
                length=height,
            )
            cylinders.append(c)

    cylinders_structure = td.Structure(
        geometry=td.GeometryGroup(geometries=cylinders), medium=hole_medium
    )

    structures.append(cylinders_structure)

    return structures
[9]:
s = slab_hex_array(
    0,
    0,
    0,
    0.3,
    0.7,
    0.8,
    8,
    10,
    0.4,
    td.Medium(permittivity=3.48**2),
    td.Medium(permittivity=1.003**2),
)

sim = td.Simulation(
    size=[10, 10, 10],
    grid_spec=td.GridSpec.auto(wavelength=1.55),
    structures=s,
    run_time=1,
)

sim.plot_3d()

Rectangular Grating#

The function grating returns a rectangular grating, with tunable height, thickness, tooth width, tooth height, tooth spacing, and tooth number.

[10]:
def grating(
    x0,
    y0,
    z0,
    tooth_width,
    tooth_height,
    spacing,
    base_height,
    thickness,
    tooth_number,
    medium,
    reference_plane="bottom",
    sidewall_angle=0,
    axis=2,
):
    # parameters
    # ------------------------------------------------------------
    # x0: x coordinate of center of the grating (um)
    # y0: y coordinate of center of the grating (um)
    # z0: z coordinate of center of the grating (um)
    # tooth_width: width of each grating tooth (um)
    # tooth_height: height of each tooth (um)
    # spacing: spacing between teeth (um)
    # base_height: height of underlying base (um)
    # thickness: width of underlying base (um)
    # tooth_number: number of teeth in grating
    # medium: medium of the grating
    # reference_plane
    # sidewall_angle: angle of slant
    # axis

    x_length, z_length = (
        tooth_width + spacing
    ) * tooth_number - spacing, tooth_height + base_height
    start_x, start_z = x0 - x_length / 2, z0 + z_length / 2

    axisArray = [0,1,2]

    # create list of points as vertices for polyslab
    points = [(start_x, start_z), (start_x + tooth_width, start_z)]
    prevPoint = points[-1]
    # adding points for teeth
    for i in range(0, tooth_number - 1):
        points.append((prevPoint[0], prevPoint[1] - tooth_height))
        points.append((prevPoint[0] + spacing, prevPoint[1] - tooth_height))
        points.append((prevPoint[0] + spacing, prevPoint[1]))
        prevPoint = (prevPoint[0] + spacing + tooth_width, prevPoint[1])
        points.append(prevPoint)
    points.append((prevPoint[0], prevPoint[1] - z_length))
    points.append((prevPoint[0] - x_length, prevPoint[1] - z_length))

    grating = td.PolySlab(
        vertices=points,
        axis=axisArray[axis-1],
        slab_bounds=(-thickness / 2, thickness / 2),
        sidewall_angle=sidewall_angle,
        reference_plane=reference_plane,
    )

    structure = td.Structure(geometry=grating, medium=medium)

    return structure
[11]:
s = grating(0, 0, 0, 0.2, 0.1, 0.3, 3, 4, 12, td.Medium(permittivity=3.48**2))

sim = td.Simulation(
    size=[10, 10, 10], grid_spec=td.GridSpec.auto(wavelength=1.55), structures=[s], run_time=1
)

sim.plot_3d()

L Cavity#

The function hex_l_cavity returns a hexagonal array of cylinders with an L cavity. It has tunable cylinder radius, cylinder spacing, height, cavity size, and separately tunable radii of cavity-bordering cylinders.

[12]:
def hex_l_cavity(
    x0,
    y0,
    z0,
    R,
    side_R,
    spacing_x,
    spacing_y,
    n_x,
    n_y,
    l_number,
    height,
    medium,
    reference_plane="bottom",
    sidewall_angle=0,
    axis=2,
):
    # parameters
    # ------------------------------------------------------------
    # x0: x coordinate of center of the array (um)
    # y0: y coordinate of center of the array (um)
    # z0: z coordinate of center of the array (um)
    # R: radius of the cylinders (um)
    # side_R: radii of the two ends of the L-cavity (um)
    # hole_spacing_x: distance between centers of cylinders in x direction (um)
    # hole_spacing_y: distance between centers of cylinders in y direction (um)
    # n_x: number of cylinders in x direction
    # n_y: number of cylinders in y direction
    # l_number: number of cylinders removed from center (along x direction)
    # height: height of cylinders
    # medium: medium of the cylinders
    # reference_plane
    # sidewall_angle: angle slant of cylinders
    # axis

    cylinders = []
    if n_y % 2 == 0:
        n_y -= 1  # only odd numbers for n_y work for symmetry

    n_middle = n_x - n_x % 2 + l_number % 2

    for i in range(-(n_y // 2), n_y // 2 + 1):  # go up columns
        n_row = (
            n_middle + (i % 2) * (-1) ** l_number
        )  # calculates number of cylinders in current row
        for j in range(-n_row + 1, n_row + 1, 2):  # go along rows
            if i != 0 or abs(j) > l_number:  # don't populate cavity with cylinders
                var_radius = R
                if i == 0 and (abs(j) == l_number + 1):  # checks if cylinder is on side of cavity
                    var_radius = side_R
                c = td.Cylinder(
                    axis=axis,
                    sidewall_angle=sidewall_angle,
                    reference_plane=reference_plane,
                    radius=var_radius,
                    center=(x0 + j * spacing_x, y0 + i * spacing_y, z0),
                    length=height,
                )
                cylinders.append(c)
    structure = td.Structure(geometry=td.GeometryGroup(geometries=cylinders), medium=medium)
    return structure
[13]:
s = hex_l_cavity(0, 0, 0, 0.3, 0.2, 0.8, 0.9, 8, 10, 2, 1, td.Medium(permittivity=3.48**2))

sim = td.Simulation(
    size=[15, 10, 3], grid_spec=td.GridSpec.auto(wavelength=1.55), structures=[s], run_time=1
)

sim.plot_3d()

H Cavity#

The function hex_h_cavity returns a hexagon-shaped hexagonal array of cylinders with a hexagon-shaped cavity in the middle. Tunable cylinder radius, spacing, height, length in cylinders, and cavity size.

[14]:
def hex_h_cavity(
    x0,
    y0,
    z0,
    R,
    n_side,
    h_number,
    spacing,
    height,
    medium,
    reference_plane="bottom",
    sidewall_angle=0,
    axis=2,
):
    # parameters
    # ------------------------------------------------------------
    # x0: x coordinate of center of the array (um)
    # y0: y coordinate of center of the array (um)
    # z0: z coordinate of center of the array (um)
    # R: radius of the cylinders (um)
    # n_side: maximum number of cylinders on hexagon side
    # h_number: maximum side number of hexagons that are removed
    # spacing: spacing between centers of cylinders
    # height: height of cylinders
    # medium: medium of the cylinders
    # reference_plane
    # sidewall_angle: angle slant of cylinders
    # axis

    cylinders = []
    for i in range(h_number + 1, n_side + 1):  # hexagon side length
        center_x, center_y = x0 + (i - 1) * spacing, y0
        for j in range(0, 6 - 5 * (i == 1)):  # iterate through hexagon sides
            for k in range(0, i - 1 + (i == 1)):  # iterate along each hexagon side
                center_x += spacing * np.cos(np.pi * 2 / 3 + j * np.pi / 3) * (i > 1)
                center_y += spacing * np.sin(np.pi * 2 / 3 + j * np.pi / 3) * (i > 1)
                c = td.Cylinder(
                    axis=axis,
                    sidewall_angle=sidewall_angle,
                    reference_plane=reference_plane,
                    radius=R,
                    center=(center_x, center_y, z0),
                    length=height,
                )
                cylinders.append(c)
    structure = td.Structure(geometry=td.GeometryGroup(geometries=cylinders), medium=medium)
    return structure
[15]:
s = hex_h_cavity(0, 0, 0, 0.3, 6, 2, 0.8, 1, td.Medium(permittivity=3.48**2))

sim = td.Simulation(
    size=[10, 10, 3],
    grid_spec=td.GridSpec.auto(wavelength=1.55),
    structures=[s],
    run_time=2,
)

sim.plot_3d()

Wood Pile#

The function wood_pile returns the popular β€˜wood pile’ structure with tunable width of the blocks comprising the pile, width and height of the entire pile, number of blocks in each row, and number of rows of blocks.

[16]:
def wood_pile(
    x0,
    y0,
    z0,
    block_width,
    side_width,
    pile_height,
    n_rows,
    n_floors,
    medium,
    reference_plane="bottom",
    axis=2,
):
    # parameters
    # ------------------------------------------------------------
    # x0: x coordinate of center of the array (um)
    # y0: y coordinate of center of the array (um)
    # z0: z coordinate of center of the array (um)
    # block_width: width of the blocks being stacked (um)
    # side_width: width of the wood pile (um)
    # pile_height: height of the wood pile (um)
    # n_rows: number of blocks on each floor
    # n_floors: number of stackings in the wood pile
    # medium: medium of the blocks
    # reference_plane
    # axis

    boxes = []
    spacing = 0.5 * side_width / n_rows
    for i in range(0, n_floors):
        for j in range(0, n_rows):
            b = td.Box(
                center=(
                    x0 + (0.5 - n_rows + 2 * j + (i % 4 == 2)) * spacing * ((i + 1) % 2),
                    y0 + (0.5 - n_rows + 2 * j + (i % 4 == 3)) * spacing * (i % 2),
                    z0 + pile_height * (-0.5 + 1 / n_floors / 2 + i / n_floors),
                ),
                size=(
                    block_width * ((i + 1) % 2) + side_width * (i % 2),
                    block_width * (i % 2) + side_width * ((i + 1) % 2),
                    pile_height / n_floors,
                ),
            )
            boxes.append(b)
    structure = td.Structure(geometry=td.GeometryGroup(geometries=boxes), medium=medium)
    return structure
[17]:
s = wood_pile(0, 0, 0, 0.2, 1.2, 1.4, 4, 6, td.Medium(permittivity=3.48**2))

sim = td.Simulation(
    size=[3, 3, 3],
    grid_spec=td.GridSpec.auto(wavelength=1.55),
    structures=[s],
    run_time=1,
)

sim.plot_3d()

FCC Sphere Array#

The function fcc_pc returns an FCC PC crystal of spheres, with tunable sphere radius, spacing, and number of spheres in each dimension.

[18]:
def fcc_pc(x0, y0, z0, spacing, R, n_x, n_y, n_z, medium):
    # parameters
    # ------------------------------------------------------------
    # x0: x coordinate of center of the array (um)
    # y0: y coordinate of center of the array (um)
    # z0: z coordinate of center of the array (um)
    # spacing: spacing between the centers of the spheres (um)
    # R: radius of the spheres
    # n_x: number of spheres in x direction
    # n_y: number of spheres in y direction
    # n_z: number of spheres in z direction
    # medium: medium of the spheres

    spheres = []
    center_x, center_y, center_z = (
        x0 - (n_x - 1) * spacing,
        y0 - (n_y - 1) * spacing,
        z0 - (n_z - 1) * spacing,
    )
    for i in range(0, n_x):
        for j in range(0, n_y):
            for k in range(0, n_z):
                for l in range(0, 4):  # single crystal unit
                    s = td.Sphere(
                        center=(
                            center_x + (2 * i + (l % 3 == 0) * (-1) ** (l < 1)) * spacing,
                            center_y + (2 * j + 0.5 * (-1) ** (l % 2)) * spacing,
                            center_z + (2 * k + 0.5 * (-1) ** (l > 1)) * spacing,
                        ),
                        radius=R,
                    )
                    spheres.append(s)
    structure = td.Structure(geometry=td.GeometryGroup(geometries=spheres), medium=medium)
    return structure
[19]:
s = fcc_pc(0, 0, 0, 0.5, 0.2, 4, 6, 5, td.Medium(permittivity=3.48**2))

sim = td.Simulation(
    size=[10, 10, 10],
    grid_spec=td.GridSpec.auto(wavelength=1.55),
    structures=[s],
    run_time=1,
)

sim.plot_3d()

BCC Sphere Array#

The function bcc_pc returns a BCC PC crystal of spheres, with tunable sphere radius, spacing, and number of spheres in each dimension.

[20]:
def bcc_pc(x0, y0, z0, spacing, R, n_x, n_y, n_z, medium):
    # parameters
    # ------------------------------------------------------------
    # x0: x coordinate of center of the array (um)
    # y0: y coordinate of center of the array (um)
    # z0: z coordinate of center of the array (um)
    # spacing: spacing between the centers of the spheres (um)
    # R: radius of the spheres
    # n_x: number of spheres in x direction
    # n_y: number of spheres in y direction
    # n_z: number of spheres in z direction
    # medium: medium of the spheres

    spheres = []
    center_x, center_y, center_z = (
        x0 - (n_x - 1) * spacing,
        y0 - (n_y - 1) * spacing,
        z0 - (n_z - 1) * spacing,
    )
    for i in range(0, n_x):
        for j in range(0, n_y):
            for k in range(0, 2 * n_z - 1):
                s = td.Sphere(
                    center=(
                        center_x + spacing * (2 * i + 0.5 * (-1) ** ((k + 1) % 2)),
                        center_y + spacing * (2 * j + 0.5 * (-1) ** (k % 2)),
                        center_z + k * spacing,
                    ),
                    radius=R,
                )
                spheres.append(s)
    structure = td.Structure(geometry=td.GeometryGroup(geometries=spheres), medium=medium)
    return structure
[21]:
s = bcc_pc(0, 0, 0, 0.5, 0.2, 4, 6, 5, td.Medium(permittivity=3.48**2))

sim = td.Simulation(
    size=[10, 10, 10],
    grid_spec=td.GridSpec.auto(wavelength=1.55),
    structures=[s],
    run_time=1,
)

sim.plot_3d()
[ ]: