from __future__ import print_function
import paddle
import paddle.fluid as fluid

from paddle.fluid import core

from paddle.fluid.layer_helper import LayerHelper
from paddle.fluid.dygraph.layer_object_helper import LayerObjectHelper

from paddle.fluid.initializer import Normal, Constant, NumpyArrayInitializer

from paddle.fluid.param_attr import ParamAttr

from paddle.fluid.framework import Variable, OpProtoHolder, in_dygraph_mode
from paddle.fluid.layers import utils
import numpy as np


def Fconv2d(input,
            filter,
            stride=1,
            padding=0,
            dilation=1,
            groups=None,
            use_cudnn=True,
            name=None):
    """
    Similar with conv2d, this is a convolution2D layers. Difference
    is filter can be token as input directly instead of setting filter size
    and number of fliters. Filter is a  4-D tensor with shape
    [num_filter, num_channel, filter_size_h, filter_size_w].
     Args:
        input (Variable): The input image with [N, C, H, W] format.
        filter(Variable): The input filter with [out_channels, in_channels, H, W] format.
        stride (int|tuple): The stride size. If stride is a tuple, it must
            contain two integers, (stride_H, stride_W). Otherwise, the
            stride_H = stride_W = stride. Default: stride = 1.
        padding (int|tuple): The padding size. If padding is a tuple, it must
            contain two integers, (padding_H, padding_W). Otherwise, the
            padding_H = padding_W = padding. Default: padding = 0.
        dilation (int|tuple): The dilation size. If dilation is a tuple, it must
            contain two integers, (dilation_H, dilation_W). Otherwise, the
            dilation_H = dilation_W = dilation. Default: dilation = 1.
        bias_attr (ParamAttr|bool|None): The parameter attribute for the bias of conv2d.
            If it is set to False, no bias will be added to the output units.
            If it is set to None or one attribute of ParamAttr, conv2d
            will create ParamAttr as bias_attr. If the Initializer of the bias_attr
            is not set, the bias is initialized zero. Default: None.
        use_cudnn (bool): Use cudnn kernel or not, it is valid only when the cudnn
            library is installed. Default: True
        act (str): Activation type, if it is set to None, activation is not appended.
            Default: None
        name (str|None): A name for this layer(optional). If set None, the layer
            will be named automatically. Default: None
    Returns:
        Variable: The tensor variable storing the convolution and \
                  non-linearity activation result.
    Raises:
        ValueError: If the shapes of input, filter_size, stride, padding and
                    groups mismatch.
    Examples:
        .. code-block:: python
          data = fluid.layers.data(name='data', shape=[3, 32, 32], \
                                  dtype='float32')
          filter = fluid.layers.data(name='filter',shape=[10,3,3,3], \
                                    dtype='float32',append_batch_size=False)
          conv2d = fluid.layers.conv2d(input=data,
                                       filter=filter,
                                       act="relu")
    """
    helper = LayerHelper("conv2d_with_filter", **locals())
    num_channels = input.shape[1]
    num_filters = filter.shape[0]
    num_filter_channels = filter.shape[1]
    l_type = 'conv2d'
    # if (num_channels == groups and
    if (num_channels == groups and num_filters % num_channels == 0 and
            not use_cudnn):
        l_type = 'depthwise_conv2d'
    if groups is None:
        assert num_filter_channels == num_channels
        groups = 1
    else:
        if num_channels % groups != 0:
            raise ValueError("num_channels must be divisible by groups.")
        if num_channels // groups != num_filter_channels:
            raise ValueError("num_filter_channels must equal to num_channels\
                              divided by groups.")

    stride = utils.convert_to_list(stride, 2, 'stride')
    padding = utils.convert_to_list(padding, 2, 'padding')
    dilation = utils.convert_to_list(dilation, 2, 'dilation')
    if not isinstance(use_cudnn, bool):
        raise ValueError("use_cudnn should be True or False")
    pre_bias = helper.create_variable_for_type_inference(dtype=input.dtype)
    helper.append_op(
        type=l_type,
        inputs={
            'Input': input,
            'Filter': filter,
        },
        outputs={"Output": pre_bias},
        attrs={
            'strides': stride,
            'paddings': padding,
            'dilations': dilation,
            'groups': groups,
            'use_cudnn': use_cudnn,
            'use_mkldnn': False
        })

    return pre_bias


