Example #1
0
    def __init__(self, config, net, domain_index):
        super(Domain, self).__init__(config, net, domain_index)

        self.ticks = 0
        self.synapse_count_by_domain = {}
        self.spike_learn_threshold \
                = self.net.config['synapse'].get('spike_learn_threshold', 0)
        self.spike_forget_threshold \
                = self.net.config['synapse'].get('spike_forget_threshold', 0)
        self.learn_rate \
                = self.net.config['synapse'].get('learn_rate', 0)
        self.learn_threshold \
                = self.net.config['synapse'].get('learn_threshold', 0)
        self.layers = []
        self.layers_vector = LayersVector()
        # domain layers config
        self.layers_config = deepcopy(self.config['layers'])
        # neurons vector. Metadata stored in layer.neurons_metadata
        self.neurons = NeuronsVector()
        self.remote_neuron_address = -1
        self.remote_neurons_metadata = NeuronsExtendableMetadata(0)

        # synapses vector
        self.synapse_address = -1
        self.synapses = SynapsesVector(
            0, self.net.config['synapse']['max_level'])
        self.synapses_metadata = None
        self.random = random.Random()
        self.seed = uuid.uuid4().hex
        self.pre_synapse_index = None
        self.post_synapse_index = None
        self.device = getattr(
            device,
            self.config['device'].get('type', 'OpenCL')
        )(self.config['device'])
        self.cache = {}
        # stats for domain (number of spikes)
        self.stat_vector = Vector()
        # fields:
        # 0 - total spikes (one per neuron) per self.config['stat_size'] ticks
        # 1 - number of the dead neurons
        # 2 - number of synapses with flag IS_STRENGTHENED
        # 3 - neurons tiredness = sum(layer.max_vitality - neuron.vitality)
        # 4 - synapse learn level
        self.stat_fields = 5
        stat_metadata = Metadata(
            (1, self.stat_fields),
            types.stat
        )
        self.stat_vector.add(stat_metadata)
        # stats for vectors
        self.layers_stat = Vector()

        self.transmitter_index = TransmitterIndex()
        self.receiver_index = ReceiverIndex()

        self.output_index = OutputIndex()

        logging.debug('Domain created (name: %s)', self.name)
