Skip to content
Closed
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
399 changes: 399 additions & 0 deletions sycl/doc/extensions/proposed/sycl_ext_oneapi_complex_marray.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,399 @@
= sycl_ext_oneapi_complex_marray

:source-highlighter: coderay
:coderay-linenums-mode: table

// This section needs to be after the document title.
:doctype: book
:toc2:
:toc: left
:encoding: utf-8
:lang: en
:dpcpp: pass:[DPC++]

// Set the default source code type in this document to C++,
// for syntax highlighting purposes. This is needed because
// docbook uses c++ and html5 uses cpp.
:language: {basebackend@docbook:c++:cpp}


== Notice

[%hardbreaks]
Copyright (C) 2022-2022 Codeplay Ltd. All rights reserved.

Khronos(R) is a registered trademark and SYCL(TM) and SPIR(TM) are trademarks
of The Khronos Group Inc. OpenCL(TM) is a trademark of Apple Inc. used by
permission by Khronos.


== Contact

To report problems with this extension, please open a new issue at:

https://github.com/intel/llvm/issues


== Dependencies

This extension is written against the SYCL 2020 revision 5 specification. All
references below to the "core SYCL specification" or to section numbers in the
SYCL specification refer to that revision.

The complex marray extension builds on the `sycl::ext::oneapi::complex` class,
therefore this extension is dependent on
link:sycl_ext_oneapi_complex.asciidoc[sycl_ext_oneapi_complex].


== Status

This is a proposed extension specification, intended to gather community
feedback. Interfaces defined in this specification may not be implemented yet
or may be in a preliminary state. The specification itself may also change in
incompatible ways before it is finalized. *Shipping software products should
not rely on APIs defined in this specification.*

[NOTE]
====
This extension is not currently implemented in {dpcpp}.
====


== Overview

{dpcpp} has support for `sycl::ext::oneapi::complex` this allows for the
addition of new complex math features. This proposes the instantiation of
`marray` to add support for complex numbers to be stored in arrays. The
proposal includes overloading the existing math functions to support complex
marrays and adding new free functions to simplify accessing, setting marray
values, and construct complex marrays.

== Specification

=== Feature test macro

This extension provides a feature-test macro as described in the core SYCL
specification. An implementation supporting this extension must predefine the
macro `SYCL_EXT_ONEAPI_COMPLEX_MARRAY` to one of the values defined in the table
below. Applications can test for the existence of this macro to determine if
the implementation supports this feature, or applications can test the macro's
value to determine which of the extension's features the implementation
supports.

[%header,cols="1,5"]
|===
|Value
|Description

|1
|Initial version of this extension.
|===

=== Marray Complex Class Specialization

The `marray` class is extended for the `sycl::ext::oneapi::complex` class.
The user interface of the `marray<sycl::ext::oneapi::complex, {N}>`
class is not changed compared to the SYCL-2020 generic `marray` interface.

The `marray` complex instantiation is trivially copyable, and the type
trait `is_device_copyable` should resolve to `std::true_type`.

As the `marray` class is not specialized in this proposal. The `marray`
definition used within this proposal assumes that any operator the `marray`
class defines is only implemented if the marray's value type also
implements the operator. So, for example, `marray<float, N>` does not
implement the modulus operator as float does not support it. As the
`marray` class is not changed in this proposal, it is not redefined below.

The `make_complex_marray` free function is added to construct complex
marrays from real and imaginary components. Additionally, the free
functions `get_real` and `get_imag` are added to access the real and
imaginary components of the `marray` class without modifying the existing
`marray` interface. The usage of free functions does cause a deviation
from the `std::complex` interface. However, it does reduce this extensions
impact on the `marray` interface.

