# Path: beacon8/layers/Module.py
# class Module:
# 
#     def __init__(self):
#         self.training_mode = True
# 
#         self.fn_forward = None
#         self.fn_accum_grads = None
#         self.fn_accum_stats = None
# 
#     #def __hash__(self):
#     #    raise NotImplementedError("You *need* to reimplement hash, even if it's just python's default. See the documentation for more info.")
# 
#     def zero_grad_parameters(self):
#         _, grads = self.parameters()
#         for grad in grads:
#             grad.set_value(0 * grad.get_value())
# 
#     def parameters(self):
#         params, grads = [], []
# 
#         if hasattr(self, 'weight'):
#             assert hasattr(self, 'grad_weight'), "The layer {} has a `weight` variable but no `grad_weight`, you probably forget to implement it.".format(type(self))
#             params += [self.weight]
#             grads += [self.grad_weight]
# 
#         if hasattr(self, 'bias'):
#             assert hasattr(self, 'grad_bias'), "The layer {} has a `bias` variable but no `grad_bias`, you probably forget to implement it.".format(type(self))
#             params += [self.bias]
#             grads += [self.grad_bias]
# 
#         return params, grads
# 
#     def evaluate(self):
#         self.training_mode = False
# 
#     def training(self):
#         self.training_mode = True
# 
#     def symb_forward(self, symb_input):
#         raise NotImplementedError
# 
#     def forward(self, data):
#         if self.fn_forward is None:
#             symb_in = _T.TensorType(_th.config.floatX, (False,) * data.ndim)('X')
#             symb_out = self.symb_forward(symb_in)
#             self.fn_forward = _th.function(inputs=[symb_in], outputs=symb_out)
# 
#         return self.fn_forward(data)
# 
#     def accumulate_gradients(self, data_in, data_tgt, loss):
#         if self.fn_accum_grads is None:
#             symb_in = _T.TensorType(_th.config.floatX, (False,) * data_in.ndim)('X')
#             symb_tgt = _T.TensorType(_th.config.floatX, (False,) * data_tgt.ndim)('T')
#             symb_out = self.symb_forward(symb_in)
#             symb_err = loss.symb_forward(symb_out, symb_tgt)
# 
#             params, grads = self.parameters()
#             symb_grads = _th.grad(cost=symb_err, wrt=params)
# 
#             grads_updates = [(grad, grad + symb_grad) for grad, symb_grad in zip(grads, symb_grads)]
#             self.fn_accum_grads = _th.function(
#                 inputs=[symb_in, symb_tgt],
#                 outputs=symb_err,
#                 updates=grads_updates
#             )
# 
#         return self.fn_accum_grads(data_in, data_tgt)
# 
#     def get_stat_updates(self):
#         return []
# 
#     def accumulate_statistics(self, data_in):
#         if self.fn_accum_stats is None:
#             symb_in = _T.TensorType(_th.config.floatX, (False,) * data_in.ndim)('X')
#             self.symb_forward(symb_in)
# 
#             stat_updates = self.get_stat_updates()
#             if not stat_updates:
#                 # If there's no layer collecting statistics, we don't need to
#                 # compile and call a function. This prevents theano errors.
#                 return
# 
#             self.fn_accum_stats = _th.function(
#                 inputs=[symb_in],
#                 updates=stat_updates
#             )
# 
#         self.fn_accum_stats(data_in)
# 
# Path: beacon8/init/Const.py
# def const(value):
#     def init(shape, fan):
#         return _np.full(shape, value)
#     return init
# 
# Path: beacon8/init/Xavier.py
# def xavier(gain=1):
#     def init(shape, fan):
#         assert fan is not None, "The parameter's `fan` needs to be specified when using Xavier initialization."
# 
#         fan_mean = _np.mean(fan)
#         bound = _np.sqrt(3./fan_mean)
#         return _np.random.uniform(low=-bound, high=bound, size=shape)
#     return init
# 
# Path: beacon8/utils.py
# def create_param_and_grad(shape, init, fan=None, name=None, type=_th.config.floatX):
#     val = init(shape, fan).astype(type)
#     param = _th.shared(val, name=name)
#     grad_name = 'grad_' + name if name is not None else None
#     grad_param = _th.shared(_np.zeros_like(val), name=grad_name)
#     return param, grad_param


from .Module import Module
from beacon8.init import const, xavier
from beacon8.utils import create_param_and_grad
import theano as _th
import numpy as _np



class SpatialConvolution(Module):
    def __init__(self, n_input_plane, n_output_plane, k_w, k_h, d_w=1, d_h=1, with_bias=True, initW=xavier(), initB=const(0), border_mode='valid', imshape=None):
        Module.__init__(self)
        self.n_input_plane = n_input_plane
        self.n_output_plane = n_output_plane
        self.k_w = k_w
        self.k_h = k_h
        self.d_w = d_w
        self.d_h = d_h
        self.with_bias = with_bias
        self.border_mode = border_mode
        self.imshape = imshape

        self.w_shape = (n_output_plane, n_input_plane, k_h, k_w)
        w_fan = (n_input_plane*k_w*k_h, n_output_plane*k_w*k_h)

        self.weight, self.grad_weight = create_param_and_grad(self.w_shape, initW, fan=w_fan, name='Wconv_{},{}@{}x{}'.format(n_input_plane, n_output_plane, k_w, k_h))
        if self.with_bias:

