From 14b984ef246213d86991df239933224ce1d0e4e6 Mon Sep 17 00:00:00 2001 From: katlun-lgtm Date: Sat, 30 May 2026 15:52:12 -0400 Subject: [PATCH 1/6] feat: add reflect and symmetric padding modes to mx.pad Implements numpy.pad-compatible "reflect" and "symmetric" modes for mx.pad, matching numpy semantics for arbitrary pad sizes (the reflection repeats when the pad width exceeds the axis length). - mlx/ops.cpp: reflect_pad helper builds a per-axis triangle-wave index map and gathers with take; one take per padded axis. reflect uses period 2(n-1) and skips the edge; symmetric uses period 2n and repeats the edge. n==1 maps to 0. - python/src/ops.cpp: extend the pad mode Literal and docstring. - python/tests/test_ops.py: test_pad_reflect_symmetric covers in-bounds, multi-reflect, asymmetric per-axis, zero-width sides, and degenerate axes (n==1, n==2), checked against numpy.pad. - tests/ops_tests.cpp: reflect/symmetric CHECK cases incl. multi-reflect. --- mlx/ops.cpp | 51 ++++++++++++++++++++++++++++++++++++++++ python/src/ops.cpp | 4 +++- python/tests/test_ops.py | 32 +++++++++++++++++++++++++ tests/ops_tests.cpp | 35 +++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 1 deletion(-) diff --git a/mlx/ops.cpp b/mlx/ops.cpp index afc9ab489d..0d32c8c057 100644 --- a/mlx/ops.cpp +++ b/mlx/ops.cpp @@ -1460,6 +1460,53 @@ array tile( return reshape(x, std::move(final_shape), s); } +array reflect_pad( + const array& a, + const std::vector& axes, + const Shape& low_pad_size, + const Shape& high_pad_size, + bool include_edge, + StreamOrDevice s /* = {} */) { + // Reflect (include_edge=false) or symmetric (include_edge=true) padding. + // Matches numpy.pad for arbitrary pad sizes (the reflection repeats as needed). + // For an out-of-range coordinate r (relative to the original axis [0, n)), + // map it back into [0, n) by reflection: + // reflect -> period 2(n-1), edge NOT repeated + // symmetric -> period 2n, edge repeated + auto reflect_coord = [](int r, int n, bool include_edge) -> int { + if (n == 1) { + return 0; + } + if (include_edge) { + int period = 2 * n; + int m = ((r % period) + period) % period; + return m < n ? m : (2 * n - 1 - m); + } else { + int period = 2 * (n - 1); + int m = ((r % period) + period) % period; + return m < n ? m : (period - m); + } + }; + array out = a; + for (size_t i = 0; i < axes.size(); i++) { + int ax = axes[i]; + int L = low_pad_size[i]; + int H = high_pad_size[i]; + if (L == 0 && H == 0) { + continue; + } + int n = out.shape(ax); + int total = L + n + H; + std::vector idx_vec(total); + for (int p = 0; p < total; p++) { + idx_vec[p] = reflect_coord(p - L, n, include_edge); + } + array idx = array(idx_vec.begin(), {total}, int32); + out = take(out, idx, ax, s); + } + return out; +} + array edge_pad( const array& a, const std::vector& axes, @@ -1552,6 +1599,10 @@ array pad( {a, astype(pad_value, a.dtype(), s)}); } else if (mode == "edge") { return edge_pad(a, axes, low_pad_size, high_pad_size, out_shape, s); + } else if (mode == "reflect") { + return reflect_pad(a, axes, low_pad_size, high_pad_size, false, s); + } else if (mode == "symmetric") { + return reflect_pad(a, axes, low_pad_size, high_pad_size, true, s); } else { std::ostringstream msg; msg << "Invalid padding mode (" << mode << ") passed to pad"; diff --git a/python/src/ops.cpp b/python/src/ops.cpp index 0941793949..d959a69c97 100644 --- a/python/src/ops.cpp +++ b/python/src/ops.cpp @@ -3509,7 +3509,7 @@ void init_ops(nb::module_& m) { nb::kw_only(), "stream"_a = nb::none(), nb::sig( - "def pad(a: array, pad_width: int | tuple[int] | tuple[int, int] | list[tuple[int, int]], mode: Literal['constant', 'edge'] = 'constant', constant_values: scalar | array = 0, *, stream: StreamOrDevice = None) -> array"), + "def pad(a: array, pad_width: int | tuple[int] | tuple[int, int] | list[tuple[int, int]], mode: Literal['constant', 'edge', 'reflect', 'symmetric'] = 'constant', constant_values: scalar | array = 0, *, stream: StreamOrDevice = None) -> array"), R"pbdoc( Pad an array with a constant value @@ -3524,6 +3524,8 @@ void init_ops(nb::module_& m) { mode: Padding mode. One of the following strings: "constant" (default): Pads with a constant value. "edge": Pads with the edge values of array. + "reflect": Pads with the reflection of the array, without repeating the edge values. + "symmetric": Pads with the reflection of the array, repeating the edge values. constant_values (array or scalar, optional): Optional constant value to pad the edges of the array with. diff --git a/python/tests/test_ops.py b/python/tests/test_ops.py index a0f4fadaa3..b387040e50 100644 --- a/python/tests/test_ops.py +++ b/python/tests/test_ops.py @@ -2265,6 +2265,38 @@ def test_nan_to_num(self): out_mx = mx.nan_to_num(a, nan=0.0, posinf=1000, neginf=-1000) self.assertTrue(np.allclose(out_mx, out_np)) + def test_pad_reflect_symmetric(self): + # mx.pad reflect/symmetric must match numpy.pad exactly (it is a gather). + # Covers in-bounds, multi-reflect (pad larger than the axis), asymmetric + # per-axis widths, zero-width sides, and degenerate axes (n == 1, n == 2). + cases = [ + ((8,), [(2, 3)]), + ((8,), [(0, 4)]), + ((8,), [(3, 0)]), + ((8,), [(7, 8)]), + ((4,), [(10, 7)]), # multi-reflect + ((4,), [(20, 20)]), # multi-reflect, both sides + ((3,), [(9, 1)]), # multi-reflect + ((1,), [(3, 2)]), # degenerate axis + ((2,), [(5, 6)]), # smallest non-trivial, multi-reflect + ((5, 6), [(2, 3), (1, 2)]), + ((5, 6), [(9, 9), (11, 0)]), # both axes multi-reflect + ((3, 4, 5), [(1, 1), (0, 0), (2, 2)]), + ((3, 4, 5), [(4, 4), (0, 0), (7, 3)]), + ] + for mode in ("reflect", "symmetric"): + for shape, pw in cases: + a_npy = np.random.randn(*shape).astype(np.float32) + a_mlx = mx.array(a_npy) + b_npy = np.pad(a_npy, pw, mode=mode) + b_mlx = mx.pad(a_mlx, pw, mode=mode) + self.assertEqual(b_mlx.shape, tuple(b_npy.shape)) + self.assertTrue( + np.array_equal(np.array(b_mlx), b_npy), + msg=f"mismatch mode={mode} shape={shape} pad={pw}", + ) + self.assertEqual(b_mlx.dtype, mx.float32) + def test_as_strided(self): x_npy = np.random.randn(128).astype(np.float32) x_mlx = mx.array(x_npy) diff --git a/tests/ops_tests.cpp b/tests/ops_tests.cpp index 0dd9385e14..4b23919b74 100644 --- a/tests/ops_tests.cpp +++ b/tests/ops_tests.cpp @@ -2978,6 +2978,41 @@ TEST_CASE("test pad") { 0.0f}, {4, 4}); CHECK(array_equal(padded_x, expected).item()); + + // reflect padding (mirror without repeating the edge value) + x = array({1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, {5}); + CHECK(array_equal( + pad(x, {{2, 2}}, array(0.0f), "reflect"), + array( + {3.0f, 2.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 4.0f, 3.0f}, + {9})) + .item()); + CHECK(array_equal( + pad(x, {{0, 3}}, array(0.0f), "reflect"), + array({1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 4.0f, 3.0f, 2.0f}, {8})) + .item()); + + // symmetric padding (mirror repeating the edge value) + CHECK(array_equal( + pad(x, {{2, 2}}, array(0.0f), "symmetric"), + array( + {2.0f, 1.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 5.0f, 4.0f}, + {9})) + .item()); + CHECK(array_equal( + pad(x, {{3, 0}}, array(0.0f), "symmetric"), + array({3.0f, 2.0f, 1.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, {8})) + .item()); + + // multi-reflect: pad larger than the axis repeats the reflection (numpy parity) + x = array({1.0f, 2.0f, 3.0f}, {3}); + CHECK(array_equal( + pad(x, {{5, 5}}, array(0.0f), "reflect"), + array( + {2.0f, 1.0f, 2.0f, 3.0f, 2.0f, 1.0f, 2.0f, 3.0f, 2.0f, 1.0f, + 2.0f, 3.0f, 2.0f}, + {13})) + .item()); } TEST_CASE("test power") { From 639b7068c16027ebbdda3e930868bb6c70e9387a Mon Sep 17 00:00:00 2001 From: Cheng Date: Sat, 13 Jun 2026 09:11:38 +0900 Subject: [PATCH 2/6] Fix lint --- mlx/ops.cpp | 6 +++--- python/tests/test_ops.py | 12 ++++++------ tests/ops_tests.cpp | 26 +++++++++++++++++--------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/mlx/ops.cpp b/mlx/ops.cpp index 0d32c8c057..de7014516f 100644 --- a/mlx/ops.cpp +++ b/mlx/ops.cpp @@ -1468,9 +1468,9 @@ array reflect_pad( bool include_edge, StreamOrDevice s /* = {} */) { // Reflect (include_edge=false) or symmetric (include_edge=true) padding. - // Matches numpy.pad for arbitrary pad sizes (the reflection repeats as needed). - // For an out-of-range coordinate r (relative to the original axis [0, n)), - // map it back into [0, n) by reflection: + // Matches numpy.pad for arbitrary pad sizes (the reflection repeats as + // needed). For an out-of-range coordinate r (relative to the original axis + // [0, n)), map it back into [0, n) by reflection: // reflect -> period 2(n-1), edge NOT repeated // symmetric -> period 2n, edge repeated auto reflect_coord = [](int r, int n, bool include_edge) -> int { diff --git a/python/tests/test_ops.py b/python/tests/test_ops.py index b387040e50..f9d789a1a6 100644 --- a/python/tests/test_ops.py +++ b/python/tests/test_ops.py @@ -2274,13 +2274,13 @@ def test_pad_reflect_symmetric(self): ((8,), [(0, 4)]), ((8,), [(3, 0)]), ((8,), [(7, 8)]), - ((4,), [(10, 7)]), # multi-reflect - ((4,), [(20, 20)]), # multi-reflect, both sides - ((3,), [(9, 1)]), # multi-reflect - ((1,), [(3, 2)]), # degenerate axis - ((2,), [(5, 6)]), # smallest non-trivial, multi-reflect + ((4,), [(10, 7)]), # multi-reflect + ((4,), [(20, 20)]), # multi-reflect, both sides + ((3,), [(9, 1)]), # multi-reflect + ((1,), [(3, 2)]), # degenerate axis + ((2,), [(5, 6)]), # smallest non-trivial, multi-reflect ((5, 6), [(2, 3), (1, 2)]), - ((5, 6), [(9, 9), (11, 0)]), # both axes multi-reflect + ((5, 6), [(9, 9), (11, 0)]), # both axes multi-reflect ((3, 4, 5), [(1, 1), (0, 0), (2, 2)]), ((3, 4, 5), [(4, 4), (0, 0), (7, 3)]), ] diff --git a/tests/ops_tests.cpp b/tests/ops_tests.cpp index 4b23919b74..f7a2b8ab92 100644 --- a/tests/ops_tests.cpp +++ b/tests/ops_tests.cpp @@ -2983,9 +2983,7 @@ TEST_CASE("test pad") { x = array({1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, {5}); CHECK(array_equal( pad(x, {{2, 2}}, array(0.0f), "reflect"), - array( - {3.0f, 2.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 4.0f, 3.0f}, - {9})) + array({3.0f, 2.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 4.0f, 3.0f}, {9})) .item()); CHECK(array_equal( pad(x, {{0, 3}}, array(0.0f), "reflect"), @@ -2995,22 +2993,32 @@ TEST_CASE("test pad") { // symmetric padding (mirror repeating the edge value) CHECK(array_equal( pad(x, {{2, 2}}, array(0.0f), "symmetric"), - array( - {2.0f, 1.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 5.0f, 4.0f}, - {9})) + array({2.0f, 1.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 5.0f, 4.0f}, {9})) .item()); CHECK(array_equal( pad(x, {{3, 0}}, array(0.0f), "symmetric"), array({3.0f, 2.0f, 1.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, {8})) .item()); - // multi-reflect: pad larger than the axis repeats the reflection (numpy parity) + // multi-reflect: pad larger than the axis repeats the reflection (numpy + // parity) x = array({1.0f, 2.0f, 3.0f}, {3}); CHECK(array_equal( pad(x, {{5, 5}}, array(0.0f), "reflect"), array( - {2.0f, 1.0f, 2.0f, 3.0f, 2.0f, 1.0f, 2.0f, 3.0f, 2.0f, 1.0f, - 2.0f, 3.0f, 2.0f}, + {2.0f, + 1.0f, + 2.0f, + 3.0f, + 2.0f, + 1.0f, + 2.0f, + 3.0f, + 2.0f, + 1.0f, + 2.0f, + 3.0f, + 2.0f}, {13})) .item()); } From f2ef917c4ea14056f73885e2583e236b5ca80f78 Mon Sep 17 00:00:00 2001 From: katlun-lgtm Date: Fri, 12 Jun 2026 23:01:00 -0400 Subject: [PATCH 3/6] docs: add ACKNOWLEDGMENTS entry for reflect/symmetric pad modes --- ACKNOWLEDGMENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index 186908f09c..d83832be8f 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -20,6 +20,7 @@ MLX was developed with contributions from the following individuals: - Paul Paczuski: Improved stability of BCE loss calculation - Max-Heinrich Laves: Added `conv_transpose1d`, `conv_transpose2d`, and `conv_transpose3d` ops. - Gökdeniz Gülmez: Added the `Muon (MomentUm Orthogonalized by Newton-schulz)` optimizer, and the `ReLU²` activation function. +- katlun-lgtm: Added `reflect` and `symmetric` padding modes. From beec433e306cd4bb24fcb5f6db93db763dcbeb9b Mon Sep 17 00:00:00 2001 From: katlun-lgtm <264247399+katlun-lgtm@users.noreply.github.com> Date: Tue, 11 Aug 2026 07:33:21 -0400 Subject: [PATCH 4/6] perf: rewrite reflect/symmetric pad to avoid host index array + gather Follows edge_pad's pattern: place the input into a zero-filled output with slice_update, then extend each axis outward via slice+flip instead of building a host-side index array of output length and gathering. A pad width larger than the axis loops in tiles, re-slicing the data just written to continue the periodic reflect/symmetric pattern (matches numpy.pad, including multi-reflect). Addresses zcbenz's review on #3608: sub-ms on 5M/4M-element arrays for the common in-bounds case, vs. an O(output size) host loop + gather before. All 154 test_ops.py cases pass, including the existing multi-reflect coverage that exercises the new tiling loop. --- mlx/ops.cpp | 101 +++++++++++++++++++++++++++------------ python/tests/test_ops.py | 5 +- 2 files changed, 73 insertions(+), 33 deletions(-) diff --git a/mlx/ops.cpp b/mlx/ops.cpp index de7014516f..887aea6770 100644 --- a/mlx/ops.cpp +++ b/mlx/ops.cpp @@ -1465,46 +1465,85 @@ array reflect_pad( const std::vector& axes, const Shape& low_pad_size, const Shape& high_pad_size, + const Shape& out_shape, bool include_edge, StreamOrDevice s /* = {} */) { - // Reflect (include_edge=false) or symmetric (include_edge=true) padding. - // Matches numpy.pad for arbitrary pad sizes (the reflection repeats as - // needed). For an out-of-range coordinate r (relative to the original axis - // [0, n)), map it back into [0, n) by reflection: - // reflect -> period 2(n-1), edge NOT repeated - // symmetric -> period 2n, edge repeated - auto reflect_coord = [](int r, int n, bool include_edge) -> int { - if (n == 1) { - return 0; - } - if (include_edge) { - int period = 2 * n; - int m = ((r % period) + period) % period; - return m < n ? m : (2 * n - 1 - m); - } else { - int period = 2 * (n - 1); - int m = ((r % period) + period) % period; - return m < n ? m : (period - m); - } - }; - array out = a; + // Reflect (include_edge=false) or symmetric (include_edge=true) padding, + // built the same way as edge_pad: place the input into a zero-filled + // output with slice_update, then extend each axis outward by + // slicing-and-flipping already-filled data (no host-side index array). + // A pad width larger than the axis needs more than one tile; each + // iteration re-slices the data it just wrote, continuing the periodic + // reflect/symmetric pattern exactly like numpy.pad. + array out = zeros(out_shape, a.dtype(), s); + Shape starts(a.ndim(), 0); + auto stops = a.shape(); + for (size_t i = 0; i < axes.size(); i++) { + int ax = axes[i]; + starts[ax] = low_pad_size[i]; + stops[ax] += low_pad_size[i]; + } + array padded = slice_update(out, a, starts, stops, s); + for (size_t i = 0; i < axes.size(); i++) { int ax = axes[i]; + int n = a.shape(ax); int L = low_pad_size[i]; int H = high_pad_size[i]; if (L == 0 && H == 0) { continue; } - int n = out.shape(ax); - int total = L + n + H; - std::vector idx_vec(total); - for (int p = 0; p < total; p++) { - idx_vec[p] = reflect_coord(p - L, n, include_edge); + // reflect skips the edge value (period 2(n-1)); symmetric repeats it + // (period 2n). A single-element axis has nothing to reflect off of, + // so it just repeats, same as the edge case. + int offset = (!include_edge && n > 1) ? 1 : 0; + int tile = n - offset; + + if (L > 0) { + int filled_start = low_pad_size[i]; + int remaining = L; + while (remaining > 0) { + int chunk = std::min(remaining, tile); + Shape src_starts(a.ndim(), 0); + Shape src_stops = out_shape; + src_starts[ax] = filled_start + offset; + src_stops[ax] = filled_start + offset + chunk; + array piece = flip(slice(padded, src_starts, src_stops, s), ax, s); + + Shape dst_starts(a.ndim(), 0); + Shape dst_stops = out_shape; + dst_starts[ax] = filled_start - chunk; + dst_stops[ax] = filled_start; + padded = slice_update(padded, piece, dst_starts, dst_stops, s); + + filled_start -= chunk; + remaining -= chunk; + } + } + + if (H > 0) { + int filled_end = low_pad_size[i] + n; + int remaining = H; + while (remaining > 0) { + int chunk = std::min(remaining, tile); + Shape src_starts(a.ndim(), 0); + Shape src_stops = out_shape; + src_starts[ax] = filled_end - offset - chunk; + src_stops[ax] = filled_end - offset; + array piece = flip(slice(padded, src_starts, src_stops, s), ax, s); + + Shape dst_starts(a.ndim(), 0); + Shape dst_stops = out_shape; + dst_starts[ax] = filled_end; + dst_stops[ax] = filled_end + chunk; + padded = slice_update(padded, piece, dst_starts, dst_stops, s); + + filled_end += chunk; + remaining -= chunk; + } } - array idx = array(idx_vec.begin(), {total}, int32); - out = take(out, idx, ax, s); } - return out; + return padded; } array edge_pad( @@ -1600,9 +1639,9 @@ array pad( } else if (mode == "edge") { return edge_pad(a, axes, low_pad_size, high_pad_size, out_shape, s); } else if (mode == "reflect") { - return reflect_pad(a, axes, low_pad_size, high_pad_size, false, s); + return reflect_pad(a, axes, low_pad_size, high_pad_size, out_shape, false, s); } else if (mode == "symmetric") { - return reflect_pad(a, axes, low_pad_size, high_pad_size, true, s); + return reflect_pad(a, axes, low_pad_size, high_pad_size, out_shape, true, s); } else { std::ostringstream msg; msg << "Invalid padding mode (" << mode << ") passed to pad"; diff --git a/python/tests/test_ops.py b/python/tests/test_ops.py index f9d789a1a6..c71458bae4 100644 --- a/python/tests/test_ops.py +++ b/python/tests/test_ops.py @@ -2266,8 +2266,9 @@ def test_nan_to_num(self): self.assertTrue(np.allclose(out_mx, out_np)) def test_pad_reflect_symmetric(self): - # mx.pad reflect/symmetric must match numpy.pad exactly (it is a gather). - # Covers in-bounds, multi-reflect (pad larger than the axis), asymmetric + # mx.pad reflect/symmetric must match numpy.pad exactly. Covers + # in-bounds, multi-reflect (pad larger than the axis, exercising the + # tiling loop), asymmetric # per-axis widths, zero-width sides, and degenerate axes (n == 1, n == 2). cases = [ ((8,), [(2, 3)]), From 0f87956b7cc2a97dd4deb9e4932120535672177e Mon Sep 17 00:00:00 2001 From: katlun-lgtm <264247399+katlun-lgtm@users.noreply.github.com> Date: Tue, 11 Aug 2026 08:28:48 -0400 Subject: [PATCH 5/6] style: wrap reflect_pad call sites to satisfy clang-format CI's clang-format hook flagged the two dispatcher lines as too long. --- mlx/ops.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mlx/ops.cpp b/mlx/ops.cpp index 887aea6770..cdbef0434e 100644 --- a/mlx/ops.cpp +++ b/mlx/ops.cpp @@ -1639,9 +1639,11 @@ array pad( } else if (mode == "edge") { return edge_pad(a, axes, low_pad_size, high_pad_size, out_shape, s); } else if (mode == "reflect") { - return reflect_pad(a, axes, low_pad_size, high_pad_size, out_shape, false, s); + return reflect_pad( + a, axes, low_pad_size, high_pad_size, out_shape, false, s); } else if (mode == "symmetric") { - return reflect_pad(a, axes, low_pad_size, high_pad_size, out_shape, true, s); + return reflect_pad( + a, axes, low_pad_size, high_pad_size, out_shape, true, s); } else { std::ostringstream msg; msg << "Invalid padding mode (" << mode << ") passed to pad"; From 1e703672c9134115192d5b4570a9f47ed0386ae8 Mon Sep 17 00:00:00 2001 From: Cheng Date: Wed, 12 Aug 2026 08:47:12 +0900 Subject: [PATCH 6/6] nit --- mlx/ops.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/mlx/ops.cpp b/mlx/ops.cpp index cdbef0434e..7cc283e399 100644 --- a/mlx/ops.cpp +++ b/mlx/ops.cpp @@ -1468,13 +1468,6 @@ array reflect_pad( const Shape& out_shape, bool include_edge, StreamOrDevice s /* = {} */) { - // Reflect (include_edge=false) or symmetric (include_edge=true) padding, - // built the same way as edge_pad: place the input into a zero-filled - // output with slice_update, then extend each axis outward by - // slicing-and-flipping already-filled data (no host-side index array). - // A pad width larger than the axis needs more than one tile; each - // iteration re-slices the data it just wrote, continuing the periodic - // reflect/symmetric pattern exactly like numpy.pad. array out = zeros(out_shape, a.dtype(), s); Shape starts(a.ndim(), 0); auto stops = a.shape(); @@ -1483,6 +1476,7 @@ array reflect_pad( starts[ax] = low_pad_size[i]; stops[ax] += low_pad_size[i]; } + // Copy over values from the unpadded array array padded = slice_update(out, a, starts, stops, s); for (size_t i = 0; i < axes.size(); i++) { @@ -1494,8 +1488,7 @@ array reflect_pad( continue; } // reflect skips the edge value (period 2(n-1)); symmetric repeats it - // (period 2n). A single-element axis has nothing to reflect off of, - // so it just repeats, same as the edge case. + // (period 2n). int offset = (!include_edge && n > 1) ? 1 : 0; int tile = n - offset;