prefab 1.1.3__py3-none-any.whl → 1.1.5__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/__init__.py CHANGED
@@ -5,7 +5,7 @@ Usage:
5
5
  import prefab as pf
6
6
  """
7
7
 
8
- __version__ = "1.1.3"
8
+ __version__ = "1.1.5"
9
9
 
10
10
  from . import compare, geometry, predict, read, shapes
11
11
  from .device import BufferSpec, Device
prefab/compare.py CHANGED
@@ -24,7 +24,7 @@ def mean_squared_error(device_a: Device, device_b: Device) -> float:
24
24
  float
25
25
  The mean squared error between two devices.
26
26
  """
27
- return np.mean((device_a.device_array - device_b.device_array) ** 2)
27
+ return float(np.mean((device_a.device_array - device_b.device_array) ** 2))
28
28
 
29
29
 
30
30
  def intersection_over_union(device_a: Device, device_b: Device) -> float:
@@ -77,7 +77,7 @@ def hamming_distance(device_a: Device, device_b: Device) -> int:
77
77
  "One or both devices are not binarized.", UserWarning, stacklevel=2
78
78
  )
79
79
 
80
- return np.sum(device_a.device_array != device_b.device_array)
80
+ return int(np.sum(device_a.device_array != device_b.device_array))
81
81
 
82
82
 
83
83
  def dice_coefficient(device_a: Device, device_b: Device) -> float:
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, 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, root_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
 
@@ -36,7 +40,7 @@ class BufferSpec(BaseModel):
36
40
  ('top', 'bottom', 'left', 'right'), where 'constant' is used for isolated
37
41
  structures and 'edge' is utilized for preserving the edge, such as for waveguide
38
42
  connections.
39
- thickness : dict[str, conint(gt=0)]
43
+ thickness : dict[str, int]
40
44
  A dictionary that defines the thickness of the buffer zone for each side of the
41
45
  device ('top', 'bottom', 'left', 'right'). Each value must be greater than 0.
42
46
 
@@ -75,7 +79,7 @@ class BufferSpec(BaseModel):
75
79
  "right": "constant",
76
80
  }
77
81
  )
