Skip to content

Commit 44910f4

Browse files
committed
add more models
1 parent a6c5f65 commit 44910f4

5 files changed

Lines changed: 396 additions & 0 deletions

File tree

src/sax/models/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
passthru,
2323
unitary,
2424
)
25+
from .isolators import (
26+
circulator,
27+
isolator,
28+
)
2529
from .mmis import (
2630
mmi1x2,
2731
mmi1x2_ideal,
@@ -31,6 +35,10 @@
3135
from .probes import (
3236
ideal_probe,
3337
)
38+
from .reflectors import (
39+
mirror,
40+
reflector,
41+
)
3442
from .splitters import (
3543
splitter_ideal,
3644
)
@@ -39,16 +47,22 @@
3947
phase_shifter,
4048
straight,
4149
)
50+
from .terminators import (
51+
terminator,
52+
)
4253

4354
__all__ = [
4455
"attenuator",
4556
"bend",
57+
"circulator",
4658
"copier",
4759
"coupler",
4860
"coupler_ideal",
4961
"crossing_ideal",
5062
"grating_coupler",
5163
"ideal_probe",
64+
"isolator",
65+
"mirror",
5266
"mmi1x2",
5367
"mmi1x2_ideal",
5468
"mmi2x2",
@@ -58,8 +72,10 @@
5872
"model_4port",
5973
"passthru",
6074
"phase_shifter",
75+
"reflector",
6176
"rf",
6277
"splitter_ideal",
6378
"straight",
79+
"terminator",
6480
"unitary",
6581
]

src/sax/models/isolators.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"""SAX Isolator and Circulator Models."""
2+
3+
from __future__ import annotations
4+
5+
import jax
6+
import jax.numpy as jnp
7+
from pydantic import validate_call
8+
9+
import sax
10+
11+
12+
@jax.jit
13+
@validate_call
14+
def isolator(
15+
*,
16+
wl: sax.FloatArrayLike = sax.WL_C,
17+
insertion_loss_dB: sax.FloatArrayLike = 0.0,
18+
isolation_dB: sax.FloatArrayLike = 40.0,
19+
) -> sax.SDict:
20+
"""Optical isolator model (non-reciprocal).
21+
22+
Transmits light in the forward direction (in0 -> out0) with low loss
23+
while blocking the reverse direction (out0 -> in0).
24+
25+
```{svgbob}
26+
in0 out0
27+
o1 ====>====== o2
28+
```
29+
30+
Args:
31+
wl: Wavelength in micrometers.
32+
insertion_loss_dB: Forward insertion loss in dB. Defaults to 0.0 dB.
33+
isolation_dB: Reverse isolation in dB. Higher values mean better
34+
blocking of backward-propagating light. Defaults to 40.0 dB.
35+
36+
Returns:
37+
S-matrix dictionary for the isolator.
38+
39+
Examples:
40+
Isolator with 1 dB insertion loss and 30 dB isolation:
41+
42+
```python
43+
# mkdocs: render
44+
import matplotlib.pyplot as plt
45+
import numpy as np
46+
import sax
47+
48+
sax.set_port_naming_strategy("optical")
49+
50+
wl = sax.wl_c()
51+
s = sax.models.isolator(
52+
wl=wl,
53+
insertion_loss_dB=1.0,
54+
isolation_dB=30.0,
55+
)
56+
fwd = np.abs(s[("o1", "o2")]) ** 2
57+
bwd = np.abs(s[("o2", "o1")]) ** 2
58+
plt.figure()
59+
plt.plot(wl, 10 * np.log10(fwd), label="forward")
60+
plt.plot(wl, 10 * np.log10(bwd + 1e-10), label="backward")
61+
plt.xlabel("Wavelength [μm]")
62+
plt.ylabel("Transmission [dB]")
63+
plt.legend()
64+
```
65+
"""
66+
one = jnp.ones_like(jnp.asarray(wl))
67+
forward = jnp.asarray(10 ** (-insertion_loss_dB / 20), dtype=complex) * one
68+
backward = jnp.asarray(10 ** (-isolation_dB / 20), dtype=complex) * one
69+
return {
70+
("o1", "o2"): forward,
71+
("o2", "o1"): backward,
72+
}
73+
74+
75+
@jax.jit
76+
@validate_call
77+
def circulator(
78+
*,
79+
wl: sax.FloatArrayLike = sax.WL_C,
80+
insertion_loss_dB: sax.FloatArrayLike = 0.0,
81+
isolation_dB: sax.FloatArrayLike = 40.0,
82+
) -> sax.SDict:
83+
"""Optical circulator model (non-reciprocal 3-port device).
84+
85+
Routes light in a circular fashion: port 1 -> port 2 -> port 3 -> port 1.
86+
Light traveling in the reverse direction is strongly attenuated.
87+
88+
```{svgbob}
89+
o2
90+
*
91+
/ \\
92+
/ \\
93+
/ --> \\
94+
*-------*
95+
o1 o3
96+
```
97+
98+
Args:
99+
wl: Wavelength in micrometers.
100+
insertion_loss_dB: Insertion loss in dB for the forward circulation
101+
path. Defaults to 0.0 dB.
102+
isolation_dB: Isolation in dB between non-adjacent ports (reverse
103+
direction). Defaults to 40.0 dB.
104+
105+
Returns:
106+
S-matrix dictionary for the circulator.
107+
108+
Examples:
109+
3-port circulator:
110+
111+
```python
112+
# mkdocs: render
113+
import matplotlib.pyplot as plt
114+
import numpy as np
115+
import sax
116+
117+
sax.set_port_naming_strategy("optical")
118+
119+
wl = sax.wl_c()
120+
s = sax.models.circulator(
121+
wl=wl,
122+
insertion_loss_dB=0.5,
123+
isolation_dB=30.0,
124+
)
125+
fwd_12 = np.abs(s[("o1", "o2")]) ** 2
126+
fwd_23 = np.abs(s[("o2", "o3")]) ** 2
127+
iso_21 = np.abs(s[("o2", "o1")]) ** 2
128+
plt.figure()
129+
plt.plot(wl, 10 * np.log10(fwd_12), label="o1→o2")
130+
plt.plot(wl, 10 * np.log10(fwd_23), label="o2→o3")
131+
plt.plot(wl, 10 * np.log10(iso_21 + 1e-10), label="o2→o1 (isolated)")
132+
plt.xlabel("Wavelength [μm]")
133+
plt.ylabel("Transmission [dB]")
134+
plt.legend()
135+
```
136+
"""
137+
one = jnp.ones_like(jnp.asarray(wl))
138+
forward = jnp.asarray(10 ** (-insertion_loss_dB / 20), dtype=complex) * one
139+
isolated = jnp.asarray(10 ** (-isolation_dB / 20), dtype=complex) * one
140+
return {
141+
# Forward circulation: o1 -> o2 -> o3 -> o1
142+
("o1", "o2"): forward,
143+
("o2", "o3"): forward,
144+
("o3", "o1"): forward,
145+
# Reverse (isolated): o2 -> o1, o3 -> o2, o1 -> o3
146+
("o2", "o1"): isolated,
147+
("o3", "o2"): isolated,
148+
("o1", "o3"): isolated,
149+
}

