Skip to content

Latest commit

 

History

History
214 lines (147 loc) · 6.5 KB

File metadata and controls

214 lines (147 loc) · 6.5 KB

Common pitfalls

xarray initialization

xt::xarray<double> a({1, 3, 4, 2});

does not initialize a 4D-array, but a 1D-array containing the values 1, 3, 4, and 2. It is strictly equivalent to

xt::xarray<double> a = {1, 3, 4, 2};

To initialize a 4D-array with the given shape, use the :cpp:func:`xt::xarray::from_shape` static method:

auto a = xt::xarray<double>::from_shape({1, 3, 4, 2});

The confusion often comes from the way :cpp:type:`xt::xtensor` can be initialized:

xt::xtensor<double, 4> a = {1, 3, 4, 2};

In this case, a 4D-tensor with shape (1, 3, 4, 2) is initialized.

Intermediate result

Consider the following function:

template <class C>
auto func(const C& c)
{
    return (1 - func_tmp(c)) / (1 + func_tmp(c));
}

where func_tmp is another unary function accepting an xtensor expression. You may be tempted to simplify it a bit:

template <class C>
auto func(const C& c)
{
    auto tmp = func_tmp(c);
    return (1 - tmp) / (1 + tmp);
}

Unfortunately, you introduced a bug; indeed, expressions in xtensor are not evaluated immediately, they capture their arguments by reference or copy depending on their nature, for future evaluation. Since tmp is an lvalue, it is captured by reference in the last statement; when the function returns, tmp is destroyed, leading to a dangling reference in the returned expression.

Replacing auto tmp with xt::xarray<double> tmp does not change anything, tmp is still an lvalue and thus captured by reference.

Warning

This issue is particularly subtle with reducer functions like :cpp:func:`xt::amax`, :cpp:func:`xt::sum`, etc. Consider the following function:

template <typename T>
xt::xtensor<T, 2> logSoftmax(const xt::xtensor<T, 2> &matrix)
{
    xt::xtensor<T, 2> maxVals = xt::amax(matrix, {1}, xt::keep_dims);
    auto shifted = matrix - maxVals;
    auto expVals = xt::exp(shifted);
    auto sumExp = xt::sum(expVals, {1}, xt::keep_dims);
    return shifted - xt::log(sumExp);
}

This function may produce incorrect results or crash, especially in optimized builds. The issue is that shifted, expVals, and sumExp are all lazy expressions that hold references to local variables. When the function returns, these local variables are destroyed, and the returned expression contains dangling references.

The fix is to evaluate reducer results and the returned expression explicitly. Element-wise lazy expressions (like shifted and expVals) are safe to leave as auto, but reducer results (like sumExp) must be materialized before being used in a subsequent element-wise expression:

template <typename T>
xt::xtensor<T, 2> logSoftmax(const xt::xtensor<T, 2> &matrix)
{
    xt::xtensor<T, 2> maxVals = xt::amax(matrix, {1}, xt::keep_dims);
    auto shifted = matrix - maxVals;
    auto expVals = xt::exp(shifted);
    xt::xtensor<T, 2> sumExp = xt::sum(expVals, {1}, xt::keep_dims);
    return xt::xtensor<T, 2>(shifted - xt::log(sumExp));
}

Random numbers not consistent

Using a random number function from xtensor actually returns a lazy generator. That means, accessing the same element of a random number generator does not give the same random number if called twice.

auto gen = xt::random::rand<double>({10, 10});
auto a0 = gen(0, 0);
auto a1 = gen(0, 0);

// a0 != a1 !!!

You need to explicitly assign or eval a random number generator, like so:

xt::xarray<double> xr = xt::random::rand<double>({10, 10});
auto xr2 = xt::eval(xt::random::rand<double>({10, 10}));

// now xr(0, 0) == xr(0, 0) is true.

variance arguments

When :cpp:func:`xt::variance` is passed an expression and an integer parameter, this latter is not the axis along which the variance must be computed, but the degree of freedom:

xt::xtensor<double, 2> a = {{1., 2., 3.}, {4., 5., 6.}};
std::cout << xt::variance(a, 1) << std::endl;
// Outputs 3.5

If you want to specify an axis, you need to pass an initializer list:

xt::xtensor<double, 2> a = {{1., 2., 3.}, {4., 5., 6.}};
std::cout << xt::variance(a, {1}) << std::endl;
.. Outputs { 0.666667, 0.666667 }

fixed_shape on Windows

Builder functions such as :cpp:func:`xt::empty` or :cpp:func:`xt::ones` accept an initializer list as argument. If the elements of this list do not have the same type, a curious compilation error may occur on Windows:

size_t N = 10ull;
xt::xarray<int> ages = xt::empty<int>({N, 4ul});

// error: cannot convert argument 1 from 'initializer list'
// to 'const xt::fixed_shape<> &'

To avoid this compiler bug (for which we don't have a workaround), ensure all the elements in the initializer list have the same type.

Alignment of fixed-size members

Note

If you are using C++ >= 17 you should not have to worry about this.

When building with xsimd (see :ref:`external-dependencies`), if you define a structure having members of fixed-size xtensor types, you must ensure that the buffers properly aligned. For this you can use the macro XTENSOR_FIXED_ALIGN available in xtensor/core/xtensor_config.hpp. Consider the following example:

template <typename T>
class alignas(XTENSOR_FIXED_ALIGN) Foo
{
public:

    using allocator_type = std::conditional_t<XTENSOR_FIXED_ALIGN != 0,
                                              xt_simd::aligned_allocator<T, XTENSOR_FIXED_ALIGN>,
                                              std::allocator<T>>;

    Foo(T fac) : m_fac(fac)
    {
        m_bar.fill(fac);
    }

    auto get() const
    {
        return m_bar;
    }

private:

    xt::xtensor_fixed<T, xt::xshape<10, 10>> m_bar;
    T m_fac;
};

Whereby it is important to store the fixed-sized xtensor type (in this case xt::xtensor_fixed<T, xt::xshape<10, 10>>) as first member.