# Path: deeppy/expr/base.py
# class Unary(Op):
#     x = None
# 
#     def __call__(self, x):
#         x = _require_op(x)
#         self.x = x
#         if isinstance(x, Constant):
#             # Propagate constant.
#             self.setup()
#             self.fprop()
#             return Constant(self.array)
#         self.bpropable = x.bpropable
#         self.inputs = [x]
#         return self


import numpy as np
import cudarray as ca
from .base import Unary

class Reduce(Unary):
    def __init__(self, axis=None, keepdims=False):
        self.axis = axis
        self.keepdims = keepdims

    def setup(self):
        self.shape = ca.sum(self.x.array, axis=self.axis,
                            keepdims=self.keepdims).shape
        self.array = ca.zeros(self.shape)
        self.grad_array = ca.zeros(self.shape)


class Mean(Reduce):
    def setup(self):
        super(Mean, self).setup()
        self.n = np.prod(self.x.shape)
        self.scale = 1.0/np.prod(self.x.shape[self.axis])

    def fprop(self):
        ca.mean(self.x.array, axis=self.axis, out=self.array,
                keepdims=self.keepdims)

    def bprop(self):
        self.x.grad_array.fill(self.scale)
        self.x.grad_array *= self.grad_array


class Sum(Reduce):
    def fprop(self):

# Path: deeppy/expr/base.py
# class Unary(Op):
#     x = None
# 
#     def __call__(self, x):
#         x = _require_op(x)
#         self.x = x
#         if isinstance(x, Constant):
#             # Propagate constant.
#             self.setup()
#             self.fprop()
#             return Constant(self.array)
#         self.bpropable = x.bpropable
#         self.inputs = [x]
#         return self


# Path: deeppy/expr/reduce.py
import numpy as np
import cudarray as ca
from .base import Unary


class Reduce(Unary):
    def __init__(self, axis=None, keepdims=False):
        self.axis = axis
        self.keepdims = keepdims

    def setup(self):
        self.shape = ca.sum(self.x.array, axis=self.axis,
                            keepdims=self.keepdims).shape
        self.array = ca.zeros(self.shape)
        self.grad_array = ca.zeros(self.shape)


class Mean(Reduce):
    def setup(self):
        super(Mean, self).setup()
        self.n = np.prod(self.x.shape)
        self.scale = 1.0/np.prod(self.x.shape[self.axis])

    def fprop(self):
        ca.mean(self.x.array, axis=self.axis, out=self.array,
                keepdims=self.keepdims)

    def bprop(self):
        self.x.grad_array.fill(self.scale)
        self.x.grad_array *= self.grad_array


class Sum(Reduce):
    def fprop(self):

        ca.sum(self.x.array, axis=self.axis, out=self.array,