Source code for gdsfactory.components.tapers.taper_hecken

"""Hecken taper for microstrip impedance matching.

Adapted from PHIDL https://github.com/amccaugh/phidl/ by Adam McCaughan
"""

from __future__ import annotations

import numpy as np
from numpy import log

import gdsfactory as gf
from gdsfactory.component import Component
from gdsfactory.components.analog.microstrip import (
    _G,
    _find_microstrip_wire_width,
    _microstrip_v_with_Lk,
    _microstrip_Z_with_Lk,
)
from gdsfactory.typings import LayerSpec


[docs] @gf.cell_with_module_name def taper_hecken( length: float = 200, B: float = 4.0091, dielectric_thickness: float = 0.25, eps_r: float = 2, Lk_per_sq: float = 250e-12, Z1: float | None = 50, Z2: float | None = 100, width1: float | None = None, width2: float | None = None, num_pts: int = 100, layer: LayerSpec = "WG", ) -> Component: """Creates a Hecken-tapered microstrip. Args: length: Length of the microstrip. B: Controls the intensity of the taper. dielectric_thickness: Thickness of the substrate. eps_r: Dielectric constant of the substrate. Lk_per_sq: Kinetic inductance per square of the microstrip. Z1: Impedance of the left side region of the microstrip. Z2: Impedance of the right side region of the microstrip. width1: Width of the left side of the microstrip. width2: Width of the right side of the microstrip. num_pts: Number of points comprising the curve of the entire microstrip. layer: Specific layer(s) to put polygon geometry on. Returns: Component containing a Hecken-tapered microstrip. """ if width1 is not None: Z1 = _microstrip_Z_with_Lk( width1 * 1e-6, dielectric_thickness * 1e-6, eps_r, Lk_per_sq ) if width2 is not None: Z2 = _microstrip_Z_with_Lk( width2 * 1e-6, dielectric_thickness * 1e-6, eps_r, Lk_per_sq ) # Normalized length of the wire [-1 to +1] xi_list = np.linspace(-1, 1, num_pts) if Z1 is None or Z2 is None: raise ValueError( "Z1 and Z2 must be specified either directly or via width1/width2" ) Z = [np.exp(0.5 * log(Z1 * Z2) + 0.5 * log(Z2 / Z1) * _G(xi, B)) for xi in xi_list] widths = np.array( [ _find_microstrip_wire_width( z, dielectric_thickness * 1e-6, eps_r, Lk_per_sq ) * 1e6 for z in Z ] ) x = (xi_list / 2) * length # Compensate for varying speed of light in the microstrip by shortening # and lengthening sections according to the speed of light in that section v = np.array( [ _microstrip_v_with_Lk( w * 1e-6, dielectric_thickness * 1e-6, eps_r, Lk_per_sq ) for w in widths ] ) dx = np.diff(x) dx_compensated = dx * v[:-1] x_compensated = np.cumsum(dx_compensated) x = np.hstack([0, x_compensated]) / max(x_compensated) * length # Create blank device and add taper polygon c = gf.Component() xpts = np.concatenate([x, x[::-1]]) ypts = np.concatenate([widths / 2, -widths[::-1] / 2]) points = np.column_stack((xpts, ypts)) c.add_polygon(points, layer=layer) # Snap port widths to even multiples of 0.002 um (2 dbu) width1_snapped = round(widths[0] / 0.002) * 0.002 width2_snapped = round(widths[-1] / 0.002) * 0.002 c.add_port( name="o1", center=(0, 0), width=width1_snapped, orientation=180, layer=layer ) c.add_port( name="o2", center=(length, 0), width=width2_snapped, orientation=0, layer=layer ) # Add meta information about the taper c.info["num_squares"] = float(np.sum(np.diff(x) / widths[:-1])) c.info["width1"] = float(widths[0]) c.info["width2"] = float(widths[-1]) c.info["Z1"] = float(Z[0]) c.info["Z2"] = float(Z[-1]) # Note there are two values for v/c (and f_cutoff) because the speed of # light is different at the beginning and end of the taper # c.info["w"] = widths.tolist() # c.info["x"] = x.tolist() # c.info["Z"] = Z if isinstance(Z, list) else list(Z) # c.info["v/c"] = (v / 3e8).tolist() time_length = float(np.sum(np.diff(x * 1e-6) / (v[:-1]))) c.info["time_length"] = time_length c.info["f_cutoff"] = 1 / (2 * time_length) c.info["length"] = float(length) return c
if __name__ == "__main__": c = taper_hecken(Z1=50, Z2=100) c.show()