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