prefab 1.1.4__py3-none-any.whl → 1.1.6__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/device.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Provides the Device class for representing photonic devices."""
2
2
 
3
- from typing import Optional
3
+ from typing import TYPE_CHECKING, Literal, Optional
4
4
 
5
5
  import cv2
6
6
  import gdstk
@@ -9,7 +9,7 @@ import numpy as np
9
9
  from matplotlib.axes import Axes
10
10
  from matplotlib.patches import Rectangle
11
11
  from PIL import Image
12
- from pydantic import BaseModel, Field, conint, root_validator, validator
12
+ from pydantic import BaseModel, Field, model_validator, validator
13
13
  from scipy.ndimage import distance_transform_edt
14
14
  from skimage import measure
15
15
 
@@ -17,6 +17,10 @@ from . import compare, geometry
17
17
  from .models import Model
18
18
  from .predict import predict_array
19
19
 
20
+ if TYPE_CHECKING:
21
+ import gdsfactory as gf
22
+ import tidy3d as td
23
+
20
24
  Image.MAX_IMAGE_PIXELS = None
21
25
 
22
26
 
@@ -33,19 +37,21 @@ class BufferSpec(BaseModel):
33
37
  ----------
34
38
  mode : dict[str, str]
35
39
  A dictionary that defines the buffer mode for each side of the device
36
- ('top', 'bottom', 'left', 'right'), where 'constant' is used for isolated
37
- structures and 'edge' is utilized for preserving the edge, such as for waveguide
38
- connections.
39
- thickness : dict[str, conint(gt=0)]
40
+ ('top', 'bottom', 'left', 'right'), where:
41
+ - 'constant' is used for isolated structures
42
+ - 'edge' is utilized for preserving the edge, such as for waveguide connections
43
+ - 'none' for no buffer on that side
44
+ thickness : dict[str, int]
40
45
  A dictionary that defines the thickness of the buffer zone for each side of the
41
- device ('top', 'bottom', 'left', 'right'). Each value must be greater than 0.
46
+ device ('top', 'bottom', 'left', 'right'). Each value must be greater than or
47
+ equal to 0.
42
48
 
43
49
  Raises
44
50
  ------
45
51
  ValueError
46
52
  If any of the modes specified in the 'mode' dictionary are not one of the
47
- allowed values ('constant', 'edge'). Or if any of the thickness values are not
48
- greater than 0.
53
+ allowed values ('constant', 'edge', 'none'). Or if any of the thickness values
54
+ are negative.
49
55
 
50
56
  Example
51
57
  -------
@@ -54,20 +60,20 @@ class BufferSpec(BaseModel):
54
60
  buffer_spec = pf.BufferSpec(
55
61
  mode={
56
62
  "top": "constant",
57
- "bottom": "edge",
63
+ "bottom": "none",
58
64
  "left": "constant",
59
65
  "right": "edge",
60
66
  },
61
67
  thickness={
62
68
  "top": 150,
63
- "bottom": 100,
69
+ "bottom": 0,
64
70
  "left": 200,
65
71
  "right": 250,
66
72
  },
67
73
  )
68
74
  """
69
75
 