Example #2
0
class Domain(DomainBase):
    """
    Домен (Domain) - содержит один и более слоев состоящих из нейронов,
    связанных друг с другом синапсами. Домен можно воспринимать как один процесс
    в операционной системе, реализующий частично или полностью какой либо
    функционал. В некоторых случаях домен может не содержать синапсов (например,
    если домен является источником данных с сенсоров)

    self.name: types.address - должен быть уникальным для всех доменов
    self.ticks: types.tick - номер тика с момента запуска. При 32 битах и 1000
                             тиков в секунду переполнение произойдет через 49
                             дней. При 64 битах через 584 млн. лет.
    self.spike_learn_threshold: types.tick - как близко друг к другу по времени
                                должен сработать pre и post нейрон, что бы
                                поменялся вес синапса в большую сторону.
                                0 - по умолчанию (сеть не обучается).
    self.spike_forget_threshold: types.tick - насколько сильной должна быть
                                 разница между pre.tick и post.tick что бы
                                 уменьшился вес синапса.
                                 0 - по умолчанию (сеть не забывает).
    self.layers - список слоев (class Layer) домена
    self.learn_rate: types.synapse_level - с какой скоростью увеличивается
                     synapse.learn если у нейронов синапса расстояние между
                     спайками меньше чем self.spike_learn_threshold
    self.learn_threshold: types.synapse_level - какой максимальный уровень может
                          быть у synapse.learn. При спайке synapse.learn
                          суммируется с synapse.level (кратковременная память)
                          и суммарный сигнал передается post-нейрону.
                          Минимальный уровень у synapse.learn == 0. При первом
                          достижении максимального уровня синапс должен
                          одноразово усиливаться (долговременная память).
    Жеательно что бы выполнялось условие:
        0 <= spike_learn_threshold <= spike_forget_threshold <= types.tick.max
    """
    def __init__(self, config, net, domain_index):
        super(Domain, self).__init__(config, net, domain_index)

        self.ticks = 0
        self.synapse_count_by_domain = {}
        self.spike_learn_threshold \
                = self.net.config['synapse'].get('spike_learn_threshold', 0)
        self.spike_forget_threshold \
                = self.net.config['synapse'].get('spike_forget_threshold', 0)
        self.learn_rate \
                = self.net.config['synapse'].get('learn_rate', 0)
        self.learn_threshold \
                = self.net.config['synapse'].get('learn_threshold', 0)
        self.layers = []
        self.layers_vector = LayersVector()
        # domain layers config
        self.layers_config = deepcopy(self.config['layers'])
        # neurons vector. Metadata stored in layer.neurons_metadata
        self.neurons = NeuronsVector()
        self.remote_neuron_address = -1
        self.remote_neurons_metadata = NeuronsExtendableMetadata(0)

        # synapses vector
        self.synapse_address = -1
        self.synapses = SynapsesVector(
            0, self.net.config['synapse']['max_level'])
        self.synapses_metadata = None
        self.random = random.Random()
        self.seed = uuid.uuid4().hex
        self.pre_synapse_index = None
        self.post_synapse_index = None
        self.device = getattr(
            device,
            self.config['device'].get('type', 'OpenCL')
        )(self.config['device'])
        self.cache = {}
        # stats for domain (number of spikes)
        self.stat_vector = Vector()
        # fields:
        # 0 - total spikes (one per neuron) per self.config['stat_size'] ticks
        # 1 - number of the dead neurons
        # 2 - number of synapses with flag IS_STRENGTHENED
        # 3 - neurons tiredness = sum(layer.max_vitality - neuron.vitality)
        # 4 - synapse learn level
        self.stat_fields = 5
        stat_metadata = Metadata(
            (1, self.stat_fields),
            types.stat
        )
        self.stat_vector.add(stat_metadata)
        # stats for vectors
        self.layers_stat = Vector()

        self.transmitter_index = TransmitterIndex()
        self.receiver_index = ReceiverIndex()

        self.output_index = OutputIndex()

        logging.debug('Domain created (name: %s)', self.name)

    def deploy_layers(self):
        """
        Create layers
        """
        logging.debug('Deploy domain (name: %s)', self.name)
        for layer_config in self.layers_config:
            layer = Layer(layer_config)
            self.neurons.add(layer.neurons_metadata)
            layer.address = layer.neurons_metadata.address
            self.layers.append(layer)
            layer_config['layer'] = layer
            self.layers_vector.add(layer.layer_metadata)
            layer_stat_metadata = Metadata(
                (1, self.stat_fields),
                types.stat
            )
            self.layers_stat.add(layer_stat_metadata)
        self.neurons.add(self.remote_neurons_metadata)
        for layer_config in self.layers_config:
            for connect in layer_config.get('connect', []):
                connect['domain_layers'] = []
            for layer in self.layers:
                for connect in layer_config.get('connect', []):
                    if connect['name'] == layer.name:
                        connect['domain_layers'].append(layer)
        logging.debug('Allocate layers vector')
        for layer_name, layer in enumerate(self.layers):
            self.layers_vector.threshold[layer_name] = layer.threshold
            self.layers_vector.relaxation[layer_name] = layer.relaxation
            self.layers_vector.spike_cost[layer_name] = layer.spike_cost
            self.layers_vector.max_vitality[layer_name] = layer.max_vitality

    def deploy_neurons(self):
        """
        Create neurons
        """
        logging.debug(
            'Total %s local neurons in domain',
            len(self.neurons)
        )
        self.create_neurons()

    def pre_deploy_synapses(self):
        """
        Init synapses vector
        """
        # allocate synapses buffer in memory
        self.synapses_metadata = SynapsesMetadata(0)
        self.synapses.add(self.synapses_metadata)

    def deploy_synapses_async(self):
        """
        Async version of deploy_synapses
        """
        ret = self.create_synapses()
        # Create synapses
        if isinstance(ret, GeneratorType):
            for res in ret:
                yield res

        domain_total_synapses = self.synapse_count_by_domain.get(self.name, 0)
        if not domain_total_synapses:
            logging.warn('No synapses in domain %s', self.name)
        logging.debug(
            'Total %s local synapses in domain',
            domain_total_synapses
        )

    def deploy_synapses(self):
        """
        Create synapses
        """
        list(self.deploy_synapses_async())

    def post_deploy_synapses(self):
        """
        Run after all domains is synced
        """
        # sync length between synapses multifield metadata fields
        self.synapses_metadata.sync_length(self.synapse_address+1)
        # sync self.neurons length after adding remote neurons
        self.remote_neurons_metadata.sync_length(self.remote_neuron_address+1)
        logging.debug(
            'Total %s neurons in domain %s',
            len(self.neurons),
            self.name
        )
        logging.debug(
            'Total %s synapses in domain %s',
            len(self.synapses),
            self.name
        )

    def deploy_indexes(self):
        """
        Create indexes
        """
        # create pre-neuron - synapse index
        logging.debug('Create pre-neuron - synapse index')
        self.pre_synapse_index = SynapsesIndex(
            len(self.neurons), self.synapses.pre)
        # create post-neuron - synapse index
        logging.debug('Create post-neuron - synapse index')
        self.post_synapse_index = SynapsesIndex(
            len(self.neurons), self.synapses.post)
        self.transmitter_index.shrink()
        self.receiver_index.shrink()
        logging.debug('Create output indexes')
        for layer in self.layers:
            self.output_index.add(layer)
        self.output_index.shrink()


    def deploy_device(self):
        """
        Upload data to device
        """
        logging.debug('Upload data to device')
        for vector in [
            self.layers_vector,
            self.neurons,
            self.synapses,
            self.pre_synapse_index,
            self.post_synapse_index,
            self.stat_vector,
            self.layers_stat,
            self.transmitter_index,
            self.receiver_index,
            self.output_index,
        ]:
            vector.create_device_data_pointer(self.device)
        logging.debug('Domain deployed (name: %s)', self.name)

    def create_synapses(self):
        """
        Создаем физически синапсы
        """
        logging.debug('Create synapses')
        # Domains synapses
        return self.connect_layers()

    def connect_layers(self):
        """
        Реализует непосредственное соединение слоев
        """
        self.random.seed(self.seed)
        layer_config_by_name = {}
        total_synapses = self.synapse_count_by_domain
        # cache
        self_connect_neurons = self.connect_neurons
        for layer_config in self.net.config['layers']:
            layer_config_by_name[layer_config['name']] = layer_config
        domain_index_to_name = []
        for domain_index, domain in enumerate(self.net.config['domains']):
            domain_index_to_name.append(domain['name'])
            total_synapses[domain['name']] = 0
            if domain['name'] == self.name:
                pre_domain_index = domain_index
        # cache neuron -> domain and neuron -> layer in domain
        if 'layer' not in self.cache:
            self.cache['layer'] = {}
            for layer_config in self.net.config['layers']:
                # heihgt x width x z,
                # where z == 0 is domain index in net and
                #       z == 1 is layer index in domain
                self.cache['layer'][layer_config['name']] = \
                    np.zeros(
                        (layer_config['height'], layer_config['width'], 2),
                        dtype=np.int
                    )
                self.cache['layer'][layer_config['name']] \
                        .fill(np.iinfo(np.int).max)
            for domain_index, domain in enumerate(self.net.config['domains']):
                layer_index = -1
                for layer in domain['layers']:
                    layer_index += 1
                    layer = deepcopy(layer)
                    layer_config = layer_config_by_name[layer['name']]
                    shape = layer.get(
                        'shape',
                        [0, 0, layer_config['width'], layer_config['height']]
                    )
                    if shape[0] < 0:
                        shape[0] = 0
                    if shape[1] < 0:
                        shape[1] = 0
                    if shape[0] + shape[2] > layer_config['width']:
                        shape[2] = layer_config['width'] - shape[0]
                    if shape[1] + shape[3] > layer_config['height']:
                        shape[3] = layer_config['height'] - shape[1]
                    layer_cache = \
                            self.cache['layer'][layer_config['name']]
                    for y in xrange(shape[1], shape[1] + shape[3]):
                        layer_cache_y = layer_cache[y]
                        for x in xrange(shape[0], shape[0] + shape[2]):
                            layer_cache_y[x][0] = domain_index
                            layer_cache_y[x][1] = layer_index

        # start connecting
        pre_layer_index = -1
        async_time = time.time()
        for layer_config in self.layers_config:
            pre_layer_index += 1
            # no connections with other layers
            if not layer_config.get('connect'):
                continue
            # pre layer. Connect only neurons in this domain
            layer = layer_config['layer']
            # precache method
            layer_to_address = layer.neurons_metadata.level.to_address
            for connect in layer_config.get('connect', []):
                shift = connect.get('shift', [0, 0])
                if callable(shift[0]):
                    def shift_x():
                        return shift[0]()
                else:
                    def shift_x():
                        return shift[0]
                if callable(shift[1]):
                    def shift_y():
                        return shift[1]()
                else:
                    def shift_y():
                        return shift[1]

                post_layer_config = layer_config_by_name[connect['name']]
                post_info_cache = self.cache['layer'][post_layer_config['name']]
                radius = connect.get('radius', max(
                    int(1.0 * layer_config['width'] \
                        / post_layer_config['width'] / 2),
                    int(1.0 * layer_config['height'] \
                        / post_layer_config['height'] / 2)
                ) + 1)
                def pre_layer_coords():
                    for pre_y in xrange(layer.y, layer.y + layer.height):
                        for pre_x in xrange(layer.x, layer.x + layer.width):
                            yield pre_y, pre_x
                for pre_y, pre_x in pre_layer_coords():
                    # Determine post x coordinate of neuron in post layer.
                    # Should be recalculated for every y because of possible
                    # random shift
                    if time.time() - async_time > 0.1:
                        async_time = time.time()
                        yield (pre_y, layer.width)
                    pre_neuron_address = layer_to_address(
                        pre_x - layer.x,
                        pre_y - layer.y
                    )
                    central_post_x = int(math.floor(
                        1.0 * pre_x / (layer_config['width']) \
                        * (post_layer_config['width'])
                        + (post_layer_config['width'] \
                           / layer_config['width'] / 2.0)
                    )) + shift_x()
                    # determine post y coordinate of neuron in post layer
                    central_post_y = int(math.floor(
                        1.0 * pre_y / (layer_config['height']) \
                        * (post_layer_config['height'])
                        + (post_layer_config['height'] \
                           / layer_config['height'] / 2.0)
                    )) + shift_y()
                    # for all neurons (in post layer) inside of the
                    # connect['radius'] with given central point
                    post_from_range_x = central_post_x - (radius - 1)
                    post_to_range_x = central_post_x + (radius - 1) + 1
                    if post_from_range_x < 0:
                        post_from_range_x = 0
                    if post_from_range_x >= post_layer_config['width']:
                        continue
                    if post_to_range_x < 0:
                        continue
                    if post_to_range_x > post_layer_config['width']:
                        post_to_range_x = post_layer_config['width']

                    post_from_range_y = central_post_y - (radius - 1)
                    post_to_range_y = central_post_y + (radius - 1) + 1
                    if post_from_range_y < 0:
                        post_from_range_y = 0
                    if post_from_range_y >= post_layer_config['height']:
                        continue
                    if post_to_range_y < 0:
                        continue
                    if post_to_range_y > post_layer_config['height']:
                        post_to_range_y = post_layer_config['height']
                    def post_layer_coords():
                        # for neurons in post layer
                        for post_y in xrange(
                            post_from_range_y,
                            post_to_range_y
                        ):
                            post_info_cache_y = post_info_cache[post_y]
                            for post_x in xrange(
                                post_from_range_x,
                                post_to_range_x
                            ):
                                inf = post_info_cache_y[post_x]
                                yield post_x, post_y, inf
                    for post_x, post_y, inf in post_layer_coords():
                        try:
                            # inf[0] - domain index
                            post_info_domain_name = domain_index_to_name[inf[0]]
                        except IndexError:
                            continue
                        # actually create connections
                        if post_info_domain_name == self.name:
                            # inf[1] - post layer index in domain
                            post_layer = self.layers[inf[1]]
                            self.synapse_address += 1
                            self_connect_neurons(
                                pre_neuron_address,
                                post_layer.neurons_metadata.level.to_address(
                                    post_x - post_layer.x,
                                    post_y - post_layer.y
                                ),
                                self.synapse_address
                            )
                        else:
                            # connect neurons with other domains
                            self.connect_remote_neurons(
                                pre_domain_index,
                                pre_layer_index,
                                pre_neuron_address,
                                int(inf[0]), # post domain index
                                int(inf[1]), # post layer index
                                post_x,
                                post_y
                            )
                        total_synapses[post_info_domain_name] += 1

    def create_neurons(self):
        """
        Создаем физически нейроны в ранее созданном векторе
        """
        logging.debug('Create neurons')
        for layer in self.layers:
            layer.create_neurons()

    def connect_neurons(self, pre_address, post_address, synapse_address):
        """
        Соединяем два нейрона с помощью синапса.
        """
        # Speedup this:
        #   synapses = self.synapses_metadata
        #   synapses.pre[synapse_address] = pre_address
        #   synapses.post[synapse_address] = post_address
        synapses_vector = self.synapses
        synapses_metadata = self.synapses_metadata
        if synapse_address >= synapses_metadata.pre.length:
            synapses_metadata.pre.resize()
            synapses_metadata.post.resize()
        synapses_vector.pre.data[synapse_address] = pre_address
        synapses_vector.post.data[synapse_address] = post_address

    def connect_remote_neurons(
        self,
        pre_domain_index, pre_layer_index, pre_neuron_address,
        post_domain_index, post_layer_index, post_x, post_y
    ):
        """
        Соединяем локальный нейрон с нейроном в другом домене
        self == pre_domain
        """
        # local pre neuron is transmitter
        self.neurons.flags[pre_neuron_address] |= IS_TRANSMITTER
        # get post_neuron_domain
        domain = self.net.domains[post_domain_index]
        # connect pre neuron with post neuron in post_neuron_domain
        domain.send_synapse(
            pre_domain_index, pre_layer_index, pre_neuron_address,
            post_layer_index, post_x, post_y)

    def send_synapse_pack(self, bytes=None):
        """
        Получаем сгруппированные данные и синапсах из удаленного домена
        """
        if not bytes:
            return
        packet = TransmitterVector()
        packet.from_bytes(bytes)
        for pos in range(packet.length):
            self.send_synapse(
                packet.pre_domain_index.data[pos],
                packet.pre_layer_index.data[pos],
                packet.pre_neuron_address.data[pos],
                packet.post_layer_index.data[pos],
                packet.post_x.data[pos],
                packet.post_y.data[pos]
            )
        # досылаем остатки
        for domain in self.net.domains:
            if isinstance(domain, RemoteDomainBase):
                domain.send_receiver_index_pack()

    def send_synapse(
        self,
        pre_domain_index, pre_layer_index, pre_neuron_address,
        post_layer_index, post_x, post_y):
        """
        Обрабатываем информацию о синапсе из другого домена
        self == post_domain
        """
        pre_domain = self.net.domains[pre_domain_index]
        pre_layer = pre_domain.layers[pre_layer_index]
        post_layer = self.layers[post_layer_index]
        local_pre_neuron_address \
                = self.receiver_index.get_local_address(
                    pre_domain_index, pre_neuron_address)
        # create IS_RECEIVER neuron
        if not local_pre_neuron_address:
            self.remote_neuron_address += 1
            # add pre_neuron to domain.remote_neurons_metadata if not added (use
            # create_neuron function)
            create_neuron(self.remote_neuron_address,
                          self.remote_neurons_metadata,
                          pre_layer,
                          pre_layer_index)
            # get local_pre_neuron_address
            local_pre_neuron_address = self.remote_neurons_metadata.level \
                    .to_address(self.remote_neuron_address, 0)
            if not self.receiver_index.add(
                local_pre_neuron_address,
                pre_domain_index,
                pre_neuron_address
            ):
                self.stat_inc('receiver_index_again')
            local_pre_neuron_receiver_index = self.receiver_index.pos
            # set IS_RECEIVER flag to pre_neuron
            self.remote_neurons_metadata.flags[self.remote_neuron_address] \
                    |= IS_RECEIVER
            # send local_pre_neuron_address back to source domain (pre_domain)
            pre_domain.send_receiver_index(self.index, pre_neuron_address,
                                           local_pre_neuron_address,
                                           local_pre_neuron_receiver_index)
            self.stat_inc('total_receiver_neurons')
            self.stat_set('send_synapse_time', datetime.datetime.utcnow())
        # get synapse_address
        self.synapse_address += 1
        self.connect_neurons(
            local_pre_neuron_address,
            post_layer.neurons_metadata.level.to_address(
                post_x - post_layer.x,
                post_y - post_layer.y
            ),
            self.synapse_address
        )

    def send_receiver_index_pack(self, bytes=None):
        """
        Получаем сгруппированные данные из удаленного домена
        """
        if not bytes:
            return
        packet = ReceiverVector()
        packet.from_bytes(bytes)
        for pos in xrange(packet.length):
            self.send_receiver_index(
                packet.post_domain_index.data[pos],
                packet.pre_neuron_address.data[pos],
                packet.remote_pre_neuron_address.data[pos],
                packet.remote_pre_neuron_receiver_index.data[pos]
            )

    def send_receiver_index(self, post_domain_index, pre_neuron_address,
                            remote_pre_neuron_address,
                            remote_pre_neuron_receiver_index):
        """
        Запоминаем remote_neuron_address (IS_RECEIVER) для pre_neuron_address
        (IS_TRANSMITTER)
        self == pre_domain
        """
        pre_domain = self
        if not pre_domain.transmitter_index.add(
            pre_neuron_address, post_domain_index, remote_pre_neuron_address,
            remote_pre_neuron_receiver_index
        ):
            pre_domain.stat_inc('transmitter_index_again')
        pre_domain.stat_inc('total_transmitter_neurons')
        pre_domain.stat_set('send_receiver_index_time',
                            datetime.datetime.utcnow())

    def send_spikes(self):
        """
        Получаем спайки из устройства (device) и отправляем их в другие домены.
        """
        # step 4
        self.device.tick_transmitter_index(self)
        # step 5
        self.transmitter_index.is_spiked.from_device(self.device)
        index = self.transmitter_index
        domains = self.net.domains
        remote_domain_data = index.remote_domain.data
        remote_receiver_index_data = index.remote_receiver_index.data
        register_spike = [domain.register_spike for domain in domains]
        for i, is_spiked in enumerate(index.is_spiked.data):
            if not is_spiked:
                continue
            receiver_neuron_index = remote_receiver_index_data[i]
            register_spike[remote_domain_data[i]](receiver_neuron_index)
        for post_domain in self.net.domains:
            if post_domain != self:
                # если post_domain локальный, то ничего не произойдет
                # если post_domain удаленный и у него накопились спайки,
                # то он опубликует эти спайки
                post_domain.register_spike_pack()

    def receive_spikes(self):
        """
        Получаем спайки из других доменов, формируем receiver index и копируем
        его в устройство (device).
        """
        # step 3
        # send to device info about new spikes
        self.receiver_index.is_spiked.to_device(self.device)
        self.device.tick_receiver_index(self)
        # get is_spiked filled with 0
        self.receiver_index.is_spiked.from_device(self.device)

    def register_spike_pack(self, bytes=None):
        """
        Обрабатываем спайки из удаленных доменов
        """
        if not bytes:
            return
        packet = SpikesVector()
        packet.from_bytes(bytes)
        self.stat_inc('spikes_received', packet.length)
        # speedup for commented version
        #for pos in range(packet.length):
        #    self.register_spike(
        #        packet.receiver_neuron_index.data[pos],
        #    )
        self.receiver_index.is_spiked.data[packet.receiver_neuron_index.data] \
                = 1


    def register_spike(self, receiver_neuron_index):
        """
        Записывает в домен пришедший спайк
        """
        # step 2
        self.receiver_index.is_spiked.data[receiver_neuron_index] = 1

    def register_input_layer_data(self, layer_index, data):
        """
        Регистрирует данные (в виде обычного или сериализованного numpy
        массива), пришедшие из других доменов.
        """
        if isinstance(self.device, device.IOBase):
            self.register_input_layer_data = self._register_input_io_data
        else:
            self.register_input_layer_data = self._register_input_layer_data
        return self.register_input_layer_data(layer_index, data)

    def _register_input_layer_data(self, layer_index, data):
        """
        Регистрирует данные (в виде обычного или сериализованного numpy
        массива), пришедшие из других доменов.
        """
        self.layers[layer_index].register_input_data(data, self.ticks)

    def _register_input_io_data(self, layer_index, data):
        """
        Сохраняет данные (в виде обычного или сериализованного numpy массива)
        для использования в IO устройствах, пришедшие из других доменов.
        """
        self.device.register_input_data(layer_index, data, self.ticks)


    def tick(self):
        """
        Один tick домена.
        0.
            - self.ticks++
            - self.total_spikes = 0 (эта информация накапливается в
                domain.stat_vector для поля 0)
        1. по всем слоям layer:
            - layer.total_spikes = 0 (эта информация накапливается в
                domain.layers_stat для поля 0)
            и по всем нейронам neuron в слое layer (device):
            - если neuron.flags & IS_DEAD - не обсчитываем нейрон
            - если флаг IS_SPIKED уже установлен - снимаем
            - если это IS_RECEIVER - заканчиваем обсчет нейрона
            - если neuron.level >= layer.threshold:
                - у neuron.flags устанавливаем флаг IS_SPIKED,
                - layer.total_spikes++
                - domain.total_spikes++
                - обнуляем neuron.level (либо уменьшаем neuron.level на
                  layer.threshold, что бы можно было сделать генераторы
                  импульсов. Тут надо подумать.)
                - neuron.tick = domain.tick
            в противном случае:
                - neuron.level -= layer.relaxation
            - если neuron.level < 0, то neuron.level = 0

        2. по всем сообщениям о спайках пришедшим из других доменов (cpu):
            - если сообщений не было - пропускаем шаг 3.
            - в противном случае формируем receiver index и копируем его в
              устройство (device)

        3. по всем записям в receiver index (device):
            - устанавливаем флаг IS_SPIKED у нейрона по адресу index.address[i]

        4. по всем записям в transmitter index (device):
            - если по адресу index.address у нейрона neuron.flags & IS_SPIKED,
              устанавливаем флаг IS_SPIKED у index.flags, в противном случае
              снимаем флаг

        5. получаем из устройства (device) transmitter index (cpu):
            - формируем сообщения для тех нейронов, у которых произошел спайк и
              асинхронно отправляем их в другие домены.

        6. по всем записям в pre_synapse_index.key (device):
            - если pre_synapse_index.key[i] == null - заканчиваем обсчет
            - если neuron.flags & IS_DEAD - обнуляем все синапсы
              (synapse.level = 0)
            - если не neuron.flags & IS_SPIKED, то заканчиваем обсчет
            - по всем записям в pre_synapse_index.value, относящимся к
              pre_synapse_index.key[i]:
                - если synapse.level == 0 - считаем что синапс мертв и не
                  обсчитываем дальше внутренний цикл
                - если post.flags & IS_DEAD - удаляем синапс (synapse.level = 0)
                  и не обсчитываем дальше внутренний цикл
                - если дошли до этого места, то neuron.flags & IS_SPIKED и
                  делаем:
                    - post.level += (neuron.flags & IS_INHIBITORY ?
                      -synapse.level : synapse.level)
                    # Обучение синапсов к post нейронам
                    - если neuron.tick - post.tick
                        < domain.spike_learn_threshold,
                      то увеличиваем вес синапса. Вес можно увеличивать,
                      например, как f(neuron.tick - post.tick), либо на
                      фиксированное значение
                    - если neuron.tick - post.tick
                        >= domain.spike_forget_threshold,
                      то уменьшаем вес синапса. Вес можно уменьшать, например,
                      как f(neuron.tick - post.tick), либо на фиксированное
                      значение
            - по всем записям в post_synapse_index.value, относящимся к
              post_synapse_index.key[i]:
                - если synapse.level == 0 - считаем что синапс мертв и не
                  обсчитываем дальше внутренний цикл
                - если pre.flags & IS_DEAD - удаляем синапс (synapse.level = 0)
                  и не обсчитываем дальше внутренний цикл
                # Обучение синапсов от pre нейронов
                - если neuron.tick - pre.tick <= domain.spike_learn_threshold,
                  то увеличиваем вес синапса. Вес можно увеличивать, например,
                  как f(neuron.tick - pre.tick), либо на фиксированное значение
                - если neuron.tick - pre.tick >= domain.spike_forget_threshold,
                  то уменьшаем вес синапса. Вес можно уменьшать, например, как
                  f(neuron.tick - pre.tick), либо на фиксированное значение

        """
        # step 0
        self.ticks += 1
        self.stat_set('ticks', self.ticks)
        # step 1
        self.device.tick_neurons(self)
        # step 2 & 3
        self.receive_spikes()
        # step 4 & 5
        self.send_spikes()
        # step 6
        self.device.tick_synapses(self)

    def clean(self):
        self.device.clean()