Skip to content

Commit

Permalink
string representation of graphs for print
Browse files Browse the repository at this point in the history
  • Loading branch information
mdeff committed Apr 10, 2018
1 parent 9c44c5c commit 53398f5
Show file tree
Hide file tree
Showing 32 changed files with 301 additions and 139 deletions.
2 changes: 1 addition & 1 deletion doc/tutorials/optimization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This tutorial focuses on the problem of recovering a label signal on a graph fro
>>> from pygsp import graphs, plotting
>>>
>>> # Create a random sensor graph
>>> G = graphs.Sensor(N=256, distribute=True, seed=42)
>>> G = graphs.Sensor(N=256, distributed=True, seed=42)
>>> G.compute_fourier_basis()
>>>
>>> # Create label signal
Expand Down
2 changes: 1 addition & 1 deletion doc/tutorials/pyramid.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ To start open a python shell (IPython is recommended here) and import the requir

For this demo we will be using a sensor graph with 512 nodes.

>>> G = graphs.Sensor(512, distribute=True)
>>> G = graphs.Sensor(512, distributed=True)
>>> G.compute_fourier_basis()

The function graph_multiresolution computes the graph pyramid for you:
Expand Down
2 changes: 1 addition & 1 deletion pygsp/graphs/airfoil.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ def __init__(self, **kwargs):
-1e-4, 1.01*data['y'].max()])}

super(Airfoil, self).__init__(W=W, coords=coords, plotting=plotting,
gtype='Airfoil', **kwargs)
**kwargs)
11 changes: 9 additions & 2 deletions pygsp/graphs/barabasialbert.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,14 @@ class BarabasiAlbert(Graph):
"""
def __init__(self, N=1000, m0=1, m=1, seed=None, **kwargs):

if m > m0:
raise ValueError('Parameter m cannot be above parameter m0.')

self.m0 = m0
self.m = m
self.seed = seed

W = sparse.lil_matrix((N, N))
rs = np.random.RandomState(seed)

Expand All @@ -58,5 +63,7 @@ def __init__(self, N=1000, m0=1, m=1, seed=None, **kwargs):
W[elem, i] = 1
W[i, elem] = 1

super(BarabasiAlbert, self).__init__(
W=W, gtype=u"Barabasi-Albert", **kwargs)
super(BarabasiAlbert, self).__init__(W=W, **kwargs)

def _get_extra_repr(self):
return dict(m0=self.m0, m=self.m, seed=self.seed)
9 changes: 6 additions & 3 deletions pygsp/graphs/comet.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class Comet(Graph):

def __init__(self, N=32, k=12, **kwargs):

self.k = k

# Create weighted adjacency matrix
i_inds = np.concatenate((np.zeros((k)), np.arange(k) + 1,
np.arange(k, N - 1),
Expand All @@ -47,12 +49,13 @@ def __init__(self, N=32, k=12, **kwargs):
tmpcoords[1:k + 1, 1] = np.sin(inds*2*np.pi/k)
tmpcoords[k + 1:, 0] = np.arange(1, N - k) + 1

self.N = N
self.k = k
plotting = {"limits": np.array([-2,
np.max(tmpcoords[:, 0]),
np.min(tmpcoords[:, 1]),
np.max(tmpcoords[:, 1])])}

super(Comet, self).__init__(W=W, coords=tmpcoords, gtype='Comet',
super(Comet, self).__init__(W=W, coords=tmpcoords,
plotting=plotting, **kwargs)

def _get_extra_repr(self):
return dict(k=self.k)
36 changes: 32 additions & 4 deletions pygsp/graphs/community.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def __init__(self,
N=256,
Nc=None,
min_comm=None,
min_deg=0,
min_deg=None,
comm_sizes=None,
size_ratio=1,
world_density=None,
Expand All @@ -76,12 +76,25 @@ def __init__(self,
Nc = int(round(np.sqrt(N) / 2))
if min_comm is None:
min_comm = int(round(N / (3 * Nc)))
if min_deg is not None:
raise NotImplementedError
if world_density is None:
world_density = 1 / N
if not 0 <= world_density <= 1:
raise ValueError('World density should be in [0, 1].')
if epsilon is None:
epsilon = np.sqrt(2 * np.sqrt(N)) / 2

self.Nc = Nc
self.min_comm = min_comm
self.comm_sizes = comm_sizes
self.size_ratio = size_ratio
self.world_density = world_density
self.comm_density = comm_density
self.k_neigh = k_neigh
self.epsilon = epsilon
self.seed = seed

rs = np.random.RandomState(seed)

self.logger = utils.build_logger(__name__)
Expand Down Expand Up @@ -113,8 +126,8 @@ def __init__(self,
# Intra-community edges construction #
if comm_density is not None:
# random picking edges following the community density (same for all communities)
comm_density = float(comm_density)
comm_density = comm_density if 0. <= comm_density <= 1. else 0.1
if not 0 <= comm_density <= 1:
raise ValueError('comm_density should be between 0 and 1.')
info['comm_density'] = comm_density
self.logger.info('Constructed using community density = {}'.format(comm_density))
elif k_neigh is not None:
Expand Down Expand Up @@ -223,4 +236,19 @@ def __init__(self,
for key, value in {'Nc': Nc, 'info': info}.items():
setattr(self, key, value)

super(Community, self).__init__(W=W, gtype='Community', coords=coords, **kwargs)
super(Community, self).__init__(W=W, coords=coords, **kwargs)

def _get_extra_repr(self):
attrs = {'Nc': self.Nc,
'min_comm': self.min_comm,
'comm_sizes': self.comm_sizes,
'size_ratio': '{:.2f}'.format(self.size_ratio),
'world_density': '{:.2f}'.format(self.world_density)}
if self.comm_density is not None:
attrs['comm_density'] = '{:.2f}'.format(self.comm_density)
elif self.k_neigh is not None:
attrs['k_neigh'] = self.k_neigh
else:
attrs['epsilon'] = '{:.2f}'.format(self.epsilon)
attrs['seed'] = self.seed
return attrs
11 changes: 8 additions & 3 deletions pygsp/graphs/davidsensornet.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class DavidSensorNet(Graph):
"""

