prefab 1.4.2__py3-none-any.whl → 1.6.0__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.4.2"
8
+ __version__ = "1.6.0"
9
9
 
10
10
  from . import compare, geometry, predict, read, shapes
11
11
  from .device import BufferSpec, Device
prefab/__main__.py CHANGED
@@ -18,8 +18,8 @@ def store_jwt(jwt, refresh_token):
18
18
  print(
19
19
  f"Token successfully stored in {prefab_file_path}.\n\n"
20
20
  "🎉 Welcome to PreFab.\n"
21
- "See our examples at https://docs.prefabphotonics.com/examples to start.\n"
22
- "Reach out to us at hi@prefabphotonics.com for support."
21
+ "See our examples at https://docs.prefabphotonics.com to start.\n"
22
+ "Reach out to us at support@prefabphotonics.com for support."
23
23
  )
24
24
 
25
25
 
prefab/predict.py CHANGED
@@ -3,12 +3,13 @@ Serverless prediction interface for nanofabrication modeling.
3
3
 
4
4
  This module provides functions for predicting nanofabrication outcomes using machine
5
5
  learning models hosted on a serverless platform. It supports multiple input formats
6
- (ndarrays, polygons, GDSII files) and model types (prediction, correction,
7
- segmentation). Gradient computation is available for inverse design applications
8
- using automatic differentiation.
6
+ (ndarrays, polygons, GDSII files) and model types (prediction and correction). Gradient
7
+ computation is available for inverse design applications using automatic
8
+ differentiation.
9
9
  """
10
10
 
11
11
  import base64
12
+ import gzip
12
13
  import io
13
14
  import json
14
15
  import os
@@ -26,8 +27,17 @@ from PIL import Image
26
27
  from .geometry import binarize_hard
27
28
  from .models import Model
28
29
 
29
- BASE_ENDPOINT_URL = "https://prefab-photonics--predict"
30
- ENDPOINT_VERSION = "3"
30
+ BASE_URL = "https://prefab-photonics"
31
+
32
+ # Endpoint versions
33
+ PREDICT_VERSION = "3"
34
+ PREDICT_POLY_VERSION = "3"
35
+ VJP_VERSION = "3"
36
+
37
+ # Endpoint URLs
38
+ PREDICT_ENDPOINT = f"{BASE_URL}--predict-v{PREDICT_VERSION}.modal.run"
39
+ PREDICT_POLY_ENDPOINT = f"{BASE_URL}--predict-poly-v{PREDICT_POLY_VERSION}.modal.run"
40
+ VJP_ENDPOINT = f"{BASE_URL}--vjp-v{VJP_VERSION}.modal.run"
31
41
 
32
42
 
33
43
  def predict_array(
@@ -75,7 +85,7 @@ def predict_array(
75
85
  ValueError
76
86
  If the server returns an error or invalid response.
77
87
  """
