# Copyright © Simphony Project Contributors
# Licensed under the terms of the MIT License
# (see simphony/__init__.py for details)
"""SiPANN models compatible with SAX circuits.
This package contains wrappers for models defined in the SiPANN (Silicon
Photonics with Artificial Neural Networks) project, another project by
CamachoLab at BYU. It leverages machine learning to simulate photonic
devices, giving greater speed and similar accuracy to a full FDTD
simulation.
"""
from itertools import product
from typing import Callable, Union
import numpy as np
import sax
from jax.typing import ArrayLike
try:
from SiPANN import comp, scee
from SiPANN.scee_opt import premade_coupler as sipann_premade_coupler
except ImportError as exc:
raise ImportError(
"SiPANN must be installed to use the SiPANN wrappers. "
"To install SiPANN, run `pip install SiPANN`."
) from exc
def _create_sdict_from_model(model, wl: Union[float, ArrayLike]) -> sax.SDict:
"""Create s-parameter dict from model.
Parameters
----------
model : SiPANN model
The component model to call.
wl : float or ArrayLike
Wavelength to evaluate at in microns.
Returns
-------
sdict : sax.SDict
The s-parameter dictionary.
"""
wl = np.array(wl).reshape(-1) # microns
s = model.sparams(wl * 1e3) # convert to nanometers, s is shape f x n x n
ports = list(range(s.shape[1]))
sdict = {}
for p_out, p_in in product(ports, ports):
sdict[(f"o{p_out}", f"o{p_in}")] = s[:, p_out, p_in]
return sdict
[docs]def gap_func_symmetric(
wl: Union[float, ArrayLike] = 1.55,
width: float = 500.0,
thickness: float = 220.0,
gap: Callable[[float], float] = 100.0,
dgap: Callable[[float], float] = 0.0,
zmin: float = 0.0,
zmax: float = 10e3,
sw_angle: float = 90.0,
) -> sax.SDict:
r"""Symmetric directional coupler, meaning both waveguides are the same
shape.
A gap function must describe the shape of the two waveguides, where the
distance between the waveguides is the return value of the gap function
at every horizontal point from left to right. The derivative of the gap
function is also required.
Ports are numbered as::
| 2---\ /---4 |
| ------ |
| ------ |
| 1---/ \---3 |
Parameters
----------
width : float
Width of waveguides in nanometers (valid from 400 to 600).
thickness : float
Thickness of waveguides in nanometers (valid from 180 to 240).
gap : callable
Gap function along the waveguide, values it returns must be in
nanometers (and must always be greater than 100).
dgap : callable
Derivative of the gap function.
zmin : float
Real number at which to begin integration of gap function.
zmax : float
Real number at which to end integration of gap function.
sw_angle : float, optional
Sidewall angle of waveguide from horizontal in degrees (valid from 80
to 90, defaults to 90).
"""
if width < 400 or width > 600:
raise ValueError("Width must be between 400 and 600 nm")
if thickness < 180 or thickness > 240:
raise ValueError("Thickness must be between 180 and 240 nm")
if sw_angle < 80 or sw_angle > 90:
raise ValueError("Sidewall angle must be between 80 and 90 degrees")
model = scee.GapFuncSymmetric(width, thickness, gap, dgap, zmin, zmax, sw_angle)
sdict = _create_sdict_from_model(model, wl)
return sdict
[docs]def gap_func_antisymmetric(
wl: Union[float, ArrayLike] = 1.55,
width: float = 500.0,
thickness: float = 220.0,
gap: Callable[[float], float] = 100.0,
zmin: float = 0.0,
zmax: float = 100.0,
arc1: float = 10e3,
arc2: float = 10e3,
arc3: float = 10e3,
arc4: float = 10e3,
sw_angle: Union[float, np.ndarray] = 90,
) -> sax.SDict:
r"""Antisymmetric directional coupler, meaning both waveguides are
differently shaped.
A gap function describing the vertical distance between the two waveguides
at any horizontal point, and arc lengths from each port to the coupling
point, describe the shape of the device.
Ports are numbered as::
| 2---\ /---4 |
| ------ |
| ------ |
| 1---/ \---3 |
Parameters
----------
width : float
Width of waveguides in nanometers (valid from 400 to 600).
thickness : float
Thickness of waveguides in nanometers (valid from 180 to 240).
gap : callable
Gap function along the waveguide, values it returns must be in
nanometers (and must always be greater than 100).
zmin : float
Real number at which to begin integration of gap function.
zmax : float
Real number at which to end integration of gap function.
arc1 : float
Arc length from port 1 to minimum coupling point in nanometers.
arc2 : float
Arc length from port 2 to minimum coupling point in nanometers.
arc3 : float
Arc length from port 3 to minimum coupling point in nanometers.
arc4 : float
Arc length from port 4 to minimum coupling point in nanometers.
sw_angle : float, optional
Sidewall angle of waveguide from horizontal in degrees (valid from 80
to 90, defaults to 90).
"""
if width < 400 or width > 600:
raise ValueError("Width must be between 400 and 600 nm")
if thickness < 180 or thickness > 240:
raise ValueError("Thickness must be between 180 and 240 nm")
if gap < 100:
raise ValueError("Gap must be greater than 100 nm")
if sw_angle < 80 or sw_angle > 90:
raise ValueError("Sidewall angle must be between 80 and 90 degrees")
model = scee.GapFuncAntiSymmetric(
width, thickness, gap, zmin, zmax, arc1, arc2, arc3, arc4, sw_angle
)
sdict = _create_sdict_from_model(model, wl)
return sdict
[docs]def half_ring(
wl: Union[float, ArrayLike] = 1.55,
width: float = 500.0,
thickness: float = 220.0,
radius: float = 10.0,
gap: float = 100.0,
sw_angle: float = 90.0,
) -> sax.SDict:
"""Half of a ring resonator.
Uses a radius and a gap to describe the shape.
.. image:: /_static/images/sipann_half_ring.png
:alt: Half ring port numbering.
:width: 400px
:align: center
Parameters
----------
wl : float or ArrayLike
The wavelengths to evaluate at in microns.
width : float
Width of waveguides in nanometers (valid from 400 to 600).
thickness : float
Thickness of waveguides in nanometers (valid from 180 to 240).
radius : float
Distance from center of ring to middle of waveguide, in microns.
gap : float
Minimum distance from ring waveguide edge to straight waveguide edge,
in nanometers (must be greater than 100).
sw_angle : float, optional
Sidewall angle of waveguide from horizontal in degrees (valid from 80
to 90, defaults to 90).
Examples
--------
>>> s = half_ring(wl, 500, 220, 5000, 100)
"""
if width < 400 or width > 600:
raise ValueError("Width must be between 400 and 600 nm")
if thickness < 180 or thickness > 240:
raise ValueError("Thickness must be between 180 and 240 nm")
if gap < 100:
raise ValueError("Gap must be greater than 100 nm")
if sw_angle < 80 or sw_angle > 90:
raise ValueError("Sidewall angle must be between 80 and 90 degrees")
model = scee.HalfRing(width, thickness, radius * 1000, gap, sw_angle)
sdict = _create_sdict_from_model(model, wl)
return sdict
[docs]def straight_coupler(
wl: Union[float, ArrayLike] = 1.55,
width: float = 500.0,
thickness: float = 220.0,
gap: float = 100.0,
length: float = 1000.0,
sw_angle: float = 90.0,
) -> sax.SDict:
"""Straight directional coupler, both waveguides run parallel.
Described by a gap and a length.
.. image:: /_static/images/sipann_straight_coupler.png
:alt: Straight coupler port numbering.
:width: 400px
:align: center
Parameters
----------
width : float
Width of waveguides in nanometers (valid from 400 to 600).
thickness : float
Thickness of waveguides in nanometers (valid from 180 to 240).
gap : float
Distance between the two waveguide edges, in nanometers (must be greater
than 100).
length : float
Length of both waveguides in nanometers.
sw_angle : float, optional
Sidewall angle of waveguide from horizontal in degrees (Valid from 80
to 90, defaults to 90).
Examples
--------
>>> s = straight_coupler(wl, 500, 220, 100, 1000)
"""
if width < 400 or width > 600:
raise ValueError("Width must be between 400 and 600 nm")
if thickness < 180 or thickness > 240:
raise ValueError("Thickness must be between 180 and 240 nm")
if gap < 100:
raise ValueError("Gap must be greater than 100 nm")
if sw_angle < 80 or sw_angle > 90:
raise ValueError("Sidewall angle must be between 80 and 90 degrees")
model = scee.StraightCoupler(width, thickness, gap, length, sw_angle)
sdict = _create_sdict_from_model(model, wl)
return sdict
[docs]def standard_coupler(
wl: Union[float, ArrayLike] = 1.55,
width: float = 500.0,
thickness: float = 220.0,
gap: float = 100.0,
length: float = 1000.0,
horizontal: float = 10e3,
vertical: float = 10e3,
sw_angle: float = 90.0,
) -> sax.SDict:
"""Standard-shaped directional coupler.
Described by a gap, length, horizontal and vertical
distance.
.. image:: /_static/images/sipann_standard_coupler.png
:alt: Standard coupler port numbering.
:width: 400px
:align: center
Parameters
----------
width : float
Width of waveguides in nanometers (valid from 400 to 600).
thickness : float
Thickness of waveguides in nanometers (Valid from 180 to 240).
gap : float
Distance between the two waveguide edges, in nanometers (must be greater
than 100).
length : float
Length of the straight portion of both waveguides, in nanometers.
horizontal : float
Horizontal distance between end of coupler and straight segment, in
nanometers.
vertical : float
Vertical distance between end of coupler and straight segment, in
nanometers.
sw_angle : float, optional
Sidewall angle of waveguide from horizontal in degrees (valid from 80
to 90, defaults to 90).
Examples
--------
>>> s = standard_coupler(wl, 500, 220, 100, 5000, 5e3, 2e3)
"""
if width < 400 or width > 600:
raise ValueError("Width must be between 400 and 600 nm")
if thickness < 180 or thickness > 240:
raise ValueError("Thickness must be between 180 and 240 nm")
if gap < 100:
raise ValueError("Gap must be greater than 100 nm")
if sw_angle < 80 or sw_angle > 90:
raise ValueError("Sidewall angle must be between 80 and 90 degrees")
model = scee.Standard(width, thickness, gap, length, horizontal, vertical, sw_angle)
sdict = _create_sdict_from_model(model, wl)
return sdict
[docs]def double_half_ring(
wl: Union[float, ArrayLike] = 1.55,
width: float = 500.0,
thickness: float = 220.0,
radius: float = 10e3,
gap: float = 100.0,
sw_angle: float = 90.0,
) -> sax.SDict:
r"""Two equally sized half-rings coupling along their edges.
Described by a radius and a gap between the two rings.
Ports are numbered as::
| 2 | | 4 |
| \ / |
| --- |
| --- |
| / \ |
| 1 | | 3 |
Parameters
----------
width : float
Width of the waveguide in nanometers (valid from 400 to 600).
thickness : float
Thickness of waveguide in nanometers (valid for 180 to 240).
radius : float
Distance from center of ring to middle of waveguide, in nanometers.
gap : float
Minimum distance from the edges of the waveguides of the two rings, in
nanometers (must be greater than 100).
sw_angle : float, optional
Sidewall angle of waveguide from horizontal in degrees (valid from 80
to 90, defaults to 90).
Notes
-----
Writing to GDS is not supported for this component.
"""
if width < 400 or width > 600:
raise ValueError("Width must be between 400 and 600 nm")
if thickness < 180 or thickness > 240:
raise ValueError("Thickness must be between 180 and 240 nm")
if gap < 100:
raise ValueError("Gap must be greater than 100 nm")
if sw_angle < 80 or sw_angle > 90:
raise ValueError("Sidewall angle must be between 80 and 90 degrees")
model = scee.DoubleHalfRing(width, thickness, radius, gap, sw_angle)
sdict = _create_sdict_from_model(model, wl)
return sdict
[docs]def angled_half_ring(
wl: Union[float, ArrayLike] = 1.55,
width: float = 500.0,
thickness: float = 220.0,
radius: float = 10e3,
gap: float = 100.0,
sw_angle: float = 90.0,
) -> sax.SDict:
r"""A halfring resonator, except what was the straight waveguide is now
curved.
Described by a radius, gap, and angle (theta) that the
"straight" waveguide is curved by.
Ports are numbered as::
| 2 \ / 4 |
| \ / |
| 1--- \ / ---3 |
| \ \ / / |
| \ -- / |
| ---- |
Parameters
----------
width : float
Width of the waveguide in nanometers (valid from 400 to 600).
thickness : float
Thickness of waveguide in nanometers (Valid for 180 to 240).
radius : float
Distance from center of ring to middle of waveguide, in nanometers.
gap : float
Minimum distance from ring waveguide edge to "straight" waveguide edge,
in nanometers (must be greater than 100).
theta : float
Angle of the curve of the "straight" waveguide, in radians.
sw_angle : float, optional
Sidewall angle of waveguide from horizontal in degrees (Valid from 80
to 90, defaults to 90).
Notes
-----
Writing to GDS is not supported for this component.
"""
width: float = (500.0,)
thickness: float = (220.0,)
radius: float = (10e3,)
gap: float = (100.0,)
theta: float = (0.0,)
sw_angle: float = (90,)
if width < 400 or width > 600:
raise ValueError("Width must be between 400 and 600 nm")
if thickness < 180 or thickness > 240:
raise ValueError("Thickness must be between 180 and 240 nm")
if gap < 100:
raise ValueError("Gap must be greater than 100 nm")
if sw_angle < 80 or sw_angle > 90:
raise ValueError("Sidewall angle must be between 80 and 90 degrees")
model = scee.AngledHalfRing(width, thickness, radius, gap, theta, sw_angle)
sdict = _create_sdict_from_model(model, wl)
return sdict
[docs]def waveguide(
wl: Union[float, ArrayLike] = 1.55,
width: float = 500.0,
thickness: float = 220.0,
length: float = 10e3,
sw_angle: float = 90.0,
) -> sax.SDict:
"""Lossless model for a straight waveguide. Main use case is for playing
nice with other models in SCEE.
.. image:: /_static/images/sipann_waveguide.png
:alt: Waveguide port numbering
:width: 200px
:align: center
Parameters
----------
width : float
Width of the waveguide in nanometers (valid from 400 to 600).
thickness : float
Thickness of waveguide in nanometers (valid for 180 to 240).
length : float
Length of waveguide in nanometers.
sw_angle : float, optional
Sidewall angle of waveguide from horizontal in degrees (valid from 80
to 90, defaults to 90).
"""
if width < 400 or width > 600:
raise ValueError("Width must be between 400 and 600 nm")
if thickness < 180 or thickness > 240:
raise ValueError("Thickness must be between 180 and 240 nm")
if sw_angle < 80 or sw_angle > 90:
raise ValueError("Sidewall angle must be between 80 and 90 degrees")
model = scee.Waveguide(width, thickness, length, sw_angle)
sdict = _create_sdict_from_model(model, wl)
return sdict
[docs]def racetrack(
wl: Union[float, ArrayLike] = 1.55,
width: float = 500.0,
thickness: float = 220.0,
radius: float = 10e3,
gap: float = 100.0,
length: float = 10e3,
sw_angle: float = 90.0,
) -> sax.SDict:
"""Racetrack waveguide arc, used to connect to a racetrack directional
coupler.
.. image:: /_static/images/sipann_racetrack.png
:alt: Racetrack port numbering
:width: 400px
:align: center
Parameters
----------
width : float
Width of the waveguide in nanometers (valid from 400 to 600).
thickness : float
Thickness of waveguide in nanometers (valid for 180 to 240).
radius : float
Distance from center of ring to middle of waveguide, in nanometers.
gap : float
Minimum distance from ring waveguide edge to "straight" waveguide edge,
in nanometers (must be greater than 100).
length : float
Length of straight portion of ring waveguide, in nanometers.
sw_angle : float, optional
Sidewall angle of waveguide from horizontal in degrees (valid from 80
to 90, defaults to 90).
Examples
--------
>>> dev = Racetrack(500, 220, 5000, 200, 5000)
Notes
-----
You can produce a GDS file of the model you instantiate using SiPANN (see
more `on SiPANN's docs <https://sipann.readthedocs.io/en/latest/>`_).
"""
if width < 400 or width > 600:
raise ValueError("Width must be between 400 and 600 nm")
if thickness < 180 or thickness > 240:
raise ValueError("Thickness must be between 180 and 240 nm")
if gap < 100:
raise ValueError("Gap must be greater than 100 nm")
if sw_angle < 80 or sw_angle > 90:
raise ValueError("Sidewall angle must be between 80 and 90 degrees")
model = comp.racetrack_sb_rr(width, thickness, radius, gap, length, sw_angle)
sdict = _create_sdict_from_model(model, wl)
return sdict
[docs]def premade_coupler(
wl: Union[float, ArrayLike] = 1.55,
split: int = 50,
) -> sax.SDict:
r"""Loads premade couplers based on the given split value.
Various splitting ratio couplers have been made and saved. This
function reloads them. Note that each of their lengths are different
and are also returned for the users info. These have all been
designed with waveguide geometry 500nm x 220nm.
Ports are numbered as::
| 2---\ /---4 |
| ------ |
| ------ |
| 1---/ \---3 |
Parameters
----------
split : int
Percent of light coming out cross port. Valid numbers are 10, 20, 30,
40, 50, 100. 100 is a full crossover.
"""
model = sipann_premade_coupler(split)[0]
sdict = _create_sdict_from_model(model, wl)
return sdict