def test_conv2d_with_filter():
    exemplar = np.random.random((8, 4, 6, 6)).astype(np.float32)
    instance = np.random.random((8, 4, 22, 22)).astype(np.float32)

    # fluid.layers.data(append_batch_size=)
    use_gpu = False
    place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace()

    train_program = fluid.Program()
    start_program = fluid.Program()

    with fluid.program_guard(train_program, start_program):
        x = fluid.layers.data(
            name="inst", shape=[8, 4, 22, 22], append_batch_size=False)
        y = fluid.layers.data(
            name="exem", shape=[8, 4, 6, 6], append_batch_size=False)
        bias_att = fluid.ParamAttr(
            name="bias_", initializer=fluid.initializer.ConstantInitializer(1.))
        out = conv2d_with_filter(x, y, groups=1)
        weight_att = fluid.ParamAttr(
            name='weight',
            initializer=fluid.initializer.NumpyArrayInitializer(exemplar))
        bias_att = fluid.ParamAttr(
            name="bias", initializer=fluid.initializer.ConstantInitializer(0.))
        res = fluid.layers.conv2d(
            x,
            8,
            6,
            param_attr=weight_att,
            bias_attr=bias_att,
            stride=1,
            padding=0,
            dilation=1)

        exe = fluid.Executor(place)
        exe.run(program=fluid.default_startup_program())
    print(out.shape)

    compiled_prog = fluid.compiler.CompiledProgram(train_program)
    out, res = exe.run(compiled_prog,
                       feed={"inst": instance,
                             "exem": exemplar},
                       fetch_list=[out.name, res.name])

    print(np.sum(out - res))
    np.testing.assert_allclose(out, res, rtol=1e-5, atol=0)

    with fluid.dygraph.guard():
        exem = fluid.dygraph.to_variable(exemplar)
        inst = fluid.dygraph.to_variable(instance)

        out = conv2d_with_filter(inst, exem, groups=1)

    print(np.sum(out.numpy() - res))
    np.testing.assert_allclose(out.numpy(), res, rtol=1e-5, atol=0)


if __name__ == '__main__':
    test_conv2d_with_filter()

from __future__ import print_function
import paddle
import paddle.fluid as fluid

from paddle.fluid import core

from paddle.fluid.layer_helper import LayerHelper
from paddle.fluid.dygraph.layer_object_helper import LayerObjectHelper

from paddle.fluid.initializer import Normal, Constant, NumpyArrayInitializer

from paddle.fluid.param_attr import ParamAttr

from paddle.fluid.framework import Variable, OpProtoHolder, in_dygraph_mode
from paddle.fluid.layers import utils
import numpy as np


def Fconv2d(input,
            filter,
            stride=1,
            padding=0,
            dilation=1,
            groups=None,
            use_cudnn=True,
            name=None):
    """
    Similar with conv2d, this is a convolution2D layers. Difference
    is filter can be token as input directly instead of setting filter size
    and number of fliters. Filter is a  4-D tensor with shape
    [num_filter, num_channel, filter_size_h, filter_size_w].
     Args:
        input (Variable): The input image with [N, C, H, W] format.
        filter(Variable): The input filter with [out_channels, in_channels, H, W] format.
        stride (int|tuple): The stride size. If stride is a tuple, it must
            contain two integers, (stride_H, stride_W). Otherwise, the
            stride_H = stride_W = stride. Default: stride = 1.
        padding (int|tuple): The padding size. If padding is a tuple, it must
            contain two integers, (padding_H, padding_W). Otherwise, the
            padding_H = padding_W = padding. Default: padding = 0.
        dilation (int|tuple): The dilation size. If dilation is a tuple, it must
            contain two integers, (dilation_H, dilation_W). Otherwise, the
            dilation_H = dilation_W = dilation. Default: dilation = 1.
        bias_attr (ParamAttr|bool|None): The parameter attribute for the bias of conv2d.
            If it is set to False, no bias will be added to the output units.
            If it is set to None or one attribute of ParamAttr, conv2d
            will create ParamAttr as bias_attr. If the Initializer of the bias_attr
            is not set, the bias is initialized zero. Default: None.
        use_cudnn (bool): Use cudnn kernel or not, it is valid only when the cudnn
            library is installed. Default: True
        act (str): Activation type, if it is set to None, activation is not appended.
            Default: None
        name (str|None): A name for this layer(optional). If set None, the layer
            will be named automatically. Default: None
    Returns:
        Variable: The tensor variable storing the convolution and \
                  non-linearity activation result.
    Raises:
        ValueError: If the shapes of input, filter_size, stride, padding and
                    groups mismatch.
    Examples:
        .. code-block:: python
          data = fluid.layers.data(name='data', shape=[3, 32, 32], \
                                  dtype='float32')
          filter = fluid.layers.data(name='filter',shape=[10,3,3,3], \
                                    dtype='float32',append_batch_size=False)
          conv2d = fluid.layers.conv2d(input=data,
                                       filter=filter,
                                       act="relu")
    """
    helper = LayerHelper("conv2d_with_filter", **locals())
    num_channels = input.shape[1]
    num_filters = filter.shape[0]
    num_filter_channels = filter.shape[1]
    l_type = 'conv2d'
    # if (num_channels == groups and
    if (num_channels == groups and num_filters % num_channels == 0 and
            not use_cudnn):
        l_type = 'depthwise_conv2d'
    if groups is None:
        assert num_filter_channels == num_channels
        groups = 1
    else:
        if num_channels % groups != 0:
            raise ValueError("num_channels must be divisible by groups.")
        if num_channels // groups != num_filter_channels:
            raise ValueError("num_filter_channels must equal to num_channels\
                              divided by groups.")

    stride = utils.convert_to_list(stride, 2, 'stride')
    padding = utils.convert_to_list(padding, 2, 'padding')
    dilation = utils.convert_to_list(dilation, 2, 'dilation')
    if not isinstance(use_cudnn, bool):
        raise ValueError("use_cudnn should be True or False")
    pre_bias = helper.create_variable_for_type_inference(dtype=input.dtype)
    helper.append_op(
        type=l_type,
        inputs={
            'Input': input,
            'Filter': filter,
        },
        outputs={"Output": pre_bias},
        attrs={
            'strides': stride,
            'paddings': padding,
            'dilations': dilation,
            'groups': groups,
            'use_cudnn': use_cudnn,
            'use_mkldnn': False
        })

    return pre_bias


