Repository: ivy-llc/ivy

Issue:
unfold



Discussion:

Resolving patch:
diff --git a/ivy/functional/frontends/paddle/nn/functional/common.py b/ivy/functional/frontends/paddle/nn/functional/common.py
--- a/ivy/functional/frontends/paddle/nn/functional/common.py
+++ b/ivy/functional/frontends/paddle/nn/functional/common.py
@@ -115,3 +115,83 @@ def linear(x, weight, bias=None, name=None):
 @with_supported_dtypes({"2.5.1 and below": ("float32", "float64")}, "paddle")
 def dropout3d(x, p=0.5, training=True, data_format="NCDHW", name=None):
     return ivy.dropout3d(x, p, training=training, data_format=data_format)
+
+
+@to_ivy_arrays_and_back
+@with_supported_dtypes({"2.5.1 and below": ("float32", "float64")}, "paddle")
+def unfold(x, kernel_sizes, strides=1, paddings=0, dilations=1, name=None):
+    # Input checking
+    if isinstance(kernel_sizes, int):
+        kernel_sizes = [kernel_sizes, kernel_sizes]
+    elif not (isinstance(kernel_sizes, list) or isinstance(kernel_sizes, tuple)):
+        raise ivy.exceptions.IvyError(
+            "Expected kernel size input as type int, tuple or list but got"
+            f" {type(kernel_sizes)}"
+        )
+
+    if isinstance(strides, int):
+        strides = [strides, strides]
+    elif not (isinstance(strides, list) or isinstance(strides, tuple)):
+        raise ivy.exceptions.IvyError(
+            f"Expected strides input as type int, tuple or list but got {type(strides)}"
+        )
+
+    if isinstance(dilations, int):
+        dilations = [dilations, dilations]
+    elif not (isinstance(dilations, list) or isinstance(dilations, tuple)):
+        raise ivy.exceptions.IvyError(
+            "Expected dilations input as type int, tuple or list but got"
+            f" {type(dilations)}"
+        )
+
+    if isinstance(paddings, int):
+        paddings = [paddings, paddings]
+    elif not (isinstance(paddings, list) or isinstance(paddings, tuple)):
+        raise ivy.exceptions.IvyError(
+            "Expected paddings, input as type int, tuple or list but got"
+            f" {type(paddings)}"
+        )
+
+    n, c, h, w = x.shape
+    # Padding
+    if paddings[0] >= 0 or paddings[1] >= 0:
+        padding_tup = [(0, 0) for i in range(2)] + [
+            (paddings[0], paddings[0]),
+            (paddings[1], paddings[1]),
+        ]
+        x = ivy.pad(x, padding_tup, mode="constant", constant_values=0.0)
+    else:
+        raise ivy.exceptions.IvyError(
+            f"Expected padding size larger than 0 but got {paddings[0]}/{paddings[1]}"
+        )
+
+    # Expected input shape
+    h_steps = int(
+        (h + (paddings[0] * 2) - dilations[0] * (kernel_sizes[0] - 1) - 1) / strides[0]
+        + 1
+    )
+
+    w_steps = int(
+        (w + (paddings[1] * 2) - dilations[1] * (kernel_sizes[1] - 1) - 1) / strides[1]
+        + 1
+    )
+
+    if h_steps < 1 or w_steps < 1:
+        raise ivy.exceptions.IvyError(
+            "Expected at least one for height and width, but got expected output shape"
+            f" H:{h_steps} W:{w_steps}]"
+        )
+
+    # sliding windows
+    folder = []
+    for i in range(0, h_steps * strides[0], strides[0]):
+        for j in range(0, w_steps * strides[1], strides[1]):
+            window = x[
+                :,
+                :,
+                i : i + dilations[0] * (kernel_sizes[0] - 1) + 1 : dilations[0],
+                j : j + dilations[1] * (kernel_sizes[1] - 1) + 1 : dilations[1],
+            ]
+            window = ivy.flatten(window, start_dim=1)
+            folder.append(window)
+    return ivy.stack(folder, axis=2)


Test patch:
diff --git a/ivy_tests/test_ivy/test_frontends/test_paddle/test_nn/test_functional/test_common.py b/ivy_tests/test_ivy/test_frontends/test_paddle/test_nn/test_functional/test_common.py
--- a/ivy_tests/test_ivy/test_frontends/test_paddle/test_nn/test_functional/test_common.py
+++ b/ivy_tests/test_ivy/test_frontends/test_paddle/test_nn/test_functional/test_common.py
@@ -455,3 +455,56 @@ def test_paddle_dropout3d(
         training=training,
         data_format=data_format,
     )
+
+
+@st.composite
+def paddle_unfold_handler(draw, dtype):
+    dtype = draw(dtype)
+    h_size = draw(helpers.ints(min_value=10, max_value=30))
+    w_size = draw(helpers.ints(min_value=10, max_value=30))
+    channels = draw(helpers.ints(min_value=1, max_value=3))
+    batch = draw(helpers.ints(min_value=1, max_value=10))
+
+    x = draw(
+        helpers.array_values(
+            dtype=dtype[0],
+            shape=[batch, channels, h_size, w_size],
+            min_value=0,
+            max_value=1,
+        )
+    )
+
+    kernel_sizes = draw(helpers.ints(min_value=1, max_value=3))
+    strides = draw(helpers.ints(min_value=1, max_value=3))
+    paddings = draw(helpers.ints(min_value=1, max_value=3))
+    dilations = draw(helpers.ints(min_value=1, max_value=3))
+    return dtype, x, kernel_sizes, strides, paddings, dilations
+
+
+@handle_frontend_test(
+    fn_tree="paddle.nn.functional.common.unfold",
+    dtype_inputs=paddle_unfold_handler(dtype=helpers.get_dtypes("valid", full=False)),
+)
+def test_unfold(
+    *,
+    dtype_inputs,
+    on_device,
+    fn_tree,
+    frontend,
+    test_flags,
+    backend_fw,
+):
+    dtype, x, kernel_sizes, strides, paddings, dilations = dtype_inputs
+    helpers.test_frontend_function(
+        input_dtypes=dtype,
+        backend_to_test=backend_fw,
+        frontend=frontend,
+        test_flags=test_flags,
+        fn_tree=fn_tree,
+        on_device=on_device,
+        x=x,
+        kernel_sizes=kernel_sizes,
+        strides=strides,
+        paddings=paddings,
+        dilations=dilations,
+    )