def __init__(self, N=64, seed=None, **kwargs):

self.seed = seed

if N == 64:
data = utils.loadmat('pointclouds/david64')
assert data['N'][0, 0] == N
Expand All @@ -54,6 +57,8 @@ def __init__(self, N=64, seed=None, **kwargs):

plotting = {"limits": [0, 1, 0, 1]}

super(DavidSensorNet, self).__init__(W=W, gtype='davidsensornet',
coords=coords, plotting=plotting,
**kwargs)
super(DavidSensorNet, self).__init__(W=W, coords=coords,
plotting=plotting, **kwargs)

def _get_extra_repr(self):
return dict(seed=self.seed)
7 changes: 3 additions & 4 deletions pygsp/graphs/erdosrenyi.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ErdosRenyi(StochasticBlockModel):
Allow self loops if True (default is False).
connected : bool
Force the graph to be connected (default is False).
max_iter : int
n_try : int
Maximum number of trials to get a connected graph (default is 10).
seed : int
Seed for the random number generator (for reproducible graphs).
Expand All @@ -40,13 +40,12 @@ class ErdosRenyi(StochasticBlockModel):
"""

def __init__(self, N=100, p=0.1, directed=False, self_loops=False,
connected=False, max_iter=10, seed=None, **kwargs):
connected=False, n_try=10, seed=None, **kwargs):

super(ErdosRenyi, self).__init__(N=N, k=1, p=p,
directed=directed,
self_loops=self_loops,
connected=connected,
max_iter=max_iter,
n_try=n_try,
seed=seed,
**kwargs)
self.gtype = u"Erdös Renyi"
3 changes: 1 addition & 2 deletions pygsp/graphs/fullconnected.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,4 @@ def __init__(self, N=10, **kwargs):
W = np.ones((N, N)) - np.identity(N)
plotting = {'limits': np.array([-1, 1, -1, 1])}

super(FullConnected, self).__init__(W=W, gtype='full',
plotting=plotting, **kwargs)
super(FullConnected, self).__init__(W=W, plotting=plotting, **kwargs)
39 changes: 24 additions & 15 deletions pygsp/graphs/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ class Graph(fourier.GraphFourier, difference.GraphDifference):
----------
W : sparse matrix or ndarray
The weight matrix which encodes the graph.
gtype : string
Graph type, a free-form string to help us recognize the kind of graph
we are dealing with (default is 'unknown').
lap_type : 'combinatorial', 'normalized'
The type of Laplacian to be computed by :func:`compute_laplacian`
(default is 'combinatorial').
Expand All @@ -43,9 +40,6 @@ class Graph(fourier.GraphFourier, difference.GraphDifference):
It is represented as an N-by-N matrix of floats.
:math:`W_{i,j} = 0` means that there is no direct connection from
i to j.
gtype : string
the graph type is a short description of the graph object designed to
help sorting the graphs.
L : sparse matrix
the graph Laplacian, an N-by-N matrix computed from W.
lap_type : 'normalized', 'combinatorial'
Expand All @@ -63,8 +57,7 @@ class Graph(fourier.GraphFourier, difference.GraphDifference):
"""

