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

1"""Superconducting nanowire single-photon detector (SNSPD).""" 

2 

3from __future__ import annotations 

4 

5import gdsfactory as gf 

6import numpy as np 

7from gdsfactory.component import Component 

8from gdsfactory.typings import LayerSpec, Port, Size 

9 

10 

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. 

23 

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. 

39 

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 

45 

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 

52 

53 num_meanders = int(np.ceil(ysize / wire_pitch)) 

54 

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 ) 

64 

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 

69 

70 port_type = "electrical" 

71 

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 

88 

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) 

94 

95 D.add_port(port=start_nw.ports["e1"], name="e1") 

96 D.add_port(port=finish_se.ports["e1"], name="e2") 

97 

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 

104 

105 

106if __name__ == "__main__": 

107 from qpdk import PDK 

108 

109 PDK.activate() 

110 

111 c = snspd() 

112 c.show()