array-api
array-api copied to clipboard
RFC: Adding complex number support to the specification
Complex Number Support
Plan for complex number support in the array API specification.
What follows is a plan for adding complex number support to the 2022 array API specification. This RFC is comprised of the following sections:
- Prior discusssions
- High-level summary concerns and questions
- Individual API changes
Prior Discussions
- Issue: https://github.com/data-apis/array-api/issues/102
- Issue: https://github.com/data-apis/array-api/issues/153
General Concerns
New APIs
abs2 (x*x^H)
real
conj/conjugate (complex conjugate)
imag
arg/angle/phase (phase angle)
linalg.eig
linalg.eigvals
Decisions
-
initial data types:
complex64
andcomplex128
? -
rounding complex numbers (component-wise)
- NumPy supports round, but not ceil, floor, and trunc
-
ordering (lexicographic?)
- necessary for sorting, maximum, minimum (e.g.,
argsort
,argmax
,argmin
,min
,max
,sort
) - issue: https://github.com/data-apis/array-api/issues/102#issuecomment-748328170
- choices are to define a complex number ordering or leave out of the specification
- necessary for sorting, maximum, minimum (e.g.,
-
branch cut policy?
- branch cut reference: https://en.wikipedia.org/wiki/Branch_point#Branch_cuts
- should we strictly follow NumPy/C99, or should we allow for implementation flexibility?
- if we strictly follow something other than NumPy/C99, obvious backward compat concern
-
casting from complex to real
- discard imaginary component (as in C, NumPy)
- other choice would to require user to explicitly invoke
real
before casting
-
complex numbers with components which are infinity and/or NaN
-
in Python, a complex number can be both infinite and NaN (according to
cmath.isinf
,cmath.isnan
)In [1]: x = complex(float('inf'), float('nan')) In [2]: x Out[2]: (inf+nanj) In [3]: cmath.isinf(x) Out[3]: True In [4]: cmath.isnan(x) Out[4]: True
-
in NumPy, if any component is NaN, the complex number is NaN
In [1]: np.isnan([np.inf+np.nan*1j]) Out[1]: array([ True], dtype=bool) In [2]: np.isinf([np.inf+np.nan*1j]) Out[2]: array([False], dtype=bool)
-
in C99 (see "complex floating types" section), one infinity model (e.g.,
inf + nan*j
===inf
)
-
Creation Functions
arange
-
No changes. No complex number support.
-
NumPy issue: https://github.com/numpy/numpy/issues/10332
- can be difficult to resolve step/length
-
asarray
-
dtype
- if one or more values are complex Python scalars, the output data type must be the default complex floating-point data type.
-
status: implemented
empty
- No changes. Output array data type has no restrictions.
empty_like
- No changes. Output array data type has no restrictions.
eye
-
No changes. Output array data type has not restrictions.
-
May want to include note that, for complex numbers, ones along the diagonal means
1 + 0j
. -
status: implemented
from_dlpack
- No changes.
full
-
fill_value
- update to accept
complex
fill value.
- update to accept
-
dtype
- if fill value is
complex
, output array data type must be the default complex floating-point data type.
- if fill value is
-
status: implemented
full_like
-
fill_value
- update to accept
complex
fill value.
- update to accept
-
dtype
- update note for when
dtype
isNone
to includecomplex
.
- update note for when
-
status: implemented
linspace
-
start
- add support for
complex
- add support for
-
stop
- add support for
complex
- add support for
-
when either
start
orstop
is complex, the result must be complex. -
status: todo
- PR: TODO
meshgrid
-
update to accept all numeric dtypes.
-
status: implemented
ones
-
No changes necessary.
-
May be useful to add a note that, for complex numbers, ones means
1 + 0j
. -
status: implemented
ones_like
-
No changes necessary.
-
May be useful to add a note that, for complex numbers, ones means
1 + 0j
. -
status: implemented
tril
- No changes necessary. No restrictions on input array data types.
triu
- No changes necessary. No restrictions on input array data types.
zeros
- No changes necessary. No restrictions on output array data types.
zeros_like
- No changes necessary. No restrictions on output array data types.
Data Type Functions
astype
-
Add note stating that, when casting from a complex data type to a real data type stating, the imaginary component is discarded and casting to integral data types is unspecified and thus implementation-dependent.
- Discarding the imaginary component follows C and is done in NumPy.
- Could also disallow/omit/not explicitly support casting from complex to real and require users to explicitly call
real
if they want to discard the imaginary component.
-
status: wip
broadcast_arrays
- No changes necessary.
broadcast_to
- No changes necessary.
can_cast
- No changes necessary. Follows promotion rules for complex numbers.
finfo
- No changes necessary.
iinfo
- No changes necessary.
result_type
- No changes necessary. Follows promotion rules for complex numbers.
Data Types
-
Add
complex64
andcomplex128
following precedent where the numeric suffix specifies the number of bits.- The real and imaginary components should be IEEE 754 floating-point numbers.
-
status: implemented
Default Data Types
-
The default complex number data type is dependent on the default floating-point data type. If the latter is
float32
, the default complex data type must becomplex64
.- PyTorch already does this.
-
status: implemented
Data Type Categories
-
Add complex data types to list of numeric data types.
-
Add "Real Data Types" category which should be equal to the current list of numeric data types.
-
Add "Complex Data Types" category which only includes the complex number data types.
-
status: implemented
Element-wise Functions
abs
- Add support by returning the complex magnitude.
- Add complex number special cases: same as
hypot(creal(z), cimag(z))
.
acos
- Add complex number support.
- Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/cacos
-
[-inf,-1]
,[1,inf]
-
- Python branch cuts: https://docs.python.org/3/library/cmath.html#cmath.acos
- same as C
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.arccos.html
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/cacos
acosh
- Add complex number support.
- Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/cacosh
-
[-inf,1]
-
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.arccosh.html
- same as C
- Python branch cuts: https://docs.python.org/3/library/cmath.html#cmath.acosh
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/cacosh
add
- Add complex number support. Complex number addition is well-defined.
- Some care should be given to infinities and nans.
asin
- Add complex number support.
- Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/casin
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.arcsin.html
- Python branch cuts: https://docs.python.org/3/library/cmath.html#cmath.asin
asinh
- Add complex number support.
- Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/casinh
-
[-inf*i, -i]
,[i, inf*i]
-
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.arcsinh.html
- same as C
- Python branch cuts: https://docs.python.org/3/library/cmath.html#cmath.asinh
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/casinh
atan
- Add complex number support.
- Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/catan
-
[-inf*i, -i]
,[i, inf*i]
-
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.arctan.html
- same as C
- Python branch cuts: https://docs.python.org/3/library/cmath.html#cmath.atan
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/catan
atan2
- No complex number support.
- No changes necessary as already limited to real number data types.
atanh
- Add complex number support.
- Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/catanh
-
[-inf, -1]
,[1,inf]
-
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.arctanh.html
- same as C
- Python branch cuts: https://docs.python.org/3/library/cmath.html#cmath.atanh
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/catanh
bitwise_and
- No changes. Complex dtypes are not allowed.
bitwise_left_shift
- No changes. Complex dtypes are not allowed.
bitwise_invert
- No changes. Complex dtypes are not allowed.
bitwise_or
- No changes. Complex dtypes are not allowed.
bitwise_right_shift
- No changes. Complex dtypes are not allowed.
bitwise_xor
- No changes. Complex dtypes are not allowed.
ceil
-
~~Add complex number support. Independently round components (e.g., as in MATLAB).~~
- NumPy does not support in np.ceil.
- No complex number support due to implementation ambiguity.
cos
-
Add complex number support.
-
Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/ccos
- no branch cuts
- NumPy: https://numpy.org/doc/stable/reference/generated/numpy.cos.html
- same as C
- Python: https://docs.python.org/3/library/cmath.html#cmath.cos
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/ccos
-
status: wip
cosh
-
Add complex number support.
-
Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/ccosh
- no branch cuts
- NumPy: https://numpy.org/doc/stable/reference/generated/numpy.cosh.html
- same as C
- Python: https://docs.python.org/3/library/cmath.html#cmath.cosh
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/ccosh
-
status: wip
divide
- Add complex number support.
- Define special cases.
equal
- Add complex number support.
- Care should be taken with infinities and nans.
exp
-
Add complex number support.
-
Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/cexp
- no branch cuts
- NumPy: https://numpy.org/doc/stable/reference/generated/numpy.exp.html
- same as C
- Python: https://docs.python.org/3/library/cmath.html#cmath.exp
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/cexp
-
status: wip
expm1
-
Add complex number support.
-
Define special cases.
- should follow
exp
- should follow
-
status: wip
floor
-
~~Add complex number support. Independently round components (e.g., as in MATLAB).~~
- NumPy does not support in np.floor.
- No complex number support due to implementation ambiguity.
floor_divide
- No complex number support.
- No changes necessary as already limited to real number data types.
greater
- Add support for complex numbers by ordering lexicographic order. Similar to sorting functions.
greater_equal
- Same as
greater
.
isfinite
- Add support for complex numbers.
- Care should be taken for infinities and nans.
isinf
- Add support for complex numbers.
- Care should be taken for infinities and nans.
- how to classify a complex number as infinite?
isnan
- Add support for complex numbers.
- Care should be taken for infinities and nans.
- how to classify a complex number as NaN?
less
- Same as
greater
.
less_equal
- Same as
less
.
log
-
Add complex number support.
-
Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/clog
- same as NumPy
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.log.html
-
[-inf, 0]
-
- Python branch cuts: https://docs.python.org/3/library/cmath.html#cmath.log
- same as NumPy
- C: https://en.cppreference.com/w/c/numeric/complex/clog
log1p
-
Add complex number support.
-
Define special cases.
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.log1p.html
- should follow similarly to
log
log2
-
Add complex number support.
-
Define special cases.
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.log2.html
- same as
log
- same as
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.log2.html
log10
-
Add complex number support.
-
Define special cases.
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.log10.html
- same as
log
- same as
- Python branch cuts: https://docs.python.org/3/library/cmath.html#cmath.log10
- same as NumPy
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.log10.html
logaddexp
- No complex number support. This function is mainly useful for stats.
- No changes necessary as already limited to real-valued data types.
logical_and
- No changes necessary. Input arrays expected to have boolean data type.
logical_not
- No changes necessary. Input arrays expected to have boolean data type.
logical_or
- No changes necessary. Input arrays expected to have boolean data type.
logical_xor
- No changes necessary. Input arrays expected to have boolean data type.
multiply
- Add complex number support. Complex number multiplication is well-defined.
- Define special cases. Care should be taken with regard to infinities and nans.
negative
-
Add complex number support. No real changes necessary, as relies on
multiply
. -
status: implemented
not_equal
- Add complex number support.
- Care should be taken with infinities and nans.
positive
-
Add complex number support. No changes necessary, as this is effectively the identity function.
-
status: implemented
pow
-
Add complex number support.
-
Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/cpow
- branch cut for the first parameter along the negative real axis
- NumPy: https://numpy.org/doc/stable/reference/generated/numpy.power.html
- Python supports, but not exposed via cmath.
- pinning down exact behavior could be tricky (see Python issue 15996)
- C: https://en.cppreference.com/w/c/numeric/complex/cpow
remainder
- No complex number support.
- No changes necessary as already limited to real-valued data types.
round
-
Add complex number support. Independently round components (e.g., as in MATLAB and NumPy).
-
status: wip
sign
-
Add complex number support.
-
Define special cases.
- NumPy: https://numpy.org/doc/stable/reference/generated/numpy.sign.html
- NumPy uses
x / sqrt(x*x)
; could also usex/|x|
- see comment: https://github.com/data-apis/array-api/issues/153#issuecomment-869059671
- NumPy uses
- NumPy: https://numpy.org/doc/stable/reference/generated/numpy.sign.html
sin
-
Add complex number support.
-
Define special cases. No branch cuts.
- C: https://en.cppreference.com/w/c/numeric/complex/csin
- no branch cuts
- NumPy: https://numpy.org/doc/stable/reference/generated/numpy.sin.html
- same as C
- Python: https://docs.python.org/3/library/cmath.html#cmath.sin
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/csin
-
status: wip
sinh
-
Add complex number support.
-
Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/csinh
- no branch cuts
- NumPy: https://numpy.org/doc/stable/reference/generated/numpy.sinh.html
- same as C
- Python: https://docs.python.org/3/library/cmath.html#cmath.sinh
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/csinh
-
status: wip
square
-
Add complex number support. Complex number multiplication is well-defined.
- question whether to add
abs2
: https://github.com/data-apis/array-api/issues/153#issuecomment-896339259
- question whether to add
sqrt
-
Add complex number support.
-
Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/csqrt
- branch cut along the negative real axis
[-inf,0)
- branch cut along the negative real axis
- NumPy branch cuts: https://numpy.org/doc/stable/reference/generated/numpy.sqrt.html
- same as C
- Python branch cuts: https://docs.python.org/3/library/cmath.html#cmath.sqrt
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/csqrt
-
status: wip
subtract
- Add complex number support. Complex number addition is well-defined.
tan
-
Add complex number support.
-
Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/ctan
- no branch cuts
- NumPy: https://numpy.org/doc/stable/reference/generated/numpy.tan.html
- same as C
- Python: https://docs.python.org/3/library/cmath.html#cmath.tan
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/ctan
-
status: wip
tanh
-
Add complex number support.
-
Define special cases.
- C: https://en.cppreference.com/w/c/numeric/complex/ctanh
- no branch cuts
- NumPy: https://numpy.org/doc/stable/reference/generated/numpy.tanh.html
- same as C
- Python branch cuts: https://docs.python.org/3/library/cmath.html#cmath.tanh
- same as C
- C: https://en.cppreference.com/w/c/numeric/complex/ctanh
-
status: wip
trunc
-
~~Add complex number support. Independently truncate components (e.g., as in MATLAB).~~
- NumPy does not support in np.trunc.
- No complex number support due to implementation ambiguity.
Linear Algebra Functions
matmul
- No changes necessary.
x1
andx2
are allowed to be any numeric data type. Complex number multiplication and addition is well-defined.
matrix_transpose
- No changes necessary.
tensordot
- No changes necessary.
x1
andx2
are allowed to be any numeric data type. Complex number multiplication and addition is well-defined.
vecdot
-
Implementations should conjugate when computing the complex dot product:
x^H y
Manipulation Functions
concat
- No changes necessary.
expand_dims
- No changes necessary.
flip
- No changes necessary.
permute_dims
- No changes necessary.
reshape
- No changes necessary.
roll
- No changes necessary.
squeeze
- No changes necessary.
stack
- No changes necessary.
Searching Functions
argmax
-
Require that complex numbers be ordered in lexicographic order?
- While not compatible with multiplication, lexicographic order is a common total order. E.g., NumPy uses lexicographic order.
- Another alternative is by magnitude and then phase angle. E.g., MATLAB supports this order.
- The specification could default to lexicographic, and revisit the ordering relation in the future with possible support for optionally specifying an alternative comparison method.
- Some care needs to be given to sorting when one or more components is NaN.
argmin
- Same as
argmax
.
nonzero
-
No changes necessary. A nonzero complex number is a complex number whose real or imaginary component is nonzero.
-
status: implemented
where
- No changes necessary.
Set Functions
unique_all
-
Specify complex number value equality.
-
In C, "In order to support the one-infinity model of complex number arithmetic, C regards any complex value with at least one infinite part as an infinity even if its other part is a NaN..."
-
NumPy (and Python) does not follow C. If one component is NaN, even if the other component is infinite,
np.equal
is false.- For Python's cmath, a complex number can be both infinite (
cmath.isinf
) and NaN (cmath.isnan
).
- For Python's cmath, a complex number can be both infinite (
unique_counts
- Same as
unique_all
.
unique_inverse
- Same as
unique_all
.
unique_values
- Same as
unique_all
.
Sorting Functions
argsort
-
Require that complex numbers be ordered in lexicographic order?
- While not compatible with multiplication, lexicographic order is a common total order. E.g., NumPy uses lexicographic order.
- Another alternative is by magnitude and then phase angle. E.g., MATLAB supports this order.
- The specification could default to lexicographic, and revisit the ordering relation in the future with possible support for optionally specifying an alternative comparison method.
- Some care needs to be given to sorting when one or more components is NaN.
sort
- Same as
argsort
Statistical Functions
max
- Similar to elsewhere, require that complex numbers be ordered in lexicographic order?
mean
- No complex number support.
- Change to only real number data types.
min
- Same as
max
.
prod
- Add support for complex numbers. Complex number multiplication is well-defined.
- The
dtype
option needs to be updated to accommodate complex numbers.
std
- No complex number support.
- Change to only real number data types.
sum
- Add support for complex numbers. Complex number addition is well-defined.
- The
dtype
option needs to be updated to accommodate complex numbers.
var
- No complex number support.
- Change to only real number data types.
Type Promotion
Rules
- Add complex number type promotion table.
- Related complex number type promotion to floating-point number type promotion (e.g.,
f8
andc16
)
Mixing arrays with Python Scalars
- Add
complex
Python scalars. - Update note concerning mixed "kind" operations.
Utility Functions
all
-
No changes necessary. The value
0+0j
should evaluate toFalse
. If a complex number has a non-zero component, the value should evaluate toTrue
. -
status: implemented
any
-
Same as
all
. -
status: implemented
Linear Algebra Extension
cholesky
-
Add complex number support.
-
Require that each square matrix must be Hermitian.
-
status: implemented
cross
- Add complex number support.
determinant
- Add complex number support.
diagonal
- No changes necessary.
eigh
- Add complex number support.
- Require that each matrix must be Hermitian and the returned Q unitary.
- May want to make the dtype of the eigevalues unconditionally real.
eigvalsh
- Add complex number support.
- Require that each matrix must be Hermitian.
inv
- Add complex number support.
matmul
- Support added in main namespace.
matrix_norm
- Add complex number support.
matrix_power
- Add complex number support.
matrix_rank
- Add complex number support.
matrix_transpose
- No changes necessary.
outer
- No changes necessary.
pinv
- Add complex number support.
qr
- Add complex number support.
slogdet
- Add complex number support.
solve
- Add complex number support.
svd
- Add coplex number support.
- Require that each U, Vh must be uniary.
- May want to require that the returned dtype of
S
is unconditionally real.
svdvals
- Add complex number support.
trace
- No changes necessary.
vecdot
- Changes made in main namespace.
vector_norm
- Add complex number support.
Would we want a data type function for complex dtypes i.e. cinfo()
? Currently NumPy and PyTorch don't have a dedicate method, but complex dtypes can fallback on finfo()
.
Interestingly there was a little discussion of a torch.cinfo()
at https://github.com/pytorch/pytorch/issues/35954#issuecomment-620223025
- branch cut policy?
I don't know why I missed it... CuPy uses Thrust (and it's likely being used by many other projects that depends on CUDA; IIUC the implementation was ported to libcudacxx) so it's worth mentioning.
Thrust's complex math funcs were based on FreeBSD's. In each FreeBSD man page the branch cut is clearly documented so it's a good starting point. However, as Thrust didn't document it clearly (I asked internally and was told to inspect the source directly, which is challenging given my lack of bandwidth) and it's unclear to me if the Thrust impl was a one-to-one translation of FreeBSD's since I don't have access to the FreeBSD source code, I'd proceed with caution.
Tracking changes to existing APIs within the array API specification:
- [x] Data Types:
complex64
andcomplex128
- [x] Type Promotion Rules (including mixing arrays with Python scalars)
- [x] asarray
- [x] eye
- [x] full
- [x] full_like
- [x] linspace
- [x] meshgrid
- [x] ones
- [x] ones_like
- [x] astype
- [x] abs/__abs__
- [x] acos
- [x] acosh
- [x] add/__add__
- [x] asin
- [x] asinh
- [x] atan
- [x] atanh
- [x] cos
- [x] cosh
- [x] divide/__truediv__
- [x] equal/__equal__
- [x] exp
- [x] expm1
- [x] greater/__gt__
- [x] greater_equal/__ge__
- [x] isfinite
- [x] isinf
- [x] isnan
- [x] less/__lt__
- [x] less_equal/__le__
- [x] log
- [x] log1p
- [x] log2
- [x] log10
- [x] multiply/__mul__
- [x] negative/__neg__
- [x] not_equal/__ne__
- [x] positive/__pos__
- [x] pow/__pow__
- [x] round
- [x] sign
- [x] sin
- [x] sinh
- [x] square
- [x] sqrt
- [x] subtract/__sub__
- [x] tan
- [x] tanh
- [x] matmul/__matmul__
- [x] tensordot
- [x] vecdot
- [x] argmax
- [x] argmin
- [x] nonzero
- [x] unique_all
- [x] unique_counts
- [x] unique_inverse
- [x] unique_values
- [x] argsort
- [x] sort
- [x] max
- [x] min
- [x] prod
- [x] sum
- [x] all
- [x] any
- [x] cholesky
- [x] cross
- [x] det
- [x] eigh
- [x] eigvalsh
- [x] inv
- [x] matrix_norm
- [x] matrix_power
- [x] matrix_rank
- [x] outer
- [x] pinv
- [x] qr
- [x] slogdet
- [x] solve
- [x] svd
- [x] svdvals
- [x] trace
- [x] vector_norm
New APIs
At this point, PRs have been submitted for all existing APIs for which we currently expect complex number support.
All PRs have been merged - adding complex number support to the API is complete. Thanks a lot to everyone who contributed - and in particular @kgryte for all the spec writing.