def __init__(self, W, gtype='unknown', lap_type='combinatorial',
coords=None, plotting={}):
def __init__(self, W, lap_type='combinatorial', coords=None, plotting={}):

self.logger = utils.build_logger(__name__)

Expand All @@ -82,7 +75,7 @@ def __init__(self, W, gtype='unknown', lap_type='combinatorial',
# real number of edges. Problematic when e.g. plotting.
self.W.eliminate_zeros()

self.N = W.shape[0]
self.n_nodes = W.shape[0]

# TODO: why would we ever want this?
# For large matrices it slows the graph construction by a factor 100.
Expand All @@ -91,16 +84,14 @@ def __init__(self, W, gtype='unknown', lap_type='combinatorial',
# Don't count edges two times if undirected.
# Be consistent with the size of the differential operator.
if self.is_directed():
self.Ne = self.W.nnz
self.n_edges = self.W.nnz
else:
diagonal = np.count_nonzero(self.W.diagonal())
off_diagonal = self.W.nnz - diagonal
self.Ne = off_diagonal // 2 + diagonal
self.n_edges = off_diagonal // 2 + diagonal

self.check_weights()

self.gtype = gtype

self.compute_laplacian(lap_type)

if coords is not None:
Expand All @@ -113,6 +104,24 @@ def __init__(self, W, gtype='unknown', lap_type='combinatorial',
'edge_style': '-'}
self.plotting.update(plotting)

# TODO: kept for backward compatibility.
self.Ne = self.n_edges
self.N = self.n_nodes

def _get_extra_repr(self):
return dict()

def __repr__(self, limit=None):
s = ''
for attr in ['n_nodes', 'n_edges']:
s += '{}={}, '.format(attr, getattr(self, attr))
for i, (key, value) in enumerate(self._get_extra_repr().items()):
if (limit is not None) and (i == limit - 2):
s += '..., '
break
s += '{}={}, '.format(key, value)
return '{}({})'.format(self.__class__.__name__, s[:-2])

def check_weights(self):
r"""Check the characteristics of the weights matrix.
Expand Down Expand Up @@ -284,7 +293,7 @@ def subgraph(self, ind):
# N = len(ind) # Assigned but never used

sub_W = self.W.tocsr()[ind, :].tocsc()[:, ind]
return Graph(sub_W, gtype="sub-{}".format(self.gtype))
return Graph(sub_W)

def is_connected(self, recompute=False):
r"""Check the strong connectivity of the graph (cached).
Expand Down Expand Up @@ -506,7 +515,7 @@ def compute_laplacian(self, lap_type='combinatorial'):
elif lap_type == 'normalized':
d = np.power(self.dw, -0.5)
D = sparse.diags(np.ravel(d), 0).tocsc()
self.L = sparse.identity(self.N) - D * self.W * D
self.L = sparse.identity(self.n_nodes) - D * self.W * D


@property
Expand Down
8 changes: 7 additions & 1 deletion pygsp/graphs/grid2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ def __init__(self, N1=16, N2=None, **kwargs):
if N2 is None:
N2 = N1

self.N1 = N1
self.N2 = N2

N = N1 * N2

# Filling up the weight matrix this way is faster than
Expand All @@ -54,5 +57,8 @@ def __init__(self, N1=16, N2=None, **kwargs):
plotting = {"limits": np.array([-1. / N2, 1 + 1. / N2,
1. / N1, 1 + 1. / N1])}

super(Grid2d, self).__init__(W=W, gtype='2d-grid', coords=coords,
super(Grid2d, self).__init__(W=W, coords=coords,
plotting=plotting, **kwargs)

def _get_extra_repr(self):
return dict(N1=self.N1, N2=self.N2)
3 changes: 1 addition & 2 deletions pygsp/graphs/logo.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,4 @@ def __init__(self, **kwargs):
plotting = {"limits": np.array([0, 640, -400, 0])}

super(Logo, self).__init__(W=data['W'], coords=data['coords'],
gtype='LogoGSP', plotting=plotting,
**kwargs)
plotting=plotting, **kwargs)
6 changes: 5 additions & 1 deletion pygsp/graphs/lowstretchtree.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class LowStretchTree(Graph):

def __init__(self, k=6, **kwargs):

self.k = k

XCoords = np.array([1, 2, 1, 2])
YCoords = np.array([1, 1, 2, 2])

Expand Down Expand Up @@ -68,5 +70,7 @@ def __init__(self, k=6, **kwargs):
super(LowStretchTree, self).__init__(W=W,
coords=coords,
plotting=plotting,
gtype="low stretch tree",
**kwargs)

def _get_extra_repr(self):
return dict(k=self.k)
19 changes: 9 additions & 10 deletions pygsp/graphs/minnesota.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Minnesota(Graph):
Parameters
----------
connect : bool
connected : bool
If True, the adjacency matrix is adjusted so that all edge weights are
equal to 1, and the graph is connected. Set to False to get the
original disconnected graph.
Expand All @@ -31,7 +31,9 @@ class Minnesota(Graph):
"""

def __init__(self, connect=True, **kwargs):
def __init__(self, connected=True, **kwargs):

self.connected = connected

data = utils.loadmat('pointclouds/minnesota')
self.labels = data['labels']
Expand All @@ -40,7 +42,7 @@ def __init__(self, connect=True, **kwargs):
plotting = {"limits": np.array([-98, -89, 43, 50]),
"vertex_size": 40}

if connect:
if connected:

# Missing edges needed to connect the graph.
A = sparse.lil_matrix(A)
Expand All @@ -51,11 +53,8 @@ def __init__(self, connect=True, **kwargs):
# Binarize: 8 entries are equal to 2 instead of 1.
A = (A > 0).astype(bool)

gtype = 'minnesota'

else:

gtype = 'minnesota-disconnected'

super(Minnesota, self).__init__(W=A, coords=data['xy'], gtype=gtype,
super(Minnesota, self).__init__(W=A, coords=data['xy'],
plotting=plotting, **kwargs)

def _get_extra_repr(self):
return dict(connected=self.connected)
2 changes: 1 addition & 1 deletion pygsp/graphs/nngraphs/bunny.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ def __init__(self, **kwargs):

super(Bunny, self).__init__(Xin=data['bunny'], epsilon=0.2,
NNtype='radius', plotting=plotting,
gtype='Bunny', **kwargs)
**kwargs)
Loading

0 comments on commit 53398f5

Please sign in to comment.