# Tutorial

## Why Evolving Graphs?

Many real-world networks store the relationship between entities with time stamps. Consider a group of online users interacting through messaging. Each message sent from user `v_i`

to user `v_j`

at time stamp `t_i`

can be represented as an edge from node `v_i`

to node `v_j`

at `t_i`

. It is natural to represent the user interaction network as an ordered sequence of networks, each has a time stamp label. Ignoring the time stamps in the network can give wrong information.

Let's see a toy example. We let `A`

, `B`

, `C`

be three co-workers working in the same internet company. There was a new assignment for `A`

, `B`

, and `C`

.

If `A`

first found out the assignment and told `B`

on day `1`

and `C`

on day `2`

. `B`

reminded `C`

about the assignment on day `3`

. Now everyone knew about this new assignment. This process is illustrated in the figure below.

We can model this example using an evolving graph. In EvolvingGraphs.jl,

```
julia> using EvolvingGraphs
julia> g = EvolvingGraph{Node{String}, Int}()
Directed EvolvingGraph 0 nodes, 0 static edges, 0 timestamps
julia> add_bunch_of_edges!(g, [("A", "B", 1), ("A", "C", 2), ("B", "C", 3)])
Directed EvolvingGraph 3 nodes, 3 static edges, 3 timestamps
julia> edges(g)
3-element Array{EvolvingGraphs.WeightedTimeEdge{EvolvingGraphs.Node{String},Int64,Float64},1}:
Node(A)-1.0->Node(B) at time 1
Node(A)-1.0->Node(C) at time 2
Node(B)-1.0->Node(C) at time 3
```

It is clear that `A`

is the most important person for passing this information. We use a generalised Katz centrality to evaluate the importance of `A`

, `B`

, and `C`

.

```
julia> using EvolvingGraphs.Centrality
julia> katz(g)
3-element Array{Tuple{EvolvingGraphs.Node{String},Float64},1}:
(Node(A), 0.717617)
(Node(B), 0.552013)
(Node(C), 0.424626)
```

Suppose our `A`

was a keen person and decided to remind `B`

again in day `2`

. Now the networks look like

In EvolvingGraphs.jl, we add a new edge at timestamp `2`

.

```
julia> add_edge!(g, "A", "B", 2)
Node(A)-1.0->Node(B) at time 2
julia> katz(g)
3-element Array{Tuple{EvolvingGraphs.Node{String},Float64},1}:
(Node(A), 0.785247)
(Node(B), 0.490779)
(Node(C), 0.377522)
```

Notice that the rating of `A`

is getting even higher than before.

If we just aggregate an evolving graph to a simple directed graph by ignoring the timestamps, they aggregate to form the same static graph and therefore the ratings stay the same.

Evolving graphs provide richer information and allow us to have a detailed understanding of a real-world problem.

## Graph Traversal

How do we traverse an evolving graph? In a breadth-first search (BFS) one first visits all the outward neighbors of the starting node, then visits all the outward neighbors of each of those nodes, and so on.

Let's look at the implementation of BFS in EvolvingGraphs.jl.

```
function breadth_first_impl(g, v)
level = Dict(v => 0)
i = 1
fronter = [v]
while length(fronter) > 0
next = []
for u in fronter
for v in forward_neighbors(g, u)
if !(v in keys(level))
level[v] = i
push!(next, v)
end
end
end
fronter = next
i += 1
end
level
end
```

It looks exactly the same as a BFS in static graphs except here we use `forward_neigbors`

not outward neighbors. `forward_neigbors`

preserves the direction of time and make sure we do not travel back in time.

## A List of Adjacency Matrices

Researchers often consider an evolving graph as an ordered sequence of adjacency matrices `A^{k}`

. In EvolvingGraphs.jl, we provide a data structure called `MatrixList`

, which stores a list of adjacency matrices.

You can convert an evolving graph to an `MatrixList`

```
julia> using EvolvingGraphs
julia> g = EvolvingGraph()
Directed EvolvingGraph 0 nodes, 0 static edges, 0 timestamps
julia> add_bunch_of_edges!(g, [(1,2,2001),(2,3,2002),(3,4,2002),(1,4,2002),(2,3,2003),(2,5,2005)])
Directed EvolvingGraph 5 nodes, 6 static edges, 4 timestamps
julia> ml = evolving_graph_to_matrices(g)
MatrixList (4 matrices)
```

and can write a `for`

loop to access its elements.

```
julia> for (i,m) in enumerate(ml)
println("Matrix $i")
println(m)
end
Matrix 1
[1, 2] = 1.0
Matrix 2
[2, 3] = 1.0
[1, 4] = 1.0
[3, 4] = 1.0
Matrix 3
[2, 3] = 1.0
Matrix 4
[2, 5] = 1.0
```