78
- thickness: dict[str, conint(gt=0)] = Field(
82
+ thickness: dict[str, int] = Field(
79
83
  default_factory=lambda: {
80
84
  "top": 128,
81
85
  "bottom": 128,
@@ -91,6 +95,12 @@ class BufferSpec(BaseModel):
91
95
  raise ValueError(f"Buffer mode must be one of {allowed_modes}, got '{v}'")
92
96
  return v
93
97
 
98
+ @validator("thickness")
99
+ def check_thickness(cls, v):
100
+ if not all(t > 0 for t in v.values()):
101
+ raise ValueError("All thickness values must be greater than 0")
102
+ return v
103
+
94
104
 
95
105
  class Device(BaseModel):
96
106
  device_array: np.ndarray = Field(...)
@@ -100,8 +110,8 @@ class Device(BaseModel):
100
110
  arbitrary_types_allowed = True
101
111
 
102
112
  @property
103
- def shape(self) -> tuple[int, int]:
104
- return self.device_array.shape
113
+ def shape(self) -> tuple[int, ...]:
114
+ return tuple(self.device_array.shape)
105
115
 
106
116
  def __init__(
107
117
  self, device_array: np.ndarray, buffer_spec: Optional[BufferSpec] = None
@@ -370,12 +380,14 @@ class Device(BaseModel):
370
380
  buffer_thickness = self.buffer_spec.thickness
371
381
  buffer_mode = self.buffer_spec.mode
372
382
 
373
- crop_top = buffer_thickness["top"] if buffer_mode["top"] == "edge" else 0
383
+ crop_top = buffer_thickness["top"] if buffer_mode["top"] == "constant" else 0
374
384
  crop_bottom = (
375
- buffer_thickness["bottom"] if buffer_mode["bottom"] == "edge" else 0
385
+ buffer_thickness["bottom"] if buffer_mode["bottom"] == "constant" else 0
386
+ )
387
+ crop_left = buffer_thickness["left"] if buffer_mode["left"] == "constant" else 0
388
+ crop_right = (
389
+ buffer_thickness["right"] if buffer_mode["right"] == "constant" else 0
376
390
  )
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
391
 
380
392
  ndarray = device_array[
381
393
  crop_top : device_array.shape[0] - crop_bottom,
@@ -529,8 +541,18 @@ class Device(BaseModel):
529
541
  polygons_to_process = hierarchy_polygons[level]
530
542
 
531
543
  if polygons_to_process:
532
- center_x_nm = self.device_array.shape[1] / 2
533
- center_y_nm = self.device_array.shape[0] / 2
544
+ buffer_thickness = self.buffer_spec.thickness
545
+
546
+ center_x_nm = (
547
+ self.device_array.shape[1]
548
+ - buffer_thickness["left"]
549
+ - buffer_thickness["right"]
550
+ ) / 2
551
+ center_y_nm = (
552
+ self.device_array.shape[0]
553
+ - buffer_thickness["top"]
554
+ - buffer_thickness["bottom"]
555
+ ) / 2
534
556
 
535
557
  center_x_um = center_x_nm / 1000
536
558
  center_y_um = center_y_nm / 1000
@@ -583,7 +605,7 @@ class Device(BaseModel):
583
605
  self,
584
606
  eps0: float,
585
607
  thickness: float,
586
- ) -> "td.Structure": # noqa: F821
608
+ ) -> "td.Structure":
587
609
  """
588
610
  Convert the device geometry to a Tidy3D Structure.
589
611
 
@@ -622,7 +644,10 @@ class Device(BaseModel):
622
644
  eps_dataset = SpatialDataArray(eps_array, coords=dict(x=X, y=Y, z=Z))
623
645
  medium = CustomMedium.from_eps_raw(eps_dataset)
624
646
  return Structure(
625
- geometry=Box(center=(0, 0, 0), size=(inf, inf, thickness)), medium=medium
647
+ geometry=Box(center=(0, 0, 0), size=(inf, inf, thickness), attrs={}),
648
+ medium=medium,
649
+ name="device",
650
+ attrs={},
626
651
  )
627
652
 
628
653
  def to_3d(self, thickness_nm: int) -> np.ndarray:
@@ -678,7 +703,7 @@ class Device(BaseModel):
678
703
  If the numpy-stl package is not installed.
679
704
  """
680
705
  try:
681
- from stl import mesh
706
+ from stl import mesh # type: ignore
682
707
  except ImportError:
683
708
  raise ImportError(
684
709
  "The stl package is required to use this function; "
@@ -764,7 +789,7 @@ class Device(BaseModel):
764
789
  self,
765
790
  show_buffer: bool = True,
766
791
  bounds: Optional[tuple[tuple[int, int], tuple[int, int]]] = None,
767
- level: int = None,
792
+ level: Optional[int] = None,
768
793
  ax: Optional[Axes] = None,
769
794
  **kwargs,
770
795
  ) -> Axes:
@@ -819,7 +844,7 @@ class Device(BaseModel):
819
844
  # label: Optional[str] = "Device contour",
820
845
  show_buffer: bool = True,
821
846
  bounds: Optional[tuple[tuple[int, int], tuple[int, int]]] = None,
822
- level: int = None,
847
+ level: Optional[int] = None,
823
848
  ax: Optional[Axes] = None,
824
849
  **kwargs,
825
850
  ):
@@ -893,7 +918,7 @@ class Device(BaseModel):
893
918
  self,
894
919
  show_buffer: bool = True,
895
920
  bounds: Optional[tuple[tuple[int, int], tuple[int, int]]] = None,
896
- level: int = None,
921
+ level: Optional[int] = None,
897
922
  ax: Optional[Axes] = None,
898
923
  **kwargs,
899
924
  ):
@@ -956,7 +981,7 @@ class Device(BaseModel):
956
981
  ref_device: "Device",
957
982
  show_buffer: bool = True,
958
983
  bounds: Optional[tuple[tuple[int, int], tuple[int, int]]] = None,
959
- level: int = None,
984
+ level: Optional[int] = None,
960
985
  ax: Optional[Axes] = None,
961
986
  **kwargs,
962
987
  ) -> Axes:
prefab/predict.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Provides prediction functions for ndarrays of device geometries."""
2
+
1
3
  import base64
2
4
  import io
3
5
  import json
@@ -6,6 +8,8 @@ import os
6
8
  import numpy as np
7
9
  import requests
8
10
  import toml
11
+ from autograd import primitive
12
+ from autograd.extend import defvjp
9
13
  from PIL import Image
10
14
  from tqdm import tqdm
11
15
 
@@ -23,24 +27,34 @@ def predict_array(
23
27
  gpu: bool = False,
24
28
  ) -> np.ndarray:
25
29
  """
26
- Predicts the output array for a given device array using a specified model.
30
+ Predict the nanofabrication outcome of a device array using a specified model.
27
31
 
28
- This function sends the device array to a prediction service, which uses a machine
29
- learning model to predict the outcome of the nanofabrication process. The prediction
30
- can be performed on a GPU if specified.
32
+ This function sends the device array to a serverless prediction service, which uses
33
+ a specified machine learning model to predict the outcome of the nanofabrication
34
+ process. The prediction can be performed on a GPU if specified.
31
35
 
32
36
  Parameters
33
37
  ----------
34
38
  device_array : np.ndarray
