Source code for gdsfactory.components.grating_coupler_elliptical

from __future__ import annotations

from functools import partial

import numpy as np
from numpy import ndarray

import gdsfactory as gf
from gdsfactory.component import Component
from gdsfactory.geometry.functions import DEG2RAD, extrude_path
from gdsfactory.typings import CrossSectionSpec, LayerSpec


def ellipse_arc(
    a: float,
    b: float,
    x0: float,
    theta_min: float,
    theta_max: float,
    angle_step: float = 0.5,
) -> ndarray:
    """Returns an elliptical arc.

    b = a *sqrt(1-e**2)

    An ellipse with a = b has zero eccentricity (is a circle)

    Args:
        a: ellipse semi-major axis.
        b: semi-minor axis.
        x0: in um.
        theta_min: in rad.
        theta_max: in rad.
        angle_step: in rad.
    """
    theta = np.arange(theta_min, theta_max + angle_step, angle_step) * DEG2RAD
    xs = a * np.cos(theta) + x0
    xs = gf.snap.snap_to_grid(xs)
    ys = b * np.sin(theta)
    ys = gf.snap.snap_to_grid(ys)
    return np.column_stack([xs, ys])


def grating_tooth_points(
    ap: float,
    bp: float,
    xp: float,
    width: float,
    taper_angle: float,
    spiked: bool = True,
    angle_step: float = 1.0,
) -> ndarray:
    theta_min = -taper_angle / 2
    theta_max = taper_angle / 2

    backbone_points = ellipse_arc(ap, bp, xp, theta_min, theta_max, angle_step)
    spike_length = width / 3 if spiked else 0.0
    return extrude_path(
        backbone_points,
        width,
        with_manhattan_facing_angles=False,
        spike_length=spike_length,
    )


def grating_taper_points(
    a: float,
    b: float,
    x0: float,
    taper_length: float,
    taper_angle: float,
    wg_width: float,
    angle_step: float = 1.0,
) -> ndarray:
    """Returns an elliptical taper.

    Args:
        a: ellipse semi-major axis.
        b: semi-minor axis.
        x0: in um.
        taper_length: in um.
        taper_angle: in degrees.
        wg_width: in um.
        angle_step: in degrees.
    """
    taper_arc = ellipse_arc(
        a=a,
        b=b,
        x0=taper_length,
        theta_min=-taper_angle / 2,
        theta_max=taper_angle / 2,
        angle_step=angle_step,
    )

    port_position = np.array((x0, 0))
    p0 = port_position + (0, wg_width / 2)
    p1 = port_position + (0, -wg_width / 2)
    return np.vstack([p0, p1, taper_arc])


[docs] @gf.cell def grating_coupler_elliptical( taper_length: float = 16.6, taper_angle: float = 40.0, wavelength: float = 1.554, fiber_angle: float = 15.0, grating_line_width: float = 0.343, neff: float = 2.638, # tooth effective index nclad: float = 1.443, n_periods: int = 30, big_last_tooth: bool = False, layer_slab: LayerSpec | None = "SLAB150", slab_xmin: float = -1.0, slab_offset: float = 2.0, spiked: bool = True, cross_section: CrossSectionSpec = "xs_sc", polarization: str = "te", **kwargs, ) -> Component: r"""Grating coupler with parametrization based on Lumerical FDTD simulation. Args: taper_length: taper length from input. taper_angle: grating flare angle. wavelength: grating transmission central wavelength (um). fiber_angle: fibre angle in degrees determines ellipticity. grating_line_width: in um. neff: tooth effective index. nclad: cladding effective index. n_periods: number of periods. big_last_tooth: adds a big_last_tooth. layer_slab: layer that protects the slab under the grating. slab_xmin: where 0 is at the start of the taper. slab_offset: in um. spiked: grating teeth have sharp spikes to avoid non-manhattan drc errors. cross_section: specification (CrossSection, string or dict). polarization: te or tm. kwargs: cross_section settings. .. code:: fiber / / / / / / / / _|-|_|-|_|-|___ layer layer_slab | o1 ______________| """ xs = gf.get_cross_section(cross_section, **kwargs) wg_width = xs.width layer = xs.layer # Compute some ellipse parameters sthc = np.sin(fiber_angle * DEG2RAD) d = neff**2 - nclad**2 * sthc**2 a1 = wavelength * neff / d b1 = wavelength / np.sqrt(d) x1 = wavelength * nclad * sthc / d a1 = round(a1, 3) b1 = round(b1, 3) x1 = round(x1, 3) period = a1 + x1 c = gf.Component() c.info["polarization"] = polarization c.info["wavelength"] = wavelength # Make the taper p = taper_length / period a_taper = a1 * p b_taper = b1 * p x_taper = x1 * p x_output = a_taper + x_taper - taper_length pts = grating_taper_points( a=a_taper, b=b_taper, x0=x_output, taper_length=x_taper, taper_angle=taper_angle, wg_width=wg_width, ) c.add_polygon(pts, layer) for section in xs.sections[1:]: pts = grating_taper_points( a=a_taper, b=b_taper, x0=x_output, taper_length=x_taper, taper_angle=taper_angle, wg_width=section.width, ) c.add_polygon(pts, section.layer) width = gf.snap.snap_to_grid(grating_line_width) gap = gf.snap.snap_to_grid(period - grating_line_width) xi = taper_length for p in range(n_periods): xi += gap + width / 2 p = xi / period pts = grating_tooth_points( p * a1, p * b1, p * x1, width, taper_angle, spiked=spiked ) c.add_polygon(pts, layer) xi += width / 2 w = 1.0 total_length = ( period * n_periods + taper_length + grating_line_width / 2 + period - grating_line_width + w / 2 ) if big_last_tooth: # Add last "large tooth" after the standard grating teeth a = total_length / (1 + x1 / a1) b = b1 / a1 * a x = x1 / a1 * a pts = grating_tooth_points(a, b, x, w, taper_angle, spiked=False) c.add_polygon(pts, layer) x = np.round(taper_length + x_output, 3) c.add_port( name="o1", center=(x_output, 0), width=wg_width, orientation=180, layer=layer ) if layer_slab: slab_xmin += x_output + taper_length slab_length = total_length + slab_offset slab_width = (c.ysize + 2 * slab_offset) / 2 c.add_polygon( [ (slab_xmin, slab_width), (slab_length, slab_width), (slab_length, -slab_width), (slab_xmin, -slab_width), ], layer_slab, ) x = gf.snap.snap_to_grid(x) c.add_port( name="o2", center=(x, 0), width=10, orientation=0, layer=layer, port_type=f"optical_{polarization}", ) xs.add_bbox(c) return c
grating_coupler_elliptical_tm = partial( grating_coupler_elliptical, grating_line_width=0.707, polarization="tm", taper_length=30, slab_xmin=-2, neff=1.8, n_periods=16, ) grating_coupler_elliptical_te = grating_coupler_elliptical if __name__ == "__main__": # c = grating_coupler_elliptical_tm(taper_length=30) # c = grating_coupler_elliptical_te(cladding_layers=((2, 0), (3, 0))) # c = grating_coupler_elliptical(layer=(2, 0), taper_length=50, slab_xmin=-5) # print(c.polarization) # print(c.wavelength) # print(c.ports) # c.pprint() # c = gf.c.extend_ports(c) # c = gf.routing.add_fiber_array(grating_coupler=grating_coupler_elliptical, with_loopback=False) # c = gf.components.grating_coupler_elliptical_te() c = gf.components.grating_coupler_elliptical_tm(cross_section="xs_rc_bbox") c.show(show_ports=True)