```C++
namespace sycl {
namespace ext {
namespace oneapi {

// Make_complex_marray
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we really add multiple make_complex_marray overloads just for complex? Wouldn't it make more sense to add a general purpose generator expression mechanism? E.g. like the one of std::simd in parallelism TSv2.
If we add template<class G> marray(G&& gen) instead of writing make_complex_marray(ar, ai) (where ar, ai are marrays) one could write marray([&](auto i){return complex{ar[i], ai[i]};}. Isn't that simple enough?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applications using arrays of complex values will likely want an interface that performs similar operations to the std::complex interface. I.e. being able to construct a complex value with a real and imaginary vector component. While marray([&](auto i){return complex{ar[i], ai[i]};} would work, it is much more verbose than make_complex_marray(ar, ai), for something that is a simple element-wise constructor and it would be a significant departure from the simple std::complex constructors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applications using arrays of complex values will likely want an interface that performs similar operations to the std::complex interface.

I can see that. The question is why should that need be met by SYCL (initially as extension)?I don't think we want to add all math functionality applications reasonable want to have and slowly end up with an API size similar to libraries like Eigen. To keep the API reasonable small we should only have in SYCL what can be considered fundamental building blocks and/or can't be implemented (efficiently) as library. And I don't think make_complex_marray qualifies as such.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So regarding your point, I think that if these functions were made member functions it may be less intrusive and awkward in the spec. I have updated the proposal to specialize marray and made these functions members of the class. Let me know your thoughts on this approach and if this addresses some of your concerns.


template <class T, std::size_t NumElements>
marray<sycl::ext::oneapi::complex<T>, NumElements> make_complex_marray(const marray<T, NumElements> &real, const marray<T, NumElements> &imag);

template <class T, std::size_t NumElements>
marray<sycl::ext::oneapi::complex<T>, NumElements> make_complex_marray(const marray<T, NumElements> &real, const T &imag);

template <class T, std::size_t NumElements>
marray<sycl::ext::oneapi::complex<T>, NumElements> make_complex_marray(const T &real, const marray<T, NumElements> &imag);

template <class T, std::size_t NumElements, std::size_t... I>
marray<sycl::ext::oneapi::complex<T>, NumElements> make_complex_marray(const marray<T, NumElements> &real, const marray<T, NumElements> &imag, std::integer_sequence<std::size_t, I...> int_seq);

template <class T, std::size_t NumElements, std::size_t... I>
marray<sycl::ext::oneapi::complex<T>, NumElements> make_complex_marray(const marray<sycl::ext::oneapi::complex<T>, NumElements> &cmplx, std::integer_sequence<std::size_t, I...> int_seq);

// Get

// return marray of component
template <class T, std::size_t NumElements>
marray<T, NumElements> get_real(const marray<sycl::ext::oneapi::complex<T>, NumElements> &input);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for creating, do we need this? If we can't instead add e.g. map/transform, then get_real(a) would become map(a, [](auto e){return e.real();}.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My opinion is the same as above. This is more verbose and when dealing with vector math it would look much more messy.


template <class T, std::size_t NumElements>
marray<T, NumElements> get_imag(const marray<sycl::ext::oneapi::complex<T>, NumElements> &input);

// return sequence of elements of component
template <class T, std::size_t NumElements, std::size_t... I>
marray<T, int_seq::size()> get_real(const marray<sycl::ext::oneapi::complex<T>, NumElements> &input, std::integer_sequence<std::size_t, I...> int_seq);

template <class T, std::size_t NumElements, std::size_t... I>
marray<T, int_seq::size()> get_imag(const marray<sycl::ext::oneapi::complex<T>, NumElements> &input, std::integer_sequence<std::size_t, I...> int_seq);

// Set

template <class T, std::size_t NumElements>
void set_real(marray<sycl::ext::oneapi::complex<T>, NumElements> &input, const marray<T, NumElements> &values);

template <class T, std::size_t NumElements>
void set_imag(marray<sycl::ext::oneapi::complex<T>, NumElements> &input, const marray<T, NumElements> &values);

template <class T, std::size_t NumElements>
void set_real(marray<sycl::ext::oneapi::complex<T>, NumElements> &input, const T value);

template <class T, std::size_t NumElements>
void set_imag(marray<sycl::ext::oneapi::complex<T>, NumElements> &input, const T value);

} // namespace oneapi
} // namespace ext
} // namespace sycl
```

The class `sycl::ext::oneapi::marray<sycl::ext::oneapi::complex<T>, N>`,
has instantiations of `T`; `float`, `double`, and `sycl::half` declared.

```C++
namespace sycl {
namespace ext {
namespace oneapi {

template <std::size_t NumElements>
class marray<sycl::ext::oneapi::complex<double>, NumElements>;

template <std::size_t NumElements>
class marray<sycl::ext::oneapi::complex<float>, NumElements>;

template <std::size_t NumElements>
class marray<sycl::ext::oneapi::complex<sycl::half>, NumElements>;

} // namespace oneapi
} // namespace ext
} // namespace sycl
```

The generic type `mgencomplex` is defined as types
`marray<sycl::ext::oneapi::complex<double>, {N}>`,
`marray<sycl::ext::oneapi::complex<float>, {N}>`,
`marray<sycl::ext::oneapi::complex<sycl::half>, {N}>`.

The table below shows the free functions operating on the `marray` complex
class. No table is provided for the `marray` class as no changes to it are
proposed.

[%header,cols="5,5"]
|===
|Function
|Description

