Convert a function to a node or a group of nodes

def set_nodes(self, func):
        """Convert a function to a node or a group of nodes"""
        op_type = self.nnabla_function_type_to_onnx_optype.get(func.type)
        if op_type is None:
            raise ValueError(
                "function {} is currently not supported for ONNX conversion".format(func.type))
        if callable(op_type):
            return op_type(func)

        variables = self._net.variable
        input_types = self._input_types
        output_types = self._output_types
        broadcast_target = self._broadcast_target

        n = onnx.helper.make_node(
            op_type,
            func.input,
            func.output,
            name=func.name)
        nl = []
        if func.type == "Convolution":
            cp = func.convolution_param
            # Calculate the kernel_shape from input weight data.
            # Weight data should be the second input for convolution
            if len(func.input) < 2:
                raise ValueError(
                    "Weight input is missing for convolution {}"
                    .format(func.name))
            weight = func.input[1]
            weight_var = [v for v in variables if v.name == weight]
            if len(weight_var) != 1:
                raise ValueError(
                    "No weight input was found, or multiple weight inputs were found"
                    " for convolution {} where there should be only one."
                    .format(func.name))
            weight_shape = weight_var[0].shape
            # The base axis for weights is the next axis from the data's base axis
            weight_base = cp.base_axis + 1
            k = onnx.helper.make_attribute("kernel_shape",
                                           weight_shape.dim[weight_base:])
            d = onnx.helper.make_attribute("dilations", cp.dilation.dim)
            s = onnx.helper.make_attribute("strides", cp.stride.dim)
            p = onnx.helper.make_attribute("pads", cp.pad.dim[:] * 2)
            g = onnx.helper.make_attribute("group", cp.group)
            n.attribute.extend([k, d, s, p, g])
            nl.append(n)
        elif func.type == "GlobalAveragePooling":
            # We wipeout the node name to avoid a bug?
            # that occurs when we use a GlobalAveragePooling node with a name
            # "Conv" or "Pool" contained.
            # Caffe2 issue is here:
            # https://github.com/caffe2/caffe2/issues/1971
            # Because a GlobalAveragePooling operator does not contain a kernel, we get an error at the
            # following code if we have a specific name.
            # https://github.com/caffe2/caffe2/blob/master/caffe2/operators/conv_pool_op_base.h#L167
            # The above caffe2 code should be checking the node's operator name and not the node's name.
            n.name = ""
            nl.append(n)
        elif func.type == "Softmax":
            # Softmax on NNabla does softmax ONLY along the specified axis.
            # ONNX first squashes the input dimensions to 2D based on the specified axis,
            # and then calculates the Softmax.
            # Since these two slightly differ, we show a warning here.
            logger.warning(SOFTMAX_WARNING)
            attr = onnx.helper.make_attribute("axis", func.softmax_param.axis)
            n.attribute.extend([attr])
            nl.append(n)
        elif func.type == "Reshape":
            # Convert Reshape size to a constant
            rp = func.reshape_param
            x = func.input[0]
            c_out = x + "_shape"
            c = generate_constant(c_out, func.name + "_shape",
                                  TensorProto.INT32, [len(rp.shape.dim)],
                                  rp.shape.dim)
            nl.append(c)
            # Add resize target shape as the second input
            del n.input[:]
            n.input.extend([x, c_out])
            nl.append(n)
        elif func.type == "Transpose":
            tp = func.transpose_param
            p = onnx.helper.make_attribute("perm", tp.axes)
            n.attribute.extend([p])
            nl.append(n)
        elif func.type == "BatchMatmul":
            bmp = func.batch_matmul_param
            if bmp.transpose_a or bmp.transpose_b:
                raise ValueError(
                    "{} with transpose is not supported yet".format(func.type))
            nl.append(n)
        elif func.type == "LeakyReLU":
            lrp = func.leaky_relu_param
            a = onnx.helper.make_attribute("alpha", lrp.alpha)
            n.attribute.extend([a])
            nl.append(n)
        elif func.type == "ELU":
            ep = func.elu_param
            a = onnx.helper.make_attribute("alpha", ep.alpha)
            n.attribute.extend([a])
            nl.append(n)
        elif func.type == "LogicalNot":
            # Store the input/output tensor's name and convert it to boolean
            input_types[n.input[0]] = TensorProto.BOOL
            output_types[n.output[0]] = TensorProto.BOOL
            nl.append(n)
        elif func.type == "SELU":
            sp = func.selu_param
            a = onnx.helper.make_attribute("alpha", sp.alpha)
            g = onnx.helper.make_attribute("gamma", sp.scale)
            n.attribute.extend([a, g])
            nl.append(n)
        elif func.type == "Sum":
            sp = func.sum_param
            set_reduction_attrs(n, sp)
            nl.append(n)
        elif func.type == "Mean":
            mp = func.mean_param
            set_reduction_attrs(n, mp)
            nl.append(n)
        elif func.type == "Max":
            mp = func.max_param
            set_reduction_attrs(n, mp)
            nl.append(n)
        elif func.type == "Min":
            mp = func.min_param
            set_reduction_attrs(n, mp)
            nl.append(n)
        elif func.type == "Prod":
            pp = func.prod_param
            set_reduction_attrs(n, pp)
            nl.append(n)
        elif func.type == "BroadcastTo":
            # BroadcastTo conversion only works when the
            # broadcasted buffer is used as second input for the following:
            # Add, And, Div, Equal, Greater,
            # Less, Mul, Or, Pow, Sub, Xor
            bp = func.broadcast_to_param
            broadcast_target[func.output[0]] = (func.input[1], bp.axis)
            # we do not append node here because BroadcastTo should disappear
        elif func.type == "MinimumScalar":
            msp = func.minimum_scalar_param
            m = onnx.helper.make_attribute("max", msp.val)
            n.attribute.extend([m])
            nl.append(n)
        elif func.type == "MaximumScalar":
            msp = func.maximum_scalar_param
            m = onnx.helper.make_attribute("min", msp.val)
            n.attribute.extend([m])
            nl.append(n)
        elif func.type == "Pad":
            pp = func.pad_param
            mode_conv = {
                "constant": "constant",
                "replicate": "edge",
                "reflect": "reflect"
            }
            # separate pad values to match ONNX format
            # (S0,E0,S1,E1) => (S0,S1,E0,E1)
            dim = len(pp.pad_width) // 2
            # If we can get the dimension of the input buffer,
            # we get it here. If we cannot, we are assuming 4D input
            in_name = func.input[0]
            in_var = [v for v in variables if v.name == in_name]
            in_dim = 4
            if len(in_var) == 1 and len(in_var[0].shape.dim) > 0:
                # Found variable with valid shape.
                # If the shape dimension is zero, it means
                # that is an intermediate buffer so we can't get
                # the exact dimension at this point
                # (thus assuming 4D input).
                in_dim = len(in_var[0].shape.dim)
            elif len(in_var) > 1:
                raise ValueError("More than one buffer with"
                                 " the same buffer name found.")
            zero_dim_num = in_dim - dim
            it = iter(pp.pad_width)
            # We need to fill empty dimensions with zero padding
            # (at least this is what Caffe2 expects)
            starts = [0] * zero_dim_num
            ends = [0] * zero_dim_num
            for x in it:
                starts.append(x)
                ends.append(next(it))
            starts.extend(ends)
            pad = onnx.helper.make_attribute("pads", starts)
            m = onnx.helper.make_attribute("mode", mode_conv[pp.mode])
            v = onnx.helper.make_attribute("value", pp.constant_value)
            n.attribute.extend([pad, m, v])
            nl.append(n)
        else:
            # Simply append node to list
            nl.append(n)
        return nl