src/sax/models/reflectors.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""SAX Reflector Models."""
2+
3+
from __future__ import annotations
4+
5+
import jax
6+
import jax.numpy as jnp
7+
from pydantic import validate_call
8+
9+
import sax
10+
11+
12+
@jax.jit
13+
@validate_call
14+
def reflector(
15+
*,
16+
wl: sax.FloatArrayLike = sax.WL_C,
17+
reflection: sax.FloatArrayLike = 0.5,
18+
loss_dB: sax.FloatArrayLike = 0.0,
19+
) -> sax.SDict:
20+
r"""Partial reflector / mirror model.
21+
22+
A 2-port component that reflects a fraction of the input power and
23+
transmits the rest (minus any loss).
24+
25+
```{svgbob}
26+
in0 | out0
27+
o1 ========= | ========= o2
28+
|
29+
mirror
30+
```
31+
32+
Args:
33+
wl: Wavelength in micrometers.
34+
reflection: Power reflection coefficient between 0 and 1.
35+
0 means full transmission, 1 means full reflection. Defaults to 0.5.
36+
loss_dB: Insertion loss in dB applied to both reflected and
37+
transmitted amplitudes. Defaults to 0.0 dB.
38+
39+
Returns:
40+
S-matrix dictionary for the reflector.
41+
42+
Examples:
43+
50% reflector:
44+
45+
```python
46+
# mkdocs: render
47+
import matplotlib.pyplot as plt
48+
import numpy as np
49+
import sax
50+
51+
sax.set_port_naming_strategy("optical")
52+
53+
wl = sax.wl_c()
54+
s = sax.models.reflector(wl=wl, reflection=0.5)
55+
thru = np.abs(s[("o1", "o2")]) ** 2
56+
refl = np.abs(s[("o1", "o1")]) ** 2
57+
plt.figure()
58+
plt.plot(wl, thru, label="transmission")
59+
plt.plot(wl, refl, label="reflection")
60+
plt.xlabel("Wavelength [μm]")
61+
plt.ylabel("Power")
62+
plt.legend()
63+
```
64+
"""
65+
one = jnp.ones_like(jnp.asarray(wl))
66+
loss_amp = jnp.asarray(10 ** (-loss_dB / 20), dtype=complex) * one
67+
r = jnp.asarray(reflection**0.5, dtype=complex) * loss_amp
68+
t = jnp.asarray((1 - reflection) ** 0.5, dtype=complex) * loss_amp
69+
p = sax.PortNamer(1, 1)
70+
return sax.reciprocal(
71+
{
72+
(p.in0, p.in0): r,
73+
(p.in0, p.out0): t,
74+
(p.out0, p.out0): r,
75+
}
76+
)
77+
78+
79+
@jax.jit
80+
@validate_call
81+
def mirror(
82+
*,
83+
wl: sax.FloatArrayLike = sax.WL_C,
84+
reflection: sax.FloatArrayLike = 1.0,
85+
loss_dB: sax.FloatArrayLike = 0.0,
86+
) -> sax.SDict:
87+
r"""Ideal mirror model (reflector with default 100% reflection).
88+
89+
```{svgbob}
90+
in0 ||
91+
o1 ========= ||
92+
||
93+
mirror
94+
```
95+
96+
Args:
97+
wl: Wavelength in micrometers.
98+
reflection: Power reflection coefficient between 0 and 1.
99+
Defaults to 1.0 (perfect mirror).
100+
loss_dB: Insertion loss in dB. Defaults to 0.0 dB.
101+
102+
Returns:
103+
S-matrix dictionary for the mirror.
104+
105+
Examples:
106+
Perfect mirror:
107+
108+
```python
109+
# mkdocs: render
110+
import matplotlib.pyplot as plt
111+
import numpy as np
112+
import sax
113+
114+
sax.set_port_naming_strategy("optical")
115+
116+
wl = sax.wl_c()
117+
s = sax.models.mirror(wl=wl)
118+
refl = np.abs(s[("o1", "o1")]) ** 2
119+
plt.figure()
120+
plt.plot(wl, refl, label="reflection")
121+
plt.xlabel("Wavelength [μm]")
122+
plt.ylabel("Power")
123+
plt.legend()
124+
```
125+
"""
126+
return reflector(wl=wl, reflection=reflection, loss_dB=loss_dB)

src/sax/models/rf.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,49 @@ def admittance(
207207
return sax.reciprocal(sdict)
208208

209209

210+
@partial(jax.jit, inline=True)
211+
def resistor(
212+
*,
213+
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
214+
resistance: sax.FloatLike = 50,
215+
z0: sax.ComplexLike = 50,
216+
) -> sax.SDict:
217+
r"""Ideal two-port resistor model.
218+
219+
Args:
220+
f: Frequency in Hz
221+
resistance: Resistance in Ohms
222+
z0: Reference impedance in Ω.
223+
224+
Returns:
225+
S-dictionary representing the resistor element
226+
227+
References:
228+
[@pozar2012]
229+
230+
Examples:
231+
```python
232+
# mkdocs: render
233+
import matplotlib.pyplot as plt
234+
import numpy as np
235+
import sax
236+
237+
sax.set_port_naming_strategy("optical")
238+
239+
f = np.linspace(1e9, 10e9, 500)
240+
s = sax.models.rf.resistor(f=f, resistance=100, z0=50)
241+
plt.figure()
242+
plt.plot(f / 1e9, np.abs(s[("o1", "o1")]), label="|S11|")
243+
plt.plot(f / 1e9, np.abs(s[("o1", "o2")]), label="|S12|")
244+
plt.plot(f / 1e9, np.abs(s[("o2", "o2")]), label="|S22|")
245+
plt.xlabel("Frequency [GHz]")
246+
plt.ylabel("Magnitude")
247+
plt.legend()
248+
```
249+
"""
250+
return impedance(f=f, z=resistance, z0=z0)
251+
252+
210253
@partial(jax.jit, inline=True)
211254
def capacitor(
212255
*,

0 commit comments

Comments
 (0)