From 6a083481e17cedd223064d2728a10e000af42ac3 Mon Sep 17 00:00:00 2001
From: Eloy Retamino <retamino@ugr.es>
Date: Thu, 19 Sep 2019 07:15:43 +0000
Subject: [PATCH] Merged in loaddistributedexp (pull request #8)

[NRPJP-83] errors in loading experiments were due to incorrectly parsing populations attached to devices. This commit fixes it.

* [NRPJP-83] errors in loading experiments were due to incorrectly parsing populations attached to devices. This commit fixes it.

* [NRRPLT-83] commented out annoying print

* [NRRPLT-83] Added suggestions from reviewer and removed unnecessary commented code

* [NRRPLT-83] Rearranged some code into a function + added function descriptions

* [NRRPLT-83] Fixed bugs introduced along PR

* [NRRPLT-83] Fixed index_from_assembly not considering that populations can be instances of Population

Approved-by: Kepa Cantero <cantero@fortiss.org>
Approved-by: Ugo Albanese <ugo.albanese@santannapisa.it>
---
 .../DistributedPyNNCommunicationAdapter.py    | 46 +++++++++++---
 .../launch/NestBrainProcess.py                | 61 ++++++++++++++++---
 2 files changed, 92 insertions(+), 15 deletions(-)

diff --git a/hbp_nrp_distributed_nest/hbp_nrp_distributed_nest/cle/DistributedPyNNCommunicationAdapter.py b/hbp_nrp_distributed_nest/hbp_nrp_distributed_nest/cle/DistributedPyNNCommunicationAdapter.py
index 6c803c4..c502e1d 100644
--- a/hbp_nrp_distributed_nest/hbp_nrp_distributed_nest/cle/DistributedPyNNCommunicationAdapter.py
+++ b/hbp_nrp_distributed_nest/hbp_nrp_distributed_nest/cle/DistributedPyNNCommunicationAdapter.py
@@ -33,6 +33,8 @@ import hbp_nrp_cle.tf_framework.config as tf_config
 
 from mpi4py import MPI
 
+import pyNN.nest as sim
+
 import logging
 import time
 
@@ -70,8 +72,12 @@ class DistributedPyNNCommunicationAdapter(PyNNNestCommunicationAdapter):
         device.timestep = ts
 
         # mark the device as MPI-aware, only used by Nest-specific devices
-        if isinstance(device, PyNNNestDevice):
+        if not isinstance(populations, list) and isinstance(device, PyNNNestDevice):
             setattr(device, 'mpi_aware', True)
+        else:
+            for d in device.devices:
+                if isinstance(d, PyNNNestDevice):
+                    setattr(d, 'mpi_aware', True)
         return device
 
     def register_spike_sink(self, populations, spike_detector_type, **params):
@@ -135,18 +141,42 @@ class DistributedPyNNCommunicationAdapter(PyNNNestCommunicationAdapter):
 
         # population information to send to the remote MPI nodes, we can't pickle Populations
         # directly and those references wouldn't be valid on the remote nodes anyway
-        if populations.label in tf_config.brain_root.__dict__:
-            label = populations.label
-        else:
-            label = populations.parent.label
-        mask = populations.mask if populations.mask else slice(0, len(populations), 1)
+
+        # create a structure describing 'populations'
+        br = tf_config.brain_root
+        assemblies = []
+        if not isinstance(populations, list):
+            populations = [populations]
+
+        def index_from_population_view(p):
+            """
+            Returns neuron indices of a PopulationView in the root Population and the root label
+            """
+            label = p.grandparent.label
+            indices = p.index_in_grandparent(range(p.size))
+            return label, indices
+
+        def index_from_assembly(a):
+            """
+            Returns neuron indices and labels for each population in an Assembly
+            """
+            return [index_from_population_view(p) if isinstance(p, sim.PopulationView) else
+                    (p.label, None) for p in a.populations]
+
+        for population in populations:
+            if isinstance(population, sim.Population):
+                assemblies += [[(population.label, None)]]
+            elif isinstance(population, sim.PopulationView):
+                assemblies += [[index_from_population_view(population)]]
+            elif isinstance(population, sim.Assembly):
+                assemblies += [index_from_assembly(population)]
 
         # propagate the synapse creation parameters to all remote notes, they will run the same
         # connection/creation commands after receiving these messages, guaranteed to be
         # run from CLE MPI process 0 only
         for rank in xrange(1, MPI.COMM_WORLD.Get_size()):
-            MPI.COMM_WORLD.send({'command': 'ConnectTF', 'type': kind, 'label': label,
-                                 'mask': mask, 'device': device, 'timestep': timestep,
+            MPI.COMM_WORLD.send({'command': 'ConnectTF', 'type': kind, 'assemblies': assemblies,
+                                 'device': device, 'timestep': timestep,
                                  'params': params},
                                 dest=rank, tag=NestBrainProcess.MPI_MSG_TAG)
 
diff --git a/hbp_nrp_distributed_nest/hbp_nrp_distributed_nest/launch/NestBrainProcess.py b/hbp_nrp_distributed_nest/hbp_nrp_distributed_nest/launch/NestBrainProcess.py
index 279506e..a887d5b 100644
--- a/hbp_nrp_distributed_nest/hbp_nrp_distributed_nest/launch/NestBrainProcess.py
+++ b/hbp_nrp_distributed_nest/hbp_nrp_distributed_nest/launch/NestBrainProcess.py
@@ -137,7 +137,7 @@ class NestBrainProcess(object):
             elif data == 'step':
 
                 # run the coordinated simulation step
-                print "[MPI] ===================== step ======================="
+                #print "[MPI] ===================== step ======================="
                 self._brain_controller.run_step(self._timestep * 1000.0)  # msec
                 self._brain_communicator.refresh_buffers(0.0)
 
@@ -173,11 +173,54 @@ class NestBrainProcess(object):
         :param params The connectivity/synapse parameters passed by the CLE.
         """
 
-        # get the population of neurons from our dictionary, we can guarantee this is a valid
-        # population that has been declared in the BIBI at this point as the CLE will validate
-        # before sending us the create message
-        brain_pop = sim.PopulationView(tf_config.brain_root.__dict__[params['label']],
-                                       selector=params['mask'])
+        # get the population of neurons from params
+
+        def get_population_from_label(pop, label):
+            """
+            Given an input population and a label returns a population with the specified label.
+            This can be the population itself or its root (grandparent)
+            """
+            if pop.label == label:
+                return pop
+            elif isinstance(pop, sim.PopulationView) and pop.grandparent.label == label:
+                return pop.grandparent
+            else:
+                return None
+
+        def get_population_from_brain(label):
+            """
+            Finds a population in brain with an specified label.
+            """
+
+            # check if label in brain
+            if label in tf_config.brain_root.__dict__:
+                return tf_config.brain_root.__dict__[label]
+            # check if label in circuit populations
+            elif 'circuit' in tf_config.brain_root.__dict__:
+                circuit = tf_config.brain_root.__dict__['circuit']
+                pops = circuit.populations if isinstance(circuit,sim.Assembly) else [circuit]
+                for pop in pops:
+                    p = get_population_from_label(pop,label)
+                    if p:
+                        return p
+            # could not find population
+            return None
+
+        populations = []
+        for assembly in params['assemblies']:
+            a = []
+            for p in assembly:
+                population = get_population_from_brain(p[0])
+                if population and p[1] is not None:
+                    population = sim.PopulationView(population,p[1])
+                a += [population]
+
+            if len(a) == 1:
+                populations += [a[0]]
+            else:
+                populations += [sim.Assembly(*a)]
+
+        brain_pop = populations[0] if len(populations) == 1 else populations
 
         # perform the actual device creation/connection
         if params['type'] == 'sink':
@@ -193,8 +236,12 @@ class NestBrainProcess(object):
         device.timestep = params['timestep']
 
         # mark the device as MPI-aware, only used by Nest-specific devices
-        if isinstance(device, PyNNNestDevice):
+        if not isinstance(brain_pop,list) and isinstance(device, PyNNNestDevice):
             setattr(device, 'mpi_aware', True)
+        else:
+            for d in device.devices:
+                if isinstance(d, PyNNNestDevice):
+                    setattr(d, 'mpi_aware', True)
 
         # store the device in a way that we can easily retrieve later
         self.devices[device.timestep] = device
-- 
GitLab