# Path: brainforge/model/abstract_model.py
# class Model(abc.ABC):
# 
#     def __init__(self, input_shape, **kw):
#         self.input_shape = input_shape
#         self.floatX = kw.get("floatX", "float64")
#         self.compiled = kw.get("compiled", False)
# 
#     @abc.abstractmethod
#     def feedforward(self, X):
#         raise NotImplementedError
# 
#     @abc.abstractmethod
#     def get_weights(self, unfold):
#         raise NotImplementedError
# 
#     @abc.abstractmethod
#     def set_weights(self, fold):
#         raise NotImplementedError
# 
#     @property
#     @abc.abstractmethod
#     def num_params(self):
#         raise NotImplementedError
# 
#     @property
#     @abc.abstractmethod
#     def outshape(self):
#         raise NotImplementedError
# 
# Path: brainforge/layers/abstract_layer.py
# class LayerBase(abc.ABC):
# 
#     trainable = True
# 
#     def __init__(self, activation="linear", **kw):
# 
#         self.brain = None
#         self.inputs = None
#         self.output = None
#         self.inshape = None
#         self.op = None
#         self.opb = None
# 
#         self.weights = None
#         self.biases = None
#         self.nabla_w = None
#         self.nabla_b = None
# 
#         self.compiled = kw.get("compiled", config.compiled)
#         self.trainable = kw.get("trainable", self.trainable)
# 
#         self.activation = atomic.activations[activation]() \
#             if isinstance(activation, str) else activation
# 
#     def connect(self, brain):
#         self.brain = brain
#         self.inshape = brain.outshape
# 
#     def shuffle(self) -> None:
#         self.weights = white_like(self.weights)
#         self.biases = zX_like(self.biases)
# 
#     def get_weights(self, unfold=True):
#         if unfold:
#             return np.concatenate((self.weights.ravel(), self.biases.ravel()))
#         return [self.weights, self.biases]
# 
#     def set_weights(self, w, fold=True):
#         if fold:
#             W = self.weights
#             self.weights = w[:W.size].reshape(W.shape)
#             self.biases = w[W.size:].reshape(self.biases.shape)
#         else:
#             self.weights, self.biases = w
# 
#     def get_gradients(self, unfold=True):
#         nabla = [self.nabla_w, self.nabla_b]
#         return nabla if not unfold else np.concatenate([grad.ravel() for grad in nabla])
# 
#     def set_gradients(self, nabla, fold=True):
#         if fold:
#             self.nabla_w = nabla[:self.nabla_w.size].reshape(self.nabla_w.shape)
#             self.nabla_b = nabla[self.nabla_w.size:].reshape(self.nabla_b.shape)
#         else:
#             self.nabla_w, self.nabla_b = nabla
# 
#     @property
#     def gradients(self):
#         return self.get_gradients(unfold=True)
# 
#     @property
#     def num_params(self):
#         return self.weights.size + self.biases.size
# 
#     @abc.abstractmethod
#     def feedforward(self, X): raise NotImplementedError
# 
#     @abc.abstractmethod
#     def backpropagate(self, delta): raise NotImplementedError
# 
#     @property
#     @abc.abstractmethod
#     def outshape(self): raise NotImplementedError
# 
#     def __str__(self):
#         return self.__class__.__name__


import numpy as np
from .abstract_model import Model
from ..layers.abstract_layer import LayerBase
        from ..layers.core import InputLayer
        else:
            for w, layer in zip(ws, trl):
                layer.set_weights(w)

    def describe(self):
        return "Architecture: " + "->".join(self.architecture),

    def reset(self):
        for layer in (l for l in self.layers if l.trainable):
            layer.reset()

    @property
    def outshape(self):
        return self.layers[-1].outshape

    @property
    def num_params(self):
        return sum(layer.num_params for layer in self.layers if layer.trainable)

    @property
    def output(self):
        return self.layers[-1].output

    @property
    def trainable_layers(self):
        return [layer for layer in self.layers if layer.trainable]

    def __iter__(self):
        return self


