prefab 0.5.1__py3-none-any.whl → 1.1.8__py3-none-any.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.
prefab/shapes.py ADDED
@@ -0,0 +1,773 @@
1
+ """Contains functions for creating various shapes as Device objects."""
2
+
3
+ from typing import Optional
4
+
5
+ import numpy as np
6
+ from skimage.draw import polygon
7
+
8
+ from .device import Device
9
+
10
+
11
+ def rectangle(width: int = 200, height: Optional[int] = None, **kwargs) -> Device:
12
+ """
13
+ Create a Device object with a rectangular shape.
14
+
15
+ Parameters
16
+ ----------
17
+ width : int
18
+ The width of the rectangle. Defaults to 200.
19
+ height : Optional[int]
20
+ The height of the rectangle. Defaults to the value of width if None.
21
+ **kwargs : dict
22
+ Additional keyword arguments to be passed to the Device constructor.
23
+
24
+ Returns
25
+ -------
26
+ Device
27
+ A Device object containing the rectangular shape.
28
+ """
29
+ if height is None:
30
+ height = width
31
+ rectangle = np.ones((height, width))
32
+ return Device(device_array=rectangle, **kwargs)
33
+
34
+
35
+ def window(
36
+ width: int = 200, height: Optional[int] = None, border_width: int = 60, **kwargs
37
+ ) -> Device:
38
+ """
39
+ Create a Device object with a window shape (hollow rectangle).
40
+
41
+ Parameters
42
+ ----------
43
+ width : int
44
+ The overall width of the window. Defaults to 200.
45
+ height : Optional[int]
46
+ The overall height of the window. Defaults to the value of width.
47
+ border_width : int
48
+ The width of the window border. Defaults to 60.
49
+ **kwargs : dict
50
+ Additional keyword arguments to be passed to the Device constructor.
51
+
52
+ Returns
53
+ -------
54
+ Device
55
+ A Device object containing the window shape.
56
+ """
57
+ if height is None:
58
+ height = width
59
+ window = np.zeros((height, width))
60
+ window[:border_width, :] = 1
61
+ window[-border_width:, :] = 1
62
+ window[:, :border_width] = 1
63
+ window[:, -border_width:] = 1
64
+ return Device(device_array=window, **kwargs)
65
+
66
+
67
+ def cross(
68
+ width: int = 200, height: Optional[int] = None, arm_width: int = 60, **kwargs
69
+ ) -> Device:
70
+ """
71
+ Create a Device object with a cross shape.
72
+
73
+ Parameters
74
+ ----------
75
+ width : int
76
+ The overall width of the cross. Defaults to 200.
77
+ height : Optional[int]
78
+ The overall height of the cross. Defaults to the value of width.
79
+ arm_width : int
80
+ The width of the cross arms. Defaults to 60.
81
+ **kwargs : dict
82
+ Additional keyword arguments to be passed to the Device constructor.
83
+
84
+ Returns
85
+ -------
86
+ Device
87
+ A Device object containing the cross shape.
88
+ """
89
+ if height is None:
90
+ height = width
91
+ cross = np.zeros((height, width))
92
+ center_x = width // 2
93
+ center_y = height // 2
94
+ half_arm_width = arm_width // 2
95
+ cross[center_y - half_arm_width : center_y + half_arm_width + 1, :] = 1
96
+ cross[:, center_x - half_arm_width : center_x + half_arm_width + 1] = 1
97
+ return Device(device_array=cross, **kwargs)
98
+
99
+
100
+ def target(
101
+ width: int = 200, height: Optional[int] = None, arm_width: int = 60, **kwargs
102
+ ) -> Device:
103
+ """
104
+ Create a Device object with a target shape (cross with center removed).
105
+
106
+ Parameters
107
+ ----------
108
+ width : int
109
+ The overall width of the target. Defaults to 200.
110
+ height : Optional[int]
111
+ The overall height of the target. Defaults to the value of width.
112
+ arm_width : int
113
+ The width of the target arms. Defaults to 60.
114
+ **kwargs : dict
115
+ Additional keyword arguments to be passed to the Device constructor.
116
+
117
+ Returns
118
+ -------
119
+ Device
120
+ A Device object containing the target shape.
121
+ """
122
+ if height is None:
123
+ height = width
124
+ target = np.zeros((height, width))
125
+ center_x = width // 2
126
+ center_y = height // 2
127
+ half_arm_width = arm_width // 2
128
+ target[center_y - half_arm_width : center_y + half_arm_width + 1, :] = 1
129
+ target[:, center_x - half_arm_width : center_x + half_arm_width + 1] = 1
130
+ target[
131
+ center_y - half_arm_width : center_y + half_arm_width + 1,
132
+ center_x - half_arm_width : center_x + half_arm_width + 1,
133
+ ] = 0
134
+ return Device(device_array=target, **kwargs)
135
+
136
+
137
+ def disk(width: int = 200, height: Optional[int] = None, **kwargs) -> Device:
138
+ """
139
+ Create a Device object with an elliptical shape.
140
+
141
+ Parameters
142
+ ----------
143
+ width : int
144
+ The width of the ellipse. Defaults to 200.
145
+ height : Optional[int]
146
+ The height of the ellipse. Defaults to the value of width.
147
+ **kwargs : dict
148
+ Additional keyword arguments to be passed to the Device constructor.
149
+
150
+ Returns
151
+ -------
152
+ Device
153
+ A Device object containing the elliptical shape.
154
+ """
155
+ if height is None:
156
+ height = width
157
+ radius_x = width // 2
158
+ radius_y = height // 2
159
+ y, x = np.ogrid[-radius_y:radius_y, -radius_x:radius_x]
160
+ mask = (x**2 / radius_x**2) + (y**2 / radius_y**2) <= 1
161
+ ellipse = np.zeros((height, width))
162
+ ellipse[mask] = 1
163
+ return Device(device_array=ellipse, **kwargs)
164
+
165
+
166
+ def ring(
167
+ width: int = 200, height: Optional[int] = None, border_width: int = 60, **kwargs
168
+ ) -> Device:
169
+ """
170
+ Create a Device object with a ring shape (hollow ellipse).
171
+
172
+ Parameters
173
+ ----------
174
+ width : int
175
+ The overall width of the ring. Defaults to 200.
176
+ height : Optional[int]
177
+ The overall height of the ring. Defaults to the value of width.
178
+ border_width : int
179
+ The width of the ring border. Defaults to 60.
180
+ **kwargs : dict
181
+ Additional keyword arguments to be passed to the Device constructor.
182
+
183
+ Returns
184
+ -------
185
+ Device
186
+ A Device object containing the ring shape.
187
+ """
188
+ if height is None:
189
+ height = width
190
+ radius_x = width // 2
191
+ radius_y = height // 2
192
+ inner_radius_x = radius_x - border_width
193
+ inner_radius_y = radius_y - border_width
194
+ y, x = np.ogrid[-radius_y:radius_y, -radius_x:radius_x]
195
+ outer_mask = x**2 / radius_x**2 + y**2 / radius_y**2 <= 1
196
+ inner_mask = x**2 / inner_radius_x**2 + y**2 / inner_radius_y**2 <= 1
197
+ ring = np.zeros((height, width))
198
+ ring[outer_mask & ~inner_mask] = 1
199
+ return Device(device_array=ring, **kwargs)
200
+
201
+
202
+ def disk_wavy(
203
+ width: int = 200, wave_amplitude: float = 10, wave_frequency: float = 10, **kwargs
204
+ ) -> Device:
205
+ """
206
+ Create a Device object with a circular shape with wavy edges.
207
+
208
+ Parameters
209
+ ----------
210
+ width : int
211
+ The overall width and height of the wavy circle. Defaults to 200.
212
+ wave_amplitude : float
213
+ The amplitude of the waves. Defaults to 10.
214
+ wave_frequency : float
215
+ The frequency of the waves. Defaults to 10.
216
+ **kwargs : dict
217
+ Additional keyword arguments to be passed to the Device constructor.
218
+
219
+ Returns
220
+ -------
221
+ Device
222
+ A Device object containing the wavy circular shape.
223
+ """
224
+ effective_radius = (width // 2) - wave_amplitude
225
+ y, x = np.ogrid[-width // 2 : width // 2, -width // 2 : width // 2]
226
+ distance_from_center = np.sqrt(x**2 + y**2)
227
+ sinusoidal_boundary = effective_radius + wave_amplitude * np.sin(
228
+ wave_frequency * np.arctan2(y, x)
229
+ )
230
+ mask = distance_from_center <= sinusoidal_boundary
231
+ circle_wavy = np.zeros((width, width))
232
+ circle_wavy[mask] = 1
233
+ return Device(device_array=circle_wavy, **kwargs)
234
+
235
+
236
+ def pie(
237
+ width: int = 200, height: Optional[int] = None, arc_angle: float = 270, **kwargs
238
+ ) -> Device:
239
+ """
240
+ Create a Device object with a pie shape.
241
+
242
+ Parameters
243
+ ----------
244
+ width : int
245
+ The width of the pie. Defaults to 200.
246
+ height : Optional[int]
247
+ The height of the pie. Defaults to the value of width.
248
+ arc_angle : float
249
+ The angle of the pie slice in degrees. Defaults to 270.
250
+ **kwargs : dict
251
+ Additional keyword arguments to be passed to the Device constructor.
252
+
253
+ Returns
254
+ -------
255
+ Device
256
+ A Device object containing the pie shape.
257
+ """
258
+ if height is None:
259
+ height = width
260
+ radius_x = width // 2
261
+ radius_y = height // 2
262
+ y, x = np.ogrid[-radius_y:radius_y, -radius_x:radius_x]
263
+ angle = np.arctan2(y, x) * 180 / np.pi
264
+ angle = (angle + 360) % 360
265
+ mask = (x**2 / radius_x**2 + y**2 / radius_y**2 <= 1) & (angle <= arc_angle)
266
+ pie = np.zeros((height, width))
267
+ pie[mask] = 1
268
+ return Device(device_array=pie, **kwargs)
269
+
270
+
271
+ def grating(
272
+ height: int = 200,
273
+ pitch: int = 120,
274
+ duty_cycle: float = 0.5,
275
+ num_gratings: int = 3,
276
+ **kwargs,
277
+ ) -> Device:
278
+ """
279
+ Create a Device object with a grating pattern.
280
+
281
+ Parameters
282
+ ----------
283
+ height : int
284
+ The height of the grating. Defaults to 200.
285
+ pitch : int
286
+ The pitch (period) of the grating. Defaults to 120.
287
+ duty_cycle : float
288
+ The duty cycle of the grating. Defaults to 0.5.
289
+ num_gratings : int
290
+ The number of grating periods. Defaults to 3.
291
+ **kwargs : dict
292
+ Additional keyword arguments to be passed to the Device constructor.
293
+
294
+ Returns
295
+ -------
296
+ Device
297
+ A Device object containing the grating pattern.
298
+ """
299
+ width = pitch * num_gratings - pitch // 2
300
+ grating = np.zeros((height, width))
301
+ grating_width = int(pitch * duty_cycle)
302
+ for i in range(num_gratings):
303
+ start = i * pitch
304
+ grating[:, start : start + grating_width] = 1
305
+ return Device(device_array=grating, **kwargs)
306
+
307
+
308
+ def star(width: int = 200, num_points: int = 5, **kwargs) -> Device:
309
+ """
310
+ Create a Device object with a star shape.
311
+
312
+ Parameters
313
+ ----------
314
+ width : int
315
+ The overall width and height of the star. Defaults to 200.
316
+ num_points : int
317
+ The number of points on the star. Defaults to 5.
318
+ **kwargs : dict
319
+ Additional keyword arguments to be passed to the Device constructor.
320
+
321
+ Returns
322
+ -------
323
+ Device
324
+ A Device object containing the star shape.
325
+ """
326
+ radius_outer = width // 2
327
+ radius_inner = radius_outer // 2
328
+ angles_outer = np.linspace(0, 2 * np.pi, num_points, endpoint=False) - np.pi / 2
329
+ angles_inner = angles_outer + np.pi / num_points
330
+ x_outer = (radius_outer * np.cos(angles_outer) + radius_outer).astype(int)
331
+ y_outer = (radius_outer * np.sin(angles_outer) + radius_outer).astype(int)
332
+ x_inner = (radius_inner * np.cos(angles_inner) + radius_outer).astype(int)
333
+ y_inner = (radius_inner * np.sin(angles_inner) + radius_outer).astype(int)
334
+ x = np.empty(2 * num_points, dtype=int)
335
+ y = np.empty(2 * num_points, dtype=int)
336
+ x[0::2] = x_outer
337
+ x[1::2] = x_inner
338
+ y[0::2] = y_outer
339
+ y[1::2] = y_inner
340
+ star = np.zeros((width, width))
341
+ rr, cc = polygon(y, x)
342
+ rr = np.clip(rr, 0, width - 1)
343
+ cc = np.clip(cc, 0, width - 1)
344
+ star[rr, cc] = 1
345
+ return Device(device_array=star, **kwargs)
346
+
347
+
348
+ def poly(width: int = 200, num_points: int = 5, **kwargs) -> Device:
349
+ """
350
+ Create a Device object with a regular polygon shape.
351
+
352
+ Parameters
353
+ ----------
354
+ width : int
355
+ The overall width and height of the polygon. Defaults to 200.
356
+ num_points : int
357
+ The number of sides of the polygon. Defaults to 5.
358
+ **kwargs : dict
359
+ Additional keyword arguments to be passed to the Device constructor.
360
+
361
+ Returns
362
+ -------
363
+ Device
364
+ A Device object containing the regular polygon shape.
365
+ """
366
+ radius = width // 2
367
+ angles = np.linspace(0, 2 * np.pi, num_points, endpoint=False) - np.pi / 2
368
+ x = (radius * np.cos(angles) + radius).astype(int)
369
+ y = (radius * np.sin(angles) + radius).astype(int)
370
+ poly = np.zeros((width, width))
371
+ rr, cc = polygon(y, x)
372
+ rr = np.clip(rr, 0, width - 1)
373
+ cc = np.clip(cc, 0, width - 1)
374
+ poly[rr, cc] = 1
375
+ return Device(device_array=poly, **kwargs)
376
+
377
+
378
+ def radial_grating(
379
+ width: int = 200, grating_skew: int = 0, num_gratings: int = 6, **kwargs
380
+ ) -> Device:
381
+ """
382
+ Create a Device object with a radial grating pattern.
383
+
384
+ Parameters
385
+ ----------
386
+ width : int
387
+ The overall width and height of the radial grating. Defaults to 200.
388
+ grating_skew : int
389
+ The skew angle of the grating arms. Defaults to 0.
390
+ num_gratings : int
391
+ The number of grating arms. Defaults to 6.
392
+ **kwargs : dict
393
+ Additional keyword arguments to be passed to the Device constructor.
394
+
395
+ Returns
396
+ -------
397
+ Device
398
+ A Device object containing the radial grating pattern.
399
+ """
400
+ radial_grating = np.zeros((width, width))
401
+ center = width // 2
402
+ radius = center
403
+ theta = np.linspace(0, 2 * np.pi, num_gratings, endpoint=False)
404
+ for angle in theta:
405
+ x0, y0 = center, center
406
+ x1 = int(center + radius * np.cos(angle))
407
+ y1 = int(center + radius * np.sin(angle))
408
+ x2 = int(
409
+ center + (radius - grating_skew) * np.cos(angle + np.pi / num_gratings)
410
+ )
411
+ y2 = int(
412
+ center + (radius - grating_skew) * np.sin(angle + np.pi / num_gratings)
413
+ )
414
+ rr, cc = polygon([y0, y1, y2], [x0, x1, x2])
415
+ rr = np.clip(rr, 0, width - 1)
416
+ cc = np.clip(cc, 0, width - 1)
417
+ radial_grating[rr, cc] = 1
418
+ return Device(device_array=radial_grating, **kwargs)
419
+
420
+
421
+ def offset_grating(
422
+ height: int = 200,
423
+ pitch: int = 120,
424
+ duty_cycle: float = 0.5,
425
+ num_gratings: int = 3,
426
+ **kwargs,
427
+ ) -> Device:
428
+ """
429
+ Create a Device object with an offset grating pattern (alternating rows).
430
+
431
+ Parameters
432
+ ----------
433
+ height : int
434
+ The height of the grating. Defaults to 200.
435
+ pitch : int
436
+ The pitch (period) of the grating. Defaults to 120.
437
+ duty_cycle : float
438
+ The duty cycle of the grating. Defaults to 0.5.
439
+ num_gratings : int
440
+ The number of grating periods. Defaults to 3.
441
+ **kwargs : dict
442
+ Additional keyword arguments to be passed to the Device constructor.
443
+
444
+ Returns
445
+ -------
446
+ Device
447
+ A Device object containing the offset grating pattern.
448
+ """
449
+ width = pitch * num_gratings
450
+ grating = np.zeros((height, width))
451
+ grating_width = int(pitch * duty_cycle)
452
+ half_height = height // 2
453
+ for i in range(num_gratings):
454
+ start = i * pitch
455
+ grating[half_height:, start : start + grating_width] = 1
456
+ for i in range(num_gratings):
457
+ start = i * pitch + pitch // 2
458
+ grating[:half_height, start : start + grating_width] = 1
459
+ return Device(device_array=grating, **kwargs)
460
+
461
+
462
+ def l_grating(
463
+ width: int = 200,
464
+ height: Optional[int] = None,
465
+ pitch: int = 100,
466
+ duty_cycle: float = 0.5,
467
+ **kwargs,
468
+ ) -> Device:
469
+ """
470
+ Create a Device object with an L-shaped grating pattern.
471
+
472
+ Parameters
473
+ ----------
474
+ width : int
475
+ The width of the L-grating. Defaults to 200.
476
+ height : Optional[int]
477
+ The height of the L-grating. Defaults to the value of width.
478
+ pitch : int
479
+ The pitch (period) of the L-shapes. Defaults to 100.
480
+ duty_cycle : float
481
+ The duty cycle of the L-shapes. Defaults to 0.5.
482
+ **kwargs : dict
483
+ Additional keyword arguments to be passed to the Device constructor.
484
+
485
+ Returns
486
+ -------
487
+ Device
488
+ A Device object containing the L-shaped grating pattern.
489
+ """
490
+ if height is None:
491
+ height = width
492
+ L_grating = np.zeros((height, width))
493
+ num_L_shapes = min(height, width) // pitch
494
+ L_width = int(pitch * duty_cycle)
495
+ for i in range(num_L_shapes):
496
+ start = i * pitch
497
+ L_grating[start : start + L_width, start:] = 1
498
+ L_grating[start:, start : start + L_width] = 1
499
+ return Device(device_array=L_grating, **kwargs)
500
+
501
+
502
+ def disks(
503
+ rows: int = 5, cols: int = 5, disk_radius: int = 30, spacing: int = 60, **kwargs
504
+ ) -> Device:
505
+ """
506
+ Create a Device object with a grid of uniform disks.
507
+
508
+ Parameters
509
+ ----------
510
+ rows : int
511
+ The number of rows in the grid. Defaults to 5.
512
+ cols : int
513
+ The number of columns in the grid. Defaults to 5.
514
+ disk_radius : int
515
+ The radius of each disk. Defaults to 30.
516
+ spacing : int
517
+ The spacing between disk centers. Defaults to 60.
518
+ **kwargs : dict
519
+ Additional keyword arguments to be passed to the Device constructor.
520
+
521
+ Returns
522
+ -------
523
+ Device
524
+ A Device object containing a grid of disks.
525
+ """
526
+ grid_height = rows * (2 * disk_radius + spacing) - spacing
527
+ grid_width = cols * (2 * disk_radius + spacing) - spacing
528
+ disks = np.zeros((grid_height, grid_width))
529
+ y, x = np.ogrid[-disk_radius:disk_radius, -disk_radius:disk_radius]
530
+ mask = x**2 + y**2 <= disk_radius**2
531
+ for row in range(rows):
532
+ for col in range(cols):
533
+ center_y = row * (2 * disk_radius + spacing) + disk_radius
534
+ center_x = col * (2 * disk_radius + spacing) + disk_radius
535
+ disks[
536
+ center_y - disk_radius : center_y + disk_radius,
537
+ center_x - disk_radius : center_x + disk_radius,
538
+ ][mask] = 1
539
+ return Device(device_array=disks, **kwargs)
540
+
541
+
542
+ def disks_offset(
543
+ rows: int = 5, cols: int = 5, disk_radius: int = 30, spacing: int = 30, **kwargs
544
+ ) -> Device:
545
+ """
546
+ Create a Device object with an offset grid of disks.
547
+
548
+ Parameters
549
+ ----------
550
+ rows : int
551
+ The number of rows in the grid. Defaults to 5.
552
+ cols : int
553
+ The number of columns in the grid. Defaults to 5.
554
+ disk_radius : int
555
+ The radius of each disk. Defaults to 30.
556
+ spacing : int
557
+ The spacing between disk centers. Defaults to 30.
558
+ **kwargs : dict
559
+ Additional keyword arguments to be passed to the Device constructor.
560
+
561
+ Returns
562
+ -------
563
+ Device
564
+ A Device object containing an offset grid of disks.
565
+ """
566
+ grid_height = rows * (2 * disk_radius + spacing) - spacing
567
+ grid_width = (
568
+ cols * (2 * disk_radius + spacing) - spacing + (disk_radius + spacing // 2)
569
+ )
570
+ disks_offset = np.zeros((grid_height, grid_width))
571
+ y, x = np.ogrid[-disk_radius:disk_radius, -disk_radius:disk_radius]
572
+ mask = x**2 + y**2 <= disk_radius**2
573
+ for row in range(rows):
574
+ for col in range(cols):
575
+ center_y = row * (2 * disk_radius + spacing) + disk_radius
576
+ center_x = (
577
+ col * (2 * disk_radius + spacing)
578
+ + disk_radius
579
+ + (disk_radius + spacing // 2 if row % 2 == 1 else 0)
580
+ )
581
+ disks_offset[
582
+ center_y - disk_radius : center_y + disk_radius,
583
+ center_x - disk_radius : center_x + disk_radius,
584
+ ][mask] = 1
585
+ return Device(device_array=disks_offset, **kwargs)
586
+
587
+
588
+ def disks_varying(
589
+ rows: int = 5,
590
+ cols: int = 5,
591
+ min_disk_radius: int = 10,
592
+ max_disk_radius: int = 30,
593
+ spacing: int = 30,
594
+ **kwargs,
595
+ ) -> Device:
596
+ """
597
+ Create a Device object with a grid of disks with varying radii.
598
+
599
+ Parameters
600
+ ----------
601
+ rows : int
602
+ The number of rows in the grid. Defaults to 5.
603
+ cols : int
604
+ The number of columns in the grid. Defaults to 5.
605
+ min_disk_radius : int
606
+ The minimum radius of the disks. Defaults to 10.
607
+ max_disk_radius : int
608
+ The maximum radius of the disks. Defaults to 30.
609
+ spacing : int
610
+ The spacing between disk centers. Defaults to 30.
611
+ **kwargs : dict
612
+ Additional keyword arguments to be passed to the Device constructor.
613
+
614
+ Returns
615
+ -------
616
+ Device
617
+ A Device object containing a grid of disks with varying radii.
618
+ """
619
+ grid_height = rows * (2 * max_disk_radius + spacing) - spacing
620
+ grid_width = cols * (2 * max_disk_radius + spacing) - spacing
621
+ disks_varying = np.zeros((grid_height, grid_width))
622
+ radius_range = np.linspace(min_disk_radius, max_disk_radius, rows * cols).reshape(
623
+ rows, cols
624
+ )
625
+ for row in range(rows):
626
+ for col in range(cols):
627
+ disk_radius = int(radius_range[row, col])
628
+ y, x = np.ogrid[-disk_radius:disk_radius, -disk_radius:disk_radius]
629
+ mask = x**2 + y**2 <= disk_radius**2
630
+ center_y = row * (2 * max_disk_radius + spacing) + max_disk_radius
631
+ center_x = col * (2 * max_disk_radius + spacing) + max_disk_radius
632
+ disks_varying[
633
+ center_y - disk_radius : center_y + disk_radius,
634
+ center_x - disk_radius : center_x + disk_radius,
635
+ ][mask] = 1
636
+ return Device(device_array=disks_varying, **kwargs)
637
+
638
+
639
+ def holes(
640
+ rows: int = 5, cols: int = 5, hole_radius: int = 30, spacing: int = 30, **kwargs
641
+ ) -> Device:
642
+ """
643
+ Create a Device object with a grid of uniform circular holes.
644
+
645
+ Parameters
646
+ ----------
647
+ rows : int
648
+ The number of rows in the grid. Defaults to 5.
649
+ cols : int
650
+ The number of columns in the grid. Defaults to 5.
651
+ hole_radius : int
652
+ The radius of each hole. Defaults to 30.
653
+ spacing : int
654
+ The spacing between hole centers. Defaults to 30.
655
+ **kwargs : dict
656
+ Additional keyword arguments to be passed to the Device constructor.
657
+
658
+ Returns
659
+ -------
660
+ Device
661
+ A Device object containing a grid of circular holes.
662
+ """
663
+ grid_height = rows * (2 * hole_radius + spacing) - spacing
664
+ grid_width = cols * (2 * hole_radius + spacing) - spacing
665
+ holes = np.ones((grid_height, grid_width))
666
+ y, x = np.ogrid[-hole_radius:hole_radius, -hole_radius:hole_radius]
667
+ mask = x**2 + y**2 <= hole_radius**2
668
+ for row in range(rows):
669
+ for col in range(cols):
670
+ center_y = row * (2 * hole_radius + spacing) + hole_radius
671
+ center_x = col * (2 * hole_radius + spacing) + hole_radius
672
+ holes[
673
+ center_y - hole_radius : center_y + hole_radius,
674
+ center_x - hole_radius : center_x + hole_radius,
675
+ ][mask] = 0
676
+ return Device(device_array=holes, **kwargs)
677
+
678
+
679
+ def holes_offset(
680
+ rows: int = 5, cols: int = 5, hole_radius: int = 30, spacing: int = 30, **kwargs
681
+ ) -> Device:
682
+ """
683
+ Create a Device object with an offset grid of circular holes.
684
+
685
+ Parameters
686
+ ----------
687
+ rows : int
688
+ The number of rows in the grid. Defaults to 5.
689
+ cols : int
690
+ The number of columns in the grid. Defaults to 5.
691
+ hole_radius : int
692
+ The radius of each hole. Defaults to 30.
693
+ spacing : int
694
+ The spacing between hole centers. Defaults to 30.
695
+ **kwargs : dict
696
+ Additional keyword arguments to be passed to the Device constructor.
697
+
698
+ Returns
699
+ -------
700
+ Device
701
+ A Device object containing an offset grid of circular holes.
702
+ """
703
+ grid_height = rows * (2 * hole_radius + spacing) - spacing
704
+ grid_width = (
705
+ cols * (2 * hole_radius + spacing) - spacing + (hole_radius + spacing // 2)
706
+ )
707
+ holes_offset = np.ones((grid_height, grid_width))
708
+ y, x = np.ogrid[-hole_radius:hole_radius, -hole_radius:hole_radius]
709
+ mask = x**2 + y**2 <= hole_radius**2
710
+ for row in range(rows):
711
+ for col in range(cols):
712
+ center_y = row * (2 * hole_radius + spacing) + hole_radius
713
+ center_x = (
714
+ col * (2 * hole_radius + spacing)
715
+ + hole_radius
716
+ + (hole_radius + spacing // 2 if row % 2 == 1 else 0)
717
+ )
718
+ holes_offset[
719
+ center_y - hole_radius : center_y + hole_radius,
720
+ center_x - hole_radius : center_x + hole_radius,
721
+ ][mask] = 0
722
+ return Device(device_array=holes_offset, **kwargs)
723
+
724
+
725
+ def holes_varying(
726
+ rows: int = 5,
727
+ cols: int = 5,
728
+ min_hole_radius: int = 10,
729
+ max_hole_radius: int = 30,
730
+ spacing: int = 30,
731
+ **kwargs,
732
+ ) -> Device:
733
+ """
734
+ Create a Device object with a grid of circular holes with varying radii.
735
+
736
+ Parameters
737
+ ----------
738
+ rows : int
739
+ The number of rows in the grid. Defaults to 5.
740
+ cols : int
741
+ The number of columns in the grid. Defaults to 5.
742
+ min_hole_radius : int
743
+ The minimum radius of the holes. Defaults to 10.
744
+ max_hole_radius : int
745
+ The maximum radius of the holes. Defaults to 30.
746
+ spacing : int
747
+ The spacing between hole centers. Defaults to 30.
748
+ **kwargs : dict
749
+ Additional keyword arguments to be passed to the Device constructor.
750
+
751
+ Returns
752
+ -------
753
+ Device
754
+ A Device object containing a grid of circular holes with varying radii.
755
+ """
756
+ grid_height = rows * (2 * max_hole_radius + spacing) - spacing
757
+ grid_width = cols * (2 * max_hole_radius + spacing) - spacing
758
+ holes_varying = np.ones((grid_height, grid_width))
759
+ radius_range = np.linspace(min_hole_radius, max_hole_radius, rows * cols).reshape(
760
+ rows, cols
761
+ )
762
+ for row in range(rows):
763
+ for col in range(cols):
764
+ hole_radius = int(radius_range[row, col])
765
+ y, x = np.ogrid[-hole_radius:hole_radius, -hole_radius:hole_radius]
766
+ mask = x**2 + y**2 <= hole_radius**2
767
+ center_y = row * (2 * max_hole_radius + spacing) + max_hole_radius
768
+ center_x = col * (2 * max_hole_radius + spacing) + max_hole_radius
769
+ holes_varying[
770
+ center_y - hole_radius : center_y + hole_radius,
771
+ center_x - hole_radius : center_x + hole_radius,
772
+ ][mask] = 0
773
+ return Device(device_array=holes_varying, **kwargs)