Data storage#
The usage of prepended namespace specifiers oneapi::math::dft
is
omitted below for conciseness.
The data storage convention observed by a descriptor
object depends on
whether it is a real or complex descriptor and, in case of complex descriptors,
on the configuration value associated with configuration parameter
config_param::COMPLEX_STORAGE
.
Complex descriptors#
For a complex descriptor, the configuration parameter
config_param::COMPLEX_STORAGE
specifies how the entries of the complex data
sequences it consumes and produces are stored. If that configuration parameter is
associated with a configuration value config_value::COMPLEX_COMPLEX
(default
behavior), those entries are accessed and stored as std::complex<float>
(resp. std::complex<double>
) elements of a single data container
(device-accessible USM allocation or sycl::buffer
object) if the
descriptor
object is a single-precision (resp. double-precision) descriptor.
If the configuration value config_value::REAL_REAL
is used instead, the real
and imaginary parts of those entries are accessed and stored as float
(resp.
double
) elements of two separate, non-overlapping data containers
(device-accessible USM allocations or sycl::buffer
objects) if the
descriptor
object is a single-precision (resp. double-precision) descriptor.
These two behaviors are further specified and illustrated below.
config_value::COMPLEX_COMPLEX
for config_param::COMPLEX_STORAGE
For complex descriptors with parameter config_param::COMPLEX_STORAGE
set to
config_value::COMPLEX_COMPLEX
, each of forward- and backward-domain data
sequences must belong to a single data container (device-accessible USM
allocation or sycl::buffer
object). Any relevant entry
\(\left(\cdot\right)^{m}_{k_1, k_2,\dots ,k_d}\) is accessed/stored from/in
a data container provided at compute time at the index value expressed in eq.
(1) (see the page dedicated to the
configuration of data layout)
of that data container, whose elementary data type is (possibly implicitly
re-interpreted as) std::complex<float>
(resp. std::complex<double>
) for
single-precision (resp. double-precision) descriptors.
The same unique data container is to be used for forward- and backward-domain
data sequences for in-place transforms (for descriptor
objects with
configuration value config_value::INPLACE
for configuration parameter
config_param::PLACEMENT
). Two separate data containers sharing no common
elements are to be used for out-of-place transforms (for descriptor
objects
with configuration value config_value::NOT_INPLACE
for configuration
parameter config_param::PLACEMENT
).
The following snippet illustrates the usage of config_value::COMPLEX_COMPLEX
for configuration parameter config_param::COMPLEX_STORAGE
, in the
context of in-place, single-precision (fp32) calculations of \(M\)
three-dimensional \(n_1 \times n_2 \times n_3\) complex transforms, using
identical (default) strides and distances in forward and backward domains, with
USM allocations.
namespace dft = oneapi::math::dft;
dft::descriptor<dft::precision::SINGLE, dft::domain::COMPLEX> desc({n1, n2, n3});
std::vector<std::int64_t> strides({0, n2*n3, n3, 1});
std::int64_t dist = n1*n2*n3;
std::complex<float> *Z = (std::complex<float> *) malloc_device(2*sizeof(float)*n1*n2*n3*M, queue);
desc.set_value(dft::config_param::FWD_STRIDES, strides);
desc.set_value(dft::config_param::BWD_STRIDES, strides);
desc.set_value(dft::config_param::FWD_DISTANCE, dist);
desc.set_value(dft::config_param::BWD_DISTANCE, dist);
desc.set_value(dft::config_param::NUMBER_OF_TRANSFORMS, M);
desc.set_value(dft::config_param::COMPLEX_STORAGE, dft::config_value::COMPLEX_COMPLEX);
desc.commit(queue);
// initialize forward-domain data such that entry {m;k1,k2,k3}
// = Z[ strides[0] + k1*strides[1] + k2*strides[2] + k3*strides[3] + m*dist ]
auto ev = compute_forward(desc, Z); // complex-to-complex in-place DFT
// Upon completion of ev, in backward domain: entry {m;k1,k2,k3}
// = Z[ strides[0] + k1*strides[1] + k2*strides[2] + k3*strides[3] + m*dist ]
config_value::REAL_REAL
for config_param::COMPLEX_STORAGE
For complex descriptors with parameter config_param::COMPLEX_STORAGE
set to
config_value::REAL_REAL
, forward- and backward-domain data sequences are
read/stored from/in two different, non-overlapping data containers
(device-accessible USM allocations or sycl::buffer
objects) encapsulating
the real and imaginary parts of the relevant entries separately. The real and
imaginary parts of any relevant complex entry
\(\left(\cdot\right)^{m}_{k_1, k_2,\dots ,k_d}\) are both stored at the
index value expressed in eq. (1) (see the page dedicated to
the configuration of data layout) of
their respective data containers, whose elementary data type is (possibly
implicitly re-interpreted as) float
(resp. double
) for single-precision
(resp. double-precision) descriptors.
The same two data containers are to be used for real and imaginary parts of
forward- and backward-domain data sequences for in-place transforms (for
descriptor
objects with configuration value config_value::INPLACE
for
configuration parameter config_param::PLACEMENT
). Four separate data
containers sharing no common elements are to be used for out-of-place transforms
(for descriptor
objects with configuration value config_value::NOT_INPLACE
for configuration parameter config_param::PLACEMENT
).
The following snippet illustrates the usage of config_value::REAL_REAL
set for configuration parameter config_param::COMPLEX_STORAGE
, in the
context of in-place, single-precision (fp32) calculation of \(M\)
three-dimensional \(n_1 \times n_2 \times n_3\) complex transforms, using
identical (default) strides and distances in forward and backward domains, with
USM allocations.
namespace dft = oneapi::math::dft;
dft::descriptor<dft::precision::SINGLE, dft::domain::COMPLEX> desc({n1, n2, n3});
std::vector<std::int64_t> strides({0, n2*n3, n3, 1});
std::int64_t dist = n1*n2*n3;
float *ZR = (float *) malloc_device(sizeof(float)*n1*n2*n3*M, queue); // data container for real parts
float *ZI = (float *) malloc_device(sizeof(float)*n1*n2*n3*M, queue); // data container for imaginary parts
desc.set_value(dft::config_param::FWD_STRIDES, strides);
desc.set_value(dft::config_param::BWD_STRIDES, strides);
desc.set_value(dft::config_param::FWD_DISTANCE, dist);
desc.set_value(dft::config_param::BWD_DISTANCE, dist);
desc.set_value(dft::config_param::NUMBER_OF_TRANSFORMS, M);
desc.set_value(dft::config_param::COMPLEX_STORAGE, dft::config_value::REAL_REAL);
desc.commit(queue);
// initialize forward-domain data such that the real part of entry {m;k1,k2,k3}
// = ZR[ strides[0] + k1*strides[1] + k2*strides[2] + k3*strides[3] + m*dist ]
// and the imaginary part of entry {m;k1,k2,k3}
// = ZI[ strides[0] + k1*strides[1] + k2*strides[2] + k3*strides[3] + m*dist ]
auto ev = compute_forward<decltype(desc), float>(desc, ZR, ZI); // complex-to-complex in-place DFT
// Upon completion of ev, in backward domain: the real part of entry {m;k1,k2,k3}
// = ZR[ strides[0] + k1*strides[1] + k2*strides[2] + k3*strides[3] + m*dist ]
// and the imaginary part of entry {m;k1,k2,k3}
// = ZI[ strides[0] + k1*strides[1] + k2*strides[2] + k3*strides[3] + m*dist ]
Real descriptors#
Real descriptors observe only one type of data storage. Any relevant (real)
entry \(\left(\cdot\right)^{m}_{k_1, k_2,\dots ,k_d}\) of a data sequence
in forward domain is accessed and stored as a float
(resp. double
)
element of a single data container (device-accessible USM allocation or
sycl::buffer
object) if the descriptor
object is a single-precision
(resp. double-precision) descriptor. Any relevant (complex) entry
\(\left(\cdot\right)^{m}_{k_1, k_2,\dots ,k_d}\) of a data sequence in
backward domain is accessed and stored as a std::complex<float>
(resp.
std::complex<double>
) element of a single data container (device-accessible
USM allocation or sycl::buffer
object) if the
descriptor
object is a single-precision (resp. double-precision) descriptor.
The following snippet illustrates the usage of a real, single-precision descriptor (and the corresponding data storage) for the in-place, single-precision (fp32), calculation of \(M\) three-dimensional \(n_1 \times n_2 \times n_3\) real transforms, using default strides in forward and backward domains, with USM allocations.
namespace dft = oneapi::math::dft;
dft::descriptor<dft::precision::SINGLE, dft::domain::REAL> desc({n1, n2, n3});
// Note: integer divisions here below
std::vector<std::int64_t> fwd_strides({0, 2*n2*(n3/2 + 1), 2*(n3/2 + 1), 1});
std::vector<std::int64_t> bwd_strides({0, n2*(n3/2 + 1), (n3/2 + 1), 1});
std::int64_t fwd_dist = 2*n1*n2*(n3/2 + 1);
std::int64_t bwd_dist = n1*n2*(n3/2 + 1);
float *data = (float *) malloc_device(sizeof(float)*fwd_dist*M, queue); // data container
desc.set_value(dft::config_param::FWD_STRIDES, fwd_strides);
desc.set_value(dft::config_param::BWD_STRIDES, bwd_strides);
desc.set_value(dft::config_param::FWD_DISTANCE, fwd_dist);
desc.set_value(dft::config_param::BWD_DISTANCE, bwd_dist);
desc.set_value(dft::config_param::NUMBER_OF_TRANSFORMS, M);
desc.commit(queue);
// initialize forward-domain data such that real entry {m;k1,k2,k3}
// = data[ fwd_strides[0] + k1*fwd_strides[1] + k2*fwd_strides[2] + k3*fwd_strides[3] + m*fwd_dist ]
auto ev = compute_forward(desc, data); // real-to-complex in-place DFT
// In backward domain, the implicitly-assumed type is complex so, consider
// std::complex<float>* complex_data = static_cast<std::complex<float>*>(data);
// upon completion of ev, the backward-domain entry {m;k1,k2,k3} is
// = complex_data[ bwd_strides[0] + k1*bwd_strides[1] + k2*bwd_strides[2] + k3*bwd_strides[3] + m*bwd_dist ]
// for 0 <= k3 <= n3/2.
// Note: if n3/2 < k3 < n3, entry {m;k1,k2,k3} is not stored explicitly
// since it is equal to std::conj(entry {m;n1-k1,n2-k2,n3-k3})
Parent topic DFT-related scoped enumeration types