# Path: brainforge/model/abstract_model.py
# class Model(abc.ABC):
# 
#     def __init__(self, input_shape, **kw):
#         self.input_shape = input_shape
#         self.floatX = kw.get("floatX", "float64")
#         self.compiled = kw.get("compiled", False)
# 
#     @abc.abstractmethod
#     def feedforward(self, X):
#         raise NotImplementedError
# 
#     @abc.abstractmethod
#     def get_weights(self, unfold):
#         raise NotImplementedError
# 
#     @abc.abstractmethod
#     def set_weights(self, fold):
#         raise NotImplementedError
# 
#     @property
#     @abc.abstractmethod
#     def num_params(self):
#         raise NotImplementedError
# 
#     @property
#     @abc.abstractmethod
#     def outshape(self):
#         raise NotImplementedError
# 
# Path: brainforge/layers/abstract_layer.py
# class LayerBase(abc.ABC):
# 
#     trainable = True
# 
#     def __init__(self, activation="linear", **kw):
# 
#         self.brain = None
#         self.inputs = None
#         self.output = None
#         self.inshape = None
#         self.op = None
#         self.opb = None
# 
#         self.weights = None
#         self.biases = None
#         self.nabla_w = None
#         self.nabla_b = None
# 
#         self.compiled = kw.get("compiled", config.compiled)
#         self.trainable = kw.get("trainable", self.trainable)
# 
#         self.activation = atomic.activations[activation]() \
#             if isinstance(activation, str) else activation
# 
#     def connect(self, brain):
#         self.brain = brain
#         self.inshape = brain.outshape
# 
#     def shuffle(self) -> None:
#         self.weights = white_like(self.weights)
#         self.biases = zX_like(self.biases)
# 
#     def get_weights(self, unfold=True):
#         if unfold:
#             return np.concatenate((self.weights.ravel(), self.biases.ravel()))
#         return [self.weights, self.biases]
# 
#     def set_weights(self, w, fold=True):
#         if fold:
#             W = self.weights
#             self.weights = w[:W.size].reshape(W.shape)
#             self.biases = w[W.size:].reshape(self.biases.shape)
#         else:
#             self.weights, self.biases = w
# 
#     def get_gradients(self, unfold=True):
#         nabla = [self.nabla_w, self.nabla_b]
#         return nabla if not unfold else np.concatenate([grad.ravel() for grad in nabla])
# 
#     def set_gradients(self, nabla, fold=True):
#         if fold:
#             self.nabla_w = nabla[:self.nabla_w.size].reshape(self.nabla_w.shape)
#             self.nabla_b = nabla[self.nabla_w.size:].reshape(self.nabla_b.shape)
#         else:
#             self.nabla_w, self.nabla_b = nabla
# 
#     @property
#     def gradients(self):
#         return self.get_gradients(unfold=True)
# 
#     @property
#     def num_params(self):
#         return self.weights.size + self.biases.size
# 
#     @abc.abstractmethod
#     def feedforward(self, X): raise NotImplementedError
# 
#     @abc.abstractmethod
#     def backpropagate(self, delta): raise NotImplementedError
# 
#     @property
#     @abc.abstractmethod
#     def outshape(self): raise NotImplementedError
# 
#     def __str__(self):
#         return self.__class__.__name__


# Path: brainforge/model/layerstack.py
import numpy as np
from .abstract_model import Model
from ..layers.abstract_layer import LayerBase
        from ..layers.core import InputLayer

        else:
            for w, layer in zip(ws, trl):
                layer.set_weights(w)

    def describe(self):
        return "Architecture: " + "->".join(self.architecture),

    def reset(self):
        for layer in (l for l in self.layers if l.trainable):
            layer.reset()

    @property
    def outshape(self):
        return self.layers[-1].outshape

    @property
    def num_params(self):
        return sum(layer.num_params for layer in self.layers if layer.trainable)

    @property
    def output(self):
        return self.layers[-1].output

    @property
    def trainable_layers(self):
        return [layer for layer in self.layers if layer.trainable]

    def __iter__(self):
        return self


    def __next__(self) -> LayerBase: