Component library¶
ph.library builds parameterised photonic components as ready-to-use geometry plus labelled Port definitions.
Types¶
Component
dataclass
¶
Port
dataclass
¶
Port(
name: str,
center_um: Tuple[float, float, float],
axis: str,
width_um: float,
out_direction: Optional[str] = None,
thickness_um: Optional[float] = None,
thickness_axis: str = "z",
mode: str = "TE0",
medium: Optional[Medium] = None,
window_um: Optional[Tuple[float, float]] = None,
source_offset_um: Optional[float] = None,
dl_um: Optional[float] = None,
supersample: int = 8,
num_modes: Optional[int] = None,
)
A waveguide port: the plane where a mode is launched or read out.
center_um is the port's centre in the user's frame; its component along
axis is the readout plane and the other two are the waveguide's
transverse centre. width_um and thickness_um size the default mode
window, width/2 + pad by thickness/2 + pad: the pad reaches 20 %
beyond the distance at which the mode's intensity is 30 dB below its peak,
at the longest wavelength the port reads, from the mode's effective index
and the highest cladding index at the core's faces (the rule of
photonhub.mode_window_um, refined by the port's own mode solve). window_um gives the two half-extents explicitly. mode is a family and an index in
one string ("TE0", "TM1"). out_direction is the side of the
plane that faces the domain wall, "+" or "-"; None lets the
simulation infer it from where the port sits relative to the device.
source_offset_um is how far behind the plane, toward the wall, a launch
sits when this port is driven. It is a LENGTH, defaulting to a third of a
wavelength in the background: what the launch injects that is not the
guide's discrete mode interferes with it at this port's plane, at a phase
set by the distance in microns, so a standoff counted in cells would move
that phase with the mesh and this port's reading would drift with it.
medium
is the port waveguide's material, used to extend the guide
through the wall. dl_um, supersample and num_modes are the
mode-solve frame controls.
sim.ports reads each port back as a HeldPort, a Port whose
center_um is in that simulation's stored frame, from the fitted box's
low corner. dataclasses.replace and a copy of the object keep it so. A
new Port built from its fields, Port(held.name, held.center_um, ...)
or Port(**dataclasses.asdict(held)), is a new port in the user's frame.
monitor_name
property
¶
The portable filename token that names this port's readout monitor:
the name itself when it already is one, otherwise + spelled
_plus and any other character outside letters, digits, _,
-, . replaced by _ (a library crossing's "x+" reads out
as "x_plus"). Results are looked up by the port name, not this.
in_direction
property
¶
The launch direction when this port is driven, into the device.
hv_center_um
¶
The transverse centre in the plane's natural (horizontal, vertical) order, the order the Yee mode solver takes.
with_out_direction
¶
A copy with out_direction set (the simulation infers it when None).
Components¶
straight
¶
straight(
length_um: float,
*,
width_um: float = DEFAULT_WIDTH_UM,
thickness_um: float = DEFAULT_THICKNESS_UM,
medium: Medium = SILICON,
center_um: Vec3Um = (0.0, 0.0, 0.0),
axis: AxisName = "x",
thickness_axis: AxisName = "z",
) -> Component
A straight strip waveguide: one Box with input/output ports at the
two ends along axis.
bend
¶
bend(
radius_um: float,
*,
width_um: float = DEFAULT_WIDTH_UM,
thickness_um: float = DEFAULT_THICKNESS_UM,
medium: Medium = SILICON,
center_um: Vec3Um = (0.0, 0.0, 0.0),
thickness_axis: AxisName = "z",
) -> Component
A 90-degree waveguide bend: an annular-sector Cylinder extruded
along thickness_axis (the slab normal), with inner_radius /
radius straddling radius_um +/- width_um/2 and a pi/2 sweep.
center_um is the bend's center of curvature. The cylinder sweeps the
first quadrant in the transverse (u, v) plane, so the two arc ends point
along the two in-plane axes. With the default thickness_axis="z" the
transverse plane is (x, y): the start end (angle 0) sits at +u and
has a tangent along +v; the stop end (angle pi/2) sits at +v with a
tangent along -u. A port's axis names the normal of its mode plane.
taper
¶
taper(
length_um: float,
width1_um: float,
width2_um: float,
*,
thickness_um: float = DEFAULT_THICKNESS_UM,
medium: Medium = SILICON,
center_um: Vec3Um = (0.0, 0.0, 0.0),
axis: AxisName = "x",
thickness_axis: AxisName = "z",
) -> Component
A linear width taper: a trapezoidal Polygon (4 vertices) whose
cross-section width grows width1_um -> width2_um along axis.
Ports at each end with the local widths.
cosine_taper
¶
cosine_taper(
length_um: float,
width1_um: float,
width2_um: float,
*,
peak_width_um: float | None = None,
n_points: int = 48,
thickness_um: float = DEFAULT_THICKNESS_UM,
medium: Medium = SILICON,
center_um: Vec3Um = (0.0, 0.0, 0.0),
axis: AxisName = "x",
thickness_axis: AxisName = "z",
) -> Component
A cosine ("beam shaping") width taper: a curved-sidewall Polygon
whose full width follows W(x) = W_m*cos(pi*x/(2*L0)) from width1_um to
width2_um along axis (Chandran et al., Opt. Lett. 45, 6230 (2020)).
peak_width_um (W_m) is the maximum width reached inside the taper, the convex lens that focuses the beam; it must be >= both end widths and
defaults to the wider end (a monotonic cosine taper). The sidewall is
discretized into n_points samples per edge; ports carry the end widths.
crossing
¶
crossing(
*,
width_um: float = DEFAULT_WIDTH_UM,
arm_length_um: Optional[float] = None,
lead_um: Optional[float] = None,
thickness_um: float = DEFAULT_THICKNESS_UM,
medium: Medium = SILICON,
center_um: Vec3Um = (0.0, 0.0, 0.0),
thickness_axis: AxisName = "z",
) -> Component
A waveguide crossing: two Box waveguides crossed at 90 degrees in
the slab plane. Four ports, one per arm end. The arm is given either as
arm_length_um, port to port (default 3.0), or as lead_um, the
straight guide from the crossing square's edge to each port.
cosine_taper_crossing
¶
cosine_taper_crossing(
*,
wg_width_um: float,
junction_width_um: float,
peak_width_um: float,
taper_length_um: float,
arm_length_um: Optional[float] = None,
lead_um: Optional[float] = None,
n_points: int = 48,
thickness_um: float = DEFAULT_THICKNESS_UM,
medium: Medium = SILICON,
center_um: Vec3Um = (0.0, 0.0, 0.0),
thickness_axis: AxisName = "z",
) -> Component
A beam-shaped ultra-compact waveguide crossing (Chandran et al., Opt.
Lett. 45, 6230 (2020)). Four convex cosine tapers connect the single-mode
routing guide (wg_width_um = W_in) to a solid central junction of side
junction_width_um (= W_out); each taper bulges to a peak width
peak_width_um (= W_m) in its middle, the lens that focuses the beam
across the intersection so it couples to the through arm instead of
scattering into the cross arms.
Each arm runs center -> port along one in-plane axis: the shared central
Box junction (W_out square), a cosine taper of taper_length_um (= L_t)
from junction_width_um at the junction to wg_width_um at the routing
end (peaking at peak_width_um inside), then a straight routing stub out to
the port at arm_length_um from center. Four ports, each wg_width_um
wide. arm_length_um must be >= junction_width_um/2 + taper_length_um;
lead_um gives the stub's length instead, from the taper's end to the
port. The paper's footprint is junction_width_um + 2*taper_length_um.
spline_taper_crossing
¶
spline_taper_crossing(
*,
wg_width_um: float,
taper_length_um: float,
widths_um,
arm_length_um: Optional[float] = None,
lead_um: Optional[float] = None,
interpolation: str = "natural",
n_points: int = 121,
thickness_um: float = DEFAULT_THICKNESS_UM,
medium: Medium = SILICON,
center_um: Vec3Um = (0.0, 0.0, 0.0),
thickness_axis: AxisName = "z",
) -> Component
A single-etch waveguide crossing made of four identical knot-defined tapers that share a centre point (Ma et al., Opt. Express 21, 29374 (2013), and the Sanchis et al. 2009 / Zhang et al. 2013 designs it descends from).
Each arm runs centre -> port along one in-plane axis: a taper of
taper_length_um (= L) whose full width is the interpolation curve
(see spline_width_profile) through the equally spaced knots
widths_um, listed the paper's way, w1 at the routing-waveguide end
and wN at the crossing centre, followed by a straight routing stub of
wg_width_um out to the port at arm_length_um from centre (or
lead_um beyond the taper's end). The four
tapers overlap at the centre; their union is the device (the wide knots next
to the centre "merge into the cross-sectional region", as the paper puts
it), so the near-centre width profile is buried inside the junction and
only the outer profile is a visible sidewall. Each taper sidewall is
sampled at n_points positions. Four ports (x-, x+, y-,
y+), each wg_width_um wide. The paper's footprint is
2 * taper_length_um.
spline_width_profile
¶
The width-vs-position function w(s) of a knot-defined taper.
widths_um are N >= 2 knot widths equally spaced along the taper,
listed from the routing-waveguide end (s = 0) to the far end
(s = length_um). interpolation is the curve drawn through them:
"natural" (cubic spline, zero end curvature, the default and the
classic "spline interpolation" of the Ma et al. crossing), "not-a-knot"
(SciPy's default cubic end condition), "pchip" (shape-preserving,
never overshoots a knot) or "linear" (straight segments, the
13-segment convention of the classic Y-junction example). Returns a callable
w(s) accepting a scalar or an array of positions in [0, length_um].
coupler
¶
coupler(
length_um: float,
*,
width_um: float = DEFAULT_WIDTH_UM,
gap_um: float = 0.2,
thickness_um: float = DEFAULT_THICKNESS_UM,
medium: Medium = SILICON,
center_um: Vec3Um = (0.0, 0.0, 0.0),
axis: AxisName = "x",
thickness_axis: AxisName = "z",
) -> Component
A directional coupler: two parallel straight waveguides separated
edge-to-edge by gap_um (along the in-plane width axis). Four ports, two per guide at the ends.
ring
¶
ring(
radius_um: float,
*,
width_um: float = DEFAULT_WIDTH_UM,
gap_um: float = 0.2,
bus_width_um: float | None = None,
bus_length_um: float | None = None,
thickness_um: float = DEFAULT_THICKNESS_UM,
medium: Medium = SILICON,
center_um: Vec3Um = (0.0, 0.0, 0.0),
axis_normal: AxisName = "z",
) -> Component
An all-pass ring resonator: a full-sweep annular Cylinder (inner =
radius - width/2, outer = radius + width/2) plus a straight bus
Box one gap_um away (edge-to-edge) from the ring's outer edge.
axis_normal is the slab normal (the ring/cylinder extrusion axis).
The bus runs along the lower-indexed in-plane axis; it is offset along the
higher-indexed in-plane axis. Two ports on the bus (input + through).
bragg_grating
¶
bragg_grating(
*,
wg_width_um: float,
corrugation_um: float,
period_um: float,
n_periods: int,
arm_length_um: Optional[float] = None,
lead_um: Optional[float] = None,
duty: float = 0.5,
thickness_um: float = DEFAULT_THICKNESS_UM,
medium: Medium = SILICON,
center_um: Vec3Um = (0.0, 0.0, 0.0),
axis: AxisName = "x",
thickness_axis: AxisName = "z",
) -> Component
A sidewall-corrugated waveguide Bragg grating (Wang et al., Opt. Express
20, 15547 (2012), strip variant). A straight strip of width wg_width_um
whose two sidewalls corrugate SYMMETRICALLY (equal ± on each side, so the
average width, hence the average effective index, is constant): over a
period_um the full width alternates between wg_width + 2*corrugation
(wide, a duty fraction of the period) and wg_width - 2*corrugation
(narrow). n_periods teeth open a photonic stopband at the Bragg
wavelength λ_B ≈ 2*n_eff*period; corrugation_um (the per-side tooth
amplitude) sets the coupling coefficient κ, hence the stopband width. Plain
routing stubs of the nominal wg_width run out to the two ports at
±arm_length_um (in at −axis, through at +axis), or lead_um
beyond the grating's ends.
y_branch
¶
y_branch(
*,
wg_width_um: float,
splitting_length_um: float,
gap_um: float,
arm_length_um: Optional[float] = None,
lead_um: Optional[float] = None,
top_radii: Tuple[float, float],
bot_radii: Tuple[float, float],
top_straight_um: float = 0.0,
bot_straight_um: float = 0.0,
output_offset_final_um: float | None = None,
fanout_length_um: float = 0.0,
region_halfwidth_um: float | None = None,
solid_junction: bool = False,
branch_tip_frac: float | None = None,
tip_gap_um: float = 0.0,
n_points: int = 60,
thickness_um: float = DEFAULT_THICKNESS_UM,
medium: Medium = SILICON,
center_um: Vec3Um = (0.0, 0.0, 0.0),
axis: AxisName = "x",
thickness_axis: AxisName = "z",
) -> Component
An asymmetric S-curve 1x2 Y-junction splitter with an arbitrary power
splitting ratio (Lin & Shi, Opt. Express 27, 14338 (2019)). In the paper's
widened solid_junction construction, top_radii=(r1, r2) and
bot_radii=(R1, R2) describe TWO successive S-shaped OUTER-BOUNDARY
segments per side: the first expands the input edge from w0/2 to the
published half-width w and the second contracts it to the outer edge of
the output guide. Each S segment is itself made from two equal-radius tangent
arcs. The paper's straight l lies between the two top-boundary S segments;
any remaining length is a straight lead-in at the input width. This is not an
output-arm-centreline construction: treating (r1, r2) as the two arcs of one
centreline S-bend erases most of the paper's split-ratio asymmetry.
solid_junction=False retains the generic constant-width two-arm routing
construction. There, each arm follows one two-radius centreline S-bend.
Because the paper's two outputs sit only gap_um apart they would remain
evanescently coupled (a directional coupler) and the per-arm power would slosh
between them, so for a clean split-ratio readout the arms are fanned apart
with a raised-cosine access bend over fanout_length_um to a decoupled
output_offset_final_um (default = the junction offset, i.e. no fanout).
Three ports: in (−axis), o_top / o_bot (+axis, offset in the width
axis), at arm_length_um from the centre or lead_um beyond the
splitting region and the access bend.