16
16
import numpy as np
17
17
import warnings
18
18
19
- from odl .set import RealNumbers
19
+ from odl .set import RealNumbers , ComplexNumbers
20
20
from odl .space .base_tensors import TensorSpace , Tensor
21
21
from odl .space .weighting import (
22
22
Weighting , ArrayWeighting , ConstWeighting ,
23
23
CustomInner , CustomNorm , CustomDist )
24
- from odl .util import dtype_str , is_floating_dtype , signature_string
24
+ from odl .util import (
25
+ array_str , dtype_str , is_floating_dtype , signature_string , indent )
25
26
26
27
try :
27
28
import cupy
@@ -599,13 +600,14 @@ def __eq__(self, other):
599
600
>>> same_space == space
600
601
True
601
602
"""
602
- return (super ().__eq__ (other ) and
603
+ return (super (CupyTensorSpace , self ).__eq__ (other ) and
603
604
self .device == other .device and
604
605
self .weighting == other .weighting )
605
606
606
607
def __hash__ (self ):
607
608
"""Return ``hash(self)``."""
608
- return hash ((super ().__hash__ (), self .device , self .weighting ))
609
+ return hash ((super (CupyTensorSpace , self ).__hash__ (), self .device ,
610
+ self .weighting ))
609
611
610
612
def _lincomb (self , a , x1 , b , x2 , out ):
611
613
"""Linear combination of ``x1`` and ``x2``.
@@ -874,12 +876,16 @@ def default_dtype(field=None):
874
876
dtype : `numpy.dtype`
875
877
Numpy data type specifier. The returned defaults are:
876
878
877
- ``RealNumbers()`` : ``np.dtype('float64')``
879
+ - ``RealNumbers()`` or ``None`` : ``np.dtype('float64')``
880
+ - ``ComplexNumbers()`` : ``np.dtype('complex128')``
878
881
879
- ``ComplexNumbers()`` : not supported
882
+ These choices correspond to the defaults of the ``cupy``
883
+ library.
880
884
"""
881
885
if field is None or field == RealNumbers ():
882
886
return np .dtype ('float64' )
887
+ elif field == ComplexNumbers ():
888
+ return np .dtype ('complex128' )
883
889
else :
884
890
raise ValueError ('no default data type defined for field {}.'
885
891
'' .format (field ))
@@ -1029,7 +1035,7 @@ def copy(self):
1029
1035
1030
1036
Returns
1031
1037
-------
1032
- copy : `pygpu._array.ndgpuarray `
1038
+ copy : `CupyTensor `
1033
1039
A deep copy.
1034
1040
1035
1041
Examples
@@ -1056,7 +1062,7 @@ def __getitem__(self, indices):
1056
1062
1057
1063
Returns
1058
1064
-------
1059
- values : scalar or `pygpu._array.ndgpuarray `
1065
+ values : scalar or `cupy.core.core.ndarray `
1060
1066
The value(s) at the index (indices).
1061
1067
1062
1068
Examples
@@ -1107,11 +1113,11 @@ def __getitem__(self, indices):
1107
1113
arr = self .data [indices ]
1108
1114
if arr .shape == ():
1109
1115
if arr .dtype .kind == 'f' :
1110
- return float (np . asarray (arr ))
1116
+ return float (cupy . asnumpy (arr ))
1111
1117
elif arr .dtype .kind == 'c' :
1112
- return complex (np . asarray (arr ))
1118
+ return complex (cupy . asnumpy (arr ))
1113
1119
elif arr .dtype .kind in ('u' , 'i' ):
1114
- return int (np . asarray (arr ))
1120
+ return int (cupy . asnumpy (arr ))
1115
1121
else :
1116
1122
raise RuntimeError ("no conversion for dtype {}"
1117
1123
"" .format (arr .dtype ))
@@ -1280,12 +1286,22 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
1280
1286
for further details. See also the `general documentation on
1281
1287
Numpy ufuncs`_.
1282
1288
1283
- .. note::
1284
- This implementation looks for native ufuncs in ``pygpu.ufuncs``
1285
- and falls back to the basic implementation with Numpy arrays
1286
- in case no native ufunc is available. That fallback version
1287
- comes with significant overhead due to data copies between
1288
- host and device.
1289
+ .. warning::
1290
+ Apart from ``'__call__'`` (invoked by, e.g., ``np.add(x, y))``,
1291
+ CuPy has no native implementation of ufunc methods like
1292
+ ``'reduce'`` or ``'accumulate'``. We manually implement the
1293
+ mappings (covering most use cases)
1294
+
1295
+ - ``np.add.reduce`` -> ``cupy.sum``
1296
+ - ``np.add.accumulate`` -> ``cupy.cumsum``
1297
+ - ``np.multiply.reduce`` -> ``cupy.prod``
1298
+ - ``np.multiply.reduce`` -> ``cupy.cumprod``.
1299
+
1300
+ **All other such methods will run Numpy code and be slow**!
1301
+
1302
+ Please consult the `CuPy documentation on ufuncs
1303
+ <https://docs-cupy.chainer.org/en/stable/reference/ufunc.html>`_
1304
+ to check the current state of the library.
1289
1305
1290
1306
.. note::
1291
1307
When an ``out`` parameter is specified, and (one of) it has
@@ -1507,10 +1523,10 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
1507
1523
inp .data if isinstance (inp , type (self )) else inp
1508
1524
for inp in inputs )
1509
1525
1510
- # For native ufuncs, we turn non-scalar inputs into cupy arrays,
1511
- # as a workaround for https://github.com/cupy/cupy/issues/594
1512
- # TODO: remove code when the upstream issue is fixed
1513
1526
if use_native :
1527
+ # TODO: remove when upstream issue is fixed
1528
+ # For native ufuncs, we turn non-scalar inputs into cupy arrays,
1529
+ # as a workaround for https://github.com/cupy/cupy/issues/594
1514
1530
inputs , orig_inputs = [], inputs
1515
1531
for inp in orig_inputs :
1516
1532
if (isinstance (inp , cupy .ndarray ) or
@@ -1519,6 +1535,20 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
1519
1535
inputs .append (inp )
1520
1536
else :
1521
1537
inputs .append (cupy .array (inp ))
1538
+ elif method != 'at' :
1539
+ # TODO: remove when upstream issue is fixed
1540
+ # For non-native ufuncs (except `at`), we need ot cast our tensors
1541
+ # and Cupy arrays to Numpy arrays explicitly, since `__array__`
1542
+ # and friends are not implemented. See
1543
+ # https://github.com/cupy/cupy/issues/589
1544
+ inputs , orig_inputs = [], inputs
1545
+ for inp in orig_inputs :
1546
+ if isinstance (inp , cupy .ndarray ):
1547
+ inputs .append (cupy .asnumpy (inp ))
1548
+ elif isinstance (inp , CupyTensor ):
1549
+ inputs .append (cupy .asnumpy (inp .data ))
1550
+ else :
1551
+ inputs .append (inp )
1522
1552
1523
1553
# --- Get some parameters for later --- #
1524
1554
@@ -1595,20 +1625,19 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
1595
1625
def eval_at_via_npy (* inputs , ** kwargs ):
1596
1626
import ctypes
1597
1627
cupy_arr = inputs [0 ]
1598
- npy_arr = np . asarray (cupy_arr )
1628
+ npy_arr = cupy . asnumpy (cupy_arr )
1599
1629
new_inputs = (npy_arr ,) + inputs [1 :]
1600
1630
super (CupyTensor , self ).__array_ufunc__ (
1601
1631
ufunc , method , * new_inputs , ** kwargs )
1602
1632
# Workaround for https://github.com/cupy/cupy/issues/593
1603
- # TODO: use cupy_arr[:] = npy_arr when it's fixed and not
1604
- # slower
1633
+ # TODO: use cupy_arr[:] = npy_arr when available
1605
1634
cupy_arr .data .copy_from_host (
1606
1635
npy_arr .ctypes .data_as (ctypes .c_void_p ), npy_arr .nbytes )
1607
1636
1608
1637
if use_native :
1609
1638
# Native method could exist but raise `NotImplementedError`
1610
- # or return `NotImplemented`, falling back to Numpy case
1611
- # then, too
1639
+ # or return `NotImplemented`. We fall back to Numpy also in
1640
+ # that situation.
1612
1641
try :
1613
1642
res = native_method (* inputs , ** kwargs )
1614
1643
except NotImplementedError :
@@ -1626,8 +1655,8 @@ def eval_at_via_npy(*inputs, **kwargs):
1626
1655
1627
1656
if use_native :
1628
1657
# Native method could exist but raise `NotImplementedError`
1629
- # or return `NotImplemented`, falling back to base case
1630
- # then, too
1658
+ # or return `NotImplemented`. We fall back to Numpy also in
1659
+ # that situation.
1631
1660
try :
1632
1661
res = native_method (* inputs , ** kwargs )
1633
1662
except NotImplementedError :
@@ -1653,8 +1682,8 @@ def eval_at_via_npy(*inputs, **kwargs):
1653
1682
if is_floating_dtype (res .dtype ):
1654
1683
if res .shape != self .shape :
1655
1684
# Don't propagate weighting if shape changes
1656
- weighting = CupyTensorSpaceConstWeighting (1.0 ,
1657
- exponent )
1685
+ weighting = CupyTensorSpaceConstWeighting (
1686
+ 1.0 , exponent )
1658
1687
spc_kwargs = {'weighting' : weighting }
1659
1688
else :
1660
1689
spc_kwargs = {}
@@ -1723,8 +1752,10 @@ def real(self):
1723
1752
real : `CupyTensor` view with real dtype
1724
1753
The real part of this tensor as an element of an `rn` space.
1725
1754
"""
1726
- # Only real dtypes currently
1727
- return self
1755
+ if self .space .is_real :
1756
+ return self
1757
+ else :
1758
+ return self .space .real_space .element (self .data .real )
1728
1759
1729
1760
@real .setter
1730
1761
def real (self , newreal ):
@@ -1737,7 +1768,7 @@ def real(self, newreal):
1737
1768
newreal : `array-like` or scalar
1738
1769
The new real part for this tensor.
1739
1770
"""
1740
- self .real . data [:] = newreal
1771
+ self .data . real [:] = newreal
1741
1772
1742
1773
@property
1743
1774
def imag (self ):
@@ -1748,8 +1779,10 @@ def imag(self):
1748
1779
imag : `CupyTensor`
1749
1780
The imaginary part of this tensor as an element of an `rn` space.
1750
1781
"""
1751
- # Only real dtypes currently
1752
- return self .space .zero ()
1782
+ if self .space .is_real :
1783
+ return self .space .zero ()
1784
+ else :
1785
+ return self .space .real_space .element (self .data .imag )
1753
1786
1754
1787
@imag .setter
1755
1788
def imag (self , newimag ):
@@ -1762,7 +1795,7 @@ def imag(self, newimag):
1762
1795
newimag : `array-like` or scalar
1763
1796
The new imaginary part for this tensor.
1764
1797
"""
1765
- raise NotImplementedError ( 'complex dtypes not supported' )
1798
+ self . data . imag [:] = newimag
1766
1799
1767
1800
def conj (self , out = None ):
1768
1801
"""Complex conjugate of this tensor.
@@ -1779,11 +1812,17 @@ def conj(self, out=None):
1779
1812
The complex conjugate tensor. If ``out`` was provided,
1780
1813
the returned object is a reference to it.
1781
1814
"""
1782
- # Only real dtypes currently
1783
1815
if out is None :
1784
- return self .copy ()
1816
+ if self .space .is_real :
1817
+ return self .copy ()
1818
+ else :
1819
+ return self .space .element (self .data .conj ())
1785
1820
else :
1786
- self .assign (out )
1821
+ if self .space .is_real :
1822
+ self .assign (out )
1823
+ else :
1824
+ # In-place not available as it seems
1825
+ out [:] = self .data .conj ()
1787
1826
return out
1788
1827
1789
1828
def __ipow__ (self , other ):
@@ -1806,7 +1845,8 @@ def _weighting(weights, exponent):
1806
1845
if np .isscalar (weights ):
1807
1846
weighting = CupyTensorSpaceConstWeighting (weights , exponent = exponent )
1808
1847
else :
1809
- # TODO: sequence of 1D array-likes
1848
+ # TODO: sequence of 1D array-likes, see
1849
+ # https://github.com/odlgroup/odl/pull/1238
1810
1850
weights = cupy .array (weights , copy = False )
1811
1851
weighting = CupyTensorSpaceArrayWeighting (weights , exponent = exponent )
1812
1852
return weighting
@@ -2065,6 +2105,28 @@ def dist(self, x1, x2):
2065
2105
else :
2066
2106
return float (distpw (x1 .data , x2 .data , self .exponent , self .array ))
2067
2107
2108
+ # TODO: remove repr_part and __repr__ when cupy.ndarray.__array__
2109
+ # is implemented. See
2110
+ # https://github.com/cupy/cupy/issues/589
2111
+ @property
2112
+ def repr_part (self ):
2113
+ """String usable in a space's ``__repr__`` method."""
2114
+ # TODO: use edgeitems
2115
+ arr_str = array_str (cupy .asnumpy (self .array ), nprint = 10 )
2116
+ optargs = [('weighting' , arr_str , '' ),
2117
+ ('exponent' , self .exponent , 2.0 )]
2118
+ return signature_string ([], optargs , sep = ',\n ' ,
2119
+ mod = [[], ['!s' , ':.4' ]])
2120
+
2121
+ def __repr__ (self ):
2122
+ """Return ``repr(self)``."""
2123
+ # TODO: use edgeitems
2124
+ posargs = [array_str (cupy .asnumpy (self .array ), nprint = 10 )]
2125
+ optargs = [('exponent' , self .exponent , 2.0 )]
2126
+ inner_str = signature_string (posargs , optargs , sep = ',\n ' ,
2127
+ mod = ['!s' , ':.4' ])
2128
+ return '{}(\n {}\n )' .format (self .__class__ .__name__ , indent (inner_str ))
2129
+
2068
2130
2069
2131
class CupyTensorSpaceConstWeighting (ConstWeighting ):
2070
2132
0 commit comments