samplemaker-sparrow 5.4.4__cp313-cp313-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ """
2
+
3
+ This is the Python version of Sample Maker, a scripting tool for designing
4
+ lithographic masks in the GDSII format. Package `samplemaker` comes
5
+ with different tools and submodules for the creation and manipulation of basic
6
+ shapes, periodic shapes, sequences (e.g. waveguides), circuits, and complex
7
+ devices.
8
+
9
+ The code has been developed primarily for nanophotonics, but it can be easily
10
+ extended to different applications in micro and nano device fabrication.
11
+
12
+ Sample Maker is developed and maintained by Leonardo Midolo (Niels Bohr Institute,
13
+ University of Copenhagen). It is based on the MATLAB(R) code developed by Leonardo Midolo
14
+ between 2013 and 2019. The first version of the rewritten Python code has been released in October 2021.
15
+
16
+ This software has been realized with the financial support from
17
+ the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (Grant agreement No. 949043, NANOMEQ).
18
+
19
+ .. include:: ./documentation.md
20
+ """
21
+
22
+ from typing import ( # noqa: F401
23
+ cast, Any, Callable, Dict, Generator, Iterable, List, Mapping, NewType,
24
+ Optional, Set, Tuple, Type, TypeVar, Union,
25
+ )
26
+
27
+ __version__ = "5.4.3"
28
+
29
+ __pdoc__: Dict[str, Union[bool, str]] = {}
30
+ __pdoc__["samplemaker.Tutorials"]=False
31
+ __pdoc__["samplemaker.tests"]=False
32
+ __pdoc__["samplemaker.resources"]=False
33
+ __pdoc__["samplemaker.gdsreader"]=False
34
+ __pdoc__["samplemaker.devices.DevicePort"]=False
35
+
36
+ # The LayoutPool contains all the current layout, this class should generally not
37
+ # be used directly, but only through the Mask class.
38
+ LayoutPool = dict() # connects a SREF name to a particular geomgroup in the current memory
39
+ # Additional cache pool
40
+ _DevicePool = dict() # connects a device hash to a SREF to be instantiated
41
+ _DeviceLocalParamPool = dict() # connects a device hash to local parameters created by the call to geom()
42
+ _DeviceCountPool = dict() # connects a device name to a device count
43
+ _BoundingBoxPool = dict() # connects a SREF name to its bounding box
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import samplemaker.baselib.devices
4
+
5
+ print("Base library loaded")
@@ -0,0 +1,161 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Base device library.
4
+
5
+ This is a collection of some simple demo devices distributed with the base
6
+ version of `samplemaker`. It can be used as template for creating new libraries
7
+ or to learn how to design them.
8
+
9
+ Note that individual device methods are not documented but should be readable
10
+ and self-explanatory.
11
+
12
+ The library provides the following devices:
13
+
14
+ """
15
+
16
+ import numpy as np
17
+ import math
18
+ from samplemaker.devices import Device, registerDevicesInModule
19
+ import samplemaker.makers as sm
20
+ from samplemaker.baselib.waveguides import BaseWaveguideSequencer, BaseWaveguidePort
21
+
22
+ class CrossMark(Device):
23
+ def initialize(self):
24
+ self.set_name("BASELIB_CMARK")
25
+ self.set_description("Generic cross marker for mask alignment.")
26
+
27
+ def parameters(self):
28
+ self.addparameter("length1", 20, "Length of inner cross",float)
29
+ self.addparameter("length2", 10, "Length of outer cross",float)
30
+ self.addparameter("width1", 0.5, "Width of inner cross", float)
31
+ self.addparameter("width2", 2, "width of outer cross", float)
32
+ self.addparameter("layer", 4, "Layer to use for cross", int,(0,255))
33
+ self.addparameter("mark_number",0,"Places a square in the corner, use 0 to remove", float, (0,4))
34
+ self.addparameter("square_size",10,"Size of the square in the corner", float)
35
+
36
+ def geom(self):
37
+ p = self.get_params();
38
+ cross = sm.make_rect(0, 0, p["length1"], p["width1"],layer=1)
39
+ cross += sm.make_rect(0, 0, p["width1"], p["length1"],layer=1)
40
+ cross.boolean_union(1)
41
+ ocross = sm.make_rect(p["length1"]/2,0,p["length2"],p["width2"],numkey=4)
42
+ for i in range(4):
43
+ c = ocross.copy()
44
+ c.rotate(0, 0, 90*i)
45
+ cross+=c
46
+ if(p["mark_number"]>0):
47
+ rot = 90*(p["mark_number"]-1)
48
+ square = sm.make_rect(p["length1"]/2+p["length2"],
49
+ p["length1"]/2+p["length2"],
50
+ p["square_size"],p["square_size"],numkey=1)
51
+ square.rotate(0,0,rot)
52
+ cross+=square
53
+
54
+ cross.set_layer(p["layer"])
55
+ return cross
56
+
57
+ class DirectionalCoupler(Device):
58
+ def initialize(self):
59
+ self.set_name("BASELIB_DCPL")
60
+ self.set_description("Simple symmetric directional coupler")
61
+
62
+ def parameters(self):
63
+ self.addparameter("length", 20, "Coupling length",float)
64
+ self.addparameter("width", 0.3, "Width of the waveguides in the coupling section",float,(0.01,1))
65
+ self.addparameter("gap", 0.5, "Distance between waveguides in the coupling section", float)
66
+ self.addparameter("input_dist", 5, "Distance between waveguides at input", float,(0.01,np.inf))
67
+ self.addparameter("input_len", 7, "Length of the input section from input to coupling", float, (3,np.inf))
68
+
69
+ def geom(self):
70
+ p = self.get_params();
71
+ # Draw the upper arm, then mirror
72
+ off = p["input_dist"]/2
73
+ clen = (p["input_len"]-1)/2
74
+ Ltot = p["length"]+p["input_len"]*2
75
+ seq = [['T',1,p["width"]],["C",-off,clen],
76
+ ['S',p["length"]/2]]
77
+ ss = BaseWaveguideSequencer(seq)
78
+ dc = ss.run()
79
+ dc2 = dc.copy()
80
+ dc2.mirrorX(Ltot/2)
81
+ dc+=dc2
82
+ dc.translate(-Ltot/2, off+p["gap"]/2+p["width"]/2)
83
+ dc3 = dc.copy()
84
+ dc3.mirrorY(0)
85
+ dc+=dc3
86
+ # Add ports
87
+ XP = Ltot/2
88
+ YP = off+p["gap"]/2+p["width"]/2
89
+ self.addlocalport(BaseWaveguidePort(-XP, YP, "west", ss.options["defaultWidth"], "p1"))
90
+ self.addlocalport(BaseWaveguidePort( XP, YP, "east", ss.options["defaultWidth"], "p2"))
91
+ self.addlocalport(BaseWaveguidePort(-XP,-YP, "west", ss.options["defaultWidth"], "p3"))
92
+ self.addlocalport(BaseWaveguidePort( XP,-YP, "east", ss.options["defaultWidth"], "p4"))
93
+
94
+ return dc
95
+
96
+ class FocusingGratingCoupler(Device):
97
+ def initialize(self):
98
+ self.set_name("BASELIB_FGC")
99
+ self.set_description("Grating coupler demo.")
100
+
101
+ def parameters(self):
102
+ self.addparameter('w0',0.3,'Width of the waveguide at the start', float)
103
+ self.addparameter('pitch',0.355,'Grating default pitch', float)
104
+ self.addparameter('ff',0.5,'Fill factor', float)
105
+ self.addparameter('theta',10,'Emission angle at central wavelength', float)
106
+ self.addparameter('lambda0',0.94,'Central wavelength', float)
107
+ self.addparameter('nr_Apo',11,'nr of the 1st arc with pitch and ff',int)
108
+ self.addparameter('ff_coef',0.5,'min ff_apod = ff_coef*ff', float);
109
+ self.addparameter('order_start',10,'Starting period', int);
110
+ self.addparameter('order',15,'Number of periods', int);
111
+ self.addparameter('diverg_angle',20,'GRT divergence angle/2, deg', float);
112
+ self.addparameter('pre_split',True,'Split in quads = false', bool);
113
+
114
+ def geom(self):
115
+ # Grating first
116
+ p = self.get_params();
117
+ theta = math.radians(p["theta"])
118
+ div_angle = p["diverg_angle"]
119
+ q0 = p["order_start"]
120
+ qN = q0+p["order"]+1
121
+ lambda0 = p["lambda0"]
122
+ pitch = p["pitch"]
123
+ n = math.sin(theta)+lambda0/pitch # Effective refractive index
124
+ p0 = lambda0/math.sqrt(n*n-np.power(math.sin(theta),2));
125
+ ff = p["ff"]
126
+ nr_Apo = p["nr_Apo"]
127
+ ff_coef = p["ff_coef"]
128
+
129
+ g = sm.GeomGroup()
130
+ for q in range(q0,qN):
131
+ b = q*p0
132
+ x0 = b*b*math.sin(theta)/(q*lambda0);
133
+ a = b*b*n/(q*lambda0);
134
+ if (q <= q0+nr_Apo-1):
135
+ ff_chi = ff-(1-ff_coef)*ff/(nr_Apo-2)*(q0+nr_Apo-q);
136
+ else:
137
+ ff_chi = ff
138
+
139
+ w = ff_chi*pitch
140
+ g+=sm.make_arc(x0, 0, a, b, 0, w, -div_angle-5, div_angle+5,
141
+ layer=3,to_poly=True,vertices=40,split=p["pre_split"])
142
+
143
+ # waveguide
144
+ Ltaper = 1
145
+ Gtaper = qN*pitch
146
+ Wtaper = Gtaper*math.tan(math.radians(div_angle))*2
147
+
148
+
149
+ seq = [["T",Ltaper,p["w0"]],
150
+ ["CENTER",0,0],
151
+ ["T",Gtaper,Wtaper],['STATE','w',2.5],['S',1]]
152
+
153
+ ss = BaseWaveguideSequencer(seq)
154
+ g += ss.run()
155
+
156
+ self.addlocalport(BaseWaveguidePort(-Ltaper, 0, "west", p["w0"], "p1"))
157
+
158
+ return g
159
+
160
+ # Register all devices here in this module
161
+ registerDevicesInModule(__name__)
@@ -0,0 +1,320 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Base waveguide library.
4
+
5
+
6
+ Implements a simple waveguide sequencer and optical ports.
7
+ This module can be used as template to develop different waveguide libraries.
8
+
9
+ """
10
+
11
+ import math
12
+ from copy import deepcopy
13
+ import numpy as np
14
+ from samplemaker.devices import DevicePort
15
+ from samplemaker.shapes import GeomGroup
16
+ import samplemaker.sequencer as smseq
17
+ import samplemaker.makers as sm
18
+ from samplemaker.routers import WaveguideConnect
19
+
20
+ # First step in defining a waveguide library is to define a sequencer
21
+ # and its dictionary.
22
+
23
+ # Let's define some options for the BaseWaveguide sequencer
24
+ def BaseWaveguideOptions():
25
+ BaseWaveguidesOptions = smseq.default_options()
26
+ # Let's define the default waveguide layer
27
+ BaseWaveguidesOptions["wgLayer"] = 1
28
+ # For waveguide bends, let's use a fixed resolution
29
+ BaseWaveguidesOptions["bendResolution"] = 30
30
+ # Let's define the default waveguide width
31
+ BaseWaveguidesOptions["defaultWidth"] = 0.3
32
+ return BaseWaveguidesOptions
33
+
34
+ # Let's define the sequencer state class
35
+ # We could use the default, but we would like to store
36
+ # the current waveguide width as well using the parameter 'w'
37
+ class BaseWaveguideState(smseq.SequencerState):
38
+ def __init__(self):
39
+ """
40
+ The sequencer state for BaseWaveguide library.
41
+ Defines 'w' as current waveguide width.
42
+
43
+ Returns
44
+ -------
45
+ None.
46
+
47
+ """
48
+ super().__init__()
49
+ self.state["w"]=0 # The value will be set by the INIT command
50
+
51
+ # Let's define the INIT command, which is always the first to execute
52
+ def BaseWaveguideINIT(state, options):
53
+ smseq.__initState(state,options)
54
+ if(not options["__no_init__"]):
55
+ state['w'] = options['defaultWidth']
56
+
57
+ # The S command to go straight
58
+ def BaseWaveguideS(args,state,options)->GeomGroup:
59
+ """
60
+ Draw straight waveguide
61
+
62
+ Parameters
63
+ ----------
64
+ args : list
65
+ 1 argument: waveguide length.
66
+ state : dict
67
+ Current state.
68
+ options : dict
69
+ The sequencer options.
70
+
71
+ Returns
72
+ -------
73
+ samplemaker.shapes.GeomGroup
74
+ The waveguide geometry.
75
+
76
+ """
77
+ dist = args[0]
78
+ if(dist==0):
79
+ return GeomGroup()
80
+
81
+ # Let's draw a simple rectangle
82
+ wg = sm.make_rect(0,0,dist,state['w'],numkey=4,layer=options["wgLayer"])
83
+ # Now rotate and translate according to pointer orientation
84
+ wg.rotate_translate(state['x'], state['y'], state['a'])
85
+ # Finally, update the state
86
+ state['x']+=dist*math.cos(math.radians(state['a']))
87
+ state['y']+=dist*math.sin(math.radians(state['a']))
88
+ state['__OL__']+=dist
89
+ return wg
90
+
91
+ # The B command to make a circular bend
92
+ def BaseWaveguideB(args,state,options)->GeomGroup:
93
+ """
94
+ Draw circular bend waveguide
95
+
96
+ Parameters
97
+ ----------
98
+ args : list
99
+ 2 arguments: angle of bend (in degrees), radius of bend.
100
+ state : dict
101
+ Current state.
102
+ options : dict
103
+ The sequencer options.
104
+
105
+ Returns
106
+ -------
107
+ samplemaker.shapes.GeomGroup
108
+ The waveguide geometry.
109
+
110
+ """
111
+ angle = args[0]
112
+ radius = args[1]
113
+ if(angle==0):
114
+ return GeomGroup()
115
+
116
+ wg = sm.make_arc(0, radius, radius, radius,
117
+ -90, state['w'], 0, abs(angle),
118
+ vertices=options["bendResolution"],
119
+ to_poly=True,layer=options["wgLayer"])
120
+ xf = radius*math.sin(math.radians(abs(angle)))
121
+ yf = radius*(1-math.cos(math.radians(abs(angle))))
122
+ if(angle<0):
123
+ wg.mirrorY(0)
124
+ yf=-yf
125
+ ept = sm.make_dot(xf, yf) # helps calculating the end point
126
+ # Now rotate and translate according to pointer orientation
127
+ wg.rotate_translate(state['x'], state['y'], state['a'])
128
+ ept.rotate_translate(state['x'], state['y'], state['a'])
129
+ # Finally, update the state
130
+ state['x']=ept.x
131
+ state['y']=ept.y
132
+ state['a']+=angle
133
+ state['__OL__']+=radius*2*math.pi/360*abs(angle)
134
+
135
+ return wg
136
+
137
+ def BaseWaveguideC(args, state, options)->GeomGroup:
138
+ """
139
+ Draw cosine bend waveguide. While keeping the same direciton,
140
+ bend the waveguide using a cosine function.
141
+
142
+ Parameters
143
+ ----------
144
+ args : list
145
+ 2 arguments: offset (in um), radius of bend.
146
+ state : dict
147
+ Current state.
148
+ options : dict
149
+ The sequencer options.
150
+
151
+ Returns
152
+ -------
153
+ samplemaker.shapes.GeomGroup
154
+ The waveguide geometry.
155
+
156
+ """
157
+ off = args[0]
158
+ radius = args[1]
159
+ delta = 0.01 # at the very beginning and at the end go straight by delta
160
+ radius -= delta
161
+ if(radius ==0):
162
+ return GeomGroup()
163
+ N = options['bendResolution']
164
+ amp = math.pi*off/4/radius
165
+ t = np.linspace(0,2,N);
166
+ s = [math.asin(math.tan(math.atan(amp)*x)/amp) for x in t if x<1]
167
+ s+= [math.asin(math.tan(math.atan(amp)*(x-2))/amp)+math.pi for x in t if x>=1]
168
+ s = np.array(s)
169
+ xpts = s/math.pi*2*radius + state['x']
170
+ ypts = off*(np.cos(s+math.pi)+1)/2 + state['y']
171
+ xpts = np.append(xpts[0],xpts+delta)
172
+ xpts = np.append(xpts,xpts[-1]+delta)
173
+ ypts = np.append(ypts[0],ypts)
174
+ ypts = np.append(ypts,ypts[-1])
175
+ OL = np.sum(np.sqrt(np.power(np.ediff1d(xpts),2)+np.power(np.ediff1d(ypts),2)))
176
+ wg = sm.make_path(xpts, ypts, state['w'],to_poly=1,layer=options["wgLayer"])
177
+ outdot = sm.make_dot(xpts[-1],ypts[-1])
178
+ wg.rotate(state['x'],state['y'],state['a'])
179
+ outdot.rotate(state["x"],state["y"],state["a"])
180
+ state['x']=outdot.x
181
+ state['y']=outdot.y
182
+ state["__OL__"]+=OL
183
+ return wg
184
+
185
+ def BaseWaveguideT(args, state, options)->GeomGroup:
186
+ """
187
+ Draw linear taper
188
+
189
+ Parameters
190
+ ----------
191
+ args : list
192
+ 2 arguments: length of taper (in um), final width (if <0, the defaultWidth value is used).
193
+ state : dict
194
+ Current state.
195
+ options : dict
196
+ The sequencer options.
197
+
198
+ Returns
199
+ -------
200
+ samplemaker.shapes.GeomGroup
201
+ The waveguide geometry.
202
+
203
+ """
204
+ dist = args[0]
205
+ wf = args[1]
206
+ if(dist==0):
207
+ return GeomGroup()
208
+ if(wf < 0): wf = options["defaultWidth"]
209
+ a = math.radians(state['a'])
210
+ xf = state['x']+dist*math.cos(a)
211
+ yf = state['y']+dist*math.sin(a)
212
+ wg = sm.make_tapered_path([state['x'],xf], [state['y'],yf], [state['w'],wf],
213
+ layer=options["wgLayer"])
214
+ state['x']=xf
215
+ state['y']=yf
216
+ state['w']=wf
217
+ state["__OL__"]+=dist
218
+ return wg
219
+
220
+ def BaseWaveguideOFF(args,state,options)->GeomGroup:
221
+ """
222
+ Offset the waveguide (jumps left or right of waveguide)
223
+
224
+ Parameters
225
+ ----------
226
+ args : list
227
+ 1 argument: offset (in um), positive means on left of waveguide direction.
228
+ state : dict
229
+ Current state.
230
+ options : dict
231
+ The sequencer options.
232
+
233
+ Returns
234
+ -------
235
+ samplemaker.shapes.GeomGroup
236
+ The waveguide geometry.
237
+
238
+ """
239
+ off = args[0]
240
+ a = math.radians(state['a']+90)
241
+ state['x']+=off*math.cos(a)
242
+ state['y']+=off*math.sin(a)
243
+ return GeomGroup()
244
+
245
+ def BaseWaveguideCommands() -> dict:
246
+ """
247
+ Creates the dictionary with the command list and corresponding
248
+ functions.
249
+
250
+ Returns
251
+ -------
252
+ dict
253
+ The command list to be used by the sequencer.
254
+
255
+ """
256
+ command_list=smseq.default_command_list()
257
+ command_list["INIT"] = (0,BaseWaveguideINIT)
258
+ command_list["S"] = (1,BaseWaveguideS)
259
+ command_list["B"] = (2,BaseWaveguideB)
260
+ command_list["C"] = (2,BaseWaveguideC)
261
+ command_list["T"] = (2,BaseWaveguideT)
262
+ command_list["OFF"] = (1,BaseWaveguideOFF)
263
+ return command_list
264
+
265
+ # Finally, create a custom sequencer
266
+ class BaseWaveguideSequencer(smseq.Sequencer):
267
+ def __init__(self,seq):
268
+ """
269
+ Creates a custom sequencer for simple waveguides.
270
+
271
+ Parameters
272
+ ----------
273
+ seq : list
274
+ The sequence to be executed.
275
+
276
+ Returns
277
+ -------
278
+ None.
279
+
280
+ """
281
+ opts = BaseWaveguideOptions()
282
+ state = BaseWaveguideState()
283
+ cmds = BaseWaveguideCommands()
284
+ super().__init__(seq,opts,state,cmds)
285
+
286
+
287
+ # some global connector options
288
+ BaseWaveguideConnectorOptions = {"bending_radius":3,
289
+ "sequencer_options":BaseWaveguideOptions()}
290
+
291
+ def BaseWaveguideConnector(port1: DevicePort,port2: DevicePort) -> GeomGroup:
292
+ res = WaveguideConnect(port1, port2,BaseWaveguideConnectorOptions["bending_radius"])
293
+ if(res[0]==True):
294
+ so = BaseWaveguideSequencer(res[1])
295
+ so.options = deepcopy(BaseWaveguideConnectorOptions["sequencer_options"])
296
+ g = so.run()
297
+ g.rotate_translate(port1.x0,port1.y0,math.degrees(port1.angle()))
298
+ return g
299
+ else:
300
+ return GeomGroup()
301
+
302
+ # Now let's create a new DevicePort with a connector function
303
+ class BaseWaveguidePort(DevicePort):
304
+ def __init__(self,x0: float, y0 : float,orient: str ="East",width: float =None,name: str =None):
305
+ orient = orient.lower()
306
+ horizontal = True
307
+ forward = True
308
+ if(orient=="west" or orient=="w"):
309
+ forward=False
310
+ if(orient=="north" or orient=="n"):
311
+ horizontal=False
312
+ if(orient=="south" or orient=="s"):
313
+ horizontal=False
314
+ forward=False
315
+
316
+ super().__init__(x0,y0,horizontal,forward)
317
+ self.width = width
318
+ self.name=name
319
+ self.connector_function=BaseWaveguideConnector
320
+