def _old_graph_tool(version_min): ''' Check for old versions of graph-tool for which some functions are not working. ''' return (nngt.get_config('backend') == 'graph-tool' and nngt.get_config('library').__version__[:4] < version_min)
def setup_module(): ''' setup any state specific to the execution of the current module.''' with_plot = nngt.get_config("with_plot") with_nest = nngt.get_config("with_nest") nngt.set_config("with_plot", False) nngt.set_config("with_nest", False)
def test_config(): ''' Check get/set_config functions. ''' old_cfg = nngt.get_config(detailed=True) # get config cfg = nngt.get_config() for k, v in cfg.items(): assert v == nngt.get_config(k) cfg_detailed = nngt.get_config(detailed=True) for k, v in cfg_detailed.items(): assert v == nngt.get_config(k) # set config (omp) num_omp = 2 nngt.set_config("omp", num_omp) assert nngt.get_config("multithreading") assert nngt.get_config("omp") == num_omp # set config (mpi) has_mpi = False try: import mpi4py has_mpi = True except: pass if has_mpi: nngt.set_config("mpi", True) assert nngt.get_config("mpi") assert not nngt.get_config("multithreading") assert nngt.get_config("omp") == 1 # key error key_error = False try: nngt.set_config("random_entry", "plop") except KeyError: key_error = True assert key_error # except for palettes nngt.set_config("palette_continuous", "viridis") nngt.set_config("palette_discrete", "Set2") # restore old config nngt.set_config(old_cfg)
def test_model_properties(self, graph, instructions, **kwargs): ''' When generating graphs from on of the preconfigured models, check that the expected properties are indeed obtained. ''' if nngt.get_config("backend") != "nngt" and nngt.on_master_process(): graph_type = instructions["graph_type"] ref_result = self.theo_prop[graph_type](instructions) computed_result = self.exp_prop[graph_type](graph, instructions) if graph_type == 'distance_rule': # average degree self.assertTrue( ref_result[0] == computed_result[0], "Avg. deg. for graph {} failed:\nref = {} vs exp {}\ ".format(graph.name, ref_result[0], computed_result[0])) # average error on distance distribution sqd = np.square( np.subtract(ref_result[1:], computed_result[1:])) avg_sqd = sqd / np.square(computed_result[1:]) err = np.sqrt(avg_sqd).mean() tolerance = (self.tolerance if instructions['rule'] == 'lin' else 0.25) self.assertTrue( err <= tolerance, "Distance distribution for graph {} failed:\nerr = {} > {}\ ".format(graph.name, err, tolerance)) elif nngt.get_config("backend") == "nngt": from mpi4py import MPI comm = MPI.COMM_WORLD num_proc = comm.Get_size() graph_type = instructions["graph_type"] ref_result = self.theo_prop[graph_type](instructions) computed_result = self.exp_prop[graph_type](graph, instructions) if graph_type == 'distance_rule': # average degree self.assertTrue( ref_result[0] == computed_result[0] * num_proc, "Avg. deg. for graph {} failed:\nref = {} vs exp {}\ ".format(graph.name, ref_result[0], computed_result[0])) # average error on distance distribution sqd = np.square( np.subtract(ref_result[1:], computed_result[1:])) avg_sqd = sqd / np.square(computed_result[1:]) err = np.sqrt(avg_sqd).mean() tolerance = (self.tolerance if instructions['rule'] == 'lin' else 0.25) self.assertTrue( err <= tolerance, "Distance distribution for graph {} failed:\nerr = {} > {}\ ".format(graph.name, err, tolerance))
def seed(msd=None, seeds=None): ''' Seed the random generator used by NNGT (i.e. the numpy `RandomState`: for details, see :class:`numpy.random.RandomState`). Parameters ---------- msd : int, optional Master seed for numpy `RandomState`. Must be convertible to 32-bit unsigned integers. seeds : list of ints, optional Seeds for `RandomState` (when using MPI). Must be convertible to 32-bit unsigned integers, one entry per MPI process. ''' # when using MPI numpy seeeds are sync-ed via the mpi_random decorator msd = np.random.randint(0, 2**31 - 1) if msd is None else msd # seed both random state and new generator np.random.seed(msd) nngt._rng = np.random.default_rng(msd) nngt._config['msd'] = msd nngt._seeded = True nngt._seeded_local = False # check subseeds if seeds is not None: with_mt = nngt.get_config('multithreading') with_mpi = nngt.get_config('mpi') err = 'Expected {} seeds.' if with_mpi: from mpi4py import MPI comm = MPI.COMM_WORLD size = comm.Get_size() assert size == len(seeds), err.format(size) nngt._config['seeds'] = seeds elif with_mt: num_omp = nngt.get_config('omp') assert num_omp == len(seeds), err.format(num_omp) nngt._config['seeds'] = seeds nngt._seeded_local = True nngt._used_local = False
def test_plot_prop(): num_nodes = 50 net = ng.erdos_renyi(nodes=num_nodes, avg_deg=5) net.set_weights(distribution="gaussian", parameters={"avg": 5, "std": 0.5}) net.new_node_attribute("attr", "int", values=np.random.randint(-10, 20, num_nodes)) nplt.degree_distribution(net, ["in", "out", "total"], show=False) nplt.edge_attributes_distribution(net, "weight", colors="g", show=False) nplt.node_attributes_distribution(net, "out-degree", colors="r", show=False) if nngt.get_config("backend") != "nngt": nplt.edge_attributes_distribution(net, ["betweenness", "weight"], colors=["g", "b"], axtitles=["Edge betw.", "Weights"], show=False) nplt.node_attributes_distribution( net, ["betweenness", "attr", "out-degree"], colors=["r", "g", "b"], show=False)
def seed(msd=None, seeds=None): ''' Seed the random generator used by NNGT (i.e. the numpy `RandomState`: for details, see :class:`numpy.random.RandomState`). ..versionchanged:: 0.8 Renamed `seed` to `msd`, added `seeds` for multithreading. Parameters ---------- msd : int, optional Master seed for numpy `RandomState`. Must be convertible to 32-bit unsigned integers. seeds : array of ints, optional Seeds for for `RandomState`. Must be convertible to 32-bit unsigned integers. ''' if msd is None and nngt.get_config("mpi"): # when using MPI we need to sync the seeds msd_tmp = np.random.randint(0, 2**32 - 1) msd_tmp = nngt.get_config("mpi_comm").bcast(msd_tmp, root=0) np.random.seed(msd_tmp) else: np.random.seed(msd) if msd is None: nngt._config['msd'] = np.random.get_state()[1][0] else: nngt._config['msd'] = msd nngt._seeded = True # check subseeds if seeds is not None: with_mt = nngt.get_config('multithreading') with_mpi = nngt.get_config('mpi') err = 'Expected {} seeds.' if with_mpi: from mpi4py import MPI comm = MPI.COMM_WORLD size = comm.Get_size() assert size == len(seeds), err.format(size) nngt._config['seeds'] = seeds elif with_mt: num_omp = nngt.get_config('omp') assert num_omp == len(seeds), err.format(num_omp) nngt._config['seeds'] = seeds
class TestGraphClasses(TestBasis): ''' Class testing the main methods of :class:`~nngt.Graph` and its subclasses. ''' matrices = {} mat_gen = { "from_scipy_sparse_rand": ssp.rand, "from_numpy_randint": np.random.randint } @property def test_name(self): return "test_graphclasses" @unittest.skipIf(nngt.get_config('mpi'), 'Not checking for MPI') def gen_graph(self, graph_name): di_instructions = self.parser.get_graph_options(graph_name) mat = self.mat_gen[graph_name](**di_instructions) self.matrices[graph_name] = mat graph = nngt.Graph.from_matrix(mat) graph.set_name(graph_name) return graph, di_instructions @foreach_graph def test_adj_mat(self, graph, **kwargs): ''' When generating graphs from :class:`numpy.ndarray`s or :class:`scipy.sparse` matrices, check that the result of graph.adjacency_matrix() is the same as the initial matrix. ''' ref_result = ssp.csr_matrix(self.matrices[graph.get_name()]) computed_result = graph.adjacency_matrix() self.assertTrue((ref_result != computed_result).nnz == 0, "AdjMat test failed for graph {}:\nref = {} vs exp {}\ ".format(graph.name, ref_result, computed_result)) @foreach_graph @unittest.skipIf( nngt._config["backend"] == "graph-tool" and nngt._config["library"].__version__.startswith("2.22"), "Known bug with graph-tool 2.22.") def test_copy_clear(self, graph, **kwargs): ''' Test that the copied graph is indeed the same as the original, but that all its properties are deep copies. Then check that clear_edges() removes all edges and no nodes. ''' ref_result = (graph.node_nb(), graph.edge_nb(), graph.node_nb(), 0) copied = graph.copy() self.assertIsNot(copied, graph) computed_result = [copied.node_nb(), copied.edge_nb()] copied.clear_all_edges() computed_result.extend((copied.node_nb(), copied.edge_nb())) self.assertEqual( ref_result, tuple(computed_result), "Copy test failed for graph {}:\nref = {} vs exp {}\ ".format(graph.name, ref_result, computed_result))
def wrapper(*args, **kwargs): self = args[0] nx = nngt.get_config("graph_library") == "networkx" for graph_name in self.graphs: if nx and "corr" in graph_name: print("Skipping correlated attributes with networkx") else: g, di = self.gen_graph(graph_name) func(self, g, instructions=di, **kwargs)
def _mpi_and_random_init(): ''' Init MPI comm and information and seed the ''' comm = MPI.COMM_WORLD size = comm.Get_size() rank = comm.Get_rank() # Random number generation seeding if rank == 0: msd = nngt.get_config('msd') else: msd = None msd = comm.bcast(msd, root=0) seeds = nngt.get_config('seeds') seed = seeds[rank] if seeds is not None else msd + rank + 1 np.random.seed(seed) return comm, size, rank
def test_connect_switch_distance_rule_max_proba(): num_omp = nngt.get_config("omp") mthread = nngt.get_config("multithreading") # switch multithreading to False nngt.set_config("multithreading", False) pop = nngt.NeuralPop.exc_and_inhib(1000) radius = 100. shape = nngt.geometry.Shape.disk(radius) net = nngt.SpatialNetwork(population=pop, shape=shape) max_proba = 0.1 avg, std = 10., 1.5 weights = {"distribution": "gaussian", "avg": avg, "std": std} ng.connect_nodes(net, pop.inhibitory, pop.excitatory, "distance_rule", scale=5 * radius, max_proba=max_proba, weights=weights) assert net.edge_nb() <= len(pop.inhibitory) * len( pop.excitatory) * max_proba # check weights ww = net.get_weights() assert avg - 0.5 * std < ww.mean() < avg + 0.5 * std assert 0.75 * std < ww.std() < 1.25 * std # restore mt parameters nngt.set_config("mpi", False) nngt.set_config("omp", num_omp) nngt.set_config("multithreading", mthread)
def teardown_function(function): ''' Cleanup the file ''' if nngt.get_config("mpi"): from mpi4py import MPI comm = MPI.COMM_WORLD.Clone() comm.Barrier() try: os.remove(gfilename) except: pass
def _library_load(filename, fmt): ''' Load the file using the library functions ''' if nngt.get_config("backend") == "networkx": import networkx as nx if fmt == "graphml": return nx.read_graphml(filename) else: raise NotImplementedError elif nngt.get_config("backend") == "igraph": import igraph as ig if fmt == "graphml": return ig.Graph.Read_GraphML(filename) else: raise NotImplementedError elif nngt.get_config("backend") == "graph-tool": import graph_tool as gt return gt.load_graph(filename, fmt=fmt) else: raise NotImplementedError
def wrapper(*args, **kwargs): self = args[0] partial_backend = nngt.get_config("backend") in ("networkx", "nngt") for graph_name in self.graphs: if partial_backend and "corr" in graph_name: _log_message(logger, "DEBUG", "Skipping correlated attributes with " "networkx and nngt backends.") else: generated = self.gen_graph(graph_name) # check for None when using MPI if generated is not None: g, di = generated func(self, g, instructions=di, **kwargs)
def _mpi_and_random_init(): ''' Init MPI comm and information and seed the RNGs ''' comm = MPI.COMM_WORLD.Clone() size = comm.Get_size() rank = comm.Get_rank() # Random number generation seeding seeds = None if not nngt._seeded_local: # no local seeds were generated, set them from initial msd msd = nngt.get_config("msd") seeds = [msd + i + 1 for i in range(size)] nngt._config['seeds'] = seeds elif not nngt._used_local: # local seeds were generated but not used, use them seeds = nngt.get_config('seeds') else: # local seeds were generated and used, generate new ones from new msd if rank == 0: msd = np.random.randint(0, 2**31 - size - 1) else: msd = None msd = comm.bcast(msd, root=0) seeds = [msd + i + 1 for i in range(size)] seed = seeds[rank] np.random.seed(seed) nngt._seeded_local = True nngt._used_local = True return comm, size, rank
def test_random_unseeded(): num_seeds = get_num_seeds() nngt.seed(msd=42) # check that seeds generated by first call indeed allow to reproduce the # graph in later calls g1 = ng.gaussian_degree(10, 1, nodes=100) seeds = nngt.get_config("seeds") nngt.seed(msd=42, seeds=seeds) g2 = ng.gaussian_degree(10, 1, nodes=100) assert np.all(g1.edges_array == g2.edges_array)
def _set_main_db(): if nngt.get_config("db_url") is None or nngt.get_config("db_to_file"): # check for db_folder abs_dbfolder = os.path.abspath( os.path.expanduser(nngt.get_config("db_folder"))) if not os.path.isdir(abs_dbfolder): try: os.mkdir(abs_dbfolder) except OSError as e: if e.errno != errno.EEXIST: raise # create database db_file = abs_dbfolder + "/" + nngt.get_config("db_name") + ".db" nngt._main_db = SqliteDatabase(db_file, pragmas=( ('journal_mode', 'wal'), ('cache_size', -1024 * 64), ('foreign_keys', 'on'), )) else: nngt._main_db = connect(nngt.get_config('db_url'), fields={'longblob': 'longblob'})
def test_group_vs_type(): ''' Gaussian degree with groups and types ''' # first with groups nngt.seed(0) pop = nngt.NeuralPop.exc_and_inhib(1000) igroup = pop["inhibitory"] egroup = pop["excitatory"] net1 = nngt.Network(population=pop) all_groups = list(pop.keys()) # necessary to have same order as types avg_e = 50 std_e = 5 ng.connect_groups(net1, egroup, all_groups, graph_model="gaussian_degree", avg=avg_e, std=std_e, degree_type="out-degree") avg_i = 100 std_i = 5 ng.connect_groups(net1, igroup, all_groups, graph_model="gaussian_degree", avg=avg_i, std=std_i, degree_type="out-degree") # then with types nngt.seed(0) pop = nngt.NeuralPop.exc_and_inhib(1000) igroup = pop["inhibitory"] egroup = pop["excitatory"] net2 = nngt.Network(population=pop) avg_e = 50 std_e = 5 ng.connect_neural_types(net2, 1, [-1, 1], graph_model="gaussian_degree", avg=avg_e, std=std_e, degree_type="out-degree") avg_i = 100 std_i = 5 ng.connect_neural_types(net2, -1, [-1, 1], graph_model="gaussian_degree", avg=avg_i, std=std_i, degree_type="out-degree") # call only on root process (for mpi) unless using distributed backend if nngt.on_master_process() or nngt.get_config("backend") == "nngt": # check that both networks are equals assert np.all(net1.get_degrees() == net2.get_degrees())
def wrapper(*args, **kwargs): # when using MPI, make sure everyone waits for the others try: from mpi4py import MPI comm = MPI.COMM_WORLD comm.Barrier() except ImportError: pass # check backend ("nngt" is fully parallel, not the others) backend = False if not logging: backend = nngt.get_config("backend") == "nngt" if backend or on_master_process(): return func(*args, **kwargs) else: return None
class TestBasics(unittest.TestCase): ''' Class testing the basic methods of the Graph object. ''' tolerance = 1e-6 @property def test_name(self): return "test_basics" @unittest.skipIf(nngt.get_config('mpi'), 'Not checking for MPI') def test_node_creation(self): ''' When making graphs, test node creation function. ''' g = nngt.Graph(100, name="new_node_test") self.assertTrue( g.node_nb() == 100, '''Error on graph {}: invalid initial nodes ({} vs {} expected). '''.format(g.name, g.node_nb(), 100)) n = g.new_node() self.assertTrue( g.node_nb() == 101 and n == 100, '''Error on graph {}: ({}, {}) vs (101, 100) expected. '''.format(g.name, g.node_nb(), n)) nn = g.new_node(2) self.assertTrue( g.node_nb() == 103 and nn[0] == 101 and nn[1] == 102, '''Error on graph {}: ({}, {}, {}) vs (103, 101, 102) expected. '''.format(g.name, g.node_nb(), nn[0], nn[1])) def test_new_node_attr(self): ''' Test node creation with attributes. ''' shape = nngt.geometry.Shape.rectangle(1000., 1000.) g = nngt.SpatialGraph(100, shape=shape, name="new_node_spatial") self.assertTrue( g.node_nb() == 100, '''Error on graph {}: invalid initial nodes ({} vs {} expected). '''.format(g.name, g.node_nb(), 100)) n = g.new_node(positions=[(0, 0)]) self.assertTrue( np.all(np.isclose(g.get_positions(n), (0, 0), self.tolerance)), '''Error on graph {}: last position is ({}, {}) vs (0, 0) expected. '''.format(g.name, *g.get_positions(n)))
def edges_array(self): ''' Edges of the graph, sorted by order of creation, as an array of 2-tuple. ''' # this dirty check is necessary to work with old versions of graph-tool gt = nngt.get_config('library') gt_major_version = int(gt.__version__[0]) gt_minor_version = int(gt.__version__[2:4]) edges = None if gt_major_version == 2 and gt_minor_version < 22: return np.array( [(int(e.source()), int(e.target())) for e in self.edges()]) else: edges = self.get_edges() order = np.argsort(edges[:, 2]) return edges[order, :2]
def test_library_plot(): ''' Check that plotting with the underlying backend library works ''' pop = nngt.NeuralPop.exc_and_inhib(50) g = ng.newman_watts(4, 0.2, population=pop) g.set_weights(np.random.uniform(1, 5, g.edge_nb())) nplt.library_draw(g, show=False) nplt.library_draw(g, ncolor="total-degree", ecolor="k", show=False) if nngt.get_config("backend") != "nngt": nplt.library_draw(g, ncolor="in-degree", ecolor="betweenness", esize='weight', max_esize=5, show=False) nplt.library_draw(g, nshape="s", esize="weight", layout="random", show=False) nplt.library_draw(g, nshape="s", esize="weight", layout="random", show=False) nplt.library_draw(g, ncolor="in-degree", esize="weight", layout="circular", show=False) edges = g.edges_array[::5] nplt.library_draw(g, ncolor="in-degree", esize="weight", layout="circular", restrict_edges=edges, show=False)
def test_random_seeded(): num_seeds = get_num_seeds() # test equality of graph generated with same seeds nngt.seed(msd=0, seeds=[i for i in range(1, num_seeds + 1)]) g1 = ng.gaussian_degree(10, 1, nodes=100) nngt.seed(msd=0, seeds=[i for i in range(1, num_seeds + 1)]) g2 = ng.gaussian_degree(10, 1, nodes=100) assert np.all(g1.edges_array == g2.edges_array) # check that subsequent graphs are different g3 = ng.gaussian_degree(10, 1, nodes=100) # with mpi onnon-distributed backends, test only on master process if nngt.get_config("backend") == "nngt" or nngt.on_master_process(): if g3.edge_nb() == g2.edge_nb(): assert np.any(g2.edges_array != g3.edges_array)
class TestExamples(unittest.TestCase): ''' Class testing saving and loading functions. ''' example_files = [ example_dir + f for f in os.listdir(example_dir) if isfile(join(example_dir, f)) ] @classmethod def tearDownClass(cls): try: os.remove("sp_graph.el") except: pass @property def test_name(self): return "test_examples" @unittest.skipIf(int(environ.get("OMP", 1)) == 1, 'Check only with OMP') @unittest.skipIf(nngt.get_config('mpi'), 'Not checking for MPI') def test_examples(self): ''' Test that the example files execute correctly. ''' for example in self.example_files: if example.endswith('.py'): try: try: execfile(example) except NameError: # python 3+ with open(example) as f: code = compile(f.read(), example, 'exec') exec(code, glob) except NotImplementedError: pass # potential IO error for gt <= 2.22
pop = nngt.NeuralPop.exc_and_inhib(num_neurons, en_model=neuron_model, in_model=neuron_model, syn_spec=synapses) # create the network and send it to NEST w_prop = {"distribution": "gaussian", "avg": 0.1, "std": .05} net = nngt.generation.gaussian_degree(avg_degree, std_degree, population=pop, weights=w_prop) ''' Send to NEST and set excitation and recorders ''' if nngt.get_config('with_nest'): import nest import nngt.simulation as ns nest.ResetKernel() gids = net.to_nest() # add noise to the excitatory neurons excs = list(pop["excitatory"].nest_gids) ns.set_noise(excs, 10., 2.) # record groups = [key for key in net.population] recorder, record = ns.monitor_groups(groups, net) '''
nngt._config["backend"] == "graph-tool" and nngt._config["library"].__version__.startswith("2.22"), "Known bug with graph-tool 2.22.") def test_copy_clear(self, graph, **kwargs): ''' Test that the copied graph is indeed the same as the original, but that all its properties are deep copies. Then check that clear_edges() removes all edges and no nodes. ''' ref_result = (graph.node_nb(), graph.edge_nb(), graph.node_nb(), 0) copied = graph.copy() self.assertIsNot(copied, graph) computed_result = [copied.node_nb(), copied.edge_nb()] copied.clear_all_edges() computed_result.extend((copied.node_nb(), copied.edge_nb())) self.assertEqual( ref_result, tuple(computed_result), "Copy test failed for graph {}:\nref = {} vs exp {}\ ".format(graph.name, ref_result, computed_result)) # ---------- # # Test suite # # ---------- # if not nngt.get_config('mpi'): suite = unittest.TestLoader().loadTestsFromTestCase(TestGraphClasses) if __name__ == "__main__": unittest.main()
def _gaussian_degree(source_ids, target_ids, avg=-1, std=-1, degree_type="in", reciprocity=-1, directed=True, multigraph=False, existing_edges=None, **kwargs): ''' Connect nodes with a Gaussian distribution ''' # mpi-related stuff comm, size, rank = _mpi_and_random_init() # switch values to float avg = float(avg) std = float(std) assert avg >= 0, "A positive value is required for `avg`." assert std >= 0, "A positive value is required for `std`." # use only local sources if degree_type == "in": source_ids = np.array(source_ids, dtype=int) target_ids = np.array(target_ids, dtype=int)[rank::size] else: source_ids = np.array(source_ids, dtype=int)[rank::size] target_ids = np.array(target_ids, dtype=int) num_source, num_target = len(source_ids), len(target_ids) # type of degree b_out = (degree_type == "out") b_total = (degree_type == "total") # compute the local number of edges num_degrees = num_target if degree_type == "in" else num_source lst_deg = np.around(np.maximum(np.random.normal(avg, std, num_degrees), 0.)).astype(int) edges = np.sum(lst_deg) b_one_pop = _check_num_edges(source_ids, target_ids, edges, directed, multigraph) num_etotal = 0 ia_edges = np.zeros((edges, 2), dtype=int) idx = 0 if b_out else 1 # differenciate source / target variables = targets_id if b_out else source_ids # nodes picked randomly max_degree = np.inf if multigraph else len(variables) for i, v in enumerate(target_ids): degree_i = lst_deg[i] edges_i, ecurrent, variables_i = np.zeros((degree_i, 2)), 0, [] if existing_edges is not None: with_v = np.where(existing_edges[:, idx] == v) variables_i.extend(existing_edges[with_v:int(not idx)]) degree_i += len(variables_i) assert degree_i < max_degree, "Required degree is greater that " +\ "maximum possible degree {}.".format(max_degree) rm = np.argwhere(variables == v)[0] rm = rm[0] if len(rm) else -1 var_tmp = (np.array(variables, copy=True) if rm == -1 else np.concatenate((variables[:rm], variables[rm + 1:]))) num_var_i = len(var_tmp) ia_edges[num_etotal:num_etotal + degree_i, idx] = v while len(variables_i) != degree_i: var = var_tmp[randint(0, num_var_i, degree_i - ecurrent)] variables_i.extend(var) if not multigraph: variables_i = list(set(variables_i)) ecurrent = len(variables_i) ia_edges[num_etotal:num_etotal + ecurrent, int(not idx)] = variables_i num_etotal += ecurrent comm.Barrier() _finalize_random(rank) # the 'nngt' backend is made to be distributed, but the others are not if nngt.get_config("backend") == "nngt": return ia_edges else: # all the data is gather on the root processus ia_edges = comm.gather(ia_edges, root=0) if rank == 0: ia_edges = np.concatenate(ia_edges, axis=0) return ia_edges else: return None
def _distance_rule(source_ids, target_ids, density=-1, edges=-1, avg_deg=-1, scale=-1, rule="exp", max_proba=-1., shape=None, positions=None, directed=True, multigraph=False, distance=None, **kwargs): ''' Returns a distance-rule graph ''' assert max_proba <= 0, "MPI distance_rule cannot use `max_proba` yet." distance = [] if distance is None else distance distance_tmp = [] edges_hash = {} # mpi-related stuff comm, size, rank = _mpi_and_random_init() # compute the required values source_ids = np.array(source_ids).astype(int) target_ids = np.array(target_ids).astype(int) num_source, num_target = len(source_ids), len(target_ids) num_edges, _ = _compute_connections(num_source, num_target, density, edges, avg_deg, directed, reciprocity=-1) b_one_pop = _check_num_edges(source_ids, target_ids, num_edges, directed, multigraph) num_neurons = len(set(np.concatenate((source_ids, target_ids)))) # for each node, check the neighbours that are in an area where # connections can be made: ± scale for lin, ± 10*scale for exp. # Get the sources and associated targets for each MPI process sources = [] targets = [] lim = scale if rule == 'lin' else 10 * scale for s in source_ids[rank::size]: keep = (np.abs(positions[0, target_ids] - positions[0, s]) < lim) keep *= (np.abs(positions[1, target_ids] - positions[1, s]) < lim) if b_one_pop: keep[s] = 0 sources.append(s) targets.append(target_ids[keep]) # the number of trials should be done depending on total number of # neighbours available, so we compute this number local_neighbours = 0 for tgt_list in targets: local_neighbours += len(tgt_list) tot_neighbours = comm.gather(local_neighbours, root=0) if rank == 0: final_tot = np.sum(tot_neighbours) assert final_tot > num_edges, \ "Scale is too small: there are not enough close neighbours to " +\ "create the required number of connections. Increase `scale` " +\ "or `neuron_density`." else: final_tot = None final_tot = comm.bcast(final_tot, root=0) neigh_norm = 1. / final_tot # try to create edges until num_edges is attained if rank == 0: ia_edges = np.zeros((num_edges, 2), dtype=int) else: ia_edges = None num_ecurrent = 0 while num_ecurrent < num_edges: trials = [] for tgt_list in targets: trials.append( max( int( len(tgt_list) * (num_edges - num_ecurrent) * neigh_norm), 1)) # try to create edges edges_tmp = [[], []] dist_local = [] total_trials = int(np.sum(trials)) local_sources = np.repeat(sources, trials) local_targets = np.zeros(total_trials, dtype=int) current_pos = 0 for tgts, num_try in zip(targets, trials): t = np.random.randint(0, len(tgts), num_try) local_targets[current_pos:current_pos + num_try] = tgts[t] current_pos += num_try test = dist_rule(rule, scale, positions[:, local_sources], positions[:, local_targets], dist=dist_local) test = np.greater(test, np.random.uniform(size=total_trials)) edges_tmp[0].extend(local_sources[test]) edges_tmp[1].extend(local_targets[test]) dist_local = np.array(dist_local)[test] comm.Barrier() # gather the result in root and assess the current number of edges edges_tmp = comm.gather(edges_tmp, root=0) dist_local = comm.gather(dist_local, root=0) if rank == 0: edges_tmp = np.concatenate(edges_tmp, axis=1).T dist_local = np.concatenate(dist_local) # if we're at the end, we'll make too many edges, so we keep only # the necessary fraction that we pick randomly num_desired = num_edges - num_ecurrent if num_desired < len(edges_tmp): chosen = {} while len(chosen) != num_desired: idx = np.random.randint(0, len(edges_tmp), num_desired - len(chosen)) for i in idx: chosen[i] = None edges_tmp = edges_tmp[list(chosen.keys())] dist_local = np.array(dist_local)[list(chosen.keys())] ia_edges, num_ecurrent = _filter(ia_edges, edges_tmp, num_ecurrent, edges_hash, b_one_pop, multigraph, distance=distance_tmp, dist_tmp=dist_local) num_ecurrent = comm.bcast(num_ecurrent, root=0) comm.Barrier() _finalize_random(rank) # the 'nngt' backend is made to be distributed, but the others are not if nngt.get_config("backend") == "nngt": local_edges = None if rank == 0: local_edges = [ia_edges[i::size, :] for i in range(size)] distance_tmp = [distance_tmp[i::size] for i in range(size)] local_edges = comm.scatter(local_edges, root=0) distance_tmp = comm.scatter(distance_tmp, root=0) distance.extend(distance_tmp) return local_edges else: # all the data is gather on the root processus if rank == 0: distance.extend(distance_tmp) return ia_edges else: return None
def connect_nodes(network, sources, targets, graph_model, density=None, edges=None, avg_deg=None, unit='um', weighted=True, directed=True, multigraph=False, check_existing=True, ignore_invalid=False, **kwargs): ''' Function to connect nodes with a given graph model. .. versionchanged:: 2.0 Added `check_existing` and `ignore_invalid` arguments. Parameters ---------- network : :class:`Network` or :class:`SpatialNetwork` The network to connect. sources : list Ids of the source nodes. targets : list Ids of the target nodes. graph_model : string The name of the connectivity model (among "erdos_renyi", "random_scale_free", "price_scale_free", and "newman_watts"). check_existing : bool, optional (default: True) Check whether some of the edges that will be added already exist in the graph. ignore_invalid : bool, optional (default: False) Ignore invalid edges: they are not added to the graph and are silently dropped. Unless this is set to true, an error is raised if an existing edge is re-generated. **kwargs : keyword arguments Specific model parameters. or edge attributes specifiers such as `weights` or `delays`. Note ---- For graph generation methods which set the properties of a specific degree (e.g. :func:`~nngt.generation.gaussian_degree`), the nodes which have their property sets are the `sources`. ''' if network.is_spatial() and 'positions' not in kwargs: kwargs['positions'] = network.get_positions().astype(np.float32).T if network.is_spatial() and 'shape' not in kwargs: kwargs['shape'] = network.shape if graph_model in _one_pop_models: assert np.array_equal(sources, targets), \ "'" + graph_model + "' can only work on a single set of nodes." sources = np.array(sources, dtype=np.uint) targets = np.array(targets, dtype=np.uint) distance = [] elist = _di_gen_edges[graph_model](sources, targets, density=density, edges=edges, avg_deg=avg_deg, weighted=weighted, directed=directed, multigraph=multigraph, distance=distance, **kwargs) # Attributes are not set by subfunctions attr = {} if 'weights' in kwargs: ww = kwargs['weights'] if isinstance(ww, dict): attr['weight'] = _generate_random(len(elist), ww) elif nonstring_container(ww): attr['weight'] = ww else: attr['weight'] = np.full(len(elist), ww) if 'delays' in kwargs: dd = kwargs['delays'] if isinstance(ww, dict): attr['delay'] = _generate_random(len(elist), dd) elif nonstring_container(dd): attr['weight'] = dd else: attr['weight'] = np.full(len(elist), dd) if network.is_spatial() and distance: attr['distance'] = distance # call only on root process (for mpi) unless using distributed backend if nngt.on_master_process() or nngt.get_config("backend") == "nngt": elist = network.new_edges(elist, attributes=attr, check_duplicates=False, check_self_loops=False, check_existing=check_existing, ignore_invalid=ignore_invalid) if not network._graph_type.endswith('_connect'): network._graph_type += "_nodes_connect" return elist
# intra-groups (Newman-Watts) prop_nw = { "coord_nb": 20, "proba_shortcut": 0.1 } ng.connect_neural_groups(net, "left", "left", "newman_watts", **prop_nw) ng.connect_neural_groups(net, "right", "right", "newman_watts", **prop_nw) ''' Plot the graph ''' if nngt.get_config("with_plot"): import matplotlib.pyplot as plt colors = np.zeros(num_nodes) colors[500:] = 1 if nngt.get_config("backend") == "graph-tool": from graph_tool.draw import graph_draw, prop_to_size, sfdp_layout pm = net.new_vertex_property("int", colors) size = net.new_vertex_property("double", val=5.) pos = sfdp_layout(net, groups=pm, C=1., K=20, gamma=5, mu=20) graph_draw(net, pos=pos, vertex_fill_color=pm, vertex_color=pm, vertex_size=size, nodesfirst=True, edge_color=[0.179, 0.203,0.210, 0.3]) elif nngt.get_config("backend") == "networkx": import networkx as nx