For using the igraph C library
The following short example program demonstrates the basic usage of
the igraph library. Save it into a file named
igraph_test.c
.
#include <igraph.h> int main(void) { igraph_integer_t num_vertices = 1000; igraph_integer_t num_edges = 1000; igraph_real_t diameter; igraph_t graph; igraph_rng_seed(igraph_rng_default(), 42); igraph_erdos_renyi_game_gnm( &graph, num_vertices, num_edges, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE ); igraph_diameter( &graph, &diameter, /* from = */ NULL, /* to = */ NULL, /* vertex_path = */ NULL, /* edge_path = */ NULL, IGRAPH_UNDIRECTED, /* unconn= */ true ); printf("Diameter of a random graph with average degree %g: %g\n", 2.0 * igraph_ecount(&graph) / igraph_vcount(&graph), (double) diameter); igraph_destroy(&graph); return 0; }
This example illustrates a couple of points:
First, programs
using the igraph library should include the
igraph.h
header
file. Note that while igraph installs several sub-headers, the organization of these may change
without notice. Only use igraph.h
in your projects, not any of the sub-headers.
Second, igraph uses the igraph_integer_t type for integers instead of int or long int, and it also uses the igraph_real_t type for real numbers instead of double. Depending on how igraph was compiled, and whether you are using a 32-bit or 64-bit system, igraph_integer_t may be a 32-bit or 64-bit integer.
Third, igraph graph objects are represented by the igraph_t data type.
Fourth, the igraph_erdos_renyi_game_gnm()
creates a graph and igraph_destroy()
destroys it, i.e. deallocates the memory associated to it.
For compiling this program you need a C compiler. Optionally, CMake can be used to automate the compilation.
It is convenient to use CMake because it can automatically discover the
necessary compilation flags on all operating systems. Many IDEs support
CMake, and can work with CMake projects directly. To create a CMake project
for this example program, create a file name CMakeLists.txt
with the
following contents:
cmake_minimum_required(VERSION 3.18) project(igraph_test) find_package(igraph REQUIRED) add_executable(igraph_test igraph_test.c) target_link_libraries(igraph_test PUBLIC igraph::igraph)
To compile the project, create a new directory called build
in
the root of the igraph source tree, and switch to it:
mkdir build cd build
Run CMake to configure the project:
cmake ..
If igraph was installed at a non-standard location, specify its prefix
using the -DCMAKE_PREFIX_PATH=...
option. The prefix must be
the same directory that was specified as the CMAKE_INSTALL_PREFIX
when compiling igraph.
If configuration has succeeded, build the program using
cmake --build .
Parts of igraph are implemented in C++; therefore, any CMake target that depends on igraph should use the C++ linker. Furthermore, OpenMP support in igraph works correctly only if C++ is enabled in the CMake project. The script that finds igraph on the host machine will throw an error if C++ support is not enabled in the CMake project.
C++ support is enabled by default when no languages are explicitly
specified in CMake's project
command, e.g. project(igraph_test)
. If you do specify some languages explicitly,
make sure to also include CXX
, e.g. project(igraph_test C CXX)
.
On most Unix-like systems, the default C compiler is called cc. To compile the test program, you will need a command similar to the following:
cc igraph_test.c -I/usr/local/include/igraph -L/usr/local/lib -ligraph -o igraph_test
The exact form depends on where igraph was installed on your
system, whether it was compiled as a shared or static library, and the external
libraries it was linked to. The directory after the -I
switch
is the one containing the igraph.h
file, while the one
following -L
should contain the library file itself, usually a
file called libigraph.a
(static library on macOS and
Linux), libigraph.so
(shared library on Linux),
libigraph.dylib
(shared library on macOS),
igraph.lib
(static library on Windows) or
igraph.dll
(shared library on Windows). If
igraph was compiled as a static library, it is also
necessary to manually link to all of its dependencies.
If your system has the pkg-config utility you are likely to get the necessary compile options by issuing the command
pkg-config --libs --cflags igraph
(if igraph was built as a shared library) or
pkg-config --static --libs --cflags igraph
(if igraph was built as a static library).
On most systems, the executable can be run by simply typing its name like this:
./igraph_test
If you use dynamic linking and the igraph
library is not installed in a standard place, you may need to add its location to the
LD_LIBRARY_PATH
(Linux), DYLD_LIBRARY_PATH
(macOS)
or PATH
(Windows) environment variables. This is typically necessary
on Windows systems.
The functions generating graph objects are called graph generators. Stochastic (i.e. randomized) graph generators are called “games”.
igraph can handle directed and undirected graphs. Most graph
generators are able to create both types of graphs and most other
functions are usually also capable of handling
both. E.g., igraph_get_shortest_paths()
,
which calculates shortest paths from a vertex to other vertices, can calculate
directed or undirected paths.
igraph has sophisticated ways for creating graphs. The simplest
graphs are deterministic regular structures like star graphs
(igraph_star()
),
ring graphs (igraph_ring()
), lattices
(igraph_square_lattice()
) or trees
(igraph_kary_tree()
).
The following example creates an undirected regular circular lattice, adds some random edges to it and calculates the average length of shortest paths between all pairs of vertices in the graph before and after adding the random edges. (The message is that some random edges can reduce path lengths a lot.)
#include <igraph.h> int main(void) { igraph_t graph; igraph_vector_int_t dimvector; igraph_vector_int_t edges; igraph_vector_bool_t periodic; igraph_real_t avg_path_len; igraph_vector_int_init(&dimvector, 2); VECTOR(dimvector)[0]=30; VECTOR(dimvector)[1]=30; igraph_vector_bool_init(&periodic, 2); igraph_vector_bool_fill(&periodic, true); igraph_square_lattice(&graph, &dimvector, 0, IGRAPH_UNDIRECTED, /* mutual= */ false, &periodic); igraph_average_path_length(&graph, &avg_path_len, NULL, IGRAPH_UNDIRECTED, /* unconn= */ true); printf("Average path length (lattice): %g\n", (double) avg_path_len); igraph_rng_seed(igraph_rng_default(), 42); /* seed RNG before first use */ igraph_vector_int_init(&edges, 20); for (igraph_integer_t i=0; i < igraph_vector_int_size(&edges); i++) { VECTOR(edges)[i] = RNG_INTEGER(0, igraph_vcount(&graph) - 1); } igraph_add_edges(&graph, &edges, NULL); igraph_average_path_length(&graph, &avg_path_len, NULL, IGRAPH_UNDIRECTED, /* unconn= */ true); printf("Average path length (randomized lattice): %g\n", (double) avg_path_len); igraph_vector_bool_destroy(&periodic); igraph_vector_int_destroy(&dimvector); igraph_vector_int_destroy(&edges); igraph_destroy(&graph); return 0; }
This example illustrates some new points. igraph uses
igraph_vector_t
and its related types (igraph_vector_int_t, igraph_vector_bool_t
and so on) instead of plain C arrays. igraph_vector_t is superior to
regular arrays in almost every sense. Vectors are created by the
igraph_vector_init()
function and, like graphs, they should be destroyed if not
needed any more by calling
igraph_vector_destroy()
on them. A vector can be indexed by the
VECTOR()
function
(right now it is a macro). The elements of a vector are of type igraph_real_t
for igraph_vector_t,
and of type igraph_integer_t for igraph_vector_int_t.
As you might expect, igraph_vector_bool_t holds
igraph_bool_t values. Vectors can be resized and most igraph
functions returning the result in a vector automatically resize it to the size they need.
igraph_square_lattice()
takes an integer vector argument specifying the dimensions of
the lattice. In this example we generate a 30x30 two dimensional
periodic lattice. See the documentation of
igraph_square_lattice()
in
the reference manual for the other arguments.
The vertices in a graph are identified by a vertex ID, an integer between
0
and n - 1
, where n
is the number of vertices in the graph.
The vertex count can be retrieved using igraph_vcount()
,
as in the example.
The igraph_add_edges()
function simply takes a graph and a vector of
vertex IDs defining the new edges. The first edge is between the first
two vertex IDs in the vector, the second edge is between the second
two, etc. This way we add ten random edges to the lattice.
Note that this example program may add loop edges, edges
pointing a vertex to itself, or multiple edges, more than one edge
between the same pair of vertices.
igraph_t can of course represent loops and multiple edges, although some
routines expect simple graphs, i.e. graphs which contain neither of these. This is because some
structural properties are ill-defined for non-simple graphs. Loop and multi-edges can be removed by calling
igraph_simplify()
.
In our next example we will calculate various centrality measures in a friendship graph. The friendship graph is from the famous Zachary karate club study. (Do a web search on "Zachary karate" if you want to know more about this.) Centrality measures quantify how central is the position of individual vertices in the graph.
#include <igraph.h> int main(void) { igraph_t graph; igraph_vector_int_t v; igraph_vector_int_t result; igraph_vector_t result_real; igraph_integer_t edges[] = { 0,1, 0,2, 0,3, 0,4, 0,5, 0,6, 0,7, 0,8, 0,10, 0,11, 0,12, 0,13, 0,17, 0,19, 0,21, 0,31, 1, 2, 1, 3, 1, 7, 1,13, 1,17, 1,19, 1,21, 1,30, 2, 3, 2, 7, 2,27, 2,28, 2,32, 2, 9, 2, 8, 2,13, 3, 7, 3,12, 3,13, 4, 6, 4,10, 5, 6, 5,10, 5,16, 6,16, 8,30, 8,32, 8,33, 9,33, 13,33, 14,32, 14,33, 15,32, 15,33, 18,32, 18,33, 19,33, 20,32, 20,33, 22,32, 22,33, 23,25, 23,27, 23,32, 23,33, 23,29, 24,25, 24,27, 24,31, 25,31, 26,29, 26,33, 27,33, 28,31, 28,33, 29,32, 29,33, 30,32, 30,33, 31,32, 31,33, 32,33 }; igraph_vector_int_view(&v, edges, sizeof(edges) / sizeof(edges[0])); igraph_create(&graph, &v, 0, IGRAPH_UNDIRECTED); igraph_vector_int_init(&result, 0); igraph_vector_init(&result_real, 0); igraph_degree(&graph, &result, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); printf("Maximum degree is %10" IGRAPH_PRId ", vertex %2" IGRAPH_PRId ".\n", igraph_vector_int_max(&result), igraph_vector_int_which_max(&result)); igraph_closeness(&graph, &result_real, NULL, NULL, igraph_vss_all(), IGRAPH_ALL, /* weights= */ NULL, /* normalized= */ false); printf("Maximum closeness is %10g, vertex %2" IGRAPH_PRId ".\n", (double) igraph_vector_max(&result_real), igraph_vector_which_max(&result_real)); igraph_betweenness(&graph, &result_real, igraph_vss_all(), IGRAPH_UNDIRECTED, /* weights= */ NULL); printf("Maximum betweenness is %10g, vertex %2" IGRAPH_PRId ".\n", (double) igraph_vector_max(&result_real), igraph_vector_which_max(&result_real)); igraph_vector_int_destroy(&result); igraph_vector_destroy(&result_real); igraph_destroy(&graph); return 0; }
This example demonstrates some new operations. First of all, it shows a
way to create a graph a list of edges stored in a plain C array.
Function igraph_vector_view()
creates a view of a C array. It does not copy any data,
which means that you must not call
igraph_vector_destroy()
on a vector created this way. This vector is then used to create the
undirected graph.
Then the degree, closeness and betweenness centrality of the vertices
is calculated and the highest values are printed. Note that the vector
result
, into which these functions will write their
result, must be initialized first, and also that the functions resize
it to be able to hold the result.
Notice that in order to print values of type igraph_integer_t,
we used the IGRAPH_PRId
format macro constant. This
macro is similar to the standard PRI
constants defined
in stdint.h
, and expands to the correct printf
format specifier on each platform that igraph supports.
The igraph_vss_all()
argument
tells the functions to calculate the property for every vertex in the graph.
It is shorthand for a vertex selector, represented by type
igraph_vs_t.
Vertex selectors help perform operations on a subset of vertices.
You can read more about them in one
of the following chapters.
← Chapter 2. Installation | Chapter 4. Basic data types and interface → |