Coverage for qpdk / cells / snspd.py: 91%
47 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-14 10:27 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-14 10:27 +0000
1"""Superconducting nanowire single-photon detector (SNSPD)."""
3from __future__ import annotations
5import gdsfactory as gf
6import numpy as np
7from gdsfactory.component import Component
8from gdsfactory.typings import LayerSpec, Port, Size
11@gf.cell
12def snspd(
13 wire_width: float = 0.2,
14 wire_pitch: float = 0.6,
15 size: Size = (10, 8),
16 num_squares: int | None = None,
17 turn_ratio: float = 4,
18 terminals_same_side: bool = False,
19 layer: LayerSpec = "M1_DRAW",
20 port_type: str = "electrical",
21) -> Component:
22 """Creates an optimally-rounded SNSPD.
24 Args:
25 wire_width: Width of the wire.
26 wire_pitch: Distance between two adjacent wires. Must be greater than `width`.
27 size: Float2
28 (width, height) of the rectangle formed by the outer boundary of the
29 SNSPD.
30 num_squares: int | None = None
31 Total number of squares inside the SNSPD length.
32 turn_ratio: float
33 Specifies how much of the SNSPD width is dedicated to the 180 degree
34 turn. A `turn_ratio` of 10 will result in 20% of the width being
35 comprised of the turn.
36 terminals_same_side: If True, both ports will be located on the same side of the SNSPD.
37 layer: layer spec to put polygon geometry on.
38 port_type: type of port to add to the component.
40 """
41 if num_squares is not None:
42 xy = np.sqrt(num_squares * wire_pitch * wire_width)
43 size = (xy, xy)
44 num_squares = None
46 xsize, ysize = size
47 if num_squares is not None:
48 if xsize is None:
49 xsize = num_squares * wire_pitch * wire_width / ysize
50 elif ysize is None:
51 ysize = num_squares * wire_pitch * wire_width / xsize
53 num_meanders = int(np.ceil(ysize / wire_pitch))
55 D = Component()
56 hairpin = gf.c.optimal_hairpin(
57 width=wire_width,
58 pitch=wire_pitch,
59 turn_ratio=turn_ratio,
60 length=xsize / 2,
61 num_pts=20,
62 layer=layer,
63 )
65 if (not terminals_same_side and (num_meanders % 2) == 0) or (
66 terminals_same_side and (num_meanders % 2) == 1
67 ):
68 num_meanders += 1
70 port_type = "electrical"
72 start_nw = D.add_ref(
73 gf.c.compass(size=(xsize / 2, wire_width), layer=layer, port_type=port_type)
74 )
75 hp_prev = D.add_ref(hairpin)
76 hp_prev.connect("e1", start_nw.ports["e3"])
77 alternate = True
78 last_port: Port | None = None
79 for _n in range(2, num_meanders):
80 hp = D.add_ref(hairpin)
81 if alternate:
82 hp.connect("e2", hp_prev.ports["e2"])
83 else:
84 hp.connect("e1", hp_prev.ports["e1"])
85 last_port = hp.ports["e2"] if terminals_same_side else hp.ports["e1"]
86 hp_prev = hp
87 alternate = not alternate
89 finish_se = D.add_ref(
90 gf.c.compass(size=(xsize / 2, wire_width), layer=layer, port_type=port_type)
91 )
92 if last_port is not None:
93 finish_se.connect("e3", last_port)
95 D.add_port(port=start_nw.ports["e1"], name="e1")
96 D.add_port(port=finish_se.ports["e1"], name="e2")
98 D.info["num_squares"] = num_meanders * (xsize / wire_width)
99 D.info["area"] = xsize * ysize
100 D.info["xsize"] = xsize
101 D.info["ysize"] = ysize
102 D.flatten()
103 return D
106if __name__ == "__main__":
107 from qpdk import PDK
109 PDK.activate()
111 c = snspd()
112 c.show()