~tfardet/NNGT

e83fd6a4faa12c9fe4fd111d57d393d43ab3fa14 — Tanguy Fardet a month ago f2dc429
Bugfix: improved spatial support (to_undirected and Graph init)
M nngt/core/graph.py => nngt/core/graph.py +22 -13
@@ 408,7 408,7 @@ class Graph(nngt.core.GraphObject):
    #-------------------------------------------------------------------------#
    # Constructor/destructor and properties

    def __new__(klass, *args, **kwargs):
    def __new__(cls, *args, **kwargs):
        '''
        Create a new Graph object.
        '''


@@ 423,18 423,18 @@ class Graph(nngt.core.GraphObject):

        if "population" in kwargs:
            has_pop = True
        if "shape" in kwargs or "positions" in kwargs:
        if kwargs.get("shape") is not None \
           or kwargs.get("positions") is not None:
            is_sptl = True

        if is_sptl and has_pop:
            klass = nngt.SpatialNetwork
            cls = nngt.SpatialNetwork
        elif is_sptl:
            klass = nngt.SpatialGraph
            cls = nngt.SpatialGraph
        elif has_pop:
            klass = nngt.Network
            cls = nngt.Network

        return super().__new__(klass)
            
        return super().__new__(cls)

    def __init__(self, nodes=None, name="Graph", weighted=True, directed=True,
                 copy_graph=None, structure=None, **kwargs):


@@ 525,8 525,9 @@ class Graph(nngt.core.GraphObject):
        remaining = set(kwargs) - kw_set

        for kw in remaining:
            _log_message(logger, "WARNING", "Unused keyword argument '" +
                         kw + "'.")
            if kwargs[kw] is not None:
                _log_message(logger, "WARNING", "Unused keyword argument '" +
                             kw + "'.")

        # update the counters
        self.__class__.__num_graphs += 1


@@ 718,11 719,19 @@ class Graph(nngt.core.GraphObject):
        shape = self.shape if self.is_spatial() else None
        pos   = self.get_positions() if self.is_spatial() else None

        g = self.__class__(self.node_nb(), structure=self.structure,
                           weighted=self.is_weighted(), directed=False)
        # Network cannot be undirected so convert NeuralPop to Structure and
        # Network to Graph if necessary
        structure = None

        if shape is not None or pos is not None:
            g.make_spatial(g, shape=shape, positions=pos)
        if isinstance(self.structure, nngt.NeuralPop):
            structure = nngt.Structure.from_groups(self.structure)

        cls = nngt.SpatialGraph if isinstance(self, nngt.SpatialGraph) \
              else nngt.Graph

        g = cls(nodes=self.node_nb(), weighted=self.is_weighted(),
                shape=shape, positions=pos, directed=False,
                structure=structure)

        # replicate node attributes
        for nattr in self.node_attributes:

M nngt/core/group_structure.py => nngt/core/group_structure.py +4 -1
@@ 83,7 83,7 @@ class Structure(OrderedDict):

        Parameters
        ----------
        groups : list of :class:`~nngt.Group` objects
        groups : dict or list of :class:`~nngt.Group` objects
            Groups that will be used to form the structure. Note that a given
            node can only belong to a single group, so the groups should form
            pairwise disjoints complementary sets.


@@ 129,6 129,9 @@ class Structure(OrderedDict):
        '''
        if not nonstring_container(groups):
            groups = [groups]
        elif isinstance(groups, dict):
            names = list(groups) if names is None else names
            groups = list(groups.values())

        gsize = len(groups)
        names = [] if names is None else list(names)

M nngt/core/gt_graph.py => nngt/core/gt_graph.py +1 -3
@@ 351,9 351,7 @@ class _GtEProperty(BaseProperty):
class _GtGraph(GraphInterface):

    '''
    Subclass of :class:`gt.Graph` that (with
    :class:`~nngt.core._SnapGraph`) unifies the methods to work with either
    `graph-tool` or `SNAP`.
    Container for :class:`gt.Graph`
    '''

    _nattr_class = _GtNProperty

M nngt/core/ig_graph.py => nngt/core/ig_graph.py +1 -1
@@ 263,7 263,7 @@ class _IgEProperty(BaseProperty):
class _IGraph(GraphInterface):

    '''
    Subclass of :class:`igraph.Graph`.
    Container for :class:`igraph.Graph`.
    '''

    _nattr_class = _IgNProperty

M nngt/core/networks.py => nngt/core/networks.py +2 -7
@@ 496,7 496,7 @@ class SpatialNetwork(Network, SpatialGraph):
    #-------------------------------------------------------------------------#
    # Constructor, destructor, and attributes

    def __init__(self, population, name="SpatialNetwork", weighted=True,
    def __init__(self, population=None, name="SpatialNetwork", weighted=True,
                 directed=True, shape=None, copy_graph=None, positions=None,
                 **kwargs):
        '''


@@ 531,13 531,8 @@ class SpatialNetwork(Network, SpatialGraph):
        self.__class__.__num_networks += 1
        self.__class__.__max_id += 1

        if population is None:
            raise InvalidArgument("Network needs a NeuralPop to be created")

        nodes = population.size

        super().__init__(
            nodes=nodes, name=name, weighted=weighted, directed=directed,
            name=name, weighted=weighted, directed=directed,
            shape=shape, positions=positions, population=population,
            copy_graph=copy_graph, **kwargs)


M nngt/core/nx_graph.py => nngt/core/nx_graph.py +1 -1
@@ 301,7 301,7 @@ class _NxEProperty(BaseProperty):
class _NxGraph(GraphInterface):

    '''
    Subclass of networkx Graph
    Container for networkx Graph
    '''

    _nattr_class = _NxNProperty

M testing/test_basics.py => testing/test_basics.py +8 -0
@@ 749,6 749,10 @@ def test_to_undirected():

    assert np.array_equal(u.edge_attributes["rnd"], [2, 10, 8, 3, 14])

    # make spatial
    pos = nngt._rng.uniform(size=(g.node_nb(), 2))
    g.make_spatial(g, positions=pos)

    # undirected max
    u = g.to_undirected("max")



@@ 760,6 764,10 @@ def test_to_undirected():

    assert np.array_equal(u.edge_attributes["rnd"], [2, 6, 8, 3, 9])

    # make network
    pop = nngt.NeuralPop.uniform(g.node_nb())
    g.make_network(g, pop)

    # undirected min
    u = g.to_undirected("min")


M testing/test_generation2.py => testing/test_generation2.py +1 -1
@@ 550,7 550,7 @@ def test_sparse_clustered():
                if c*num_nodes > deg:
                    g = ng.sparse_clustered(
                        c, nodes=num_nodes, avg_deg=deg, connected=False,
                        directed=directed, rtol=0.08)
                        directed=directed, rtol=0.09)

                    g = ng.sparse_clustered(
                        c, nodes=num_nodes, avg_deg=deg, directed=directed,