# Path: beacon8/layers/Module.py
# class Module:
# 
#     def __init__(self):
#         self.training_mode = True
# 
#         self.fn_forward = None
#         self.fn_accum_grads = None
#         self.fn_accum_stats = None
# 
#     #def __hash__(self):
#     #    raise NotImplementedError("You *need* to reimplement hash, even if it's just python's default. See the documentation for more info.")
# 
#     def zero_grad_parameters(self):
#         _, grads = self.parameters()
#         for grad in grads:
#             grad.set_value(0 * grad.get_value())
# 
#     def parameters(self):
#         params, grads = [], []
# 
#         if hasattr(self, 'weight'):
#             assert hasattr(self, 'grad_weight'), "The layer {} has a `weight` variable but no `grad_weight`, you probably forget to implement it.".format(type(self))
#             params += [self.weight]
#             grads += [self.grad_weight]
# 
#         if hasattr(self, 'bias'):
#             assert hasattr(self, 'grad_bias'), "The layer {} has a `bias` variable but no `grad_bias`, you probably forget to implement it.".format(type(self))
#             params += [self.bias]
#             grads += [self.grad_bias]
# 
#         return params, grads
# 
#     def evaluate(self):
#         self.training_mode = False
# 
#     def training(self):
#         self.training_mode = True
# 
#     def symb_forward(self, symb_input):
#         raise NotImplementedError
# 
#     def forward(self, data):
#         if self.fn_forward is None:
#             symb_in = _T.TensorType(_th.config.floatX, (False,) * data.ndim)('X')
#             symb_out = self.symb_forward(symb_in)
#             self.fn_forward = _th.function(inputs=[symb_in], outputs=symb_out)
# 
#         return self.fn_forward(data)
# 
#     def accumulate_gradients(self, data_in, data_tgt, loss):
#         if self.fn_accum_grads is None:
#             symb_in = _T.TensorType(_th.config.floatX, (False,) * data_in.ndim)('X')
#             symb_tgt = _T.TensorType(_th.config.floatX, (False,) * data_tgt.ndim)('T')
#             symb_out = self.symb_forward(symb_in)
#             symb_err = loss.symb_forward(symb_out, symb_tgt)
# 
#             params, grads = self.parameters()
#             symb_grads = _th.grad(cost=symb_err, wrt=params)
# 
#             grads_updates = [(grad, grad + symb_grad) for grad, symb_grad in zip(grads, symb_grads)]
#             self.fn_accum_grads = _th.function(
#                 inputs=[symb_in, symb_tgt],
#                 outputs=symb_err,
#                 updates=grads_updates
#             )
# 
#         return self.fn_accum_grads(data_in, data_tgt)
# 
#     def get_stat_updates(self):
#         return []
# 
#     def accumulate_statistics(self, data_in):
#         if self.fn_accum_stats is None:
#             symb_in = _T.TensorType(_th.config.floatX, (False,) * data_in.ndim)('X')
#             self.symb_forward(symb_in)
# 
#             stat_updates = self.get_stat_updates()
#             if not stat_updates:
#                 # If there's no layer collecting statistics, we don't need to
#                 # compile and call a function. This prevents theano errors.
#                 return
# 
#             self.fn_accum_stats = _th.function(
#                 inputs=[symb_in],
#                 updates=stat_updates
#             )
# 
#         self.fn_accum_stats(data_in)
# 
# Path: beacon8/init/Const.py
# def const(value):
#     def init(shape, fan):
#         return _np.full(shape, value)
#     return init
# 
# Path: beacon8/init/Xavier.py
# def xavier(gain=1):
#     def init(shape, fan):
#         assert fan is not None, "The parameter's `fan` needs to be specified when using Xavier initialization."
# 
#         fan_mean = _np.mean(fan)
#         bound = _np.sqrt(3./fan_mean)
#         return _np.random.uniform(low=-bound, high=bound, size=shape)
#     return init
# 
# Path: beacon8/utils.py
# def create_param_and_grad(shape, init, fan=None, name=None, type=_th.config.floatX):
#     val = init(shape, fan).astype(type)
#     param = _th.shared(val, name=name)
#     grad_name = 'grad_' + name if name is not None else None
#     grad_param = _th.shared(_np.zeros_like(val), name=grad_name)
#     return param, grad_param


# Path: beacon8/layers/SpatialConvolution.py
from .Module import Module
from beacon8.init import const, xavier
from beacon8.utils import create_param_and_grad
import theano as _th
import numpy as _np




class SpatialConvolution(Module):
    def __init__(self, n_input_plane, n_output_plane, k_w, k_h, d_w=1, d_h=1, with_bias=True, initW=xavier(), initB=const(0), border_mode='valid', imshape=None):
        Module.__init__(self)
        self.n_input_plane = n_input_plane
        self.n_output_plane = n_output_plane
        self.k_w = k_w
        self.k_h = k_h
        self.d_w = d_w
        self.d_h = d_h
        self.with_bias = with_bias
        self.border_mode = border_mode
        self.imshape = imshape

        self.w_shape = (n_output_plane, n_input_plane, k_h, k_w)
        w_fan = (n_input_plane*k_w*k_h, n_output_plane*k_w*k_h)

        self.weight, self.grad_weight = create_param_and_grad(self.w_shape, initW, fan=w_fan, name='Wconv_{},{}@{}x{}'.format(n_input_plane, n_output_plane, k_w, k_h))
        if self.with_bias:

            self.bias, self.grad_bias = create_param_and_grad(n_output_plane, initB, name='bconv_{}'.format(n_output_plane))