'Backpropagation of errors, output delta for previous layer, and
calculate the update on model params
Arguments:
error (list[Tensor]): error tensors for each time step
of unrolling
alpha (float, optional): scale to apply to input for activation
gradient bprop.  Defaults to 1.0
beta (float, optional): scale to apply to output activation
gradient bprop.  Defaults to 0.0
Returns:
Tensor: Backpropagated errors for each time step of model unrolling'

def bprop(self, error, alpha=1.0, beta=0.0):

    self.dW[:] = 0
    if (self.in_deltas_f is None):
        self.in_deltas_f = get_steps(error[:self.o_shape[0]], self.o_shape)
        self.prev_in_deltas = (self.in_deltas_f[(-1):] + self.in_deltas_f[:(-1)])
        self.ifog_delta_last_steps = self.ifog_delta_buffer[:, self.be.bsz:]
        self.h_first_steps = self.h_buffer_f[:, :(- self.be.bsz)]
    if (self.in_deltas_b is None):
        self.in_deltas_b = get_steps(error[self.o_shape[0]:], self.o_shape)
        self.next_in_deltas = (self.in_deltas_b[1:] + self.in_deltas_b[:1])
        self.ifog_delta_first_steps = self.ifog_delta_buffer[:, :(- self.be.bsz)]
        self.h_last_steps = self.h_buffer_b[:, self.be.bsz:]
    params_f = (self.in_deltas_f, self.prev_in_deltas, self.i_f, self.f_f, self.o_f, self.g_f, self.ifog_delta, self.i_delta, self.f_delta, self.o_delta, self.g_delta, self.c_delta, self.c_delta_prev, self.c_prev_bprop, self.c_act_f)
    params_b = (self.in_deltas_b, self.next_in_deltas, self.i_b, self.f_b, self.o_b, self.g_b, self.ifog_delta, self.i_delta, self.f_delta, self.o_delta, self.g_delta, self.c_delta, self.c_delta_next, self.c_next_bprop, self.c_act_b)
    self.c_delta_buffer[:] = 0
    self.ifog_delta_buffer[:] = 0
    self.ifog_delta_f = None
    self.ifog_delta_b = None
    for (in_deltas, prev_in_deltas, i, f, o, g, ifog_delta, i_delta, f_delta, o_delta, g_delta, c_delta, c_delta_prev, c_prev, c_act) in reversed(list(zip(*params_f))):
        c_delta[:] = (c_delta + (self.activation.bprop(c_act) * (o * in_deltas)))
        i_delta[:] = ((self.gate_activation.bprop(i) * c_delta) * g)
        f_delta[:] = ((self.gate_activation.bprop(f) * c_delta) * c_prev)
        o_delta[:] = ((self.gate_activation.bprop(o) * in_deltas) * c_act)
        g_delta[:] = ((self.activation.bprop(g) * c_delta) * i)
        self.be.compound_dot(self.W_recur_f.T, ifog_delta, prev_in_deltas, beta=1.0)
        if (c_delta_prev is not None):
            c_delta_prev[:] = (c_delta * f)
    self.be.compound_dot(self.ifog_delta_last_steps, self.h_first_steps.T, self.dW_recur_f)
    self.be.compound_dot(self.ifog_delta_buffer, self.x_f.T, self.dW_input_f)
    self.db_f[:] = self.be.sum(self.ifog_delta_buffer, axis=1)
    if self.out_deltas_buffer:
        self.be.compound_dot(self.W_input_f.T, self.ifog_delta_buffer, self.out_deltas_buffer_f_v, alpha=alpha, beta=beta)
    self.c_delta_buffer[:] = 0
    self.ifog_delta_buffer[:] = 0
    for (in_deltas, next_in_deltas, i, f, o, g, ifog_delta, i_delta, f_delta, o_delta, g_delta, c_delta, c_delta_next, c_next, c_act) in zip(*params_b):
        c_delta[:] = (c_delta[:] + (self.activation.bprop(c_act) * (o * in_deltas)))
        i_delta[:] = ((self.gate_activation.bprop(i) * c_delta) * g)
        f_delta[:] = ((self.gate_activation.bprop(f) * c_delta) * c_next)
        o_delta[:] = ((self.gate_activation.bprop(o) * in_deltas) * c_act)
        g_delta[:] = ((self.activation.bprop(g) * c_delta) * i)
        self.be.compound_dot(self.W_recur_b.T, ifog_delta, next_in_deltas, beta=1.0)
        if (c_delta_next is not None):
            c_delta_next[:] = (c_delta * f)
    self.be.compound_dot(self.ifog_delta_first_steps, self.h_last_steps.T, self.dW_recur_b)
    self.be.compound_dot(self.ifog_delta_buffer, self.x_b.T, self.dW_input_b)
    self.db_b[:] = self.be.sum(self.ifog_delta_buffer, axis=1)
    if self.out_deltas_buffer:
        self.be.compound_dot(self.W_input_b.T, self.ifog_delta_buffer, self.out_deltas_buffer_b_v, alpha=alpha, beta=(beta if self.inputs else 1.0))
    return self.out_deltas_buffer