|`mgencomplex make_complex_marray(const mgenfloat& x, const mgenfloat& y);`
|Constructs a marray of complex numbers with real values in marray x, and the imaginary values in marray y.
|`mgencomplex make_complex_marray(const mgenfloat& x, const genfloat& y);`
|Constructs a marray of complex numbers with real values in marray x, and the imaginary value y.
|`mgencomplex make_complex_marray(const genfloat& x, const mgenfloat& y);`
|Constructs a marray of complex numbers with real value x, and the imaginary values in marray y.
|`mgencomplex make_complex_marray(const mgenfloat& x, const mgenfloat& y, std::integer_sequence int_seq);`
|Constructs a marray of complex numbers from real values in marray x, and the imaginary values in marray y. Each element should be constructed from the corresponding index within `int_seq` and the returned marray size should be the same as the `int_seq` size.
|`mgencomplex make_complex_marray(const mgencomplex& x, std::integer_sequence int_seq);`
|Constructs a marray of complex numbers from a complex marray x. Each element should be constructed from the corresponding index within `int_seq` and the returned marray size should be the same as the `int_seq` size.
|`mgenfloat get_real(const mgencomplex& x);`
|Returns an marray of the real components for marray of complex numbers.
|`mgenfloat get_imag(const mgencomplex& x);`
|Returns an marray of the imaginary components for marray of complex numbers.
|`mgenfloat get_real(const mgencomplex& x, std::integer_sequence int_seq);`
|Returns a sequence of real components of the complex number x. Each element should be constructed from the corresponding index within `int_seq` and the returned marray size should be the same as the `int_seq` size.
|`mgenfloat get_imag(const mgencomplex& x, std::integer_sequence int_seq);`
|Returns a sequence of imaginary components of the complex number x. Each element should be constructed from the corresponding index within `int_seq` and the returned marray size should be the same as the `int_seq` size.
|`void set_real(mgencomplex& x, const mgenfloat& y);`
|Set each element of the real components in x to the corresponding element in y.
|`void set_imag(mgencomplex& x, const mgenfloat& y);`
|Set each element of the imaginary components in x to the corresponding element in y.
|`void set_real(mgencomplex& x, const genfloat& y);`
|Set each element of the real components in x to the decimal number y.
|`void set_imag(mgencomplex& x, const genfloat& y);`
|Set each element of the imaginary components in x to the decimal number y.
|===

=== Mathematical operations

This proposal extends `sycl::ext::oneapi` namespace math functions to accept
`mgencomplex` for the SYCL math functions, `abs`, `acos`, `asin`, `atan`,
`acosh`, `asinh`, `atanh`, `arg`, `conj`, `cos`, `cosh`, `exp`, `log`, `log10`,
`norm`, `polar`, `pow`, `proj`, `sin`, `sinh`, `sqrt`, `tan`, and `tanh`.
For math functions with two parameters marray-scalar and scalar-marray overloads
are added.

The functions execute as-if the math operation is performed elementwise across the
marray. The math function between each element should follow the C++
standard for handling NaN's and Inf values.

The proposal additionally adds overloads between marrays and scalar inputs.
Overloads with marray's and scalar parameters should execute the operation
across the marray while keeping the scalar value constant.

```C++
namespace sycl {
namespace ext {
namespace oneapi {

mgenfloat abs(const mgencomplex& x);

mgencomplex acos(const mgencomplex& x);

mgencomplex asin(const mgencomplex& x);

mgencomplex atan(const mgencomplex& x);

mgencomplex acosh(const mgencomplex& x);

mgencomplex asinh(const mgencomplex& x);

mgencomplex atanh(const mgencomplex& x);

mgenfloat arg(const mgencomplex& x);

mgencomplex conj(const mgencomplex& x);

mgencomplex cos(const mgencomplex& x);

mgencomplex cosh(const mgencomplex& x);

mgencomplex exp(const mgencomplex& x);

mgencomplex log(const mgencomplex& x);

mgencomplex log10(const mgencomplex& x);

mgenfloat norm(const mgencomplex& x);

mgencomplex polar(const mgenfloat& rho, const mgenfloat& theta);
mgencomplex polar(const mgenfloat& rho, const genfloat& theta = 0);
mgencomplex polar(const genfloat& rho, const mgenfloat& theta);

mgencomplex pow(const mgencomplex& x, const mgenfloat& y);
mgencomplex pow(const mgencomplex& x, const genfloat& y);
mgencomplex pow(const gencomplex& x, const mgenfloat& y);

mgencomplex pow(const mgencomplex& x, const mgencomplex& y);
mgencomplex pow(const mgencomplex& x, const gencomplex& y);
mgencomplex pow(const gencomplex& x, const mgencomplex& y);

mgencomplex pow(const mgenfloat& x, const mgencomplex& y);
mgencomplex pow(const mgenfloat& x, const gencomplex& y);
mgencomplex pow(const genfloat& x, const mgencomplex& y);

mgencomplex proj(const mgencomplex& x);
mgencomplex proj(const mgenfloat& x);

mgencomplex sin(const mgencomplex& x);

mgencomplex sinh(const mgencomplex& x);

mgencomplex sqrt(const mgencomplex& x);

mgencomplex tan(const mgencomplex& x);

mgencomplex tanh(const mgencomplex& x);

} // namespace oneapi
} // namespace ext
} // namespace sycl
```

