Skip to content

Commit 821ea73

Browse files
committed
Respect custom strides in scalar compound assignment
1 parent 8e14e7b commit 821ea73

2 files changed

Lines changed: 208 additions & 4 deletions

File tree

‎include/xtensor/core/xassign.hpp‎

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,43 @@ namespace xt
252252

253253
namespace detail
254254
{
255+
template <class E>
256+
inline bool is_dense_storage(const E& e)
257+
{
258+
if constexpr (strided_expression<E>)
259+
{
260+
const auto& shape = e.shape();
261+
const auto& strides = e.strides();
262+
using size_type = typename E::size_type;
263+
size_type remaining = e.size();
264+
size_type stride = 1;
265+
// Custom strides can cover dense storage in any axis order.
266+
while (remaining > 1)
267+
{
268+
bool found = false;
269+
for (std::size_t axis = 0; axis < shape.size(); ++axis)
270+
{
271+
if (shape[axis] > 1 && std::cmp_equal(strides[axis], stride))
272+
{
273+
remaining /= shape[axis];
274+
stride *= shape[axis];
275+
found = true;
276+
break;
277+
}
278+
}
279+
if (!found)
280+
{
281+
return false;
282+
}
283+
}
284+
return true;
285+
}
286+
else
287+
{
288+
return false;
289+
}
290+
}
291+
255292
template <class E1, class E2>
256293
constexpr bool linear_static_layout()
257294
{
@@ -528,11 +565,22 @@ namespace xt
528565
{
529566
E1& d = e1.derived_cast();
530567
using size_type = typename E1::size_type;
531-
auto dst = d.storage().begin();
532-
for (size_type i = d.size(); i > 0; --i)
568+
auto assign = [&](auto dst)
533569
{
534-
*dst = f(*dst, e2);
535-
++dst;
570+
const size_type n = d.size();
571+
for (size_type i = 0; i < n; ++i)
572+
{
573+
*dst = f(*dst, e2);
574+
++dst;
575+
}
576+
};
577+
if (E1::contiguous_layout || d.is_contiguous() || detail::is_dense_storage(d))
578+
{
579+
assign(d.storage().begin());
580+
}
581+
else
582+
{
583+
assign(d.begin());
536584
}
537585
}
538586

‎test/test_xadaptor_semantic.cpp‎

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
* The full license is in the file LICENSE, distributed with this software. *
88
****************************************************************************/
99

10+
#include "xtensor/containers/xadapt.hpp"
11+
#include "xtensor/core/xnoalias.hpp"
12+
1013
#include "test_common.hpp"
1114
#include "test_xsemantic.hpp"
1215

@@ -47,6 +50,159 @@ namespace xt
4750

4851
#define ADAPTOR_SEMANTIC_TYPES xarray_dynamic, xtensor_dynamic
4952

53+
TEST_CASE_TEMPLATE("scalar_assignment_with_strides", TypeParam, ADAPTOR_SEMANTIC_TYPES)
54+
{
55+
using shape_type = typename TypeParam::shape_type;
56+
using strides_type = typename TypeParam::strides_type;
57+
shape_type shape = {2, 1, 3};
58+
strides_type strides;
59+
std::vector<int> data;
60+
std::vector<int> expected;
61+
std::vector<int> doubled;
62+
63+
SUBCASE("padded rows")
64+
{
65+
strides = {4, 0, 1};
66+
data = {2, 4, 6, -99, 8, 10, 12, -99};
67+
expected = {4, 6, 8, -99, 10, 12, 14, -99};
68+
doubled = {4, 8, 12, -99, 16, 20, 24, -99};
69+
}
70+
SUBCASE("padded columns")
71+
{
72+
strides = {1, 0, 3};
73+
data = {2, 4, -99, 6, 8, -99, 10, 12, -99};
74+
expected = {4, 6, -99, 8, 10, -99, 12, 14, -99};
75+
doubled = {4, 8, -99, 12, 16, -99, 20, 24, -99};
76+
}
77+
78+
auto original = data;
79+
auto a = adapt(data.data(), 6, no_ownership(), shape, strides);
80+
a += 2;
81+
EXPECT_EQ(data, expected);
82+
noalias(a) -= 2;
83+
EXPECT_EQ(data, original);
84+
a *= 2;
85+
EXPECT_EQ(data, doubled);
86+
noalias(a) /= 2;
87+
EXPECT_EQ(data, original);
88+
EXPECT_EQ(a.shape(), shape);
89+
EXPECT_EQ(a.strides(), strides);
90+
}
91+
92+
TEST_CASE_TEMPLATE("scalar_assignment_empty_strided", TypeParam, ADAPTOR_SEMANTIC_TYPES)
93+
{
94+
using shape_type = typename TypeParam::shape_type;
95+
using strides_type = typename TypeParam::strides_type;
96+
std::vector<int> data = {-99};
97+
shape_type shape = {2, 0, 3};
98+
strides_type strides = {4, 0, 1};
99+
auto a = adapt(data.data(), 0, no_ownership(), shape, strides);
100+
a += 2;
101+
EXPECT_EQ(data, std::vector<int>{-99});
102+
}
103+
104+
TEST_CASE_TEMPLATE("scalar_assignment_aliased_scalar", TypeParam, ADAPTOR_SEMANTIC_TYPES)
105+
{
106+
using shape_type = typename TypeParam::shape_type;
107+
using strides_type = typename TypeParam::strides_type;
108+
shape_type shape = {2, 1, 2};
109+
strides_type strides;
110+
std::vector<int> data;
111+
std::vector<int> expected;
112+
std::size_t scalar_index = 0;
113+
std::size_t size = 4;
114+
115+
SUBCASE("contiguous")
116+
{
117+
strides = {2, 0, 1};
118+
data = {1, 2, 3, 4};
119+
expected = {2, 4, 5, 6};
120+
}
121+
SUBCASE("contiguous columns")
122+
{
123+
strides = {1, 0, 2};
124+
data = {1, 2, 3, 4};
125+
expected = {3, 4, 7, 8};
126+
scalar_index = 1;
127+
}
128+
SUBCASE("contiguous axis permutation")
129+
{
130+
shape = {2, 3, 2};
131+
strides = {1, 4, 2};
132+
data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
133+
expected = {8, 9, 10, 11, 12, 13, 14, 22, 23, 24, 25, 26};
134+
scalar_index = 6;
135+
size = 12;
136+
}
137+
SUBCASE("padded")
138+
{
139+
strides = {3, 0, 1};
140+
data = {1, 2, -99, 3, 4, -99};
141+
expected = {2, 4, -99, 5, 6, -99};
142+
}
143+
144+
auto a = adapt(data.data(), size, no_ownership(), shape, strides);
145+
a += data[scalar_index];
146+
EXPECT_EQ(data, expected);
147+
}
148+
149+
TEST_CASE_TEMPLATE("scalar_assignment_noncopyable_functor", TypeParam, ADAPTOR_SEMANTIC_TYPES)
150+
{
151+
struct counted_add
152+
{
153+
counted_add() = default;
154+
counted_add(const counted_add&) = delete;
155+
156+
int operator()(int lhs, int rhs)
157+
{
158+
return lhs + rhs + ++calls;
159+
}
160+
161+
int calls = 0;
162+
};
163+
164+
using shape_type = typename TypeParam::shape_type;
165+
using strides_type = typename TypeParam::strides_type;
166+
shape_type shape = {2, 1, 2};
167+
strides_type strides = {3, 0, 1};
168+
std::vector<int> data = {1, 2, -99, 3, 4, -99};
169+
std::vector<int> expected;
170+
std::size_t size = 0;
171+
172+
SUBCASE("padded")
173+
{
174+
size = 4;
175+
expected = {4, 6, -99, 8, 10, -99};
176+
}
177+
SUBCASE("contiguous columns")
178+
{
179+
strides = {1, 0, 2};
180+
size = 4;
181+
data = {1, 2, 3, 4};
182+
expected = {4, 6, 8, 10};
183+
}
184+
SUBCASE("contiguous axis permutation")
185+
{
186+
shape = {2, 3, 2};
187+
strides = {1, 4, 2};
188+
size = 12;
189+
data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
190+
expected = {4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26};
191+
}
192+
SUBCASE("empty")
193+
{
194+
shape = {2, 0, 2};
195+
size = 0;
196+
expected = data;
197+
}
198+
199+
auto a = adapt(data.data(), size, no_ownership(), shape, strides);
200+
counted_add f;
201+
scalar_computed_assign(a, 2, f);
202+
EXPECT_EQ(data, expected);
203+
EXPECT_EQ(f.calls, static_cast<int>(size));
204+
}
205+
50206
TEST_CASE_TEMPLATE("xsimd_info", TypeParam, ADAPTOR_SEMANTIC_TYPES)
51207
{
52208
#if defined(XTENSOR_USE_XSIMD)

0 commit comments

Comments
 (0)