Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
101 changes: 81 additions & 20 deletions include/xtensor/misc/xpad.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
#ifndef XTENSOR_PAD_HPP
#define XTENSOR_PAD_HPP

#include <cstddef>
#include <limits>
#include <stdexcept>
#include <type_traits>
#include <vector>

#include "../containers/xarray.hpp"
#include "../containers/xtensor.hpp"
#include "../views/xstrided_view.hpp"
Expand Down Expand Up @@ -47,15 +53,67 @@ namespace xt

namespace detail
{
template <class S>
inline std::size_t checked_count(S value, const char* error)
{
if constexpr (std::is_signed_v<S>)
{
if (value < S(0))
{
XTENSOR_THROW(std::runtime_error, error);
}
}
return static_cast<std::size_t>(value);
}

// pad_width is indexed by axis, and the modes other than constant read the
// padding back from the values of the array: without these checks, invalid
// widths read past the end of pad_width or return uninitialized values.
template <class S, class T>
inline bool check_pad_width(const std::vector<std::vector<S>>& pad_width, const T& shape)
inline void check_pad_width(const std::vector<std::vector<S>>& pad_width, const T& shape, pad_mode mode)
{
if (pad_width.size() != shape.size())
{
return false;
XTENSOR_THROW(std::runtime_error, "pad: pad_width must have one {before, after} pair per axis.");
}
for (std::size_t axis = 0; axis < shape.size(); ++axis)
{
if (pad_width[axis].size() != 2)
{
XTENSOR_THROW(
std::runtime_error,
"pad: pad_width must have one {before, after} pair per axis."
);
}
const char* negative = "pad: pad widths must not be negative.";
const std::size_t before = checked_count(pad_width[axis][0], negative);
const std::size_t after = checked_count(pad_width[axis][1], negative);
const std::size_t size = static_cast<std::size_t>(shape[axis]);
std::size_t max_width = (std::numeric_limits<std::size_t>::max)();
switch (mode)
{
case pad_mode::constant:
break;
case pad_mode::symmetric:
case pad_mode::wrap:
case pad_mode::periodic:
max_width = size;
break;
case pad_mode::reflect:
max_width = size == 0 ? 0 : size - 1;
break;
case pad_mode::edge:
max_width = size == 0 ? 0 : max_width;
break;
}
if (before > max_width || after > max_width)
{
XTENSOR_THROW(
std::runtime_error,
"pad: pad width too large for the size of the axis in this mode."
);
}
}

return true;
}
}

