Source code for photonforge.abstract

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