Skip to content

Commit afc58d0

Browse files
committed
add examples to docs
1 parent 08cabce commit afc58d0

File tree

5 files changed

+405
-0
lines changed

5 files changed

+405
-0
lines changed

docs/examples/index.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@ Examples
55
:maxdepth: 1
66

77
isotropic
8+
static_network_basics
9+
static_hypergraph_basics
10+
network_manipulation
11+
temporal_network_basics
812
temporal_network
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
Modifying networks the immutable way
2+
====================================
3+
4+
All Reticula networks are immutable. Instead of changing a graph in place,
5+
operations like adding or removing edges return a new object. This tutorial
6+
demonstrates the basic ``with_*`` and ``without_*`` functions.
7+
8+
Adding edges
9+
------------
10+
11+
Start with a simple cycle and add a few new links::
12+
13+
>>> import reticula as ret
14+
>>> g = ret.cycle_graph[ret.int64](size=8)
15+
>>> extra = [(0, 5), (1, 6)]
16+
>>> g2 = ret.with_edges(g, extra)
17+
>>> len(g.edges()), len(g2.edges())
18+
(8, 10)
19+
20+
Because ``g`` is unchanged we can safely reuse it elsewhere.
21+
22+
Removing vertices
23+
-----------------
24+
25+
Vertices can be removed in a similar fashion::
26+
27+
>>> g3 = ret.without_vertices(g2, [0, 1])
28+
>>> len(g3.vertices())
29+
6
30+
31+
Any edges incident to the removed vertices disappear automatically.
32+
33+
Adding and removing multiple items at once is more efficient than
34+
repeatedly calling these functions in a loop, so try to batch your
35+
operations when possible.
36+
37+
38+
Induced subgraphs
39+
-----------------
40+
41+
A subset of a network can be created by selecting a set of vertices or edges,
42+
using :py:func:`vertex_induced_subgraph` or :py:func:`edge_induced_subgraph`.
43+
For example, to create a subgraph of the first five vertices::
44+
45+
>>> g4 = ret.vertex_induced_subgraph(g, [0, 1, 2, 3, 4])
46+
>>> len(g4.vertices()), len(g4.edges())
47+
(5, 4)
48+
49+
50+
Vertex and edge occupation
51+
--------------------------
52+
53+
Another way to create a subset of the network is to prbabilistically keep a
54+
fraction of the vertices or edges. This is useful for sampling large networks.
55+
Inspired by the percolation literature, Reticula calls this process occupation.
56+
57+
Vertex or edge occupation is done with a uniform probability, using :py:func:`uniformly_occupy_edges` and :py:func:`uniformly_occupy_edges`::
58+
59+
>>> gen = ret.mersenne_twister(42)
60+
>>> g5 = ret.uniformly_occupy_vertices(
61+
... g, occupation_prob=0.5, random_state=gen)
62+
>>> g5
63+
<undirected_network[int64] with 5 verts and 2 edges>
64+
65+
Similarly, the probability of keeping edges can be determined using a lambda
66+
function. Here for example we set the odd vertices to be kept (occupied) at a
67+
higher probability::
68+
69+
>>> gen = ret.mersenne_twister(42)
70+
>>> g6 = ret.occupy_vertices(g,
71+
... lambda x: 0.3 if x%2 == 0 else 0.7,
72+
... random_state=gen)
73+
>>> g6
74+
<undirected_network[int64] with 5 verts and 3 edges>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
Getting started with hypergraphs
2+
================================
3+
4+
Hypergraphs generalise standard graphs by allowing edges to contain any number
5+
of vertices. Reticula provides simple ways of generating and inspecting them.
6+
Reticula provides both directed and undirected hypergraphs. Make sure you have
7+
installed the library with ``pip install reticula`` and imported it as ``ret``::
8+
9+
>>> import reticula as ret
10+
11+
Creating a random hypergraph
12+
----------------------------
13+
14+
Let's stat with a simple example of a random hypergraph. The function :py:func:`random_uniform_hypergraph` generates a hypergraph with a given number of vertices, edges, and edge degree, each edge existing independently with a given probability. It requires a pseudo random number generator, which we create here with :py:func:`mersenne_twister`::
15+
16+
>>> gen = ret.mersenne_twister(42)
17+
>>> h = ret.random_uniform_hypergraph[ret.int64](
18+
... size=100, edge_degree=3, edge_prob=0.01, random_state=gen)
19+
>>> h
20+
<undirected_hypergraph[int64] with 100 verts and 1659 edges>
21+
22+
Examining the structure
23+
-----------------------
24+
25+
Edges and vertices are again simple Python lists::
26+
27+
>>> h.vertices()[:5]
28+
[0, 1, 2, 3, 4]
29+
>>> h.edges()[:2]
30+
[undirected_hyperedge[int64]([0, 3, 96]), undirected_hyperedge[int64]([0, 4, 11])]
31+
>>> len(h.edges())
32+
1659
33+
34+
Hypergraph degrees count how many hyperedges a vertex belongs to::
35+
36+
>>> ret.degree(h, 0)
37+
3
38+
39+
Since hypergraph h is undirected, the degree of a vertex is the same as its in- and
40+
out-degree::
41+
42+
>>> ret.in_degree(h, 0)
43+
3
44+
>>> ret.out_degree(h, 0)
45+
3
46+
>>> ret.incident_degree(h, 0)
47+
3
48+
49+
You can also probe each edge, for example to find out how many vertices it
50+
contains, using :py:func:`edge_degree`. This is also known as the edge degree
51+
or edge rank::
52+
53+
>>> e = h.edges()[0]
54+
>>> e
55+
undirected_hyperedge[int64]([0, 3, 96])
56+
>>> ret.edge_degree(e)
57+
3
58+
59+
This also provices directed variants, but they are identical for undirected hypergraphs::
60+
>>> ret.in_edge_degree(e)
61+
3
62+
>>> ret.out_edge_degree(e)
63+
3
64+
65+
Hyperedge mutators and mutated vertices work similarly to undirected edges in
66+
dyadic networks. The mutator vertices are those that can change the state of
67+
the other vertices through the hyperedge, while the mutated vertices are those
68+
whose state can change because of the mutators. For undirected hyperedges, all
69+
vertices incident to the hyperedge can act as mutators or mutated vertices::
70+
71+
>>> e.mutator_verts()
72+
[0, 3, 96]
73+
>>> e.mutated_verts()
74+
[0, 3, 96]
75+
>>> e.incident_verts()
76+
[0, 3, 96]
77+
78+
For directed hyperedges, the mutator and mutated vertices can be different::
79+
80+
>>> e = ret.directed_hyperedge[int64]([1, 2, 3], [4, 5, 6])
81+
>>> e.mutator_verts()
82+
[1, 2, 3]
83+
>>> e.tails()
84+
[1, 2, 3]
85+
86+
>>> e.mutated_verts()
87+
[4, 5, 6]
88+
>>> e.heads()
89+
[4, 5, 6]
90+
91+
>>> e.incident_verts()
92+
[1, 2, 3, 4, 5, 6]
93+
94+
95+
Connected components
96+
--------------------
97+
98+
Connected components work exactly the same as for dyadic static graphs::
99+
100+
>>> comps = ret.connected_components(h)
101+
>>> comps
102+
[<component[int64] of 100 nodes: {99, 98, 97, 96, 95, ...})>]
103+
>>> len(comps)
104+
1
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
Basic static network exploration
2+
================================
3+
4+
This tutorial introduces a few simple functions for static undirected networks.
5+
All examples assume that you already installed Reticula with ``pip install
6+
reticula`` and have ``ret`` imported as the usual alias::
7+
8+
>>> import reticula as ret
9+
10+
Generating a random network
11+
---------------------------
12+
13+
We start by creating a random :math:`G(n,p)` network. The function
14+
:py:func:`random_gnp_graph` requires a pseudo random number generator, created
15+
here with :py:func:`mersenne_twister`::
16+
17+
>>> gen = ret.mersenne_twister(42)
18+
>>> g = ret.random_gnp_graph[ret.int64](n=100, p=0.02, random_state=gen)
19+
>>> g
20+
<undirected_network[int64] with 100 verts and 110 edges>
21+
22+
Inspecting the network
23+
----------------------
24+
25+
The vertices and edges can be retrieved as Python lists::
26+
27+
>>> g.vertices()[:5]
28+
[0, 1, 2, 3, 4]
29+
>>> g.edges()[:3]
30+
[undirected_edge[int64](0, 16), undirected_edge[int64](0, 20), ...]
31+
32+
Several helper functions report basic properties such as the number of vertices
33+
or edges and the network density::
34+
35+
>>> len(g.vertices())
36+
100
37+
>>> len(g.edges())
38+
110
39+
>>> ret.density(g)
40+
0.022223
41+
42+
You can probe the edges of the network, for example to find out how which nodes
43+
are connected by a specific edge.
44+
45+
Mutator vertices are those vertices that can change the state of the other
46+
nodes through a specific edge, and mutated vertices are those whose internal
47+
state can change because of mutators. For undirected_network, all nodes
48+
incident to an edge can act as mutators or mutated vertices::
49+
50+
>>> e = g.edges()[0]
51+
>>> e
52+
undirected_edge[int64](0, 16)
53+
>>> e.mutator_verts()
54+
[0, 16]
55+
>>> e.mutated_verts()
56+
[0, 16]
57+
>>> e.incident_verts()
58+
[0, 16]
59+
60+
This is not the case for directed networks, where the mutator and mutated
61+
vertices can be different.
62+
63+
>>> e = ret.directed_edge[int64](0, 16)
64+
>>> e
65+
directed_edge[int64](0, 16)
66+
>>> e.mutator_verts()
67+
[0]
68+
>>> e.tail()
69+
0
70+
>>> e.mutated_verts()
71+
[16]
72+
>>> e.head()
73+
16
74+
>>> e.incident_verts()
75+
[0, 16]
76+
77+
78+
Connected components
79+
--------------------
80+
81+
The function :py:func:`connected_components` returns all components of an
82+
undirected network::
83+
84+
>>> comps = ret.connected_components(g)
85+
>>> lcc = max(comps, key=len)
86+
>>> lcc
87+
<component[int64] of 93 nodes: {99, 96, 95, 94, 92, 91, 90, 89, 88, 87, ...})>
88+
>>> len(lcc)
89+
93
90+
91+
If you are only interested in the largest component, you can directly use
92+
:py:func:`largest_connected_component`::
93+
94+
>> lcc = ret.largest_connected_component(g)
95+
>> lcc
96+
<component[int64] of 93 nodes: {99, 96, 95, 94, 92, 91, 90, 89, 88, 87, ...})>
97+
98+
99+
Degrees and neighbourhoods
100+
--------------------------
101+
102+
Degree information for each vertex is accessible via :py:func:`degree`::
103+
104+
>>> ret.degree(g, 0)
105+
4
106+
107+
Since graph g is undirected, the degree of a vertex is the same as its in- and
108+
out-degree::
109+
110+
>>> ret.in_degree(g, 0)
111+
4
112+
>>> ret.out_degree(g, 0)
113+
4
114+
>>> ret.incident_degree(g, 0)
115+
4
116+
117+
You can also directly generate the degree sequence of the network, using
118+
:py:func:`degree_sequence`::
119+
120+
>>> ret.degree_sequence(g)
121+
[4, 1, 0, 6, 1, 2, 1, 0, ...]
122+
>>> ret.in_degree_sequence(g)
123+
[4, 1, 0, 6, 1, 2, 1, 0, ...]
124+
>>> ret.out_degree_sequence(g)
125+
[4, 1, 0, 6, 1, 2, 1, 0, ...]
126+
>>> ret.incident_degree_sequence(g)
127+
[4, 1, 0, 6, 1, 2, 1, 0, ...]
128+
129+
You can get a list of neighbours of a node::
130+
131+
>>> g.neighbours(0)
132+
[20, 51, 31, 16]
133+
134+
135+
Similar to before, you can also get the successors and predecessors, the
136+
directed in- and out-neighbours, of a vertex::
137+
138+
>>> g.successors(0)
139+
[20, 51, 31, 16]
140+
>>> g.prdecessors(0)
141+
[20, 51, 31, 16]
142+
143+
144+
These simple building blocks will let you start exploring static networks in
145+
Reticula.

0 commit comments

Comments
 (0)