|
37 | 37 | import numpy as np |
38 | 38 | import numpy.typing as npt |
39 | 39 | import sparse |
| 40 | + import tensorflow as tf |
40 | 41 | import torch |
41 | 42 |
|
42 | 43 | # TODO: import from typing (requires Python >=3.13) |
|
51 | 52 | | jax.Array |
52 | 53 | | ndx.Array |
53 | 54 | | sparse.SparseArray |
| 55 | + | tf.Tensor |
54 | 56 | | torch.Tensor |
55 | 57 | | SupportsArrayNamespace[Any] |
56 | 58 | ) |
@@ -168,6 +170,29 @@ def is_torch_array(x: object) -> TypeIs[torch.Tensor]: |
168 | 170 | return _issubclass_fast(cls, "torch", "Tensor") |
169 | 171 |
|
170 | 172 |
|
| 173 | +def is_tensorflow_array(x: object) -> TypeIs[tf.Tensor]: |
| 174 | + """ |
| 175 | + Return True if `x` is a TensorFlow tensor. |
| 176 | +
|
| 177 | + This function does not import TensorFlow if it has not already been |
| 178 | + imported and is therefore cheap to use. |
| 179 | +
|
| 180 | + See Also |
| 181 | + -------- |
| 182 | +
|
| 183 | + array_namespace |
| 184 | + is_array_api_obj |
| 185 | + is_numpy_array |
| 186 | + is_cupy_array |
| 187 | + is_torch_array |
| 188 | + is_dask_array |
| 189 | + is_jax_array |
| 190 | + is_pydata_sparse_array |
| 191 | + """ |
| 192 | + cls = cast(Hashable, type(x)) |
| 193 | + return _issubclass_fast(cls, "tensorflow", "Tensor") |
| 194 | + |
| 195 | + |
171 | 196 | def is_ndonnx_array(x: object) -> TypeIs[ndx.Array]: |
172 | 197 | """ |
173 | 198 | Return True if `x` is a ndonnx Array. |
@@ -308,6 +333,7 @@ def _is_array_api_cls(cls: type) -> bool: |
308 | 333 | or _issubclass_fast(cls, "numpy", "generic") |
309 | 334 | or _issubclass_fast(cls, "cupy", "ndarray") |
310 | 335 | or _issubclass_fast(cls, "torch", "Tensor") |
| 336 | + or _issubclass_fast(cls, "tensorflow", "Tensor") |
311 | 337 | or _issubclass_fast(cls, "dask.array", "Array") |
312 | 338 | or _issubclass_fast(cls, "sparse", "SparseArray") |
313 | 339 | # TODO: drop support for jax<0.4.32 which didn't have __array_namespace__ |
@@ -387,6 +413,30 @@ def is_torch_namespace(xp: Namespace) -> bool: |
387 | 413 | return xp.__name__ in {"torch", _compat_module_name() + ".torch"} |
388 | 414 |
|
389 | 415 |
|
| 416 | +@lru_cache(100) |
| 417 | +def is_tensorflow_namespace(xp: Namespace) -> bool: |
| 418 | + """ |
| 419 | + Returns True if `xp` is a TensorFlow namespace. |
| 420 | +
|
| 421 | + This includes both TensorFlow itself and the version wrapped by |
| 422 | + array-api-compat. |
| 423 | +
|
| 424 | + See Also |
| 425 | + -------- |
| 426 | +
|
| 427 | + array_namespace |
| 428 | + is_numpy_namespace |
| 429 | + is_cupy_namespace |
| 430 | + is_torch_namespace |
| 431 | + is_ndonnx_namespace |
| 432 | + is_dask_namespace |
| 433 | + is_jax_namespace |
| 434 | + is_pydata_sparse_namespace |
| 435 | + is_array_api_strict_namespace |
| 436 | + """ |
| 437 | + return xp.__name__ in {"tensorflow", _compat_module_name() + ".tensorflow"} |
| 438 | + |
| 439 | + |
390 | 440 | def is_ndonnx_namespace(xp: Namespace) -> bool: |
391 | 441 | """ |
392 | 442 | Returns True if `xp` is an NDONNX namespace. |
@@ -551,6 +601,14 @@ def _cls_to_namespace( |
551 | 601 | import torch as xp # type: ignore[no-redef] |
552 | 602 | return xp, None |
553 | 603 |
|
| 604 | + if _issubclass_fast(cls_, "tensorflow", "Tensor"): |
| 605 | + if _use_compat: |
| 606 | + _check_api_version(api_version) |
| 607 | + from .. import tensorflow as xp # type: ignore[no-redef] |
| 608 | + else: |
| 609 | + import tensorflow as xp # type: ignore[no-redef] |
| 610 | + return xp, None |
| 611 | + |
554 | 612 | if _issubclass_fast(cls_, "dask.array", "Array"): |
555 | 613 | if _use_compat: |
556 | 614 | _check_api_version(api_version) |
@@ -790,6 +848,8 @@ def device(x: _ArrayApiObj, /) -> Device: |
790 | 848 | return x_device() |
791 | 849 | else: |
792 | 850 | return x_device |
| 851 | + elif is_tensorflow_array(x): |
| 852 | + return x.device |
793 | 853 | elif is_pydata_sparse_array(x): |
794 | 854 | # `sparse` will gain `.device`, so check for this first. |
795 | 855 | x_device = getattr(x, "device", None) |
@@ -851,6 +911,20 @@ def _torch_to_device( |
851 | 911 | return x.to(device) |
852 | 912 |
|
853 | 913 |
|
| 914 | +def _tensorflow_to_device( |
| 915 | + x: tf.Tensor, |
| 916 | + device: str, |
| 917 | + /, |
| 918 | + stream: int | Any | None = None, |
| 919 | +) -> tf.Tensor: |
| 920 | + if stream is not None: |
| 921 | + raise ValueError("The stream argument to to_device() is not supported") |
| 922 | + import tensorflow as tf |
| 923 | + |
| 924 | + with tf.device(device): |
| 925 | + return tf.identity(x) |
| 926 | + |
| 927 | + |
854 | 928 | def to_device(x: Array, device: Device, /, *, stream: int | Any | None = None) -> Array: |
855 | 929 | """ |
856 | 930 | Copy the array from the device on which it currently resides to the specified ``device``. |
@@ -911,6 +985,8 @@ def to_device(x: Array, device: Device, /, *, stream: int | Any | None = None) - |
911 | 985 | return _cupy_to_device(x, device, stream=stream) |
912 | 986 | elif is_torch_array(x): |
913 | 987 | return _torch_to_device(x, device, stream=stream) |
| 988 | + elif is_tensorflow_array(x): |
| 989 | + return _tensorflow_to_device(x, device, stream=stream) |
914 | 990 | elif is_dask_array(x): |
915 | 991 | if stream is not None: |
916 | 992 | raise ValueError("The stream argument to to_device() is not supported") |
@@ -965,6 +1041,7 @@ def _is_writeable_cls(cls: type) -> bool | None: |
965 | 1041 | or _issubclass_fast(cls, "jax", "Array") |
966 | 1042 | or _issubclass_fast(cls, "jax.core", "Tracer") # see is_jax_array for limitations |
967 | 1043 | or _issubclass_fast(cls, "sparse", "SparseArray") |
| 1044 | + or _issubclass_fast(cls, "tensorflow", "Tensor") |
968 | 1045 | ): |
969 | 1046 | return False |
970 | 1047 | if _is_array_api_cls(cls): |
@@ -998,6 +1075,7 @@ def _is_lazy_cls(cls: type) -> bool | None: |
998 | 1075 | or _issubclass_fast(cls, "numpy", "generic") |
999 | 1076 | or _issubclass_fast(cls, "cupy", "ndarray") |
1000 | 1077 | or _issubclass_fast(cls, "torch", "Tensor") |
| 1078 | + or _issubclass_fast(cls, "tensorflow", "Tensor") |
1001 | 1079 | or _issubclass_fast(cls, "sparse", "SparseArray") |
1002 | 1080 | ): |
1003 | 1081 | return False |
@@ -1083,6 +1161,8 @@ def is_lazy_array(x: object) -> TypeGuard[_ArrayApiObj]: |
1083 | 1161 | "is_numpy_namespace", |
1084 | 1162 | "is_torch_array", |
1085 | 1163 | "is_torch_namespace", |
| 1164 | + "is_tensorflow_array", |
| 1165 | + "is_tensorflow_namespace", |
1086 | 1166 | "is_ndonnx_array", |
1087 | 1167 | "is_ndonnx_namespace", |
1088 | 1168 | "is_pydata_sparse_array", |
|
0 commit comments