def test_conv2d_with_filter():
    exemplar = np.random.random((8, 4, 6, 6)).astype(np.float32)
    instance = np.random.random((8, 4, 22, 22)).astype(np.float32)

    # fluid.layers.data(append_batch_size=)
    use_gpu = False
    place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace()

    train_program = fluid.Program()
    start_program = fluid.Program()

    with fluid.program_guard(train_program, start_program):
        x = fluid.layers.data(
            name="inst", shape=[8, 4, 22, 22], append_batch_size=False)
        y = fluid.layers.data(
            name="exem", shape=[8, 4, 6, 6], append_batch_size=False)
        bias_att = fluid.ParamAttr(
            name="bias_", initializer=fluid.initializer.ConstantInitializer(1.))
        out = conv2d_with_filter(x, y, groups=1)
        weight_att = fluid.ParamAttr(
            name='weight',
            initializer=fluid.initializer.NumpyArrayInitializer(exemplar))
        bias_att = fluid.ParamAttr(
            name="bias", initializer=fluid.initializer.ConstantInitializer(0.))
        res = fluid.layers.conv2d(
            x,
            8,
            6,
            param_attr=weight_att,
            bias_attr=bias_att,
            stride=1,
            padding=0,
            dilation=1)

        exe = fluid.Executor(place)
        exe.run(program=fluid.default_startup_program())
    print(out.shape)

    compiled_prog = fluid.compiler.CompiledProgram(train_program)
    out, res = exe.run(compiled_prog,
                       feed={"inst": instance,
                             "exem": exemplar},
                       fetch_list=[out.name, res.name])

    print(np.sum(out - res))
    np.testing.assert_allclose(out, res, rtol=1e-5, atol=0)

    with fluid.dygraph.guard():
        exem = fluid.dygraph.to_variable(exemplar)
        inst = fluid.dygraph.to_variable(instance)

        out = conv2d_with_filter(inst, exem, groups=1)

    print(np.sum(out.numpy() - res))
    np.testing.assert_allclose(out.numpy(), res, rtol=1e-5, atol=0)


if __name__ == '__main__':
    test_conv2d_with_filter()

Similar with conv2d, this is a convolution2D layers. Difference
     is filter can be token as input directly instead of setting filter size
     and number of fliters. Filter is a  4-D tensor with shape
     [num_filter, num_channel, filter_size_h, filter_size_w].
      Args:
         input (Variable): The input image with [N, C, H, W] format.
         filter(Variable): The input filter with [out_channels, in_channels, H, W] format.
         stride (int|tuple): The stride size. If stride is a tuple, it must
             contain two integers, (stride_H, stride_W). Otherwise, the
             stride_H = stride_W = stride. Default: stride = 1.
         padding (int|tuple): The padding size. If padding is a tuple, it must
             contain two integers, (padding_H, padding_W). Otherwise, the
             padding_H = padding_W = padding. Default: padding = 0.
         dilation (int|tuple): The dilation size. If dilation is a tuple, it must
             contain two integers, (dilation_H, dilation_W). Otherwise, the
             dilation_H = dilation_W = dilation. Default: dilation = 1.
         bias_attr (ParamAttr|bool|None): The parameter attribute for the bias of conv2d.
             If it is set to False, no bias will be added to the output units.
             If it is set to None or one attribute of ParamAttr, conv2d
             will create ParamAttr as bias_attr. If the Initializer of the bias_attr
             is not set, the bias is initialized zero. Default: None.
         use_cudnn (bool): Use cudnn kernel or not, it is valid only when the cudnn
             library is installed. Default: True
         act (str): Activation type, if it is set to None, activation is not appended.
             Default: None
         name (str|None): A name for this layer(optional). If set None, the layer
             will be named automatically. Default: None
     Returns:
         Variable: The tensor variable storing the convolution and \
                   non-linearity activation result.
     Raises:
         ValueError: If the shapes of input, filter_size, stride, padding and
                     groups mismatch.
     Examples:
         .. code-block:: python
           data = fluid.layers.data(name='data', shape=[3, 32, 32], \
                                   dtype='float32')
           filter = fluid.layers.data(name='filter',shape=[10,3,3,3], \
                                     dtype='float32',append_batch_size=False)
           conv2d = fluid.layers.conv2d(input=data,
                                        filter=filter,
                                        act="relu") # if (num_channels == groups and # fluid.layers.data(append_batch_size=)