import typing as _typ
from collections.abc import Sequence as _Sequence
import numpy as _np
from . import extension as _ext
from . import time_steppers as _ts
from . import typing as _pft
from .models import analytic as _am
from .models._error import ErrorModel as _ErrorModel
from .models.mux import MuxDemuxModel as _MuxDemuxModel
from .parametric_utils import parametric_component as _parametric_component
from .time_steppers.source import RIN as _RIN
from .utils import C_0
from .utils import virtual_port_spec as _virtual_port_spec
_ThermoOpticCoeff = _pft.annotate(complex, label="dn/dT", units="1/K")
_LossTemperatureCoeff = _pft.annotate(float, label="dL/dT", units="dB/μm/K")
[docs]
@_parametric_component
def straight(
*,
length: _pft.Dimension = 10,
n_eff: complex = 2.4,
n_group: float | None = None,
reference_frequency: _pft.Frequency = C_0 / 1.55,
propagation_loss: _pft.PropagationLoss = 0.0,
dispersion: _pft.Dispersion = 0.0,
dispersion_slope: _pft.DispersionSlope = 0.0,
dn_dT: _ThermoOpticCoeff = 0.0,
dL_dT: _LossTemperatureCoeff = 0.0,
temperature: _pft.Temperature = 293.0,
reference_temperature: _pft.Temperature = 293.0,
) -> _ext.Component:
"""Abstract waveguide section.
Based on :class:`photonforge.AnalyticWaveguideModel`.
Args:
length: Waveguide length.
n_eff: Effective refractive index (loss can be included here by
using complex values).
n_group: Group index. If ``None``, the value of ``n_eff`` is used.
reference_frequency: Reference frequency dispersion coefficients.
propagation_loss: Propagation loss.
dispersion: Chromatic dispersion coefficient.
dispersion_slope: Chromatic dispersion slope.
dn_dT: Temperature sensitivity for ``n_eff``.
dL_dT: Temperature sensitivity for ``propagation_loss``.
temperature: Operating temperature.
reference_temperature: Reference temperature.
"""
model = _am.AnalyticWaveguideModel(
n_eff=n_eff,
length=length,
propagation_loss=propagation_loss,
n_group=n_group,
dispersion=dispersion,
dispersion_slope=dispersion_slope,
reference_frequency=reference_frequency,
dn_dT=dn_dT,
dL_dT=dL_dT,
temperature=temperature,
reference_temperature=reference_temperature,
)
comp = model.black_box_component(_virtual_port_spec(), name="WG")
return comp
[docs]
@_parametric_component
def bend(
*,
radius: _pft.Dimension = 10.0,
angle: _pft.Angle = 90.0,
n_eff: complex = 2.4,
n_group: float | None = None,
reference_frequency: _pft.Frequency = C_0 / 1.55,
propagation_loss: _pft.PropagationLoss = 0.0,
extra_loss: _pft.Loss = 0.0,
dispersion: _pft.Dispersion = 0.0,
dispersion_slope: _pft.DispersionSlope = 0.0,
dn_dT: _ThermoOpticCoeff = 0.0,
dL_dT: _LossTemperatureCoeff = 0.0,
temperature: _pft.Temperature = 293.0,
reference_temperature: _pft.Temperature = 293.0,
) -> _ext.Component:
"""Abstract bend waveguide section.
Based on :class:`photonforge.AnalyticWaveguideModel`.
Args:
radius: Bend radius.
angle: Bend angle.
n_eff: Effective refractive index (loss can be included here by
using complex values).
n_group: Group index. If ``None``, the value of ``n_eff`` is used.
reference_frequency: Reference frequency dispersion coefficients.
propagation_loss: Propagation loss.
extra_loss: Length-independent loss.
dispersion: Chromatic dispersion coefficient.
dispersion_slope: Chromatic dispersion slope.
dn_dT: Temperature sensitivity for ``n_eff``.
dL_dT: Temperature sensitivity for ``propagation_loss``.
temperature: Operating temperature.
reference_temperature: Reference temperature.
"""
model = _am.AnalyticWaveguideModel(
n_eff=n_eff,
reference_frequency=reference_frequency,
length=float(abs(angle / 180.0 * _np.pi * radius)),
propagation_loss=propagation_loss,
extra_loss=extra_loss,
n_group=n_group,
dispersion=dispersion,
dispersion_slope=dispersion_slope,
dn_dT=dn_dT,
dL_dT=dL_dT,
temperature=temperature,
reference_temperature=reference_temperature,
)
comp = _ext.Component("WG Bend")
comp.properties.__thumbnail__ = "bend"
port_spec = _virtual_port_spec()
width = port_spec.width
comp.add_port(_ext.Port((0, 0), 0, port_spec))
if angle > 0:
radians = (angle - 90) / 180.0 * _np.pi
endpoint = (radius * _np.cos(radians), radius * (1 + _np.sin(radians)))
comp.add(_am._bb_layer, _ext.Path((0, 0), width).arc(-90, angle - 90, radius))
comp.add_port(_ext.Port(endpoint, angle - 180, port_spec))
else:
radians = (90 + angle) / 180.0 * _np.pi
endpoint = (radius * _np.cos(radians), radius * (-1 + _np.sin(radians)))
comp.add(_am._bb_layer, _ext.Path((0, 0), width).arc(90, angle + 90, radius))
comp.add_port(_ext.Port(endpoint, angle + 180, port_spec))
_am._add_bb_text(comp, width)
model_name = model.__class__.__name__[:-5]
comp.add_model(model, model_name)
return comp
[docs]
@_parametric_component
def y_splitter(
*,
insertion_loss: _pft.Loss = 0.01,
return_loss0: _pft.Loss = 40.0,
return_loss1: _pft.Loss = 40.0,
isolation: _pft.Fraction = 0.0,
) -> _ext.Component:
"""Abstract 3-port optical Y-splitter.
Based on :class:`photonforge.PowerSplitterModel`. This is a lumped,
symmetric splitter with no geometry, phase, or dispersion.
Args:
insertion_loss: Total insertion loss. The transmission amplitude per
arm is scaled as ``10^(-IL/20) / sqrt(2)``.
return_loss0: Return loss seen from the input port.
return_loss1: Return loss seen from either output port.
isolation: Output-to-output leakage amplitude.
"""
a = 10.0 ** (-insertion_loss / 20.0)
r0 = 10.0 ** (-return_loss0 / 20.0)
r1 = 10.0 ** (-return_loss1 / 20.0)
i = float(isolation)
t = a / _np.sqrt(2.0)
model = _am.PowerSplitterModel(t=t, i=i, r0=r0, r1=r1)
comp = model.black_box_component(_virtual_port_spec(), name="Y Splitter")
return comp
@_parametric_component
def circulator(
*,
insertion_loss: _pft.Loss = 0.01,
return_loss: _pft.Loss = 40.0,
isolation: _pft.Loss = 40.0,
) -> _ext.Component:
"""Abstract 3-port optical circulator.
Based on :class:`photonforge.CirculatorModel`. This is a lumped device
with no geometry, phase, or dispersion.
Args:
insertion_loss: Insertion loss in the transmission paths.
return_loss: Return loss applied to each port.
isolation: Insertion loss in the leakage paths.
"""
t = 10.0 ** (insertion_loss / -20.0)
r = 10.0 ** (return_loss / -20.0)
i = 10.0 ** (isolation / -20.0)
model = _am.CirculatorModel(t=t, i=i, r=r)
comp = model.black_box_component(_virtual_port_spec(), name="Circulator")
return comp
@_parametric_component
def isolator(
*,
insertion_loss: _pft.Loss = 0.01,
isolation: _pft.Loss = 40.0,
return_loss0: _pft.Loss = 40.0,
return_loss1: _pft.Loss = 40.0,
) -> _ext.Component:
"""Abstract 2-port optical isolator.
Based on :class:`photonforge.IsolatorModel`. This is a lumped device
with no geometry, phase, or dispersion.
Args:
insertion_loss: Insertion loss in the transmission path.
isolation: Insertion loss in the isolation path.
return_loss0: Return loss as seen from port 0.
return_loss1: Return loss as seen from port 1.
"""
t = 10.0 ** (insertion_loss / -20.0)
i = 10.0 ** (isolation / -20.0)
r0 = 10.0 ** (return_loss0 / -20.0)
r1 = 10.0 ** (return_loss1 / -20.0)
model = _am.IsolatorModel(t=t, i=i, r0=r0, r1=r1)
comp = model.black_box_component(_virtual_port_spec(), name="Isolator")
return comp
[docs]
@_parametric_component
def taper(
*,
insertion_loss: _pft.Loss = 0.01,
return_loss0: _pft.Loss = 40.0,
return_loss1: _pft.Loss = 40.0,
) -> _ext.Component:
"""Abstract waveguide taper.
Based on :class:`photonforge.TwoPortModel`. Constant-phase, lumped model
with no dispersion.
Args:
insertion_loss: Total one-pass insertion loss.
return_loss0: Return loss as seen from port 0.
return_loss1: Return loss as seen from port 1.
"""
t = 10 ** (-insertion_loss / 20.0)
r0 = 10 ** (-return_loss0 / 20.0)
r1 = 10 ** (-return_loss1 / 20.0)
model = _am.TwoPortModel(t=t, r0=r0, r1=r1)
comp = model.black_box_component(_virtual_port_spec(), name="WG Taper")
comp.properties.__thumbnail__ = "taper"
return comp
[docs]
@_parametric_component
def transition(
*,
insertion_loss: _pft.Loss = 0.01,
return_loss0: _pft.Loss = 40.0,
return_loss1: _pft.Loss = 40.0,
) -> _ext.Component:
"""Abstract waveguide transition.
Based on :class:`photonforge.TwoPortModel`. Constant-phase, lumped model
with no dispersion.
Args:
insertion_loss: Total one-pass insertion loss.
return_loss0: Return loss as seen from port 0.
return_loss1: Return loss as seen from port 1.
"""
t = 10 ** (-insertion_loss / 20.0)
r0 = 10 ** (-return_loss0 / 20.0)
r1 = 10 ** (-return_loss1 / 20.0)
model = _am.TwoPortModel(t=t, r0=r0, r1=r1)
comp = model.black_box_component(_virtual_port_spec(), name="WG Transition")
comp.properties.__thumbnail__ = "transition"
return comp
[docs]
@_parametric_component
def impedance_mismatch(*, z0: _pft.Impedance = 50.0, z1: _pft.Impedance = 35.0) -> _ext.Component:
"""Abstract transmission line impedance mismatch.
Based on :class:`photonforge.TwoPortModel`. Constant-phase, lumped model
with no dispersion.
Args:
z0: Impedance on port 0.
z1: Impedance on port 1.
"""
t = 2 * (z0 * z1) ** 0.5 / (z1 + z0)
r0 = (z1 - z0) / (z1 + z0)
model = _am.TwoPortModel(t=t, r0=r0, r1=-r0)
comp = model.black_box_component(
_virtual_port_spec(classification="electrical"), name="Impedance Mismatch"
)
comp.properties.__thumbnail__ = "electrical_mismatch"
return comp
[docs]
@_parametric_component
def grating_coupler(
*,
insertion_loss: _pft.Loss = 1.5,
return_loss_fiber: _pft.Loss = 40.0,
return_loss_wg: _pft.Loss = 35.0,
) -> _ext.Component:
"""Abstract grating coupler.
Based on :class:`photonforge.TwoPortModel`. Constant-phase, lumped
model. Port 0 is the fiber side and port 1 is the waveguide side.
Args:
insertion_loss: Total insertion loss.
return_loss_fiber: Return loss seen from the fiber side.
return_loss_wg: Return loss seen from the waveguide side.
"""
t = 10 ** (-insertion_loss / 20.0)
r0 = 10 ** (-return_loss_fiber / 20.0)
r1 = 10 ** (-return_loss_wg / 20.0)
model = _am.TwoPortModel(t=t, r0=r0, r1=r1)
comp = model.black_box_component(_virtual_port_spec(), name="Grating Coupler")
comp.properties.__thumbnail__ = "grating_coupler"
return comp
[docs]
@_parametric_component
def edge_coupler(
*,
insertion_loss: _pft.Loss = 0.5,
return_loss_fiber: _pft.Loss = 40.0,
return_loss_wg: _pft.Loss = 40.0,
) -> _ext.Component:
"""Abstract edge coupler.
Based on :class:`photonforge.TwoPortModel`. Constant-phase, lumped
model. Port 0 is the fiber side and port 1 is the chip/waveguide side.
Args:
insertion_loss: Total insertion loss including scattering.
return_loss_fiber: Return loss seen from the fiber side.
return_loss_wg: Return loss seen from the chip side.
"""
t = 10 ** (-insertion_loss / 20.0)
r0 = 10 ** (-return_loss_fiber / 20.0)
r1 = 10 ** (-return_loss_wg / 20.0)
model = _am.TwoPortModel(t=t, r0=r0, r1=r1)
comp = model.black_box_component(_virtual_port_spec(), name="Edge Coupler")
comp.properties.__thumbnail__ = "edge_coupler"
return comp
[docs]
@_parametric_component
def directional_coupler(
*,
coupling_ratio: _pft.Fraction = 0.5,
propagation_length: _pft.Dimension = 0.0,
cross_phase: _pft.Angle = -90.0,
insertion_loss: _pft.Loss = 0.0,
isolation: complex = 0.0,
reflection: complex = 0.0,
n_eff: complex = 2.4,
n_group: float | None = None,
reference_frequency: _pft.Frequency = C_0 / 1.55,
) -> _ext.Component:
r"""Abstract directional coupler.
Based on :class:`photonforge.DirectionalCouplerModel`. The transmission
and cross-port amplitudes are derived from the coupling ratio and
insertion loss:
.. math::
t &= a \sqrt{1 - \kappa}
c &= a \sqrt{\kappa}\, e^{j\theta}
in which ``a = 10^(-IL/20)`` and ``IL`` is the insertion loss.
Args:
coupling_ratio: Fraction of input power coupled to the cross port.
propagation_length: Extra physical length contributing to phase
delay.
cross_phase: Relative phase on the cross port.
insertion_loss: Total excess loss, including reflection and
isolation contributions.
isolation: Complex leakage amplitude to the isolated port.
reflection: Complex reflection amplitude back into the input ports.
n_eff: Effective index used for phase accumulation.
n_group: Group index. If ``None``, the value of ``n_eff`` is used.
reference_frequency: Reference frequency for dispersion.
"""
a = 10 ** (-insertion_loss / 20)
t = a * _np.sqrt(1 - coupling_ratio)
c = a * _np.sqrt(coupling_ratio) * _np.exp(1j * (cross_phase / 180 * _np.pi))
model = _am.DirectionalCouplerModel(
t=t,
c=c,
i=isolation,
r=reflection,
propagation_length=propagation_length,
n_eff=n_eff,
n_group=n_group,
reference_frequency=reference_frequency,
)
comp = model.black_box_component(_virtual_port_spec(), name="Directional Coupler")
return comp
[docs]
@_parametric_component
def crossing(
*,
t: complex = 1.0,
x: complex = 0.0,
r: complex = 0.0,
propagation_length: _pft.Dimension = 0.0,
n_eff: complex = 2.4,
n_group: float | None = None,
reference_frequency: _pft.Frequency = C_0 / 1.55,
) -> _ext.Component:
"""Abstract waveguide crossing.
Based on :class:`photonforge.CrossingModel`. Four-port, single-mode
crossing with straight-through, cross, and reflection amplitudes.
Args:
t: Complex straight-through transmission amplitude.
x: Complex cross-port transmission amplitude.
r: Complex reflection amplitude.
propagation_length: Extra optical length used for phase
accumulation.
n_eff: Effective index for phase. The imaginary part may encode
attenuation.
n_group: Group index for first-order dispersion. If ``None``, the
value of ``n_eff`` is used.
reference_frequency: Reference frequency for dispersion.
"""
model = _am.CrossingModel(
t=t,
x=x,
r=r,
propagation_length=propagation_length,
n_eff=n_eff,
n_group=n_group,
reference_frequency=reference_frequency,
)
comp = model.black_box_component(_virtual_port_spec(), name="WG Crossing")
return comp
_dwdm_freqs = _np.arange(193.7, 197.21, 0.1) * 1e12
@_parametric_component
def mux_demux(
*,
frequencies: _pft.annotate(_Sequence[_pft.Frequency], minItems=1) = _dwdm_freqs,
bandwidth: _pft.Frequency = 0.100e12,
order: _pft.annotate(float, minimum=1) = 4,
insertion_loss: _pft.Loss = 0.01,
group_delay: _pft.TimeDelay = 0,
reflection: complex = 0,
temperature_sensitivity: _pft.annotate(float, units="Hz/K") = 0.0,
temperature: _pft.Temperature = 293.0,
reference_temperature: _pft.Temperature = 293.0,
) -> _ext.Component:
"""Abstract wavelength multiplexer/demultiplexer (WDM).
Based on :class:`photonforge.MuxDemuxModel`. Single-mode device with
one common port and one port per channel.
Args:
frequencies: Central frequency for each channel.
bandwidth: 3 dB bandwidth per channel.
order: Super-Gaussian order for the passband shape.
insertion_loss: Insertion loss per channel.
group_delay: Constant group delay per channel.
reflection: Reflection coefficient for incident fields.
temperature_sensitivity: Temperature sensitivity for channel
centers.
temperature: Operating temperature.
reference_temperature: Reference temperature.
"""
model = _MuxDemuxModel(
frequencies=frequencies,
bandwidth=bandwidth,
order=order,
insertion_loss=insertion_loss,
group_delay=group_delay,
reflection=reflection,
temperature_sensitivity=temperature_sensitivity,
temperature=temperature,
reference_temperature=reference_temperature,
)
comp = model.black_box_component(_virtual_port_spec(), name="Mux/Demux")
return comp
[docs]
@_parametric_component
def electrical_termination(
*,
return_loss: _pft.Loss = 60.0,
) -> _ext.Component:
"""Abstract electrical one-port termination.
Based on :class:`photonforge.TerminationModel`. The reflection
coefficient magnitude is ``10^(-RL/20)``.
Args:
return_loss: Return loss. Larger values correspond to a
better match (smaller reflection).
"""
r_mag = 10.0 ** (-return_loss / 20.0)
r_mag = float(_np.clip(r_mag, 0.0, 1.0)) # safety clamp
model = _am.TerminationModel(r=r_mag)
comp = model.black_box_component(
_virtual_port_spec(classification="electrical"), name="Electrical Termination"
)
comp.properties.__thumbnail__ = "electrical_termination"
return comp
[docs]
@_parametric_component
def optical_termination(
*,
return_loss: _pft.Loss = 60.0,
) -> _ext.Component:
"""Abstract optical one-port termination.
Based on :class:`photonforge.TerminationModel`. The reflection
coefficient magnitude is ``10^(-RL/20)``.
Args:
return_loss: Return loss. Larger values correspond to a
better match (smaller reflection).
"""
r_mag = 10.0 ** (-return_loss / 20.0)
r_mag = float(_np.clip(r_mag, 0.0, 1.0)) # safety clamp
model = _am.TerminationModel(r=r_mag)
comp = model.black_box_component(_virtual_port_spec(), name="WG Termination")
return comp
[docs]
@_parametric_component
def polarization_splitter_rotator(
*,
t_0: complex = 1.0 + 0.0j,
t_1: complex = 1.0 + 0.0j,
x_0_to_p2: complex = 0.0 + 0.0j,
x_1_to_p1: complex = 0.0 + 0.0j,
leak_p1_p2: complex = 0.0 + 0.0j,
r_00: complex = 0.0 + 0.0j,
r_01: complex = 0.0 + 0.0j,
r_10: complex = 0.0 + 0.0j,
r_11: complex = 0.0 + 0.0j,
r_20: complex = 0.0 + 0.0j,
r_21: complex = 0.0 + 0.0j,
all_multimode: bool = False,
pol_out: _typ.Literal[0, 1] = 0,
) -> _ext.Component:
"""Abstract polarization splitter-rotator.
Based on :class:`photonforge.PolarizationSplitterRotatorModel`. All
S-parameters are frequency-independent constants.
Args:
t_0: Functional transmission from P0 mode 0 to P1.
t_1: Functional transmission from P0 mode 1 to P2.
x_0_to_p2: Crosstalk from P0 mode 0 to P2.
x_1_to_p1: Crosstalk from P0 mode 1 to P1.
leak_p1_p2: Leakage between the output ports at the functional mode.
r_00: Reflection at P0, mode 0.
r_01: Reflection at P0, mode 1.
r_10: Reflection at P1, mode 0.
r_11: Reflection at P1, mode 1. Unused when ``all_multimode`` is
``False``.
r_20: Reflection at P2, mode 0.
r_21: Reflection at P2, mode 1. Unused when ``all_multimode`` is
``False``.
all_multimode: If ``False``, P0 is 2-mode and P1/P2 are single-mode
(4×4 reduced matrix). If ``True``, all ports are 2-mode (6×6 full
matrix).
pol_out: Output mode index at P1/P2 for the functional and crosstalk
paths. Only used when ``all_multimode=True``.
"""
if not all_multimode:
# === Layout A: 4-port effective topology ===
specs = (_virtual_port_spec(2), _virtual_port_spec())
model = _am.PolarizationSplitterRotatorModel(
# Input reflections (P0@m0,m1); no inter-mode reflection parameterized
s00=r_00,
s01=0.0 + 0.0j,
s11=r_01,
# Functional & crosstalk paths to P1@m0
s02=t_0, # P0@0 → P1@0
s12=x_1_to_p1, # P0@1 → P1@0 (crosstalk)
# Functional & crosstalk paths to P2@m0
s04=x_0_to_p2, # P0@0 → P2@0 (crosstalk)
s14=t_1, # P0@1 → P2@0
# Output reflections (kept mode 0 at outputs)
s22=r_10, # P1@0 reflection
s44=r_20, # P2@0 reflection
# Output↔output leakage (kept mode)
s24=leak_p1_p2,
# Zero everything else explicitly touched by signature
s23=0.0 + 0.0j,
s25=0.0 + 0.0j,
s33=0.0 + 0.0j,
s34=0.0 + 0.0j,
s35=0.0 + 0.0j,
s45=0.0 + 0.0j,
s55=0.0 + 0.0j,
# Reduce to 4-port view on output mode 0
output_mode=0,
ports=None,
)
else:
# === Layout B: 6-port full topology ===
specs = (_virtual_port_spec(2), _virtual_port_spec(2))
pol = int(pol_out)
if pol not in (0, 1):
raise ValueError("pol_out must be 0 or 1 when all_multimode=True.")
kwargs = {
# Input reflections (P0)
"s00": r_00,
"s01": 0.0 + 0.0j,
"s11": r_01,
# Output reflections (P1 m0/m1, P2 m0/m1)
"s22": r_10,
"s33": r_11,
"s44": r_20,
"s55": r_21,
# Initialize all possibly used couplings to 0
"s02": 0.0 + 0.0j,
"s03": 0.0 + 0.0j, # P0@0 → P1@m0/m1
"s04": 0.0 + 0.0j,
"s05": 0.0 + 0.0j, # P0@0 → P2@m0/m1
"s12": 0.0 + 0.0j,
"s13": 0.0 + 0.0j, # P0@1 → P1@m0/m1
"s14": 0.0 + 0.0j,
"s15": 0.0 + 0.0j, # P0@1 → P2@m0/m1
"s24": 0.0 + 0.0j,
"s35": 0.0 + 0.0j, # leakage (mode-diagonal)
"s23": 0.0 + 0.0j,
"s25": 0.0 + 0.0j,
"s34": 0.0 + 0.0j,
"s45": 0.0 + 0.0j,
}
if pol == 0:
kwargs.update(
s02=t_0, # P0@0 → P1@0
s14=t_1, # P0@1 → P2@0
s04=x_0_to_p2, # P0@0 → P2@0
s12=x_1_to_p1, # P0@1 → P1@0
s24=leak_p1_p2, # P1@0 ↔ P2@0
)
else: # pol == 1
kwargs.update(
s03=t_0, # P0@0 → P1@1
s15=t_1, # P0@1 → P2@1
s05=x_0_to_p2, # P0@0 → P2@1
s13=x_1_to_p1, # P0@1 → P1@1
s35=leak_p1_p2, # P1@1 ↔ P2@1
)
model = _am.PolarizationSplitterRotatorModel(
**kwargs,
output_mode=pol,
ports=None,
)
comp = model.black_box_component(*specs, name="PSR")
return comp
[docs]
@_parametric_component
def polarization_beam_splitter(
*,
t_0: complex = 1.0 + 0.0j,
x_0: complex = 0.0 + 0.0j,
t_1: complex = 1.0 + 0.0j,
x_1: complex = 0.0 + 0.0j,
r_00: complex = 0.0 + 0.0j,
r_10: complex = 0.0 + 0.0j,
r_20: complex = 0.0 + 0.0j,
r_01: complex = 0.0 + 0.0j,
r_11: complex = 0.0 + 0.0j,
r_21: complex = 0.0 + 0.0j,
mode_routing: _pft.annotate(_Sequence[int], minItems=2, maxItems=2) = (1, 2),
) -> _ext.Component:
"""Abstract polarization beam splitter (PBS).
Based on :class:`photonforge.PolarizationBeamSplitterModel`. Three-port,
two-mode device with no mode conversion.
Args:
t_0: Mode-0 transmission amplitude to the preferred output.
x_0: Mode-0 crosstalk amplitude to the other output.
t_1: Mode-1 transmission amplitude to the preferred output.
x_1: Mode-1 crosstalk amplitude to the other output.
r_00: Reflection at P0, mode 0.
r_10: Reflection at P1, mode 0.
r_20: Reflection at P2, mode 0.
r_01: Reflection at P0, mode 1.
r_11: Reflection at P1, mode 1.
r_21: Reflection at P2, mode 1.
mode_routing: Maps each mode index to its preferred output port
index.
"""
tm = [t_0, t_1]
xm = [x_0, x_1]
t1 = [0.0 + 0.0j, 0.0 + 0.0j]
t2 = [0.0 + 0.0j, 0.0 + 0.0j]
for m, pref in enumerate(mode_routing):
if pref == 1:
t1[m] = tm[m]
t2[m] = xm[m]
elif pref == 2:
t1[m] = xm[m]
t2[m] = tm[m]
r0 = [r_00, r_01]
r1 = [r_10, r_11]
r2 = [r_20, r_21]
i_leak = [0.0 + 0.0j, 0.0 + 0.0j]
model = _am.PolarizationBeamSplitterModel(t1=t1, t2=t2, i=i_leak, r0=r0, r1=r1, r2=r2)
comp = model.black_box_component(_virtual_port_spec(2), name="PBS")
comp.properties.__thumbnail__ = "psr"
return comp
[docs]
@_parametric_component
def polarization_splitter_grating_coupler(
*,
t_0: complex = 1.0 + 0.0j,
x_0: complex = 0.0 + 0.0j,
t_1: complex = 1.0 + 0.0j,
x_1: complex = 0.0 + 0.0j,
r_00: complex = 0.0 + 0.0j,
r_10: complex = 0.0 + 0.0j,
r_20: complex = 0.0 + 0.0j,
r_01: complex = 0.0 + 0.0j,
r_11: complex = 0.0 + 0.0j,
r_21: complex = 0.0 + 0.0j,
mode_routing: _pft.annotate(_Sequence[_typ.Literal[1, 2]], minItems=2, maxItems=2) = (1, 2),
) -> _ext.Component:
"""Abstract polarization splitter grating coupler (PSGC).
Based on :class:`photonforge.PolarizationBeamSplitterModel`. Three-port,
two-mode device with no mode conversion. Port 0 represents the fiber
side (two orthogonal modes); P1 and P2 are on-chip waveguides.
Args:
t_0: Mode-0 transmission amplitude to the preferred output.
x_0: Mode-0 crosstalk amplitude to the other output.
t_1: Mode-1 transmission amplitude to the preferred output.
x_1: Mode-1 crosstalk amplitude to the other output.
r_00: Reflection at P0, mode 0.
r_10: Reflection at P1, mode 0.
r_20: Reflection at P2, mode 0.
r_01: Reflection at P0, mode 1.
r_11: Reflection at P1, mode 1.
r_21: Reflection at P2, mode 1.
mode_routing: Maps each mode index to its preferred output port
index.
"""
tm = [t_0, t_1]
xm = [x_0, x_1]
t1 = [0.0 + 0.0j, 0.0 + 0.0j]
t2 = [0.0 + 0.0j, 0.0 + 0.0j]
for m, pref in enumerate(mode_routing):
if pref == 1:
t1[m] = tm[m]
t2[m] = xm[m]
elif pref == 2:
t1[m] = xm[m]
t2[m] = tm[m]
r0 = [r_00, r_01]
r1 = [r_10, r_11]
r2 = [r_20, r_21]
i_leak = [0.0 + 0.0j, 0.0 + 0.0j]
model = _am.PolarizationBeamSplitterModel(t1=t1, t2=t2, i=i_leak, r0=r0, r1=r1, r2=r2)
comp = model.black_box_component(_virtual_port_spec(2), name="PSGC")
comp.properties.__thumbnail__ = "psgc"
return comp
[docs]
@_parametric_component
def phase_modulator(
*,
length: _pft.Dimension = 10,
n_eff: float = 2.4,
n_group: float = 0,
v_piL: _pft.annotate(float, label="VπL", units="V·μm") = 0,
z0: _pft.Impedance = 50.0,
propagation_loss: _pft.PropagationLoss = 0,
k2: _pft.annotate(float, label="k₂", units="rad/μm/V²") = 0,
k3: _pft.annotate(float, label="k₃", units="rad/μm/V³") = 0,
dloss_dv: _pft.annotate(float, label="dL/dV", units="dB/μm/V") = 0,
dloss_dv2: _pft.annotate(float, label="d²L/dV²", units="dB/μm/V²") = 0,
f_3dB: _pft.annotate(_pft.Frequency, label="f 3dB") = 0,
) -> _ext.Component:
r"""Abstract phase modulator.
Based on :class:`photonforge.PhaseModTimeStepper`.
Args:
length: Physical length of the modulator segment.
n_eff: Effective index of the optical mode at the carrier frequency.
n_group: Group index of the optical mode, used to calculate delay.
v_piL: Electro-optic phase coefficient :math:`V_{\pi L}`.
z0: Characteristic impedance of the electrical port used to convert
the input field amplitude to voltage.
propagation_loss: Optical propagation loss.
k2: Quadratic nonlinear phase coefficient.
k3: Cubic nonlinear phase coefficient.
dloss_dv: Linear voltage-dependent optical loss coefficient.
dloss_dv2: Quadratic voltage-dependent optical loss coefficient.
f_3dB: -3 dB frequency cutoff for bandwidth limiting. Only active
for positive values.
"""
model = _am.AnalyticWaveguideModel(
n_eff=n_eff,
n_group=n_group,
length=length,
v_piL=v_piL,
propagation_loss=propagation_loss,
k2=k2,
k3=k3,
dloss_dv=dloss_dv,
dloss_dv2=dloss_dv2,
)
model.time_stepper = _ts.modulator.PhaseModTimeStepper(
length=length,
n_eff=n_eff,
n_group=n_group,
v_piL=v_piL,
z0=z0,
propagation_loss=propagation_loss,
k2=k2,
k3=k3,
dloss_dv=dloss_dv,
dloss_dv2=dloss_dv2,
f_3dB=f_3dB,
)
comp = model.black_box_component(port_spec=_virtual_port_spec(), name="Phase Modulator")
(x_min, _), (x_max, y_max) = comp.bounds()
c = (0.5 * (x_min + x_max), y_max)
comp.add_port(_ext.Port(c, -90, _virtual_port_spec(classification="electrical")))
comp.properties.__thumbnail__ = "eo_ps"
return comp
@_parametric_component
def mach_zehnder_modulator(
*,
drive: _typ.Literal["push-pull", "dual"] = "push-pull",
v_pi: _pft.annotate(float, label="Vπ", units="V") = 0,
z0: _pft.Impedance = 50.0,
extinction_ratio: _pft.Loss | None = None,
insertion_loss: _pft.Loss = 0,
f_3dB: _pft.annotate(_pft.Frequency, label="f 3dB") = 0,
phase_bias: _pft.Angle = 0,
k2: _pft.annotate(float, label="k₂", units="rad/V²") = 0,
k3: _pft.annotate(float, label="k₃", units="rad/V³") = 0,
dloss_dv: _pft.annotate(float, label="dL/dV", units="dB/V") = 0,
dloss_dv2: _pft.annotate(float, label="d²L/dV²", units="dB/V²") = 0,
) -> _ext.Component:
r"""Abstract Mach-Zehnder modulator.
Based on :class:`photonforge.MZMTimeStepper`.
Args:
drive: Modulator drive selection: dual input from independent ports
or from a single electrical port in push-pull configuration.
v_pi: Half-wave voltage of the modulator, :math:`V_\pi`.
z0: Characteristic impedance of the electrical port used to convert
the input field amplitude to voltage.
extinction_ratio: Optical extinction ratio for the Mach-Zehnder.
insertion_loss: Optical insertion loss.
f_3dB: -3 dB frequency cutoff for bandwidth limiting. Only active
for positive values.
phase_bias: Constant phase bias applied to the first arm.
k2: Quadratic nonlinear phase coefficient.
k3: Cubic nonlinear phase coefficient.
dloss_dv: Linear voltage-dependent optical loss coefficient.
dloss_dv2: Quadratic voltage-dependent optical loss coefficient.
"""
model = _am.TwoPortModel(t=0.5)
model.time_stepper = _ts.modulator.MZMTimeStepper(
v_pi=v_pi,
z0=z0,
extinction_ratio=extinction_ratio,
insertion_loss=insertion_loss,
f_3dB=f_3dB,
phase_bias=phase_bias,
k2=k2,
k3=k3,
dloss_dv=dloss_dv,
dloss_dv2=dloss_dv2,
)
comp = model.black_box_component(port_spec=_virtual_port_spec(), name="Mach-Zehnder Modulator")
(x_min, _), (x_max, y_max) = comp.bounds()
c = (0.5 * (x_min + x_max), y_max)
comp.add_port(_ext.Port(c, -90, _virtual_port_spec(classification="electrical")))
if drive == "dual":
c = (0.5 * (x_min + x_max), -y_max)
comp.add_port(_ext.Port(c, 90, _virtual_port_spec(classification="electrical")))
comp.properties.__thumbnail__ = "mzm"
return comp
[docs]
@_parametric_component
def terminated_modulator(
*,
length: _pft.Dimension = 1000,
n_eff: complex = 2.4,
n_group: float = 4.2,
n_rf: complex = 4.2,
v_piL: _pft.annotate(_pft.PositiveFloat, label="VπL", units="V·μm") = 4.0,
z0: _pft.Impedance = 50.0,
z_load: _pft.Impedance | _typ.Literal["z0"] = "z0",
z_source: _pft.Impedance | _typ.Literal["z0"] = "z0",
propagation_loss: _pft.PropagationLoss = 0,
rf_propagation_loss: _pft.PropagationLoss = 0,
k2: _pft.annotate(float, label="k₂", units="rad/μm/V²") = 0,
k3: _pft.annotate(float, label="k₃", units="rad/μm/V³") = 0,
fir_taps: _pft.annotate(_pft.PositiveInt, label="FIR Taps") = 4096,
optical_input: str | None = None,
) -> _ext.Component:
r"""Abstract terminated travelling-wave phase modulator.
Based on :class:`photonforge.TerminatedModTimeStepper`.
Args:
length: Physical length of the modulator.
n_eff: Effective index of the optical mode at the carrier frequency.
n_group: Group index of the optical mode.
n_rf: Effective index of the electrical (RF/microwave) mode.
v_piL: Electro-optic phase coefficient :math:`V_{\pi L}`.
z0: Characteristic impedance of the transmission line.
z_load: Load impedance.
z_source: Source impedance.
propagation_loss: Optical propagation loss.
rf_propagation_loss: Electrical propagation loss.
k2: Quadratic nonlinear phase coefficient.
k3: Cubic nonlinear phase coefficient.
fir_taps: Number of FIR filter taps used for the travelling-wave
convolution.
optical_input: Name of the optical port used as input.
"""
model = _am.AnalyticWaveguideModel(
n_eff=n_eff,
n_group=n_group,
length=length,
v_piL=v_piL,
propagation_loss=propagation_loss,
k2=k2,
k3=k3,
)
model.time_stepper = _ts.modulator.TerminatedModTimeStepper(
length=length,
n_eff=n_eff,
n_group=n_group,
n_rf=n_rf,
v_piL=v_piL,
z0=z0,
z_load=z_load,
z_source=z_source,
propagation_loss=propagation_loss,
rf_propagation_loss=rf_propagation_loss,
k2=k2,
k3=k3,
fir_taps=fir_taps,
optical_input=optical_input,
)
comp = model.black_box_component(port_spec=_virtual_port_spec(), name="Terminated TW Modulator")
(x_min, _), (x_max, y_max) = comp.bounds()
c = (0.5 * (x_min + x_max), y_max)
comp.add_port(_ext.Port(c, -90, _virtual_port_spec(classification="electrical")))
comp.properties.__thumbnail__ = "eo_ps"
return comp
@_parametric_component
def amplitude_modulator(
*,
a0: _pft.annotate(float, label="a₀") = 1.0,
a1: _pft.annotate(float, label="a₁", units="1/V") = 0.0,
a2: _pft.annotate(float, label="a₂", units="1/V²") = 0.0,
a3: _pft.annotate(float, label="a₃", units="1/V³") = 0.0,
z0: _pft.Impedance = 50.0,
insertion_loss: _pft.Loss = 0,
extinction_ratio: _pft.Loss | None = None,
chirp: float = 0,
dloss_dv: _pft.annotate(float, label="dL/dV", units="dB/μm/V") = 0.0,
dloss_dv2: _pft.annotate(float, label="d²L/dV²", units="dB/μm/V²") = 0.0,
f_3dB: _pft.annotate(_pft.Frequency, label="f 3dB") = 0,
) -> _ext.Component:
r"""Abstract amplitude modulator.
Based on :class:`photonforge.AmplitudeModTimeStepper`. Maps an
electrical drive voltage to an optical amplitude scaling factor via
a cubic polynomial transfer function:
.. math::
s_0 = a_0 + a_1 V + a_2 V^2 + a_3 V^3
Args:
a0: DC term of the polynomial transfer function.
a1: Linear coefficient of the polynomial transfer function.
a2: Quadratic coefficient of the polynomial transfer function.
a3: Cubic coefficient of the polynomial transfer function.
z0: Characteristic impedance of the electrical port used to
convert the input field amplitude to voltage.
insertion_loss: Optical insertion loss.
extinction_ratio: Optical extinction ratio.
chirp: Chirp parameter coupling amplitude to phase modulation.
dloss_dv: Linear voltage-dependent optical loss coefficient.
dloss_dv2: Quadratic voltage-dependent optical loss coefficient.
f_3dB: -3 dB frequency cutoff for bandwidth limiting. Only
active for positive values.
"""
t = 10.0 ** (-insertion_loss / 20.0)
model = _am.TwoPortModel(t=t)
model.time_stepper = _ts.modulator.AmplitudeModTimeStepper(
a0=a0,
a1=a1,
a2=a2,
a3=a3,
z0=z0,
insertion_loss=insertion_loss,
extinction_ratio=extinction_ratio,
chirp=chirp,
dloss_dv=dloss_dv,
dloss_dv2=dloss_dv2,
f_3dB=f_3dB,
)
comp = model.black_box_component(port_spec=_virtual_port_spec(), name="Amplitude Modulator")
(x_min, _), (x_max, y_max) = comp.bounds()
c = (0.5 * (x_min + x_max), y_max)
comp.add_port(_ext.Port(c, -90, _virtual_port_spec(classification="electrical")))
comp.properties.__thumbnail__ = "eo_ps"
return comp
@_parametric_component
def ring_resonator(
*,
kappa1: complex = 0.3,
kappa2: complex | None = None,
n_eff: complex = 2.4,
length: _pft.Dimension = 60,
propagation_loss: _pft.PropagationLoss = 0.0,
n_group: float | None = None,
dispersion: _pft.Dispersion = 0.0,
dispersion_slope: _pft.DispersionSlope = 0.0,
reference_frequency: _pft.Frequency = C_0 / 1.55,
dn_dT: _ThermoOpticCoeff = 0.0,
dL_dT: _LossTemperatureCoeff = 0.0,
temperature: _pft.Temperature = 293.0,
reference_temperature: _pft.Temperature = 293.0,
dn_dv: _pft.annotate(complex, label="dn/dV", units="1/V") = 0.0,
dn_dv2: _pft.annotate(complex, label="d²n/dV²", units="1/V²") = 0.0,
dloss_dv: _pft.annotate(float, label="dL/dV", units="dB/μm/V") = 0.0,
dloss_dv2: _pft.annotate(float, label="d²L/dV²", units="dB/μm/V²") = 0.0,
z0: _pft.Impedance = 50.0,
f_3dB: _pft.annotate(_pft.Frequency, label="f 3dB") = 0,
) -> _ext.Component:
"""Abstract ring resonator with optional electrical tuning.
Based on :class:`photonforge.RingModel` and
:class:`photonforge.RingTimeStepper`. Set ``kappa2`` for a double-bus
(add-drop) configuration; leave it as ``None`` for single-bus
(all-pass).
Args:
kappa1: Coupling coefficient for the first bus waveguide.
kappa2: Coupling coefficient for the second bus waveguide. If
``None``, models a single-bus ring.
n_eff: Effective refractive index (loss can be included here
by using complex values).
length: Round-trip length of the ring.
propagation_loss: Propagation loss.
n_group: Group index. If ``None``, the value of ``n_eff`` is
used.
dispersion: Chromatic dispersion coefficient.
dispersion_slope: Chromatic dispersion slope.
reference_frequency: Reference frequency for dispersion
coefficients.
dn_dT: Temperature sensitivity for ``n_eff``.
dL_dT: Temperature sensitivity for ``propagation_loss``.
temperature: Operating temperature.
reference_temperature: Reference temperature.
dn_dv: Linear voltage-dependent effective index coefficient.
dn_dv2: Quadratic voltage-dependent effective index coefficient.
dloss_dv: Linear voltage-dependent propagation loss coefficient.
dloss_dv2: Quadratic voltage-dependent propagation loss
coefficient.
z0: Characteristic impedance of the electrical port used to
convert the input field amplitude to voltage.
f_3dB: -3 dB frequency cutoff for bandwidth limiting. Only
active for positive values.
"""
model = _am.RingModel(
kappa1=kappa1,
kappa2=kappa2,
n_eff=n_eff,
length=length,
propagation_loss=propagation_loss,
n_group=n_group,
dispersion=dispersion,
dispersion_slope=dispersion_slope,
reference_frequency=reference_frequency,
dn_dT=dn_dT,
dL_dT=dL_dT,
temperature=temperature,
reference_temperature=reference_temperature,
dn_dv=dn_dv,
dn_dv2=dn_dv2,
dloss_dv=dloss_dv,
dloss_dv2=dloss_dv2,
)
model.time_stepper = _ts.analytic.RingTimeStepper(
kappa1=abs(kappa1),
kappa2=None if kappa2 is None else abs(kappa2),
n_eff=n_eff,
length=length,
propagation_loss=propagation_loss,
n_group=n_group if n_group is not None else 0,
dn_dT=dn_dT,
dL_dT=dL_dT,
temperature=temperature,
reference_temperature=reference_temperature,
dn_dv=dn_dv,
dn_dv2=dn_dv2,
dloss_dv=dloss_dv,
dloss_dv2=dloss_dv2,
z0=z0,
f_3dB=f_3dB,
)
comp = model.black_box_component(port_spec=_virtual_port_spec(), name="Ring Resonator")
(x_min, _), (x_max, y_max) = comp.bounds()
c = (0.5 * (x_min + x_max), y_max)
comp.add_port(_ext.Port(c, -90, _virtual_port_spec(classification="electrical")))
return comp
[docs]
@_parametric_component
def cw_laser(
*,
power: _pft.Power = 1,
rel_intensity_noise: _RIN = 0,
linewidth: _pft.Frequency = 0,
frequency: _pft.Frequency | None = None,
phase: _pft.Angle = 0,
reflection: complex = 0,
seed: _pft.NonNegativeInt | None = None,
) -> _ext.Component:
"""Abstract CW Laser source.
Based on :class:`photonforge.CWLaserTimeStepper`.
Args:
power: Mean optical output power.
rel_intensity_noise: One-sided relative intensity noise (RIN) power
spectral density.
linewidth: Full-width at half-maximum (FWHM) of the laser's
Lorentzian shape.
frequency: Absolute laser frequency. If ``None``, equals the carrier
frequency. If detuned from the carrier by Δf, the output envelope
rotates at 2πΔf.
phase: Starting phase of the output envelope.
reflection: Reflection coefficient for incident fields.
seed: Random number generator seed to ensure reproducibility.
"""
model = _am.TerminationModel(r=reflection)
model.time_stepper = _ts.source.CWLaserTimeStepper(
power=power,
rel_intensity_noise=rel_intensity_noise,
linewidth=linewidth,
frequency=frequency,
phase=phase,
reflection=reflection,
seed=seed,
)
src_component = model.black_box_component(_virtual_port_spec())
comp = _ext.Reference(src_component, rotation=180).transformed_component("CW Laser")
comp.properties.__thumbnail__ = "cw_laser"
return comp
[docs]
@_parametric_component
def dm_laser(
*,
quantum_efficiency: _pft.NonNegativeFloat = 0.4,
spontaneous_emission_factor: _pft.NonNegativeFloat = 3.0e-5,
carrier_lifetime: _pft.annotate(_pft.PositiveFloat, units="s") = 1.0e-9,
gain_compression_factor: _pft.annotate(_pft.NonNegativeFloat, units="μm³") = 1.0e-5,
transparency_carrier_density: _pft.annotate(_pft.NonNegativeFloat, units="μm⁻³") = 1.1e6,
differential_gain: _pft.annotate(_pft.NonNegativeFloat, units="μm³/s") = 2.5e-2,
n_group: _pft.NonNegativeFloat = 3.53,
linewidth_enhancement_factor: _pft.NonNegativeFloat = 5.0,
confinement_factor: _pft.PositiveFloat = 0.4,
photon_lifetime: _pft.annotate(_pft.PositiveFloat, units="s") = 3.0e-12,
active_region_volume: _pft.annotate(_pft.PositiveFloat, units="μm³") = 1.5e2,
rel_intensity_noise: _RIN = 0,
linewidth: _pft.Frequency = 0,
reflection: complex = 0,
z0: _pft.Impedance = 50.0,
seed: _pft.NonNegativeInt | None = None,
) -> _ext.Component:
r"""Abstract directly modulated laser source.
Based on :class:`photonforge.DMLaserTimeStepper`.
Args:
quantum_efficiency: Total quantum efficiency (:math:`\eta_0`) for
power calibration.
spontaneous_emission_factor: Fraction of spontaneous emission
coupled into the lasing mode (:math:`\beta`).
carrier_lifetime: Effective carrier recombination lifetime
(:math:`\tau_n`).
gain_compression_factor: Gain compression coefficient
(:math:`\epsilon`).
transparency_carrier_density: Transparency carrier density
(:math:`n_0`).
differential_gain: Differential material gain coefficient
(:math:`a_0`).
n_group: Optical group index in the gain medium (:math:`n_g`).
linewidth_enhancement_factor: Henry's α factor (:math:`\alpha`) for
AM-FM coupling.
confinement_factor: Modal confinement in the active region
(:math:`\Gamma`).
photon_lifetime: Photon lifetime in the optical cavity
(:math:`\tau_p`).
active_region_volume: Volume of the active gain region
(:math:`V_a`).
rel_intensity_noise: One-sided relative intensity noise (RIN) power
spectral density.
linewidth: Full-width at half-maximum (FWHM) of the laser's
Lorentzian shape.
reflection: Reflection coefficient for incident fields.
z0: Characteristic impedance of the electrical port used to convert
the input field amplitude to voltage. If ``None``, derived from
port impedance, calculated by mode-solving, or set to 50 Ω.
seed: Random number generator seed to ensure reproducibility.
"""
model = _am.TerminationModel(r=reflection)
model.time_stepper = _ts.source.DMLaserTimeStepper(
quantum_efficiency=quantum_efficiency,
spontaneous_emission_factor=spontaneous_emission_factor,
carrier_lifetime=carrier_lifetime,
gain_compression_factor=gain_compression_factor * 1e-18,
transparency_carrier_density=transparency_carrier_density * 1e18,
differential_gain=differential_gain * 1e-18,
n_group=n_group,
linewidth_enhancement_factor=linewidth_enhancement_factor,
confinement_factor=confinement_factor,
photon_lifetime=photon_lifetime,
active_region_volume=active_region_volume * 1e-18,
rel_intensity_noise=rel_intensity_noise,
linewidth=linewidth,
reflection=reflection,
z0=z0,
seed=seed,
)
src_component = model.black_box_component(_virtual_port_spec())
comp = _ext.Reference(src_component, rotation=180).transformed_component("DM Laser")
(x_min, _), (x_max, y_max) = comp.bounds()
c = (0.5 * (x_min + x_max), y_max)
comp.add_port(_ext.Port(c, -90, _virtual_port_spec(classification="electrical")))
comp.properties.__thumbnail__ = "laser"
return comp
[docs]
@_parametric_component
def optical_pulse(
*,
energy: _pft.annotate(_pft.NonNegativeFloat, units="J") = 1e-12,
width: _pft.TimeDelay = 50e-12,
offset: _pft.TimeDelay | None = None,
repetition_rate: _pft.Frequency = 0,
phase: _pft.Angle | _Sequence[_pft.Angle] = 0,
chirp: _pft.Angle = 0,
order: _pft.annotate(float, minimum=1) = 1,
frequency: _pft.Frequency | None = None,
rel_intensity_noise: _RIN = 0,
linewidth: _pft.Frequency = 0,
jitter: _pft.TimeDelay = 0,
prbs: _typ.Literal[0, 7, 15, 31] = 0,
reflection: complex = 0,
seed: _pft.NonNegativeInt | None = None,
) -> _ext.Component:
"""Abstract optical pulse source.
Based on :class:`photonforge.OpticalPulseTimeStepper`.
Args:
energy: Total pulse energy.
width: Full-width at half-maximum (FWHM) of the pulse intensity.
offset: Time shift for the center of the first pulse. If ``None``,
a value is chosen automatically.
repetition_rate: If positive, generates a periodic train of pulses
at this rate.
phase: Phase shift applied to the pulse. A sequence of values can be
used to define the phase of each pulse in a periodic train. The
sequence is wrapped around if necessary.
chirp: Chirp parameter for adding quadratic phase across the pulse.
order: Order of the super-Gaussian pulse.
frequency: Absolute laser frequency. If ``None``, equals the carrier
frequency. If detuned from the carrier by Δf, the output envelope
rotates at 2πΔf.
rel_intensity_noise: One-sided relative intensity noise (RIN) power
spectral density.
linewidth: Full-width at half-maximum (FWHM) of the laser's
Lorentzian shape.
jitter: RMS clock jitter for pulse trains.
prbs: PRBS polynomial degree. Value 0 disables PRBS.
reflection: Reflection coefficient for incident fields.
seed: Random number generator seed to ensure reproducibility.
"""
model = _am.TerminationModel(r=reflection)
model.time_stepper = _ts.source.OpticalPulseTimeStepper(
energy=energy,
width=width,
offset=offset,
frequency=frequency,
repetition_rate=repetition_rate,
phase=phase,
chirp=chirp,
order=order,
rel_intensity_noise=rel_intensity_noise,
linewidth=linewidth,
jitter=jitter,
prbs=prbs,
reflection=reflection,
seed=seed,
)
src_component = model.black_box_component(_virtual_port_spec())
comp = _ext.Reference(src_component, rotation=180).transformed_component("Optical Pulse")
comp.properties.__thumbnail__ = "laser"
return comp
[docs]
@_parametric_component
def optical_noise(
*,
noise: _pft.annotate(_pft.NonNegativeFloat, units="√(W/Hz)") = 1e-9,
reflection: complex = 0,
seed: _pft.NonNegativeInt | None = None,
) -> _ext.Component:
"""Abstract optical noise source.
Based on :class:`photonforge.OpticalNoiseTimeStepper`.
Args:
noise: One-sided, amplitude spectral density (ASD) of noise.
reflection: Reflection coefficient for incident fields.
seed: Random number generator seed to ensure reproducibility.
"""
model = _am.TerminationModel(r=reflection)
model.time_stepper = _ts.source.OpticalNoiseTimeStepper(
noise=noise, reflection=reflection, seed=seed
)
src_component = model.black_box_component(_virtual_port_spec())
comp = _ext.Reference(src_component, rotation=180).transformed_component("Optical Noise")
comp.properties.__thumbnail__ = "cw_laser"
return comp
[docs]
@_parametric_component
def photodiode(
*,
responsivity: _pft.annotate(_pft.NonNegativeFloat, units="A/W") = 1,
gain: _pft.annotate(float, units="V/A") = 1,
saturation_voltage: _pft.annotate(_pft.NonNegativeFloat, units="V") = 0,
saturation_current: _pft.NonNegativeFloat = 0,
roll_off: _pft.NonNegativeFloat = 2,
dark_current: _pft.annotate(float, units="A") = 0,
thermal_noise: _pft.annotate(_pft.NonNegativeFloat, units="A²/Hz") = 0,
pink_noise_frequency: _pft.Frequency = 0,
current_time_constant: _pft.TimeDelay = 0,
filter_frequency: _pft.Frequency = 0,
filter_quality: _pft.NonNegativeFloat = 0,
filter_gain: _pft.PositiveFloat = 1,
reflection: complex = 0,
z0: _pft.Impedance = 50.0,
seed: _pft.NonNegativeInt | None = None,
) -> _ext.Component:
"""Abstract photodiode.
Based on :class:`photonforge.PhotodiodeTimeStepper`.
Args:
responsivity: Optical power to current conversion factor.
gain: TIA gain.
saturation_voltage: If non-zero, output saturation voltage of the
TIA.
saturation_current: If non-zero, photocurrent of the space-charge
saturation model.
roll_off: Roll-off factor for the space-charge saturation model.
dark_current: Photodiode's dark current.
thermal_noise: One-sided power spectral density of the TIA's
input-referred thermal noise current.
pink_noise_frequency: Pink (1/f) noise corner frequency. If set to
0, pink noise is disabled.
current_time_constant: Time constant for the running photocurrent
average. A value of zero sets a default of 100 time steps.
filter_frequency: If positive, sets the -3 dB frequency bandwidth
for the first-order low-pass TIA filter. If a second-order filter
is used (``filter_quality > 0``), this is its natural frequency.
filter_quality: If positive, enables a second-order filter for the
TIA with this quality factor. Only when ``filter_frequency > 0``.
filter_gain: Gain of the second-order TIA filter. Only used when
``filter_frequency > 0`` and ``filter_quality > 0``.
reflection: Reflection coefficient for incident fields.
z0: Characteristic impedance of the electrical port used to convert
the output voltage to field amplitude. If ``None``, derived from
port impedance, calculated by mode-solving, or set to 50 Ω.
seed: Random number generator seed to ensure reproducibility.
"""
model = _am.TerminationModel(r=reflection)
model.time_stepper = _ts.sink.PhotodiodeTimeStepper(
responsivity=responsivity,
gain=gain,
saturation_voltage=saturation_voltage,
saturation_current=saturation_current,
roll_off=roll_off,
dark_current=dark_current,
thermal_noise=thermal_noise,
pink_noise_frequency=pink_noise_frequency,
current_time_constant=current_time_constant,
filter_frequency=filter_frequency,
filter_quality=filter_quality,
filter_gain=filter_gain,
reflection=reflection,
z0=z0,
seed=seed,
)
comp = model.black_box_component(_virtual_port_spec(), name="Photodiode")
(x_min, _), (x_max, y_max) = comp.bounds()
c = (0.5 * (x_min + x_max), y_max)
comp.add_port(_ext.Port(c, -90, _virtual_port_spec(classification="electrical")))
comp.add_model(_am.TerminationModel(r=0), "Termination (electrical)", set_active="electrical")
comp.properties.__thumbnail__ = "photodiode"
return comp
[docs]
@_parametric_component
def signal_source(
*,
frequency: _pft.Frequency = 10e9,
amplitude: _pft.FieldAmplitude = 1,
offset: _pft.FieldAmplitude = 0,
start: _pft.Time | None = None,
stop: _pft.Time | None = None,
skew: _pft.Fraction = 0.5,
width: _pft.NonNegativeFloat = 0.5,
rise: _pft.NonNegativeFloat = 0,
fall: _pft.NonNegativeFloat = 0,
order: _pft.annotate(float, minimum=1) = 1,
noise: _pft.annotate(_pft.NonNegativeFloat, units="√(W/Hz)") = 0,
jitter: _pft.TimeDelay = 0,
seed: _pft.NonNegativeInt | None = None,
waveform: _typ.Literal["sine", "triangle", "trapezoid", "raised-cosine", "gaussian"] = "sine",
prbs: _typ.Literal[0, 7, 15, 31] = 0,
bit_sequence: str | _typ.Sequence[int] = "",
) -> _ext.Component:
"""Abstract electrical signal generator.
Based on :class:`photonforge.WaveformTimeStepper`.
Args:
frequency: Source frequency. The carrier frequency, if any, is *not*
taken into account. The generated signals are always real. This is
equivalent to the bit rate for PRBS signals.
amplitude: Source amplitude. Sine and triangle waves range from
``offset - amplitude`` to ``offset + amplitude``. Other waveforms
range from ``offset`` to ``offset + amplitude``.
offset: Constant source offset.
start: Start time of the source.
stop: Stop time of the source. The effective stop time is after the
current pulse finishes, so it can be after ``stop``.
skew: Triangle wave asymmetry parameter. A value of 1 produces a
sawtooth wave, whereas a value of 0, a reversed sawtooth.
width: Full-width at half-maximum for trapezoid, raised-cosine, and
Gaussian pulses, as a fraction of the source period.
rise: Trapezoidal and raised-cosine pulses rise time, as a fraction
of the source period.
fall: Trapezoidal and raised-cosine pulses fall time, as a fraction
of the source period.
order: Order of the super-Gaussian pulse.
noise: One-sided, amplitude spectral density (ASD) of noise.
jitter: RMS clock jitter.
seed: Random number generator seed to ensure reproducibility.
waveform: Source waveform.
prbs: PRBS polynomial degree. Value 0 disables PRBS.
bit_sequence: User-provided output bit sequence. PRBS must be set to
0 for this sequence to be used.
"""
model = _am.TerminationModel()
model.time_stepper = _ts.source.WaveformTimeStepper(
frequency=frequency,
amplitude=amplitude,
offset=offset,
start=start,
stop=stop,
waveform=waveform,
skew=skew,
width=width,
rise=rise,
fall=fall,
order=order,
noise=noise,
jitter=jitter,
seed=seed,
prbs=prbs,
bit_sequence=bit_sequence,
)
src_component = model.black_box_component(_virtual_port_spec(classification="electrical"))
comp = _ext.Reference(src_component, rotation=180).transformed_component("Source")
comp.properties.__thumbnail__ = "source"
return comp
[docs]
@_parametric_component
def filter(
*,
family: _typ.Literal[
"digital",
"rc",
"butterworth",
"bessel",
"cheby1",
"rectangular",
"gaussian",
] = "rc",
shape: _typ.Literal["lp", "hp", "bp", "bs"] = "lp",
f_cutoff: _pft.Frequency
| _pft.annotate(_Sequence[_pft.Frequency], minItems=2, maxItems=2) = 20e9,
order: _pft.PositiveInt = 1,
ripple: _pft.Loss = 0,
window: str
| tuple[str, float]
| tuple[str, float, float]
| tuple[str, _Sequence[float]] = "hann",
a: _Sequence[complex] = (1.0,),
b: _Sequence[complex] = (),
taps: _pft.PositiveInt = 101,
insertion_loss: _pft.Loss = 0,
) -> _ext.Component:
"""Abstract filter for electrical signals.
Based on :class:`photonforge.FilterTimeStepper`.
Args:
family: Filter family.
shape: Filter shape.
f_cutoff: Cutoff frequency for low- and high-pass filter shapes. For
``family=="tunable_lp_rc"``, this can be an
:class:`photonforge.Interpolator` to provide the dependency with
the input voltage. For band-pass and band-stop shapes, this is a
2-value sequence with low and high cutoff frequencies.
order: Filter order.
ripple: Maximum ripple for Chebyshev filters.
window: Window specification for rectangular filters. Please consult
the help for ``scipy.signal.firwin`` for valid options.
a: Recursive (denominator) coefficients for a digital IIR filter.
b: Direct (numerator) coefficients for a digital FIR or IIR filter.
taps: Length of rectangular or Gaussian filters.
insertion_loss: Insertion loss added to the filter response.
"""
model = _ErrorModel(
"Analytical filters are only available as time steppers. Frequency response is not "
"implemented yet.",
"NotImplementedError",
)
model.time_stepper = _ts.filter.FilterTimeStepper(
family=family,
shape=shape,
f_cutoff=f_cutoff,
order=order,
ripple=ripple,
window=window,
a=a,
b=b,
taps=taps,
insertion_loss=insertion_loss,
)
comp = model.black_box_component(
2, _virtual_port_spec(classification="electrical"), name="Filter"
)
comp.properties.__thumbnail__ = "filter"
return comp
[docs]
@_parametric_component
def optical_amplifier(
*,
gain: _pft.Gain = 15,
noise_figure: _pft.Gain | None = None,
seed: _pft.NonNegativeInt | None = None,
) -> _ext.Component:
r"""Abstract optical amplifier with constant gain.
Based on :class:`photonforge.OpticalAmplifierTimeStepper`.
Args:
gain: The amplifier's power gain.
noise_figure: The amplifier's noise figure (NF).
seed: Random number generator seed to ensure reproducibility.
"""
model = _ErrorModel("Nonlinear gain model cannot be used in frequency domain computation.")
model.time_stepper = _ts.amplifier.OpticalAmplifierTimeStepper(
gain=gain, noise_figure=noise_figure, seed=seed
)
comp = model.black_box_component(2, port_spec=_virtual_port_spec(), name="Optical Amplifier")
comp.properties.__thumbnail__ = "optical_amplifier"
return comp
[docs]
@_parametric_component
def electrical_amplifier(
*,
gain: _pft.Gain = 15,
f_3dB: _pft.annotate(_pft.Frequency | None, label="f 3dB") = None,
saturation_power: _pft.Power_dBm | None = None,
compression_power: _pft.Power_dBm | None = None,
ip3: _pft.annotate(_pft.Power_dBm | None, label="IP3") = None,
noise_figure: _pft.Gain | None = None,
r0: _pft.annotate(float, minimum=-1, maximum=1) = 0,
r1: _pft.annotate(float, minimum=-1, maximum=1) = 0,
seed: _pft.NonNegativeInt | None = None,
) -> _ext.Component:
r"""Abstract electrical amplifier with constant gain.
Based on :class:`photonforge.ElectricalAmplifierTimeStepper`.
Args:
gain: The amplifier's power gain.
f_3dB: -3 dB frequency cutoff for bandwidth limiting.
saturation_power: Output saturation power.
compression_power: 1 dB compression power.
ip3: Third order intercept point.
noise_figure: The amplifier's noise figure (NF).
r0: Reflection coefficient for the input port.
r1: Reflection coefficient for the output port.
seed: Random number generator seed to ensure reproducibility.
"""
model = _ErrorModel("Nonlinear gain model cannot be used in frequency domain computation.")
model.time_stepper = _ts.amplifier.ElectricalAmplifierTimeStepper(
gain=gain,
f_3dB=f_3dB,
saturation_power=saturation_power,
compression_power=compression_power,
ip3=ip3,
noise_figure=noise_figure,
r0=r0,
r1=r1,
seed=seed,
)
comp = model.black_box_component(
2, port_spec=_virtual_port_spec(classification="electrical"), name="Electrical Amplifier"
)
comp.properties.__thumbnail__ = "electrical_amplifier"
return comp
[docs]
@_parametric_component
def scaler(*, scale: float = 1.0) -> _ext.Component:
r"""Abstract electrical scaler: constant gain multiplier.
Based on :class:`photonforge.ExpressionTimeStepper`.
Args:
scale: Output scaling factor.
"""
model = _ErrorModel("Nonlinear gain model cannot be used in frequency domain computation.")
model.time_stepper = _ts.math.ExpressionTimeStepper(expressions={"E1": f"{scale} * E0"})
comp = model.black_box_component(
2, port_spec=_virtual_port_spec(classification="electrical"), name="Scaler"
)
comp.properties.__thumbnail__ = "electrical_amplifier"
return comp
[docs]
@_parametric_component
def absolute(*, scale: float = 1.0) -> _ext.Component:
r"""Absolute value of the input signal.
Based on :class:`photonforge.ExpressionTimeStepper`.
Args:
scale: Output scaling factor.
"""
model = _ErrorModel("Nonlinear model cannot be used in frequency domain computation.")
model.time_stepper = _ts.math.ExpressionTimeStepper(expressions={"E1": f"{scale} * abs(E0)"})
comp = model.black_box_component(
2, port_spec=_virtual_port_spec(classification="electrical"), name="Absolute"
)
comp.properties.__thumbnail__ = "electrical_absolute"
return comp
[docs]
@_parametric_component
def adder(*, scale: float = 1.0, weight0: float = 1.0, weight1: float = 1.0) -> _ext.Component:
r"""Abstract electrical adder: linearly combine 2 inputs.
Based on :class:`photonforge.ExpressionTimeStepper`.
Args:
scale: Output scaling factor.
weight0: Weight applied to the signal from the first port.
weight1: Weight applied to the signal from the second port.
"""
model = _ErrorModel(
"Math operation model not implemented for frequency domain computation.",
"NotImplementedError",
)
model.time_stepper = _ts.math.ExpressionTimeStepper(
expressions={"E2": f"{scale} * ({weight0} * E0 + {weight1} * E1)"}
)
comp = model.black_box_component(
(0, 0, 180), port_spec=_virtual_port_spec(classification="electrical"), name="Adder"
)
comp.properties.__thumbnail__ = "electrical_adder"
return comp
[docs]
@_parametric_component
def multiplier(
*, scale: float = 1.0, exponent0: float = 1.0, exponent1: float = 1.0
) -> _ext.Component:
r"""Abstract electrical multiplier: compute the product of 2 inputs.
Based on :class:`photonforge.ExpressionTimeStepper`.
Args:
scale: Output scaling factor.
exponent0: Exponent used for the first input. Fractional values will
fail on negative inputs.
exponent1: Exponent used for the second input. Fractional values
will fail on negative inputs.
"""
model = _ErrorModel("Nonlinear model cannot be used in frequency domain computation.")
model.time_stepper = _ts.math.ExpressionTimeStepper(
expressions={"E2": f"{scale} * (E0^{exponent0} * E1^{exponent1})"}
)
comp = model.black_box_component(
(0, 0, 180), port_spec=_virtual_port_spec(classification="electrical"), name="Multiplier"
)
comp.properties.__thumbnail__ = "electrical_multiplier"
return comp
[docs]
@_parametric_component
def differentiator(
*, scale: float = 1.0, scheme: _typ.Literal["backwards", "central"] = "backwards"
) -> _ext.Component:
r"""Derivative of the input signal.
Based on :class:`photonforge.DifferentialTimeStepper`.
Args:
scheme: Differentiation scheme.
scale: Output scaling factor.
"""
model = _ErrorModel(
"Math operation model not implemented for frequency domain computation.",
"NotImplementedError",
)
model.time_stepper = _ts.math.DifferentialTimeStepper(scheme=scheme, scale=scale)
comp = model.black_box_component(
2, port_spec=_virtual_port_spec(classification="electrical"), name="Differentiator"
)
comp.properties.__thumbnail__ = "electrical_differential"
return comp
[docs]
@_parametric_component
def integrator(
*,
scale: float = 1.0,
start_value: _pft.FieldAmplitude = 0.0,
limits: _pft.annotate(_Sequence[_pft.FieldAmplitude | None], minItems=2, maxItems=2) = (
None,
None,
),
reset_trigger: _typ.Literal["fall", "rise", "both"] = "rise",
reset_tolerance: float = 0.0,
) -> _ext.Component:
r"""Integral of the input signal.
Based on :class:`photonforge.IntegralTimeStepper`.
Args:
scale: Output scaling factor.
start_value: Starting output value after reset.
limits: Output value limits.
reset_trigger: Type of edge used for triggering a reset.
reset_tolerance: Value change tolerance for triggering a reset.
"""
model = _ErrorModel(
"Math operation model not implemented for frequency domain computation.",
"NotImplementedError",
)
model.time_stepper = _ts.math.IntegralTimeStepper(
scale=scale,
start_value=start_value,
limits=limits,
output_port="E2",
reset_port="E1",
reset_trigger=reset_trigger,
reset_tolerance=reset_tolerance,
)
comp = model.black_box_component(
(0, -90, 180),
port_spec=_virtual_port_spec(classification="electrical"),
name="Integrator",
)
comp.properties.__thumbnail__ = "electrical_integral"
return comp