The table below shows each function along with a description of its
mathematical operation.

[%header,cols="5,5"]
|===
|Function
|Description

|`mgenfloat abs(const mgencomplex& x)`
|Compute the magnitude for each complex number in marray x.
|`mgencomplex acos(const mgencomplex& x)`
|Compute the inverse cosine for each complex number in marray x.
|`mgencomplex asin(const mgencomplex& x)`
|Compute the inverse sine for each complex number in marray x.
|`mgencomplex atan(const mgencomplex& x)`
|Compute the inverse tangent for each complex number in marray x.
|`mgencomplex acosh(const mgencomplex& x)`
|Compute the inverse hyperbolic cosine for each complex number in marray x.
|`mgencomplex asinh(const mgencomplex& x)`
|Compute the inverse hyperbolic sine for each complex number in marray x.
|`mgencomplex atanh(const mgencomplex& x)`
|Compute the inverse hyperbolic tangent for each complex number in marray x.
|`mgenfloat arg(const mgencomplex& x);`
|Compute phase angle in radians for each complex number in marray x.
|`mgencomplex conj(const mgencomplex& x)`
|Compute the conjugate for each complex number in marray x.
|`mgencomplex cos(const mgencomplex& x)`
|Compute the cosine for each complex number in marray x.
|`mgencomplex cosh(const mgencomplex& x)`
|Compute the hyperbolic cosine for each complex number in marray x.
|`mgencomplex exp(const mgencomplex& x)`
|Compute the base-e exponent for each complex number in marray x.
|`mgencomplex log(const mgencomplex& x)`
|Compute the natural log for each complex number in marray x.
|`mgencomplex log10(const mgencomplex& x)`
|Compute the base-10 log for each complex number in marray x.
|`mgenfloat norm(const mgencomplex& x)`
|Compute the squared magnitude for each complex number in marray x.
|`mgencomplex polar(const mgenfloat& rho, const mgenfloat& theta)`
|Construct an marray, elementwise, of complex numbers from each polar coordinate in marray rho and marray theta.
|`mgencomplex polar(const mgenfloat& rho, const genfloat& theta = 0)`
|Construct an marray, elementwise, of complex numbers from each polar coordinate in marray rho and scalar theta.
|`mgencomplex polar(const genfloat& rho, const mgenfloat& theta)`
|Construct an marray, elementwise, of complex numbers from each polar coordinate in scalar rho and marray theta.
|`mgencomplex pow(const mgencomplex& x, const mgenfloat& y)`
|Raise each complex element in x to the power of the corresponding decimal element in y.
|`mgencomplex pow(const mgencomplex& x, const genfloat& y)`
|Raise each complex element in x to the power of the decimal number y.
|`mgencomplex pow(const gencomplex& x, const mgenfloat& y)`
|Raise complex number x to the power of each decimal element in y.
|`mgencomplex pow(const mgencomplex& x, const mgencomplex& y)`
|Raise each complex element in x to the power of the corresponding complex element in y.
|`mgencomplex pow(const mgencomplex& x, const gencomplex& y)`
|Raise each complex element in x to the power of the complex number y.
|`mgencomplex pow(const gencomplex& x, const mgencomplex& y)`
|Raise complex number x to the power of each complex element in y.
|`mgencomplex pow(const mgenfloat& x, const mgencomplex& y)`
|Raise each decimal element in x to the power of the corresponding complex element in y.
|`mgencomplex pow(const mgenfloat& x, const gencomplex& y)`
|Raise each decimal element in x to the power of the complex number y.
|`mgencomplex pow(const genfloat& x, const mgencomplex& y)`
|Raise decimal number x to the power of each complex element in y.
|`mgencomplex proj(const mgencomplex& x)`
|Compute the projection for each complex number in marray x.
|`mgencomplex proj(const mgenfloat& x)`
|Compute the projection for each real number in marray x.
|`mgencomplex sin(const mgencomplex& x)`
|Compute the sine for each complex number in marray x.
|`mgencomplex sinh(const mgencomplex& x)`
|Compute the hyperbolic sine for each complex number in marray x.
|`mgencomplex sqrt(const mgencomplex& x)`
|Compute the square root for each complex number in marray x.
|`mgencomplex tan(const mgencomplex& x)`
|Compute the tangent for each complex number in marray x.
|`mgencomplex tanh(const mgencomplex& x)`
|Compute the hyperbolic tangent for each complex number in marray x.
|===