70
- mode: dict[str, str] = Field(
76
+ mode: dict[str, Literal["constant", "edge", "none"]] = Field(
71
77
  default_factory=lambda: {
72
78
  "top": "constant",
73
79
  "bottom": "constant",
@@ -75,7 +81,7 @@ class BufferSpec(BaseModel):
75
81
  "right": "constant",
76
82
  }
77
83
  )
78
- thickness: dict[str, conint(gt=0)] = Field(
84
+ thickness: dict[str, int] = Field(
79
85
  default_factory=lambda: {
80
86
  "top": 128,
81
87
  "bottom": 128,
@@ -86,11 +92,32 @@ class BufferSpec(BaseModel):
86
92
 
87
93
  @validator("mode", pre=True)
88
94
  def check_mode(cls, v):
89
- allowed_modes = ["constant", "edge"]
95
+ allowed_modes = ["constant", "edge", "none"]
90
96
  if not all(mode in allowed_modes for mode in v.values()):
91
- raise ValueError(f"Buffer mode must be one of {allowed_modes}, got '{v}'")
97
+ raise ValueError(f"Buffer mode must be one of {allowed_modes}, got '{v}'.")
98
+ return v
99
+
100
+ @validator("thickness")
101
+ def check_thickness(cls, v):
102
+ if not all(t >= 0 for t in v.values()):
103
+ raise ValueError("All thickness values must be greater than or equal to 0.")
92
104
  return v
93
105
 
106
+ @model_validator(mode="after")
107
+ def check_none_thickness(cls, values):
108
+ mode = values.mode
109
+ thickness = values.thickness
110
+ for side in mode:
111
+ if mode[side] == "none" and thickness[side] != 0:
112
+ raise ValueError(
113
+ f"Thickness must be 0 when mode is 'none' for {side} side"
114
+ )
115
+ if mode[side] != "none" and thickness[side] == 0:
116
+ raise ValueError(
117
+ f"Mode must be 'none' when thickness is 0 for {side} side"
118
+ )
119
+ return values
120
+
94
121
 
95
122
  class Device(BaseModel):
96
123
  device_array: np.ndarray = Field(...)
@@ -100,8 +127,8 @@ class Device(BaseModel):
100
127
  arbitrary_types_allowed = True
101
128
 
102
129
  @property
103
- def shape(self) -> tuple[int, int]:
104
- return self.device_array.shape
130
+ def shape(self) -> tuple[int, ...]:
131
+ return tuple(self.device_array.shape)
105
132
 
106
133
  def __init__(
107
134
  self, device_array: np.ndarray, buffer_spec: Optional[BufferSpec] = None
@@ -112,11 +139,11 @@ class Device(BaseModel):
112
139
 
113
140
  This class is designed to encapsulate the geometric representation of a photonic
114
141
  device, facilitating operations such as padding, normalization, binarization,
115
- ternarization, trimming, and blurring. These operations are useful for preparing
116
- the device design for prediction or correction. Additionally, the class provides
117
- methods for exporting the device representation to various formats, including
118
- ndarray, image files, and GDSII files, supporting a range of analysis and
119
- fabrication workflows.
142
+ erosion/dilation, trimming, and blurring. These operations are useful for
143
+ preparingthe device design for prediction or correction. Additionally, the class
144
+ providesmethods for exporting the device representation to various formats,
145
+ includingndarray, image files, and GDSII files, supporting a range of analysis
146
+ and fabrication workflows.
120
147
 
121
148
  Parameters
122
149
  ----------
@@ -124,7 +151,7 @@ class Device(BaseModel):
124
151
  A 2D array representing the planar geometry of the device. This array
125
152
  undergoes various transformations to predict or correct the nanofabrication
126
153
  process.
127
- buffer_spec : BufferSpec, optional
154
+ buffer_spec : Optional[BufferSpec]
128
155
  Defines the parameters for adding a buffer zone around the device geometry.
129
156
  This buffer zone is needed for providing surrounding context for prediction
130
157
  or correction and for ensuring seamless integration with the surrounding
@@ -154,30 +181,37 @@ class Device(BaseModel):
154
181
  buffer_thickness = self.buffer_spec.thickness
155
182
  buffer_mode = self.buffer_spec.mode
156
183
 
157
- self.device_array = np.pad(
158
- self.device_array,
159
- pad_width=((buffer_thickness["top"], 0), (0, 0)),
160
- mode=buffer_mode["top"],
161
- )
162
- self.device_array = np.pad(
163
- self.device_array,
164
- pad_width=((0, buffer_thickness["bottom"]), (0, 0)),
165
- mode=buffer_mode["bottom"],
166
- )
167
- self.device_array = np.pad(
168
- self.device_array,
169
- pad_width=((0, 0), (buffer_thickness["left"], 0)),
170
- mode=buffer_mode["left"],
171
- )
172
- self.device_array = np.pad(
173
- self.device_array,
174
- pad_width=((0, 0), (0, buffer_thickness["right"])),
175
- mode=buffer_mode["right"],
176
- )
184
+ if buffer_mode["top"] != "none":
185
+ self.device_array = np.pad(
186
+ self.device_array,
187
+ pad_width=((buffer_thickness["top"], 0), (0, 0)),
188
+ mode=buffer_mode["top"],
189
+ )
190
+
191
+ if buffer_mode["bottom"] != "none":
192
+ self.device_array = np.pad(
193
+ self.device_array,
194
+ pad_width=((0, buffer_thickness["bottom"]), (0, 0)),
195
+ mode=buffer_mode["bottom"],
196
+ )
197
+
198
+ if buffer_mode["left"] != "none":
199
+ self.device_array = np.pad(
200
+ self.device_array,
201
+ pad_width=((0, 0), (buffer_thickness["left"], 0)),
202
+ mode=buffer_mode["left"],
203
+ )
204
+
205
+ if buffer_mode["right"] != "none":
206
+ self.device_array = np.pad(
207
+ self.device_array,
208
+ pad_width=((0, 0), (0, buffer_thickness["right"])),
209
+ mode=buffer_mode["right"],
210
+ )
177
211
 
178
212
  self.device_array = np.expand_dims(self.device_array, axis=-1)
179
213
 
180
- @root_validator(pre=True)
214
+ @model_validator(mode="before")
181
215
  def check_device_array(cls, values):
182
216
  device_array = values.get("device_array")
183
217
  if not isinstance(device_array, np.ndarray):
@@ -226,11 +260,11 @@ class Device(BaseModel):
226
260
  in `models.py`. Each model is associated with a version and dataset that
227
261
  detail its creation and the data it was trained on, ensuring the prediction
228
262
  is tailored to specific fabrication parameters.
229
- binarize : bool, optional
263
+ binarize : bool
230
264
  If True, the predicted device geometry will be binarized using a threshold
231
265
  method. This is useful for converting probabilistic predictions into binary
232
266
  geometries. Defaults to False.
233
- gpu : bool, optional
267
+ gpu : bool
234
268
  If True, the prediction will be performed on a GPU. Defaults to False.
235
269
  Note: The GPU option has more overhead and will take longer for small
236
270
  devices, but will be faster for larger devices.
@@ -242,7 +276,7 @@ class Device(BaseModel):
242
276
 
243
277
  Raises
244
278
  ------
245
- ValueError
279
+ RuntimeError
246
280
  If the prediction service returns an error or if the response from the
247
281
  service cannot be processed correctly.
248
282
  """
@@ -279,11 +313,11 @@ class Device(BaseModel):
279
313
  in `models.py`. Each model is associated with a version and dataset that
280
314
  detail its creation and the data it was trained on, ensuring the correction
281
315
  is tailored to specific fabrication parameters.
282
- binarize : bool, optional
316
+ binarize : bool
283
317
  If True, the corrected device geometry will be binarized using a threshold
284
318
  method. This is useful for converting probabilistic corrections into binary
285
319
  geometries. Defaults to True.
286
- gpu : bool, optional
320
+ gpu : bool
287
321
  If True, the prediction will be performed on a GPU. Defaults to False.
288
322
  Note: The GPU option has more overhead and will take longer for small
289
323
  devices, but will be faster for larger devices.
@@ -295,7 +329,7 @@ class Device(BaseModel):
295
329
 
296
330
  Raises
297
331
  ------
298
- ValueError
332
+ RuntimeError
299
333
  If the correction service returns an error or if the response from the
300
334
  service cannot be processed correctly.
301
335
  """
@@ -331,16 +365,27 @@ class Device(BaseModel):
331
365
  in `models.py`. Each model is associated with a version and dataset that
332
366
  detail its creation and the data it was trained on, ensuring the SEMulation
333
367
  is tailored to specific fabrication parameters.
334
- gpu : bool, optional
368
+ gpu : bool
335
369
  If True, the prediction will be performed on a GPU. Defaults to False.
336
370
  Note: The GPU option has more overhead and will take longer for small
337
371
  devices, but will be faster for larger devices.
338
372
 
373
+ Notes
374
+ -----
375
+ The salt-and-pepper noise is added manually until the model is trained to
376
+ generate this noise (not a big priority).
377
+
339
378
  Returns
340
379
  -------
341
380
  Device
342
381
  A new instance of the Device class with its geometry transformed to simulate
343
382
  an SEM image style.
383
+
384
+ Raises
385
+ ------
386
+ RuntimeError
387
+ If the prediction service returns an error or if the response from the
388
+ service cannot be processed correctly.
344
389
  """
345
390
  semulated_array = predict_array(
346
391
  device_array=self.device_array,
@@ -370,12 +415,14 @@ class Device(BaseModel):
370
415
  buffer_thickness = self.buffer_spec.thickness
371
416
  buffer_mode = self.buffer_spec.mode
372
417
 
373
- crop_top = buffer_thickness["top"] if buffer_mode["top"] == "edge" else 0
418
+ crop_top = buffer_thickness["top"] if buffer_mode["top"] == "constant" else 0
374
419
  crop_bottom = (
375
- buffer_thickness["bottom"] if buffer_mode["bottom"] == "edge" else 0
420
+ buffer_thickness["bottom"] if buffer_mode["bottom"] == "constant" else 0
421
+ )
422
+ crop_left = buffer_thickness["left"] if buffer_mode["left"] == "constant" else 0
423
+ crop_right = (
424
+ buffer_thickness["right"] if buffer_mode["right"] == "constant" else 0
376
425
  )
377
- crop_left = buffer_thickness["left"] if buffer_mode["left"] == "edge" else 0
378
- crop_right = buffer_thickness["right"] if buffer_mode["right"] == "edge" else 0
379
426
 
380
427
  ndarray = device_array[
381
428
  crop_top : device_array.shape[0] - crop_bottom,
@@ -393,12 +440,12 @@ class Device(BaseModel):
393
440
 
394
441
  Parameters
395
442
  ----------
396
- img_path : str, optional
443
+ img_path : str
397
444
  The path where the image file will be saved. If not specified, the image is
398
445
  saved as "prefab_device.png" in the current directory.
399
446
  """
400
447
  cv2.imwrite(img_path, 255 * self.flatten().to_ndarray())
401
- print(f"Saved Device to '{img_path}'")
448
+ print(f"Saved Device image to '{img_path}'")
402
449
 
403
450
  def to_gds(
404
451
  self,
@@ -417,21 +464,21 @@ class Device(BaseModel):
417
464
 
418
465
  Parameters
419
466
  ----------
420
- gds_path : str, optional
467
+ gds_path : str
421
468
  The path where the GDSII file will be saved. If not specified, the file is
422
469
  saved as "prefab_device.gds" in the current directory.
423
- cell_name : str, optional
470
+ cell_name : str
424
471
  The name of the cell within the GDSII file. If not specified, defaults to
425
472
  "prefab_device".
426
- gds_layer : tuple[int, int], optional
473
+ gds_layer : tuple[int, int]
427
474
  The layer and datatype to use within the GDSII file. Defaults to (1, 0).
428
- contour_approx_mode : int, optional
475
+ contour_approx_mode : int
429
476
  The mode of contour approximation used during the conversion. Defaults to 2,
430
477
  which corresponds to `cv2.CHAIN_APPROX_SIMPLE`, a method that compresses
431
478
  horizontal, vertical, and diagonal segments and leaves only their endpoints.
432
- origin : tuple[float, float], optional
433
- The x and y coordinates of the origin for the GDSII export. Defaults to
434
- (0.0, 0.0).
479
+ origin : tuple[float, float]
480
+ The x and y coordinates of the origin in µm for the GDSII export. Defaults
481
+ to (0.0, 0.0).
435
482
  """
436
483
  gdstk_cell = self.flatten()._device_to_gdstk(
437
484
  cell_name=cell_name,
@@ -461,17 +508,17 @@ class Device(BaseModel):
461
508
 
462
509
  Parameters
463
510
  ----------
464
- cell_name : str, optional
511
+ cell_name : str
465
512
  The name of the cell to be created. Defaults to "prefab_device".
466
- gds_layer : tuple[int, int], optional
513
+ gds_layer : tuple[int, int]
467
514
  The layer and datatype to use within the GDSTK cell. Defaults to (1, 0).
468
- contour_approx_mode : int, optional
515
+ contour_approx_mode : int
469
516
  The mode of contour approximation used during the conversion. Defaults to 2,
470
517
  which corresponds to `cv2.CHAIN_APPROX_SIMPLE`, a method that compresses
471
518
  horizontal, vertical, and diagonal segments and leaves only their endpoints.
472
- origin : tuple[float, float], optional
473
- The x and y coordinates of the origin for the GDSTK cell. Defaults to
474
- (0.0, 0.0).
519
+ origin : tuple[float, float]
520
+ The x and y coordinates of the origin in µm for the GDSTK cell. Defaults
521
+ to (0.0, 0.0).
475
522
 
476
523
  Returns
477
524
  -------
@@ -529,17 +576,29 @@ class Device(BaseModel):
529
576
  polygons_to_process = hierarchy_polygons[level]
530
577
 
531
578
  if polygons_to_process:
532
- center_x_nm = self.device_array.shape[1] / 2
533
- center_y_nm = self.device_array.shape[0] / 2
579
+ buffer_thickness = self.buffer_spec.thickness
580
+
581
+ center_x_nm = (
582
+ self.device_array.shape[1]
583
+ - buffer_thickness["left"]
584
+ - buffer_thickness["right"]
585
+ ) / 2
586
+ center_y_nm = (
587
+ self.device_array.shape[0]
588
+ - buffer_thickness["top"]
589
+ - buffer_thickness["bottom"]
590
+ ) / 2
534
591
 
535
592
  center_x_um = center_x_nm / 1000
536
593
  center_y_um = center_y_nm / 1000
537
594
 
538
595
  adjusted_polygons = [
539
- [
540
- (x - center_x_um + origin[0], y - center_y_um + origin[1])
541
- for x, y in polygon
542
- ]
596
+ gdstk.Polygon(
597
+ [
598
+ (x - center_x_um + origin[0], y - center_y_um + origin[1])
599
+ for x, y in polygon
600
+ ]
601
+ )
543
602
  for polygon in polygons_to_process
544
603
  ]
545
604
  processed_polygons = gdstk.boolean(
@@ -554,7 +613,7 @@ class Device(BaseModel):
554
613
 
555
614
  return cell
556
615
 
557
- def to_gdsfactory(self) -> "gf.Component": # noqa: F821
616
+ def to_gdsfactory(self) -> "gf.Component":
558
617
  """
559
618
  Convert the device geometry to a gdsfactory Component.
560
619
 
@@ -583,7 +642,7 @@ class Device(BaseModel):
583
642
  self,
584
643
  eps0: float,
585
644
  thickness: float,
586
- ) -> "td.Structure": # noqa: F821
645
+ ) -> "td.Structure":
587
646
  """
588
647
  Convert the device geometry to a Tidy3D Structure.
589
648
 
@@ -622,7 +681,10 @@ class Device(BaseModel):
622
681
  eps_dataset = SpatialDataArray(eps_array, coords=dict(x=X, y=Y, z=Z))
623
682
  medium = CustomMedium.from_eps_raw(eps_dataset)
624
683
  return Structure(
625
- geometry=Box(center=(0, 0, 0), size=(inf, inf, thickness)), medium=medium
684
+ geometry=Box(center=(0, 0, 0), size=(inf, inf, thickness), attrs={}),
685
+ medium=medium,
686
+ name="device",
687
+ attrs={},
626
688
  )
627
689
 
628
690
  def to_3d(self, thickness_nm: int) -> np.ndarray:
@@ -644,11 +706,11 @@ class Device(BaseModel):
644
706
  """
645
707
  bottom_layer = self.device_array[:, :, 0]
646
708
  top_layer = self.device_array[:, :, -1]
647
- dt_bottom = distance_transform_edt(bottom_layer) - distance_transform_edt(
648
- 1 - bottom_layer
709
+ dt_bottom = np.array(distance_transform_edt(bottom_layer)) - np.array(
710
+ distance_transform_edt(1 - bottom_layer)
649
711
  )
650
- dt_top = distance_transform_edt(top_layer) - distance_transform_edt(
651
- 1 - top_layer
712
+ dt_top = np.array(distance_transform_edt(top_layer)) - np.array(
713
+ distance_transform_edt(1 - top_layer)
652
714
  )
653
715
  weights = np.linspace(0, 1, thickness_nm)
654
716
  layered_array = np.zeros(
@@ -667,7 +729,7 @@ class Device(BaseModel):
667
729
  ----------
668
730
  thickness_nm : int
669
731
  The thickness of the 3D representation in nanometers.
670
- filename : str, optional
732
+ filename : str
671
733
  The name of the STL file to save. Defaults to "prefab_device.stl".
672
734
 
673
735
  Raises
@@ -678,7 +740,7 @@ class Device(BaseModel):
678
740
  If the numpy-stl package is not installed.
679
741
  """
680
742
  try:
681
- from stl import mesh
743
+ from stl import mesh # type: ignore
682
744
  except ImportError:
683
745
  raise ImportError(
684
746
  "The stl package is required to use this function; "
@@ -725,7 +787,6 @@ class Device(BaseModel):
725
787
  plot_array.shape[0] - max_y : plot_array.shape[0] - min_y,
726
788
  min_x:max_x,
727
789
  ]
728
- extent = [min_x, max_x, min_y, max_y]
729
790
 
730
791
  if not np.ma.is_masked(plot_array):
731
792
  max_size = (1000, 1000)
@@ -744,7 +805,12 @@ class Device(BaseModel):
744
805
 
745
806
  mappable = ax.imshow(
746
807
  plot_array,
747
- extent=extent,
808
+ extent=(
809
+ float(min_x),
810
+ float(max_x),
811
+ float(min_y),
812
+ float(max_y),
813
+ ),
748
814
  **kwargs,
749
815
  )
750
816
 
@@ -764,7 +830,7 @@ class Device(BaseModel):
764
830
  self,
765
831
  show_buffer: bool = True,
766
832
  bounds: Optional[tuple[tuple[int, int], tuple[int, int]]] = None,
767
- level: int = None,
833
+ level: Optional[int] = None,
768
834
  ax: Optional[Axes] = None,
769
835
  **kwargs,
770
836
  ) -> Axes:
@@ -778,17 +844,17 @@ class Device(BaseModel):
778
844
 
779
845
  Parameters
780
846
  ----------
781
- show_buffer : bool, optional
847
+ show_buffer : bool
782
848
  If True, visualizes the buffer zones around the device. Defaults to True.
783
849
  bounds : Optional[tuple[tuple[int, int], tuple[int, int]]], optional
784
850
  Specifies the bounds for zooming into the device geometry, formatted as
785
851
  ((min_x, min_y), (max_x, max_y)). If 'max_x' or 'max_y' is set to "end", it
786
852
  will be replaced with the corresponding dimension size of the device array.
787
853
  If None, the entire device geometry is visualized.
788
- level : int, optional
854
+ level : int
789
855
  The vertical layer to plot. If None, the device geometry is flattened.
790
856
  Defaults to None.
791
- ax : Optional[Axes], optional
857
+ ax : Optional[Axes]
792
858
  An existing matplotlib Axes object to draw the device geometry on. If
793
859
  None, a new figure and axes will be created. Defaults to None.
794
860
  **kwargs
@@ -819,7 +885,7 @@ class Device(BaseModel):
819
885
  # label: Optional[str] = "Device contour",
820
886
  show_buffer: bool = True,
821
887
  bounds: Optional[tuple[tuple[int, int], tuple[int, int]]] = None,
822
- level: int = None,
888
+ level: Optional[int] = None,
823
889
  ax: Optional[Axes] = None,
824
890
  **kwargs,
825
891
  ):
@@ -833,21 +899,21 @@ class Device(BaseModel):
833
899
 
834
900
  Parameters
835
901
  ----------
836
- linewidth : Optional[int], optional
902
+ linewidth : Optional[int]
837
903
  The width of the contour lines. If None, the linewidth is automatically
838
904
  determined based on the size of the device array. Defaults to None.
839
- show_buffer : bool, optional
905
+ show_buffer : bool
840
906
  If True, the buffer zones around the device will be visualized. By default,
841
907
  it is set to True.
842
- bounds : Optional[tuple[tuple[int, int], tuple[int, int]]], optional
908
+ bounds : Optional[tuple[tuple[int, int], tuple[int, int]]]
843
909
  Specifies the bounds for zooming into the device geometry, formatted as
844
910
  ((min_x, min_y), (max_x, max_y)). If 'max_x' or 'max_y' is set to "end", it
845
911
  will be replaced with the corresponding dimension size of the device array.
846
912
  If None, the entire device geometry is visualized.
847
- level : int, optional
913
+ level : int
848
914
  The vertical layer to plot. If None, the device geometry is flattened.
849
915
  Defaults to None.
850
- ax : Optional[Axes], optional
916
+ ax : Optional[Axes]
851
917
  An existing matplotlib Axes object to draw the device contour on. If None, a
852
918
  new figure and axes will be created. Defaults to None.
853
919
  **kwargs
@@ -893,7 +959,7 @@ class Device(BaseModel):
893
959
  self,
894
960
  show_buffer: bool = True,
895
961
  bounds: Optional[tuple[tuple[int, int], tuple[int, int]]] = None,
896
- level: int = None,
962
+ level: Optional[int] = None,
897
963
  ax: Optional[Axes] = None,
898
964
  **kwargs,
899
965
  ):
@@ -909,18 +975,18 @@ class Device(BaseModel):
909
975
 
910
976
  Parameters
911
977
  ----------
912
- show_buffer : bool, optional
978
+ show_buffer : bool
913
979
  If True, the buffer zones around the device will also be visualized. By
914
980
  default, it is set to True.
915
- bounds : Optional[tuple[tuple[int, int], tuple[int, int]]], optional
981
+ bounds : Optional[tuple[tuple[int, int], tuple[int, int]]]
916
982
  Specifies the bounds for zooming into the device geometry, formatted as
917
983
  ((min_x, min_y), (max_x, max_y)). If 'max_x' or 'max_y' is set to "end", it
918
984
  will be replaced with the corresponding dimension size of the device array.
919
985
  If None, the entire device geometry is visualized.
920
- level : int, optional
986
+ level : int
921
987
  The vertical layer to plot. If None, the device geometry is flattened.
922
988
  Defaults to None.
923
- ax : Optional[Axes], optional
989
+ ax : Optional[Axes]
924
990
  An existing matplotlib Axes object to draw the uncertainty visualization on.
925
991
  If None, a new figure and axes will be created. Defaults to None.
926
992
  **kwargs
@@ -956,7 +1022,7 @@ class Device(BaseModel):
956
1022
  ref_device: "Device",
957
1023
  show_buffer: bool = True,
958
1024
  bounds: Optional[tuple[tuple[int, int], tuple[int, int]]] = None,
959
- level: int = None,
1025
+ level: Optional[int] = None,
960
1026
  ax: Optional[Axes] = None,
961
1027
  **kwargs,
962
1028
  ) -> Axes:
@@ -972,17 +1038,17 @@ class Device(BaseModel):
972
1038
  ----------
973
1039
  ref_device : Device
974
1040
  The reference device to compare against.
975
- show_buffer : bool, optional
1041
+ show_buffer : bool
976
1042
  If True, visualizes the buffer zones around the device. Defaults to True.
977
- bounds : Optional[tuple[tuple[int, int], tuple[int, int]]], optional
1043
+ bounds : Optional[tuple[tuple[int, int], tuple[int, int]]]
978
1044
  Specifies the bounds for zooming into the device geometry, formatted as
979
1045
  ((min_x, min_y), (max_x, max_y)). If 'max_x' or 'max_y' is set to "end", it
980
1046
  will be replaced with the corresponding dimension size of the device array.
981
1047
  If None, the entire device geometry is visualized.
982
- level : int, optional
1048
+ level : int
983
1049
  The vertical layer to plot. If None, the device geometry is flattened.
984
1050
  Defaults to None.
985
- ax : Optional[Axes], optional
1051
+ ax : Optional[Axes]
986
1052
  An existing matplotlib Axes object to draw the comparison on. If None, a new
987
1053
  figure and axes will be created. Defaults to None.
988
1054
  **kwargs
@@ -1088,9 +1154,9 @@ class Device(BaseModel):
1088
1154
 
1089
1155
  Parameters
1090
1156
  ----------
1091
- eta : float, optional
1157
+ eta : float
1092
1158
  The threshold value for binarization. Defaults to 0.5.
1093
- beta : float, optional
1159
+ beta : float
1094
1160
  The scaling factor for the binarization process. A higher value makes the
1095
1161
  transition sharper. Defaults to np.inf, which results in a hard threshold.
1096
1162
 
@@ -1112,15 +1178,15 @@ class Device(BaseModel):
1112
1178
  is generally preferred for most use cases, but it can create numerical artifacts
1113
1179
  for large beta values.
1114
1180
 
1115
- Parameters
1116
- ----------
1117
- eta : float, optional
1118
- The threshold value for binarization. Defaults to 0.5.
1181
+ Parameters
1182
+ ----------
1183
+ eta : float
1184
+ The threshold value for binarization. Defaults to 0.5.
1119
1185
 
1120
- Returns
1121
- -------
1122
- Device
1123
- A new instance of the Device with the threshold-binarized geometry.
1186
+ Returns
1187
+ -------
1188
+ Device
1189
+ A new instance of the Device with the threshold-binarized geometry.
1124
1190
  """
1125
1191
  binarized_device_array = geometry.binarize_hard(
1126
1192
  device_array=self.device_array, eta=eta
@@ -1131,26 +1197,31 @@ class Device(BaseModel):
1131
1197
 
1132
1198
  def binarize_monte_carlo(
1133
1199
  self,
1134
- threshold_noise_std: float = 2.0,
1135
- threshold_blur_std: float = 8.0,
1200
+ noise_magnitude: float = 2.0,
1201
+ blur_radius: float = 8.0,
1136
1202
  ) -> "Device":
1137
1203
  """
1138
- Binarize the device geometry using a Monte Carlo approach with Gaussian
1139
- blurring.
1204
+ Binarize the input ndarray using a dynamic thresholding approach to simulate
1205
+ surfaceroughness.
1140
1206
 
1141
- This method applies a dynamic thresholding technique where the threshold value
1207
+ This function applies a dynamic thresholding technique where the threshold value
1142
1208
  is determined by a base value perturbed by Gaussian-distributed random noise.
1143
- The threshold is then spatially varied across the device array using Gaussian
1144
- blurring, simulating a more realistic scenario where the threshold is not
1209
+ Thethreshold is then spatially varied across the array using Gaussian blurring,
1210
+ simulating a potentially more realistic scenario where the threshold is not
1145
1211
  uniform across the device.
1146
1212
 
1213
+ Notes
1214
+ -----
1215
+ This is a temporary solution, where the defaults are chosen based on what looks
1216
+ good. A better, data-driven approach is needed.
1217
+
1147
1218
  Parameters
1148
1219
  ----------
1149
- threshold_noise_std : float, optional
1220
+ noise_magnitude : float
1150
1221
  The standard deviation of the Gaussian distribution used to generate noise
1151
1222
  for the threshold values. This controls the amount of randomness in the
1152
1223
  threshold. Defaults to 2.0.
1153
- threshold_blur_std : float, optional
1224
+ blur_radius : float
1154
1225
  The standard deviation for the Gaussian kernel used in blurring the
1155
1226
  threshold map. This controls the spatial variation of the threshold across
1156
1227
  the array. Defaults to 9.0.
@@ -1162,8 +1233,8 @@ class Device(BaseModel):
1162
1233
  """
1163
1234
  binarized_device_array = geometry.binarize_monte_carlo(
1164
1235
  device_array=self.device_array,
1165
- threshold_noise_std=threshold_noise_std,
1166
- threshold_blur_std=threshold_blur_std,
1236
+ noise_magnitude=noise_magnitude,
1237
+ blur_radius=blur_radius,
1167
1238
  )
1168
1239
  return self.model_copy(update={"device_array": binarized_device_array})
1169
1240
 
@@ -1174,9 +1245,9 @@ class Device(BaseModel):
1174
1245
 
1175
1246
  Parameters
1176
1247
  ----------
1177
- eta1 : float, optional
1248
+ eta1 : float
1178
1249
  The first threshold value for ternarization. Defaults to 1/3.
1179
- eta2 : float, optional
1250
+ eta2 : float
1180
1251
  The second threshold value for ternarization. Defaults to 2/3.
1181
1252
 
1182
1253
  Returns
@@ -1204,13 +1275,22 @@ class Device(BaseModel):
1204
1275
  )
1205
1276
  return self.model_copy(update={"device_array": trimmed_device_array})
1206
1277
 
1278
+ def pad(self, pad_width: int) -> "Device":
1279
+ """
1280
+ Pad the device geometry with a specified width on all sides.
1281
+ """
1282
+ padded_device_array = geometry.pad(
1283
+ device_array=self.device_array, pad_width=pad_width
1284
+ )
1285
+ return self.model_copy(update={"device_array": padded_device_array})
1286
+
1207
1287
  def blur(self, sigma: float = 1.0) -> "Device":
1208
1288
  """
1209
1289
  Apply Gaussian blur to the device geometry and normalize the result.
1210
1290
 
1211
1291
  Parameters
1212
1292
  ----------
1213
- sigma : float, optional
1293
+ sigma : float
1214
1294
  The standard deviation for the Gaussian kernel. This controls the amount of
1215
1295
  blurring. Defaults to 1.0.
1216
1296
 
@@ -1322,11 +1402,16 @@ class Device(BaseModel):
1322
1402
  device geometry are at least the specified minimum size. It uses either a disk
1323
1403
  or square structuring element for the operations.
1324
1404
 
1405
+ Notes
1406
+ -----
1407
+ This function does not guarantee that the minimum feature size is enforced in
1408
+ all cases. A better process is needed.
1409
+
1325
1410
  Parameters
1326
1411
  ----------
1327
1412
  min_feature_size : int
1328
1413
  The minimum feature size to enforce, in nanometers.
1329
- strel : str, optional
1414
+ strel : str
1330
1415
  The type of structuring element to use. Can be either "disk" or "square".
1331
1416
  Defaults to "disk".
1332
1417
 
@@ -1358,11 +1443,16 @@ class Device(BaseModel):
1358
1443
  between the original and modified geometries, providing a measure of the changes
1359
1444
  introduced by the feature size enforcement.
1360
1445
 
1446
+ Notes
1447
+ -----
1448
+ This is not a design-rule-checking function, but it can be useful for quick
1449
+ checks.
1450
+
1361
1451
  Parameters
1362
1452
  ----------
1363
1453
  min_feature_size : int
1364
1454
  The minimum feature size to enforce, in nanometers.
1365
- strel : str, optional
1455
+ strel : str
1366
1456
  The type of structuring element to use. Can be either "disk" or "square".
1367
1457
  Defaults to "disk".
1368
1458