35
- The input device array to be predicted.
39
+ A 2D array representing the planar geometry of the device. This array undergoes
40
+ various transformations to predict the nanofabrication process.
36
41
  model : Model
37
- The model to use for prediction.
42
+ The model to use for prediction, representing a specific fabrication process and
43
+ dataset. This model encapsulates details about the fabrication foundry, process,
44
+ material, technology, thickness, and sidewall presence, as defined in
45
+ `models.py`. Each model is associated with a version and dataset that detail its
46
+ creation and the data it was trained on, ensuring the prediction is tailored to
47
+ specific fabrication parameters.
38
48
  model_type : str
39
49
  The type of model to use (e.g., 'p', 'c', 's').
40
50
  binarize : bool
41
- Whether to binarize the output.
51
+ If True, the predicted device geometry will be binarized using a threshold
52
+ method. This is useful for converting probabilistic predictions into binary
53
+ geometries.
42
54
  gpu : bool, optional
43
- Whether to use GPU for prediction. Defaults to False.
55
+ If True, the prediction will be performed on a GPU. Defaults to False. Note: The
56
+ GPU option has more overhead and will take longer for small devices, but will be
57
+ faster for larger devices.
44
58
 
45
59
  Returns
46
60
  -------
@@ -69,38 +83,11 @@ def predict_array(
69
83
  raise RuntimeError(f"Request failed: {e}") from e
70
84
 
71
85
 
72
- def predict_array_with_grad(
73
- device_array: np.ndarray, model: Model, model_type: str
86
+ def _predict_array_with_grad(
87
+ device_array: np.ndarray, model: Model
74
88
  ) -> tuple[np.ndarray, np.ndarray]:
75
- """
76
- Predicts the output array and its gradient for a given device array using a
77
- specified model.
78
-
79
- This function sends the device array to a prediction service, which uses a machine
80
- learning model to predict both the outcome and the gradient of the nanofabrication
81
- process.
82
-
83
- Parameters
84
- ----------
85
- device_array : np.ndarray
86
- The input device array to be predicted.
87
- model : Model
88
- The model to use for prediction.
89
- model_type : str
90
- The type of model to use (e.g., 'p', 'c', 's').
91
-
92
- Returns
93
- -------
94
- tuple[np.ndarray, np.ndarray]
95
- A tuple containing the predicted output array and its gradient.
96
-
97
- Raises
98
- ------
99
- RuntimeError
100
- If the request to the prediction service fails.
101
- """
102
89
  headers = _prepare_headers()
103
- predict_data = _prepare_predict_data(device_array, model, model_type, False)
90
+ predict_data = _prepare_predict_data(device_array, model, "p", False)
104
91
  endpoint_url = f"{BASE_URL}-with-grad-v1.modal.run"
105
92
 
106
93
  response = requests.post(
@@ -116,8 +103,68 @@ def predict_array_with_grad(
116
103
  return (prediction_array, gradient_array)
117
104
 
118
105
 
106
+ @primitive
107
+ def predict_array_with_grad(device_array: np.ndarray, model: Model) -> np.ndarray:
108
+ """
109
+ Predict the nanofabrication outcome of a device array and compute its gradient.
110
+
111
+ This function predicts the outcome of the nanofabrication process for a given
112
+ device array using a specified model. It also computes the gradient of the
113
+ prediction with respect to the input device array.
114
+
115
+ Parameters
116
+ ----------
117
+ device_array : np.ndarray
118
+ A 2D array representing the planar geometry of the device.
119
+ model : Model
120
+ The model to use for prediction, representing a specific fabrication process.
121
+
122
+ Returns
123
+ -------
124
+ np.ndarray
125
+ The predicted output array.
126
+ """
127
+ prediction_array, gradient_array = _predict_array_with_grad(
128
+ device_array=device_array, model=model
129
+ )
130
+ predict_array_with_grad.gradient_array = gradient_array
131
+ return prediction_array
132
+
133
+
134
+ def predict_array_with_grad_vjp(ans: np.ndarray, x: np.ndarray, model: Model):
135
+ """
136
+ Define the vector-Jacobian product (VJP) for the prediction function.
137
+
138
+ This function provides the VJP for the `predict_array_with_grad` function,
139
+ which is used in reverse-mode automatic differentiation to compute gradients.
140
+
141
+ Parameters
142
+ ----------
143
+ ans : np.ndarray
144
+ The output of the `predict_array_with_grad` function.
145
+ x : np.ndarray
146
+ The input device array for which the gradient is computed.
147
+ model : Model
148
+ The model used for prediction.
149
+
150
+ Returns
151
+ -------
152
+ function
153
+ A function that computes the VJP given an upstream gradient `g`.
154
+ """
155
+ grad_x = predict_array_with_grad.gradient_array
156
+
157
+ def vjp(g: np.ndarray) -> np.ndarray:
158
+ return g * grad_x
159
+
160
+ return vjp
161
+
162
+
163
+ defvjp(predict_array_with_grad, predict_array_with_grad_vjp)
164
+
165
+
119
166
  def _encode_array(array):
120
- """Encode a numpy array as a PNG image and return the base64 encoded string."""
167
+ """Encode an ndarray as a base64 encoded image for transmission."""
121
168
  image = Image.fromarray(np.uint8(array * 255))
122
169
  buffered = io.BytesIO()
123
170
  image.save(buffered, format="PNG")
@@ -126,7 +173,7 @@ def _encode_array(array):
126
173
 
127
174
 
128
175
  def _decode_array(encoded_png):
129
- """Decode a base64 encoded PNG image and return a numpy array."""
176
+ """Decode a base64 encoded image and return an ndarray."""
130
177
  binary_data = base64.b64decode(encoded_png)
131
178
  image = Image.open(io.BytesIO(binary_data))
132
179
  return np.array(image) / 255
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: prefab
3
- Version: 1.1.3
3
+ Version: 1.1.5
4
4
  Summary: Artificial nanofabrication of integrated photonic circuits using deep learning
5
5
  Project-URL: Homepage, https://prefabphotonics.com
6
6
  Project-URL: Repository, https://github.com/PreFab-Photonics/PreFab
@@ -0,0 +1,13 @@
1
+ prefab/__init__.py,sha256=0QHTuxxeHo3xJIUq20iQCLiC2nk-rgf2C374l-Aby-g,425
2
+ prefab/__main__.py,sha256=aAgt1WXa44k1nJqsiSD3uAfNeGpwtjWqMUYCHN5_Qrw,2759
3
+ prefab/compare.py,sha256=iz0H5EvZFZ5O-N2QOQ6wFe-Hftkfyd6oMYpjfjnrMIw,3251
4
+ prefab/device.py,sha256=kKmud8QxfSBvadeZw56W_MNXqjRIyTA16jNTQ8v69U0,53048
5
+ prefab/geometry.py,sha256=0sa6ietUWZGkxOnUPUzD3q2QpFuOpWkSANoopGpPd6s,11035
6
+ prefab/models.py,sha256=JpBqNFIqbo1ymKEl0NWWF4ZkhvK23rEVVBEFl05TXCI,3671
7
+ prefab/predict.py,sha256=upGTeNC6vY9Tdko6JEM0gKQH1PGUVYXACpep1OT8EVs,10593
8
+ prefab/read.py,sha256=MuF-cugFQ7MWBJ8DOvQuwktIk0fJ8PXBeLye0ydrB8o,14734
9
+ prefab/shapes.py,sha256=2qaqyNzu5WG3wVdk4oQzeNXmhwXRHcPnRZlgRrM4MoA,25576
10
+ prefab-1.1.5.dist-info/METADATA,sha256=tk_MMU6lQcjqt4FBDgjx87ziII0ulouu2zCj2Ayr-g8,34824
11
+ prefab-1.1.5.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
12
+ prefab-1.1.5.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
13
+ prefab-1.1.5.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- prefab/__init__.py,sha256=DuARo1QC4MGMw-yzCBLEoRlOU3MsKZTbWDCAqUpQjWM,425
2
- prefab/__main__.py,sha256=aAgt1WXa44k1nJqsiSD3uAfNeGpwtjWqMUYCHN5_Qrw,2759
3
- prefab/compare.py,sha256=2MKUT7N2A639tUGCnJHpfF9MmS-v3oARDkTqHbWJ9OM,3239
4
- prefab/device.py,sha256=l0etqB0559xa9Frb0IKl5N3kU56vWHlwHc-qehINTlw,52292
5
- prefab/geometry.py,sha256=0sa6ietUWZGkxOnUPUzD3q2QpFuOpWkSANoopGpPd6s,11035
6
- prefab/models.py,sha256=JpBqNFIqbo1ymKEl0NWWF4ZkhvK23rEVVBEFl05TXCI,3671
7
- prefab/predict.py,sha256=AuCh_vOMP0dD68u75WQaOTog6TMi1FG3nLQxv6UgIkA,8579
8
- prefab/read.py,sha256=MuF-cugFQ7MWBJ8DOvQuwktIk0fJ8PXBeLye0ydrB8o,14734
9
- prefab/shapes.py,sha256=2qaqyNzu5WG3wVdk4oQzeNXmhwXRHcPnRZlgRrM4MoA,25576
10
- prefab-1.1.3.dist-info/METADATA,sha256=TGbeM7u0kktbeGeOwGIFS43dwp8rWvLSwHDYlXEcsfI,34824
11
- prefab-1.1.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
12
- prefab-1.1.3.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
13
- prefab-1.1.3.dist-info/RECORD,,
File without changes