78
- endpoint_url = f"{BASE_ENDPOINT_URL}-v{ENDPOINT_VERSION}.modal.run"
88
+ endpoint_url = PREDICT_ENDPOINT
79
89
  predict_data = {
80
90
  "device_array": _encode_array(np.squeeze(device_array)),
81
91
  "model": model.to_json(),
@@ -167,7 +177,7 @@ def _predict_poly(
167
177
  "eta": eta,
168
178
  }
169
179
 
170
- endpoint_url = f"{BASE_ENDPOINT_URL}-poly-v{ENDPOINT_VERSION}.modal.run"
180
+ endpoint_url = PREDICT_POLY_ENDPOINT
171
181
  headers = _prepare_headers()
172
182
 
173
183
  try:
@@ -364,47 +374,32 @@ def predict_gdstk(
364
374
  return result_cell
365
375
 
366
376
 
367
- def _predict_array_with_grad(
368
- device_array: npt.NDArray[Any], model: Model
369
- ) -> tuple[npt.NDArray[Any], npt.NDArray[Any]]:
370
- """
371
- Predict the nanofabrication outcome of a device array and compute its gradient.
372
-
373
- This function predicts the outcome of the nanofabrication process for a given
374
- device array using a specified model. It also computes the gradient of the
375
- prediction with respect to the input device array.
376
-
377
- Parameters
378
- ----------
379
- device_array : np.ndarray
380
- A 2D array representing the planar geometry of the device.
381
- model : Model
382
- The model to use for prediction, representing a specific fabrication process.
377
+ # Storage for caching prediction results for VJP computation
378
+ _diff_cache: dict[int, tuple[npt.NDArray[Any], Model]] = {}
383
379
 
384
- Returns
385
- -------
386
- tuple[np.ndarray, np.ndarray]
387
- The predicted output array and gradient array.
388
380
 
389
- Raises
390
- ------
391
- RuntimeError
392
- If the request to the prediction service fails.
393
- ValueError
394
- If the server returns an error or invalid response.
395
- """
381
+ def _compute_vjp(
382
+ device_array: npt.NDArray[Any],
383
+ upstream_gradient: npt.NDArray[Any],
384
+ model: Model,
385
+ ) -> npt.NDArray[Any]:
386
+ """Compute J.T @ upstream_gradient via the server-side VJP endpoint."""
396
387
  headers = _prepare_headers()
397
- predict_data = {
388
+ upstream_arr = np.squeeze(upstream_gradient).astype(np.float32)
389
+ vjp_data = {
398
390
  "device_array": _encode_array(np.squeeze(device_array)),
391
+ "upstream_gradient": base64.b64encode(
392
+ gzip.compress(upstream_arr.tobytes(), compresslevel=1)
393
+ ).decode("utf-8"),
394
+ "upstream_gradient_shape": list(upstream_arr.shape),
399
395
  "model": model.to_json(),
400
396
  "model_type": "p",
401
- "binary": False,
402
397
  }
403
- endpoint_url = f"{BASE_ENDPOINT_URL}-with-grad-v{ENDPOINT_VERSION}.modal.run"
398
+ endpoint_url = VJP_ENDPOINT
404
399
 
405
400
  try:
406
401
  response = requests.post(
407
- endpoint_url, data=json.dumps(predict_data), headers=headers
402
+ endpoint_url, data=json.dumps(vjp_data), headers=headers
408
403
  )
409
404
  response.raise_for_status()
410
405
 
@@ -414,89 +409,152 @@ def _predict_array_with_grad(
414
409
  response_data = response.json()
415
410
 
416
411
  if "error" in response_data:
417
- raise ValueError(f"Prediction error: {response_data['error']}")
412
+ raise ValueError(f"VJP error: {response_data['error']}")
418
413
 
419
- prediction_array = _decode_array(response_data["prediction_array"])
420
- gradient_array = _decode_array(response_data["gradient_array"])
421
- gradient_min = response_data["gradient_min"]
422
- gradient_max = response_data["gradient_max"]
423
- gradient_range = gradient_max - gradient_min
424
- gradient_array = gradient_array * gradient_range + gradient_min
425
- return (prediction_array, gradient_array)
414
+ vjp_array = _decode_array(response_data["vjp_array"])
415
+ vjp_min = response_data["vjp_min"]
416
+ vjp_max = response_data["vjp_max"]
417
+ vjp_range = vjp_max - vjp_min
418
+ vjp_array = vjp_array * vjp_range + vjp_min
419
+ return vjp_array
426
420
 
427
421
  except requests.exceptions.RequestException as e:
428
- raise RuntimeError(f"Request failed: {e}") from e
422
+ raise RuntimeError(f"VJP request failed: {e}") from e
429
423
  except json.JSONDecodeError as e:
430
424
  raise ValueError(f"JSON decode error: {e}") from e
431
425
 
432
426
 
433
427
  @primitive
434
- def predict_array_with_grad(
428
+ def predict_array_diff(
435
429
  device_array: npt.NDArray[Any], model: Model
436
430
  ) -> npt.NDArray[Any]:
437
431
  """
438
- Predict the nanofabrication outcome of a device array and compute its gradient.
432
+ Differentiable fab prediction with exact gradient support.
439
433
 
440
- This function predicts the outcome of the nanofabrication process for a given
441
- device array using a specified model. It also computes the gradient of the
442
- prediction with respect to the input device array, making it suitable for use in
443
- automatic differentiation applications (e.g., autograd).
434
+ Compatible with autograd for automatic differentiation. Gradients are computed via
435
+ a server-side VJP endpoint during the backward pass.
444
436
 
445
437
  Parameters
446
438
  ----------
447
439
  device_array : np.ndarray
448
440
  A 2D array representing the planar geometry of the device.
449
441
  model : Model
450
- The model to use for prediction, representing a specific fabrication process.
442
+ The model to use for prediction.
451
443
 
452
444
  Returns
453
445
  -------
454
446
  np.ndarray
455
- The predicted output array.
456
-
457
- Raises
458
- ------
459
- RuntimeError
460
- If the request to the prediction service fails.
461
- ValueError
462
- If the server returns an error or invalid response.
447
+ The predicted fabrication outcome array.
463
448
  """
464
- prediction_array, gradient_array = _predict_array_with_grad(
465
- device_array=device_array, model=model
449
+ # Use standard forward pass
450
+ prediction_array = predict_array(
451
+ device_array=device_array,
452
+ model=model,
453
+ model_type="p",
454
+ binarize=False,
466
455
  )
467
- predict_array_with_grad.gradient_array = gradient_array # pyright: ignore[reportFunctionMemberAccess]
456
+
457
+ # Cache the input for VJP computation during backward pass
458
+ _diff_cache[id(prediction_array)] = (device_array.copy(), model)
459
+
468
460
  return prediction_array
469
461
 
470
462
 
471
- def predict_array_with_grad_vjp(
472
- ans: npt.NDArray[Any], device_array: npt.NDArray[Any], *args: Any
463
+ def _predict_array_diff_vjp(
464
+ ans: npt.NDArray[Any], device_array: npt.NDArray[Any], model: Model
473
465
  ) -> Any:
466
+ """Define the exact VJP for predict_array_diff using server-side computation."""
467
+ cache_key = id(ans)
468
+ cached_device_array, cached_model = _diff_cache.get(
469
+ cache_key, (device_array, model)
470
+ )
471
+
472
+ def vjp(g: npt.NDArray[Any]) -> tuple[npt.NDArray[Any], None]:
473
+ # Compute exact VJP: J.T @ g via server endpoint
474
+ vjp_result = _compute_vjp(
475
+ device_array=cached_device_array,
476
+ upstream_gradient=g,
477
+ model=cached_model,
478
+ )
479
+ # Clean up cache
480
+ _diff_cache.pop(cache_key, None)
481
+ # Ensure gradient shape matches input shape
482
+ vjp_result = vjp_result.reshape(cached_device_array.shape)
483
+ # Return gradient for device_array, None for model (not differentiable)
484
+ return (vjp_result, None)
485
+
486
+ return vjp
487
+
488
+
489
+ defvjp(predict_array_diff, _predict_array_diff_vjp)
490
+
491
+
492
+ # Alias for backward compatibility with existing code
493
+ predict_array_with_grad = predict_array_diff
494
+ """Alias for predict_array_diff. Deprecated, use predict_array_diff directly."""
495
+
496
+
497
+ def differentiable(model: Model):
474
498
  """
475
- Define the vector-Jacobian product (VJP) for the prediction function.
499
+ Create a model-bound differentiable predictor for clean autograd integration.
500
+
501
+ Returns a function that takes only `device_array` as input, enabling seamless
502
+ composition with other differentiable functions. The VJP returns a single
503
+ gradient array (not a tuple), making it compatible with standard autograd workflows.
476
504
 
477
505
  Parameters
478
506
  ----------
479
- ans : np.ndarray
480
- The output of the `predict_array_with_grad` function.
481
- device_array : np.ndarray
482
- The input device array for which the gradient is computed.
483
- *args :
484
- Additional arguments that aren't used in the VJP computation.
507
+ model : Model
508
+ The model to use for prediction.
485
509
 
486
510
  Returns
487
511
  -------
488
- function
489
- A function that computes the VJP given an upstream gradient `g`.
512
+ callable
513
+ A differentiable prediction function that takes `device_array` and returns
514
+ the predicted fabrication outcome.
515
+
516
+ Examples
517
+ --------
518
+ >>> predictor = pf.predict.differentiable(model)
519
+ >>> def loss_fn(x):
520
+ ... pred = predictor(x)
521
+ ... return np.mean((pred - target) ** 2)
522
+ >>> gradient = grad(loss_fn)(device_array) # Returns array, not tuple
490
523
  """
491
- grad_x = predict_array_with_grad.gradient_array # pyright: ignore[reportFunctionMemberAccess]
492
524
 
493
- def vjp(g: npt.NDArray[Any]) -> npt.NDArray[Any]:
494
- return g * grad_x # type: ignore[no-any-return]
525
+ @primitive
526
+ def predict(device_array: npt.NDArray[Any]) -> npt.NDArray[Any]:
527
+ prediction = predict_array(
528
+ device_array=device_array,
529
+ model=model,
530
+ model_type="p",
531
+ binarize=False,
532
+ )
533
+ _diff_cache[id(prediction)] = (device_array.copy(), model)
534
+ return prediction
535
+
536
+ def predict_vjp(
537
+ ans: npt.NDArray[Any], device_array: npt.NDArray[Any]
538
+ ) -> Any:
539
+ cache_key = id(ans)
540
+ cached_device_array, cached_model = _diff_cache.get(
541
+ cache_key, (device_array, model)
542
+ )
495
543
 
496
- return vjp
544
+ def vjp(g: npt.NDArray[Any]) -> npt.NDArray[Any]:
545
+ vjp_result = _compute_vjp(
546
+ device_array=cached_device_array,
547
+ upstream_gradient=g,
548
+ model=cached_model,
549
+ )
550
+ _diff_cache.pop(cache_key, None)
551
+ # Ensure gradient shape matches input shape
552
+ return vjp_result.reshape(device_array.shape)
497
553
 
554
+ return vjp
498
555
 
499
- defvjp(predict_array_with_grad, predict_array_with_grad_vjp)
556
+ defvjp(predict, predict_vjp)
557
+ return predict
500
558
 
501
559
 
502
560
  def _encode_array(array: npt.NDArray[Any]) -> str:
@@ -512,7 +570,7 @@ def _decode_array(encoded_png: str) -> npt.NDArray[Any]:
512
570
  """Decode a base64 encoded image and return an ndarray."""
513
571
  binary_data = base64.b64decode(encoded_png)
514
572
  image = Image.open(io.BytesIO(binary_data))
515
- return np.array(image) / 255 # type: ignore[no-any-return]
573
+ return np.array(image) / 255
516
574
 
517
575
 
518
576
  def _prepare_headers() -> dict[str, str]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prefab
3
- Version: 1.4.2
3
+ Version: 1.6.0
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
@@ -511,14 +511,13 @@ License: GNU LESSER GENERAL PUBLIC LICENSE
511
511
 
512
512
  That's all there is to it!
513
513
  License-File: LICENSE
514
- Keywords: computer-vision,deep-learning,electronic-design-automation,integrated-photonics,machine-learning,nanofabrication,semiconductor-manufacturing
514
+ Keywords: electronic-design-automation,integrated-photonics,inverse-design,lithography,nanofabrication,photonics,semiconductor-manufacturing,silicon-photonics
515
515
  Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)
516
516
  Classifier: Operating System :: OS Independent
517
517
  Classifier: Programming Language :: Python :: 3
518
518
  Requires-Python: >=3.9
519
519
  Requires-Dist: autograd
520
520
  Requires-Dist: gdstk>=0.9.55
521
- Requires-Dist: ipykernel>=6.31.0
522
521
  Requires-Dist: matplotlib
523
522
  Requires-Dist: numpy
524
523
  Requires-Dist: opencv-python-headless
@@ -528,9 +527,6 @@ Requires-Dist: requests
528
527
  Requires-Dist: scikit-image
529
528
  Requires-Dist: scipy
530
529
  Requires-Dist: toml
531
- Provides-Extra: dev
532
- Requires-Dist: jupyter; extra == 'dev'
533
- Requires-Dist: jupyterlab; extra == 'dev'
534
530
  Provides-Extra: docs
535
531
  Requires-Dist: mkdocs; extra == 'docs'
536
532
  Requires-Dist: mkdocs-material; extra == 'docs'
@@ -1,17 +1,17 @@
1
- prefab/__init__.py,sha256=XffqgQ1AexVSwGAgt7Wbfo-4raEefrzmXwE2EEG3WUI,425
2
- prefab/__main__.py,sha256=M5ulRfgSMdPjeHPoMGQ3P86zFMJDII5I18NGrpFUcsM,3458
1
+ prefab/__init__.py,sha256=WmddNmU7gmyH4cqkT6BW4iQ3RDG_l5SpKxg4poS6wsU,425
2
+ prefab/__main__.py,sha256=uL6AdCeimPbXiWv0gnq9TDeZhAIrxGwJsEKNYbg9MZg,3454
3
3
  prefab/compare.py,sha256=aX7nr9tznSebYeeztvqIPz57npnJ4-iUeKEedrZdksE,3676
4
4
  prefab/device.py,sha256=1O6vTOq4wQRGVYvFWLH0uj1XhhYCfnDnIapDEYnBKHw,47996
5
5
  prefab/geometry.py,sha256=-nTaGjdw3KN1SVoyvqdcrE2GJP7OqPF6ivUhrO78rUk,11244
6
- prefab/predict.py,sha256=LSOf-lo7l8JRzRSXTDWosGfr2baE-D5zZjinkObeJV4,19182
6
+ prefab/predict.py,sha256=gxxHCcaMv2TE5O639mQzO3IzFRsLGjY-FQowVp5vLkI,20659
7
7
  prefab/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  prefab/read.py,sha256=5BmvFemobA72urxs4j2VZRVvanZZGu1mDB1Uh-epyvI,8635
9
9
  prefab/shapes.py,sha256=mRGwsPS-A9XsW3jgvUuMCEeNv9BLXsEnJkytlIHUAKE,28802
10
10
  prefab/models/__init__.py,sha256=rRrjcOcHPcob98Coksc0tbvYcXbm6SoLEb-Md233Jvo,1391
11
11
  prefab/models/base.py,sha256=t4VNMsOztPedj3kN5fZ1-4tk0SRHWrMuqnIVHztsCs4,1514
12
12
  prefab/models/evaluation.py,sha256=2_Klui6tY8xPvOSVD8VpZCVAnT1RX15FONqWG-_x-J8,484
13
- prefab-1.4.2.dist-info/METADATA,sha256=JFb3uFdmPuf8thzsPnOim8F47qqSjVajVG9plmBuonQ,33880
14
- prefab-1.4.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
- prefab-1.4.2.dist-info/entry_points.txt,sha256=h1_A9O9F3NAIoKXD1RPb3Eo-WCSiHhMB_AnagBi6XTQ,48
16
- prefab-1.4.2.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
17
- prefab-1.4.2.dist-info/RECORD,,
13
+ prefab-1.6.0.dist-info/METADATA,sha256=CvuP1Khblh_LA-vx5NLgTH5A_zaewTkklkaPEsIY4hI,33754
14
+ prefab-1.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
+ prefab-1.6.0.dist-info/entry_points.txt,sha256=h1_A9O9F3NAIoKXD1RPb3Eo-WCSiHhMB_AnagBi6XTQ,48
16
+ prefab-1.6.0.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
17
+ prefab-1.6.0.dist-info/RECORD,,
File without changes