Generic-PDK grating coupler: fiber to chip¶
Use GDSFactory's built-in grating_coupler_elliptical_arbitrary to simulate coupling from a tilted Gaussian beam into a silicon waveguide. The 16-period grating has a 14 µm taper and a short straight input for guided-mode measurement. The FDTD cell size is 60 nm; 21 wavelength samples keep the run fast.
from math import cos, radians, sin
import gdsfactory as gf
from gsim import fdtd
FIBER_ANGLE_DEG = 15.0
FIBER_HEIGHT_UM = 1.05
BEAM_WAIST_UM = 5.0
BEAM_OFFSET_UM = -0.75
Z_BOUNDS = (-1.0, 1.55)
Build the generic-PDK grating coupler¶
pdk = gf.gpdk.PDK
pdk.activate()
# Explicit gap and tooth widths make it easy to try an apodized pattern.
# These uniform values preserve the pitch and tooth width of the tuned design.
gaps_um = [0.324] * 16
widths_um = [0.343] * 16
component = gf.Component()
grating = component.add_ref(
gf.components.grating_coupler_elliptical_arbitrary(
gaps=gaps_um,
widths=widths_um,
fiber_angle=FIBER_ANGLE_DEG,
taper_length=14.0,
taper_angle=40.0,
wavelength=1.510,
)
)
straight = component.add_ref(gf.components.straight(length=0.5))
straight.connect("o2", grating.ports["o1"])
component.add_port("o1", port=straight.ports["o1"])
component.add_port("o2", port=grating.ports["o2"])
component

Place the fiber beam¶
The grating's vertical port o2 locates the tooth array; the straight waveguide provides the guided output port o1. Shift the beam 0.75 µm toward the taper to keep the beam centered at the position used for the tuned grating.
fiber_port = component.ports["o2"]
fiber_x_um, fiber_y_um = map(float, fiber_port.center)
beam_x_um = fiber_x_um + BEAM_OFFSET_UM
beam_size_um = (2 * BEAM_WAIST_UM, 2 * BEAM_WAIST_UM, 0.0)
beam_angle_rad = radians(FIBER_ANGLE_DEG)
beam_direction = (-sin(beam_angle_rad), 0.0, -cos(beam_angle_rad))
simulation = fdtd.Simulation(pdk=pdk)
simulation.materials(background="sio2", overrides={"si": 3.47, "sio2": 1.443})
simulation.geometry(component, mesh_size_nm=500, geometry_tolerance_nm=20)
simulation.source = fdtd.GaussianBeamSource(
center_um=(beam_x_um, fiber_y_um, FIBER_HEIGHT_UM),
size_um=beam_size_um,
aperture_normal="-z",
propagation_direction=beam_direction,
e_polarization=(0.0, 1.0, 0.0),
focal_point_um=(beam_x_um, fiber_y_um, 0.22),
waist_radius_um=BEAM_WAIST_UM,
refractive_index=1.443,
wavelength_um=1.554,
wavelength_span_um=0.1,
num_wavelengths=21,
)
simulation.monitors.add_plane(
name="src_down",
center_um=(beam_x_um, fiber_y_um, 0.4),
size_um=beam_size_um,
normal="-z",
flux=True,
)
simulation.domain(padding=0.5, z_bounds=Z_BOUNDS, pml_cells=16)
simulation.solver(cell_size_nm=60, energy_decay_fraction=4e-4)
Solver(cell_size_nm=60.0, max_timesteps=None, energy_decay_fraction=0.0004, max_wall_seconds=3600.0)
Run and inspect coupling¶
The flux plane below the source measures net downward power, while guided port o1 measures transmitted waveguide power. Their ratio shows the guided fraction of net downward flux. Reflections cross the plane upward, so this is not incident-normalized coupling efficiency or a guided-port S-parameter.
fdtd-fb59abde [####################] 100% complete elapsed 0m 00s
Extracting results.tar.gz...
Downloaded 2 files to sim-data-fdtd-fb59abde
import plotly.graph_objects as go
guided_power = result.port_outputs["o1"].modal_power
downward_flux = result.monitors["src_down"].flux
# Ignore the low-power spectral tails before dividing the two powers.
usable = result.valid & (downward_flux > 0.01 * downward_flux.max())
guided_fraction_percent = 100 * guided_power[usable] / downward_flux[usable]
figure = go.Figure()
figure.add_scatter(
x=result.wavelength_um[usable],
y=guided_fraction_percent,
mode="lines+markers",
name="o1",
)
figure.update_layout(
xaxis_title="Wavelength (µm)",
yaxis_title="Guided fraction of net downward flux (%)",
)
figure