Skip to content

Commit e4a5ff2

Browse files
InterdisciplinaryPhysicsTeamClaudMorpitmonticone
committed
Create tutorial.jl
Co-Authored-By: Claudio Moroni <43729990+ClaudMor@users.noreply.github.com> Co-Authored-By: Pietro Monticone <38562595+pitmonticone@users.noreply.github.com>
1 parent 3186eeb commit e4a5ff2

File tree

1 file changed

+340
-0
lines changed

1 file changed

+340
-0
lines changed

docs/src/tutorial.jl

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
# Install necessary tutorial dependencies
2+
using Pkg
3+
Pkg.add(["Revise", "Distributions", "Graphs", "SimpleValueGraphs",
4+
"LoggingExtras", "StatsBase", "SimpleWeightedGraphs",
5+
"MetaGraphs", "Agents", "MultilayerGraphs"])
6+
7+
# Import necessary tutorial dependencies
8+
using Revise
9+
using StatsBase, Distributions
10+
using Graphs, SimpleWeightedGraphs, MetaGraphs, SimpleValueGraphs
11+
using MultilayerGraphs
12+
13+
# Set the minimum and maximum number of nodes_list and edges for random graphs
14+
const vertextype = Int64
15+
const _weighttype = Float64
16+
const min_vertices = 5
17+
const max_vertices = 7
18+
const n_nodes = max_vertices
19+
20+
# The constructor for nodes (which are immutable) only requires a name (`id`) for the node
21+
const nodes_list = [Node("node_$i") for i in 1:n_nodes]
22+
23+
## Convert nodes to multilayer vertices without metadata
24+
const multilayervertices = MV.(nodes_list)
25+
## Convert nodes multilayer vertices with metadata
26+
const multilayervertices_meta = [MV(node, ("I'm node $(node.id)",)) for node in nodes_list] # `MV` is an alias for `MultilayerVertex`
27+
28+
multilayervertices_meta[1]
29+
30+
# Utility function that returns a random number of vertices and edges each time it is called:
31+
function rand_nv_ne_layer(min_vertices, max_vertices)
32+
_nv = rand(min_vertices:max_vertices)
33+
_ne = rand(1:(_nv*(_nv-1)) ÷ 2 )
34+
return (_nv,_ne)
35+
end
36+
37+
# Utility function that returns two vertices of a Layer that are not adjacent.
38+
function _get_srcmv_dstmv_layer(layer::Layer)
39+
mvs = MultilayerGraphs.get_bare_mv.(collect(mv_vertices(layer)))
40+
41+
src_mv_idx = findfirst(mv -> !isempty(setdiff(
42+
Set(mvs),
43+
Set(
44+
vcat(MultilayerGraphs.get_bare_mv.(mv_outneighbors(layer, mv)), mv)
45+
),
46+
)), mvs)
47+
48+
src_mv = mvs[src_mv_idx]
49+
50+
_collection = setdiff(
51+
Set(mvs),
52+
Set(
53+
vcat(MultilayerGraphs.get_bare_mv.(mv_outneighbors(layer, src_mv)), src_mv)
54+
),
55+
)
56+
57+
dst_mv = MultilayerGraphs.get_bare_mv(rand(_collection))
58+
59+
return mvs, src_mv, dst_mv
60+
end
61+
62+
# An unweighted simple layer:
63+
_nv, _ne = rand_nv_ne_layer(min_vertices,max_vertices)
64+
layer_sg = Layer( :layer_sg,
65+
sample(nodes_list, _nv, replace = false),
66+
_ne,
67+
SimpleGraph{vertextype}(),
68+
_weighttype
69+
)
70+
71+
# A weighted `Layer`
72+
_nv, _ne = rand_nv_ne_layer(min_vertices,max_vertices)
73+
layer_swg = Layer( :layer_swg,
74+
sample(nodes_list, _nv, replace = false),
75+
_ne,
76+
SimpleWeightedGraph{vertextype, _weighttype}(),
77+
_weighttype;
78+
default_edge_weight = (src,dst) -> rand()
79+
)
80+
# A `Layer` with an underlying `MetaGraph`:
81+
_nv, _ne = rand_nv_ne_layer(min_vertices,max_vertices)
82+
layer_mg = Layer( :layer_mg,
83+
sample(nodes_list, _nv, replace = false),
84+
_ne,
85+
MetaGraph{vertextype, _weighttype}(),
86+
_weighttype;
87+
default_edge_metadata = (src,dst) -> (from_to = "from_$(src)_to_$(dst)",)
88+
)
89+
# `Layer` with an underlying `ValGraph` from `SimpleValueGraphs.jl`
90+
_nv, _ne = rand_nv_ne_layer(min_vertices,max_vertices)
91+
layer_vg = Layer( :layer_vg,
92+
sample(nodes_list, _nv, replace = false),
93+
_ne,
94+
MultilayerGraphs.ValGraph(SimpleGraph{vertextype}();
95+
edgeval_types=(Float64, String, ),
96+
edgeval_init=(s, d) -> (s+d, "hi"),
97+
vertexval_types=(String,),
98+
vertexval_init=v -> ("$v",),),
99+
_weighttype;
100+
default_edge_metadata = (src,dst) -> (rand(), "from_$(src)_to_$(dst)",),
101+
default_vertex_metadata = mv -> ("This metadata had been generated via the default_vertex_metadata method",)
102+
)
103+
104+
# Collect all layers in an ordered list. Order will be recorded when instantiating the multilayer graph.
105+
layers = [layer_sg, layer_swg, layer_mg, layer_vg]
106+
107+
# Utilities for Interlayer
108+
## Utility function that returns two vertices of an Interlayer that are not adjacent.
109+
function _get_srcmv_dstmv_interlayer(interlayer::Interlayer)
110+
111+
mvs = get_bare_mv.(collect(mv_vertices(interlayer)))
112+
113+
src_mv = nothing
114+
_collection = []
115+
116+
while isempty(_collection)
117+
src_mv = rand(mvs)
118+
_collection = setdiff(Set(mvs), Set(vcat(get_bare_mv.(mv_outneighbors(interlayer, src_mv)), src_mv, get_bare_mv.(mv_vertices( eval(src_mv.layer) ))) ) )
119+
end
120+
121+
dst_mv = get_bare_mv(rand(_collection))
122+
123+
return mvs, src_mv, dst_mv
124+
end
125+
126+
## Utility function that returns a random number edges between its arguments `layer_1` and `layer_2`:
127+
function rand_ne_interlayer(layer_1, layer_2)
128+
nv_1 = nv(layer_1)
129+
nv_2 = nv(layer_2)
130+
_ne = rand(1: (nv_1 * nv_2 - 1) )
131+
return _ne
132+
end
133+
134+
# Define the random undirected simple Interlayer
135+
_ne = rand_ne_interlayer(layer_sg, layer_swg)
136+
interlayer_sg_swg = Interlayer( layer_sg, # The first layer to be connected
137+
layer_swg, # The second layer to be connected
138+
_ne, # The number of edges to randomly generate
139+
SimpleGraph{vertextype}(), # The underlying graph, passed as a null graph
140+
interlayer_name = :random_interlayer # The name of the interlayer. We will be able to access it as a property of the multilayer graph via its name. This kwarg's default value is given by a combination of the two layers' names.
141+
)
142+
# Define a weighted `Interlayer`
143+
_ne = rand_ne_interlayer(layer_swg, layer_mg)
144+
interlayer_swg_mg = Interlayer( layer_swg,
145+
layer_mg,
146+
_ne,
147+
SimpleWeightedGraph{vertextype, _weighttype}();
148+
default_edge_weight = (x,y) -> rand() # Arguments follow the same rules as in Layer
149+
)
150+
# Define an `Interlayer` with an underlying `MetaGraph`
151+
_ne = rand_ne_interlayer(layer_mg, layer_vg)
152+
interlayer_mg_vg = Interlayer( layer_mg,
153+
layer_vg,
154+
_ne,
155+
MetaGraph{vertextype, _weighttype}();
156+
default_edge_metadata = (x,y) -> (mymetadata = rand(),),
157+
transfer_vertex_metadata = true # This boolean kwarg controls whether vertex metadata found in both connected layers are carried over to the vertices of the Interlayer. NB: not all choice of underlying graph may support this feature. Graphs types that don't support metadata or that pose limitations to it may result in errors.
158+
)
159+
# Define an `Interlayer` with an underlying `ValGraph` from `SimpleValueGraphs.jl`, with diagonal couplings only:
160+
interlayer_multiplex_sg_mg = multiplex_interlayer( layer_sg,
161+
layer_mg,
162+
ValGraph(SimpleGraph{vertextype}(); edgeval_types=(from_to = String,), edgeval_init=(s, d) -> (from_to = "from_$(s)_to_$(d)"));
163+
default_edge_metadata = (x,y) -> (from_to = "from_$(src)_to_$(dst)",)
164+
)
165+
# Finally, An `Interlayer` with no couplings (an "empty" interlayer):
166+
interlayer_empty_sg_vg = empty_interlayer( layer_sg,
167+
layer_vg,
168+
SimpleGraph{vertextype}()
169+
)
170+
171+
# Collect all interlayers. Even though the list is ordered, order will not matter when instantiating the multilayer graph.
172+
interlayers = [interlayer_sg_swg, interlayer_swg_mg, interlayer_mg_vg, interlayer_multiplex_sg_mg, interlayer_empty_sg_vg]
173+
174+
# Nodes
175+
layer_sg_nodes = nodes(layer_sg)
176+
interlayer_sg_swg_nodes = nodes(interlayer_sg_swg)
177+
has_node(layer_sg, layer_sg_nodes[1])
178+
179+
# Vertices
180+
layer_sg_vertices = mv_vertices(layer_sg)
181+
mv_vertices(layer_mg)
182+
interlayer_sg_swg_vertices = mv_vertices(interlayer_sg_swg)
183+
184+
new_node = Node("missing_node")
185+
new_metadata = (meta = "my_metadata",)
186+
new_vertex = MV(new_node, new_metadata)
187+
188+
add_vertex!(layer_mg, new_vertex)
189+
add_vertex!(layer_mg, new_node, metadata = new_metadata)
190+
add_vertex!(layer_mg, new_node, Dict(pairs(new_metadata)))
191+
192+
metagraph = MetaGraph()
193+
add_vertex!(metagraph, Dict(pairs(new_metadata))) # true
194+
rem_vertex!(layer_sg, new_vertex) # Returns true if succeeds
195+
196+
get_metadata(layer_mg, MV(new_node))
197+
198+
# Edges
199+
edgetype(layer_sg)
200+
collect(edges(layer_sg))
201+
# Define a weighted edge for the layer_swg
202+
## Define the weight
203+
_weight = rand()
204+
## Select two non-adjacent vertices in layer_swg
205+
_, src_w, dst_w = _get_srcmv_dstmv_layer(layer_swg)
206+
## Construct a weighted MultilayerEdge
207+
me_w = ME(src_w, dst_w, _weight) # ME is an alias for MultilayerEdge
208+
209+
add_edge!(layer_swg, me_w)
210+
add_edge!(layer_swg, src_w, dst_w, weight = _weight)
211+
add_edge!(layer_swg, src_w, dst_w, _weight)
212+
213+
simpleweightedgraph = SimpleWeightedGraph(SimpleGraph(5, 0))
214+
add_edge!(simpleweightedgraph, 1, 2, _weight)
215+
216+
rem_edge!(layer_swg, src_w, dst_w) # Returns true if succeeds
217+
218+
get_weight(layer_swg, src_w, dst_w)
219+
220+
# Define an edge with metadata for the layer_mg
221+
## Define the metadata
222+
_metadata = (meta = "mymetadata",)
223+
## Select two non-adjacent vertices in layer_mg
224+
_, src_m, dst_m = _get_srcmv_dstmv_layer(layer_mg)
225+
## Construct a MultilayerEdge with metadata
226+
me_m = ME(src_m, dst_m, _metadata)
227+
228+
add_edge!(layer_mg, me_m)
229+
add_edge!(layer_mg, src_m, dst_m, metadata = _metadata)
230+
add_edge!(layer_mg, src_m, dst_m, Dict(pairs(_metadata)))
231+
get_metadata(layer_mg, src_m, dst_m)
232+
add_edge!(layer_swg, me_w)
233+
add_edge!(layer_swg, src_w, dst_w, weight = _weight)
234+
add_edge!(layer_swg, src_w, dst_w, _weight)
235+
236+
rem_edge!(layer_swg, src_w, dst_w)
237+
238+
# Multilayer Graphs
239+
multilayergraph = MultilayerGraph( layers, # The (ordered) list of layers the multilayer graph will have
240+
interlayers; # The list of interlayers specified by the user. Note that the user does not need to specify all interlayers, as the unspecified ones will be automatically constructed using the indications given by the `default_interlayers_null_graph` and `default_interlayers_structure` keywords.
241+
default_interlayers_null_graph = SimpleGraph{vertextype}(), # Sets the underlying graph for the interlayers that are to be automatically specified. Defaults to `SimpleGraph{T}()`, where `T` is the `T` of all the `layers` and `interlayers`. See the `Layer` constructors for more information.
242+
default_interlayers_structure = "multiplex" # Sets the structure of the interlayers that are to be automatically specified. May be "multiplex" for diagonally coupled interlayers, or "empty" for empty interlayers (no edges). "multiplex". See the `Interlayer` constructors for more information.
243+
)
244+
245+
# The configuration model-like constructor will be responsible for creating the edges, so we need to provide it with empty layers and interlayers.
246+
# To create empty layers and interlayers, we will empty the above subgraphs, and, for compatibility reasons, we'll remove the ones having a `SimpleWeightedGraph`s. These lines are not necessary to comprehend the tutorial, they may be skipped. Just know that the variables `empty_layers` and `empty_interlayers` are two lists of, respectively, empty layers and interlayers that do not have `SimpleWeightedGraph`s as their underlying graphs
247+
248+
empty_layers = deepcopy([layer for layer in layers if !(layer.graph isa SimpleWeightedGraphs.AbstractSimpleWeightedGraph)])
249+
250+
empty_layers_names = name.(empty_layers)
251+
252+
empty_interlayers = deepcopy([interlayer for interlayer in interlayers if all(in.(interlayer.layers_names, Ref(empty_layers_names))) && !(interlayer.graph isa SimpleWeightedGraphs.AbstractSimpleWeightedGraph) ])
253+
254+
for layer in empty_layers
255+
for edge in edges(layer)
256+
rem_edge!(layer, edge)
257+
end
258+
end
259+
260+
for interlayer in empty_interlayers
261+
for edge in edges(interlayer)
262+
rem_edge!(interlayer, edge)
263+
end
264+
end
265+
266+
# Construct a multilayer graph that has a normal degree distribution. The support of the distribution must be positive, since negative degrees are not possible
267+
configuration_multilayergraph = MultilayerGraph(empty_layers, empty_interlayers, truncated(Normal(10), 0.0, 20.0));
268+
269+
new_node = Node("new_node")
270+
add_node!(multilayergraph, new_node) # Return true if succeeds
271+
new_vertex = MV(new_node, :layer_sg)
272+
add_vertex!(multilayergraph, new_vertex)
273+
rem_node!(multilayergraph, new_node) # Return true if succeeds
274+
275+
# This will succeed
276+
random_weighted_edge = rand(collect(edges(multilayergraph.layer_swg)))
277+
set_weight!(multilayergraph, src(random_weighted_edge), dst(random_weighted_edge), rand())
278+
279+
# This will not succeed
280+
random_unweighted_edge = rand(collect(edges(multilayergraph.layer_sg)))
281+
set_weight!(multilayergraph, src(random_unweighted_edge), dst(random_unweighted_edge), rand())
282+
283+
# Instantiate a new Layer
284+
_nv, _ne = rand_nv_ne_layer(min_vertices,max_vertices)
285+
new_layer = Layer( :new_layer,
286+
sample(nodes_list, _nv, replace = false),
287+
_ne,
288+
SimpleGraph{vertextype}(),
289+
_weighttype
290+
)
291+
292+
# Add the Layer
293+
add_layer!(
294+
multilayergraph, # the `Multilayer(Di)Graph` which the new layer will be added to;
295+
new_layer; # the new `Layer` to add to the `multilayergraph`
296+
default_interlayers_null_graph = SimpleGraph{vertextype}(), # upon addition of a new `Layer`, all the `Interlayer`s between the new and the existing `Layer`s are immediately created. This keyword argument specifies their `null_graph` See the `Layer` constructor for more information. Defaults to `SimpleGraph{T}()`
297+
default_interlayers_structure = "empty" # The structure of the `Interlayer`s created by default. May either be "multiplex" to have diagonally-coupled only interlayers, or "empty" for empty interlayers. Defaults to "multiplex".
298+
)
299+
300+
# Check that the new layer now exists within the multilayer graph
301+
has_layer(multilayergraph, :new_layer)
302+
303+
# Instantiate a new Interlayer. Notice that its name will be given by default as
304+
_ne = rand_ne_interlayer(layer_sg, new_layer)
305+
new_interlayer = Interlayer( layer_sg,
306+
new_layer,
307+
_ne,
308+
SimpleGraph{vertextype}(),
309+
interlayer_name = :new_interlayer
310+
)
311+
312+
# Modify an existing interlayer with the latter i.e. specify the latter interlayer:
313+
specify_interlayer!( multilayergraph,
314+
new_interlayer)
315+
316+
# Now the interlayer between `layer_sg` and `new_layer` is `new_interlayer`
317+
318+
# Get a layer by name
319+
multilayergraph.new_layer
320+
321+
# Get an Interlayer by name
322+
multilayergraph.new_interlayer
323+
324+
# Get an Interlayer from the names of the two layers that it connects
325+
get_interlayer(multilayergraph, :new_layer, :layer_sg )
326+
327+
# Remove the layer. This will also remove all the interlayers associated to it.
328+
rem_layer!( multilayergraph,
329+
:new_layer;
330+
remove_nodes = false # Whether to also remove all nodes represented in the to-be-removed layer from the multilayer graph
331+
)
332+
333+
wgt = weight_tensor(multilayergraph)
334+
array(wgt)
335+
336+
# Get two random vertices from the MultilayerGraph
337+
mv1, mv2 = rand(mv_vertices(multilayergraph), 2)
338+
339+
# Get the strength of the edge between them (0 for no edge):
340+
wgt[mv1, mv2]

0 commit comments

Comments
 (0)