Expand All @@ -77,7 +135,7 @@ namespace xt
pad_mode mode = pad_mode::constant,
V constant_value = 0)
{
XTENSOR_ASSERT(detail::check_pad_width(pad_width, e.shape()));
detail::check_pad_width(pad_width, e.shape(), mode);

using size_type = typename std::decay_t<E>::size_type;
using return_type = temporary_type_t<E>;
Expand Down Expand Up @@ -122,19 +180,16 @@ namespace xt

if (mode == pad_mode::wrap || mode == pad_mode::periodic)
{
XTENSOR_ASSERT(nb <= e.shape(axis));
svs[axis] = xt::range(e.shape(axis), nb + e.shape(axis));
xt::strided_view(out, svt) = xt::strided_view(out, svs);
}
else if (mode == pad_mode::symmetric)
{
XTENSOR_ASSERT(nb <= e.shape(axis));
svs[axis] = xt::range(2 * nb - 1, nb - 1, -1);
xt::strided_view(out, svt) = xt::strided_view(out, svs);
}
else if (mode == pad_mode::reflect)
{
XTENSOR_ASSERT(nb <= e.shape(axis) - 1);
svs[axis] = xt::range(2 * nb, nb, -1);
xt::strided_view(out, svt) = xt::strided_view(out, svs);
}
Expand All @@ -154,13 +209,11 @@ namespace xt

if (mode == pad_mode::wrap || mode == pad_mode::periodic)
{
XTENSOR_ASSERT(ne <= e.shape(axis));
svs[axis] = xt::range(nb, nb + ne);
xt::strided_view(out, svt) = xt::strided_view(out, svs);
}
else if (mode == pad_mode::symmetric)
{
XTENSOR_ASSERT(ne <= e.shape(axis));
if (ne == nb + e.shape(axis))
{
svs[axis] = xt::range(nb + e.shape(axis) - 1, _, -1);
Expand All @@ -173,7 +226,6 @@ namespace xt
}
else if (mode == pad_mode::reflect)
{
XTENSOR_ASSERT(ne <= e.shape(axis) - 1);
if (ne == nb + e.shape(axis) - 1)
{
svs[axis] = xt::range(nb + e.shape(axis) - 2, _, -1);
Expand Down Expand Up @@ -249,26 +301,31 @@ namespace xt

using return_type = temporary_type_t<E>;

XTENSOR_ASSERT(e.shape().size() == reps.size());
const size_type dim = e.dimension();
if (reps.size() != dim)
{
XTENSOR_THROW(std::runtime_error, "tile: reps must have one value per axis.");
}

using new_shape_type = typename return_type::shape_type;
auto new_shape = xtl::make_sequence<new_shape_type>(e.shape().size());
auto new_shape = xtl::make_sequence<new_shape_type>(dim);

xt::xstrided_slice_vector sv(reps.size());
xt::xstrided_slice_vector sv(dim);

for (size_type axis = 0; axis < reps.size(); ++axis)
for (size_type axis = 0; axis < dim; ++axis)
{
new_shape[axis] = e.shape(axis) * reps[axis];
const size_type n = checked_count(reps[axis], "tile: reps must not be negative.");
new_shape[axis] = e.shape(axis) * n;
sv[axis] = xt::range(0, e.shape(axis));
}
return_type out(new_shape);

xt::strided_view(out, sv) = e;

xt::xstrided_slice_vector svs(e.shape().size(), xt::all());
xt::xstrided_slice_vector svt(e.shape().size(), xt::all());
xt::xstrided_slice_vector svs(dim, xt::all());
xt::xstrided_slice_vector svt(dim, xt::all());

for (size_type axis = 0; axis < e.shape().size(); ++axis)
for (size_type axis = 0; axis < dim; ++axis)
{
for (size_type i = 1; i < static_cast<size_type>(reps[axis]); ++i)
{
Expand Down Expand Up @@ -314,7 +371,11 @@ namespace xt
template <class E, class S = typename std::decay_t<E>::size_type, XTL_REQUIRES(xtl::is_integral<S>)>
inline auto tile(E&& e, S reps)
{
std::vector<S> tw(e.shape().size(), static_cast<S>(1));
if (e.dimension() == 0)
{
XTENSOR_THROW(std::runtime_error, "tile: a 0-D expression has no first axis to repeat along.");
}
std::vector<S> tw(e.dimension(), static_cast<S>(1));
tw[0] = reps;
return detail::tile(std::forward<E>(e), tw);
}
Expand Down
51 changes: 51 additions & 0 deletions test/test_xpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,4 +361,55 @@ namespace xt
};
EXPECT_EQ(m, expected);
}

TEST(xpad, invalid_pad_width)
{
xt::xtensor<int, 2> a = {{1, 2, 3}, {4, 5, 6}};
using widths = std::vector<std::vector<std::size_t>>;

XT_EXPECT_THROW(xt::pad(a, widths{{1, 1}}), std::runtime_error);
XT_EXPECT_THROW(xt::pad(a, widths{{1, 1}, {1, 1}, {1, 1}}), std::runtime_error);
XT_EXPECT_THROW(xt::pad(a, widths{{1}, {1}}), std::runtime_error);
XT_EXPECT_THROW(xt::pad(a, std::vector<std::size_t>{1}), std::runtime_error);
XT_EXPECT_THROW(xt::pad(a, std::vector<std::vector<int>>{{1, -1}, {0, 0}}), std::runtime_error);
XT_EXPECT_THROW(xt::pad(a, -1), std::runtime_error);

// The other modes read the padding from the array, which limits the widths.
XT_EXPECT_THROW(xt::pad(a, widths{{3, 0}, {0, 0}}, xt::pad_mode::wrap), std::runtime_error);
XT_EXPECT_THROW(xt::pad(a, widths{{0, 3}, {0, 0}}, xt::pad_mode::periodic), std::runtime_error);
XT_EXPECT_THROW(xt::pad(a, widths{{0, 0}, {4, 0}}, xt::pad_mode::symmetric), std::runtime_error);
XT_EXPECT_THROW(xt::pad(a, widths{{0, 0}, {0, 3}}, xt::pad_mode::reflect), std::runtime_error);

xt::xtensor<int, 2> empty = xt::xtensor<int, 2>::from_shape({0, 2});
XT_EXPECT_THROW(xt::pad(empty, widths{{1, 0}, {0, 0}}, xt::pad_mode::edge), std::runtime_error);
XT_EXPECT_THROW(xt::pad(empty, widths{{1, 0}, {0, 0}}, xt::pad_mode::reflect), std::runtime_error);
EXPECT_EQ(
xt::pad(empty, widths{{1, 0}, {0, 0}}, xt::pad_mode::constant, 7),
(xt::xtensor<int, 2>{{7, 7}})
);
}

TEST(xpad, largest_pad_width)
{
xt::xtensor<int, 1> a = {1, 2, 3};

EXPECT_EQ(xt::pad(a, 3, xt::pad_mode::wrap), (xt::xtensor<int, 1>{1, 2, 3, 1, 2, 3, 1, 2, 3}));
EXPECT_EQ(xt::pad(a, 3, xt::pad_mode::symmetric), (xt::xtensor<int, 1>{3, 2, 1, 1, 2, 3, 3, 2, 1}));
EXPECT_EQ(xt::pad(a, 2, xt::pad_mode::reflect), (xt::xtensor<int, 1>{3, 2, 1, 2, 3, 2, 1}));
EXPECT_EQ(xt::pad(a, 4, xt::pad_mode::edge), (xt::xtensor<int, 1>{1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3}));
}

TEST(xpad, tile_invalid_reps)
{
xt::xtensor<int, 1> a = {1, 2, 3};
xt::xarray<int> b = {{1, 2}, {3, 4}};
xt::xarray<int> scalar = 5;

XT_EXPECT_THROW(xt::tile(a, {2u, 3u}), std::runtime_error);
XT_EXPECT_THROW(xt::tile(b, {2u, 3u, 4u}), std::runtime_error);
XT_EXPECT_THROW(xt::tile(b, std::vector<std::size_t>{2}), std::runtime_error);
XT_EXPECT_THROW(xt::tile(b, std::vector<int>{2, -1}), std::runtime_error);
XT_EXPECT_THROW(xt::tile(scalar, 3u), std::runtime_error);
XT_EXPECT_THROW(xt::tile(a, -2), std::runtime_error);
}
}
Loading