# Chapter 4. Basic data types and interface

## 1. The igraph data model

The igraph library can handle directed and undirected graphs. The igraph graphs are multisets of ordered (if directed) or unordered (if undirected) labeled pairs. The labels of the pairs plus the number of vertices always starts with zero and ends with the number of edges minus one. In addition to that, a table of metadata is also attached to every graph, its most important entries being the number of vertices in the graph and whether the graph is directed or undirected.

Like the edges, the igraph vertices are also labeled by numbers between zero and the number of vertices minus one. So, to summarize, a directed graph can be imagined like this:

      ( vertices: 6,
        directed: yes,
        {
         (0,2),
         (2,2),
         (3,2),
         (3,3),
         (3,4),
         (3,4),
         (4,3),
         (4,1)
        }
      )

Here the edges are ordered pairs or vertex ids, and the graph is a multiset of edges plus some metadata.

An undirected graph is like this:

      ( vertices: 6,
        directed: no,
        {
         (0,2),
         (2,2),
         (2,3),
         (3,3),
         (3,4),
         (3,4),
         (3,4),
         (1,4)
        }
      )

Here, an edge is an unordered pair of two vertex IDs. A graph is a multiset of edges plus metadata, just like in the directed case.

It is possible to convert between directed and undirected graphs, see the `igraph_to_directed()` and `igraph_to_undirected()` functions.

igraph aims to robustly support multigraphs, i.e. graphs which have more than one edge between some pairs of vertices, as well as graphs with self-loops. Most functions which do not support such graphs will check their input and issue an error if it is not valid. Those rare functions which do not perform this check clearly indicate this in their documentation. To eliminate multiple edges from a graph, you can use `igraph_simplify()`.

## 2. General conventions of igraph functions

igraph has a simple and consistent interface. Most functions check their input for validity and display an informative error message when something goes wrong. In order to support this, the majority of functions return an error code. In basic usage, this code can be ignored, as the default behaviour is to abort the program immediately upon error. See the section on error handling for more information on this topic.

Results are typically returned through output arguments, i.e. pointers to a data structure into which the result will be written. In almost all cases, this data structure is expected to be pre-initialized. A few simple functions communicate their result directly through their return value—these functions can never encounter an error.

## 3. Atomic data types

igraph introduces a few aliases to standard C data types that are then used throughout the library. The most important of these types is igraph_int_t, which is an alias to either a 32-bit or a 64-bit signed integer, depending on whether igraph was compiled in 32-bit or 64-bit mode. The size of igraph_int_t also influences the maximum number of vertices that an igraph graph can represent as the number of vertices is stored in a variable of type igraph_int_t.

Before igraph 1.0, igraph_int_t was called igraph_integer_t. This is still available as an alias to igraph_int_t and will remain accessible until at least version 2.0 of the library.

Since the size of a variable of type igraph_int_t may change depending on how igraph is compiled, you cannot simply use `%d` or `%ld` as a placeholder for igraph integers in `printf` format strings. igraph provides the `IGRAPH_PRId` macro, which maps to `d`, `ld` or `lld` depending on the size of igraph_int_t, and you must use this macro in `printf` format strings to avoid compiler warnings.

Similarly to how igraph_int_t maps to the standard size signed integer in the library, igraph_uint_t maps to a 32-bit or a 64-bit unsigned integer. It is guaranteed that the size of igraph_int_t is the same as the size of igraph_uint_t. igraph provides `IGRAPH_PRIu` as a format string placeholder for variables of type igraph_uint_t.

Real numbers (i.e. quantities that can potentially be fractional or infinite) are represented with a type named igraph_real_t. Currently igraph_real_t is always aliased to double, but it is still good practice to use igraph_real_t in your own code for sake of consistency.

Boolean values are represented with a type named igraph_bool_t. It tries to be as small as possible since it only needs to represent a truth value. For printing purposes, you can treat it as an integer and use `%d` in format strings as a placeholder for an igraph_bool_t.

Upper and lower limits of igraph_int_t and igraph_uint_t are provided by the constants named `IGRAPH_INTEGER_MIN`, `IGRAPH_INTEGER_MAX`, `IGRAPH_UINT_MIN` and `IGRAPH_UINT_MAX`.

## 4. Setup and initialization

Certain parts of igraph must be initialized before first use, which can be accomplished using the setup functions below. As of igraph 1.0, most functions will work correctly even if setup is not performed, as currently the only setup action is seeding the random number generator. That said, it is strongly recommended to call `igraph_setup()` before using any other function, as future igraph versions may add critical initialization steps.

### 4.1. `igraph_setup` — Initializes the igraph library.

    igraph_error_t igraph_setup(void);

This function is a convenience function to call all setup functions that are provided by the igraph library.

Most of the library functions will work even if this function is not called, but it is recommended to call it before using any igraph functions that may use random numbers, such as graph generators or random sampling functions. This function initializes the random number generator with a seed based on the current time, ensuring that the random numbers generated by igraph are different each time the program is run.

Returns:

`` | Error code; currently always `IGRAPH_SUCCESS`.

## 5. The basic interface

This is the very minimal API in igraph. All the other functions use this minimal set for creating and manipulating graphs.

This is a very important principle since it makes possible to implement other data representations by implementing only this minimal set.

This section lists all the functions and macros that are considered as part of the core API from the point of view of the users of igraph. Some of these functions and macros have sensible default implementations that simply call some other core function (e.g., `igraph_empty()` calls `igraph_empty_attrs()` with a null attribute table pointer). If you wish to experiment with implementing an alternative data type, the actual number of functions that you need to replace is lower as you can rely on the same default implementations in most cases.

### 5.1. Graph constructors and destructors

#### 5.1.1. `igraph_empty` — Creates an empty graph with some vertices and no edges.

    igraph_error_t igraph_empty(igraph_t *graph, igraph_int_t n, igraph_bool_t directed);

The most basic constructor, all the other constructors should call this to create a minimal graph object. Our use of the term "empty graph" in the above description should be distinguished from the mathematical definition of the empty or null graph. Strictly speaking, the empty or null graph in graph theory is the graph with no vertices and no edges. However by "empty graph" as used in igraph we mean a graph having zero or more vertices, but no edges.

Arguments:

`graph`: | Pointer to a not-yet initialized graph object.
`n`: | The number of vertices in the graph, a non-negative integer number is expected.

`directed`:

Boolean; whether the graph is directed or not. Supported values are:

`IGRAPH_DIRECTED` | The graph will be directed.
`IGRAPH_UNDIRECTED` | The graph will be undirected.

Returns:

`` | Error code: `IGRAPH_EINVAL`: invalid number of vertices.

Time complexity: O(|V|) for a graph with |V| vertices (and no edges).

Example 4.1. File `examples/simple/creation.c`

    #include <igraph.h>
    #include <assert.h>

    int main(void) {
        igraph_t graph;
        igraph_vector_int_t edges;

        /* Initialize the library. */
        igraph_setup();

        /* Create a directed graph with no vertices or edges. */
        igraph_empty(&graph, 0, IGRAPH_DIRECTED);

        /* Add 5 vertices. Vertex IDs will range from 0 to 4, inclusive. */
        igraph_add_vertices(&graph, 5, NULL);

        /* Add 5 edges, specified as 5 consecutive pairs of vertex IDs
         * stored in an integer vector. */
        igraph_vector_int_init_int(&edges, 10,
                                   0,1, 0,2, 3,1, 2,1, 0,4);
        igraph_add_edges(&graph, &edges, NULL);

        igraph_vector_int_destroy(&edges);

        /* Now the graph has 5 vertices and 5 edges. */
        assert(igraph_vcount(&graph) == 5);
        assert(igraph_ecount(&graph) == 5);

        igraph_destroy(&graph);

        return 0;
    }

#### 5.1.2. `igraph_empty_attrs` — Creates an empty graph with some vertices, no edges and some graph attributes.

    igraph_error_t igraph_empty_attrs(
        igraph_t *graph, igraph_int_t n, igraph_bool_t directed,
        const igraph_attribute_record_list_t *attr
    );

Use this instead of `igraph_empty()` if you wish to add some graph attributes right after initialization. This function is currently not very interesting for the ordinary user. Just supply 0 here or use `igraph_empty()`.

This function does not set any vertex attributes. To create a graph which has vertex attributes, call this function specifying 0 vertices, then use `igraph_add_vertices()` to add vertices and their attributes.

Arguments:

`graph`: | Pointer to a not-yet initialized graph object.
`n`: | The number of vertices in the graph; a non-negative integer number is expected.

`directed`:

Boolean; whether the graph is directed or not. Supported values are:

`IGRAPH_DIRECTED` | Create a directed graph.
`IGRAPH_UNDIRECTED` | Create an undirected graph.

`attr`: | The graph attributes. Supply `NULL` if not graph attributes are to be set.

Returns:

`` | Error code: `IGRAPH_EINVAL`: invalid number of vertices.

See also:

`` | `igraph_empty()` to create an empty graph without attributes; `igraph_add_vertices()` and `igraph_add_edges()` to add vertices and edges, possibly with associated attributes.

Time complexity: O(|V|) for a graph with |V| vertices (and no edges).

#### 5.1.3. `igraph_copy` — Creates an exact (deep) copy of a graph.

    igraph_error_t igraph_copy(igraph_t *to, const igraph_t *from);

This function deeply copies a graph object to create an exact replica of it. The new replica should be destroyed by calling `igraph_destroy()` on it when not needed any more.

You can also create a shallow copy of a graph by simply using the standard assignment operator, but be careful and do not destroy a shallow replica. To avoid this mistake, creating shallow copies is not recommended.

Arguments:

`to`: | Pointer to an uninitialized graph object.
`from`: | Pointer to the graph object to copy.

Returns:

`` | Error code.

Time complexity: O(|V|+|E|) for a graph with |V| vertices and |E| edges.

Example 4.2. File `examples/simple/igraph_copy.c`

    #include <igraph.h>

    int main(void) {

        igraph_t g1, g2;
        igraph_vector_int_t v1, v2;

        /* Initialize the library. */
        igraph_setup();

        igraph_vector_int_init(&v1, 8);
        VECTOR(v1)[0] = 0;
        VECTOR(v1)[1] = 1;
        VECTOR(v1)[2] = 1;
        VECTOR(v1)[3] = 2;
        VECTOR(v1)[4] = 2;
        VECTOR(v1)[5] = 3;
        VECTOR(v1)[6] = 2;
        VECTOR(v1)[7] = 2;

        igraph_create(&g1, &v1, 0, 0);
        igraph_copy(&g2, &g1);

        igraph_vector_int_init(&v2, 0);
        igraph_get_edgelist(&g2, &v2, 0);
        if (!igraph_vector_int_all_e(&v1, &v2)) {
            return 1;
        }

        igraph_vector_int_destroy(&v1);
        igraph_vector_int_destroy(&v2);
        igraph_destroy(&g1);
        igraph_destroy(&g2);

        return 0;
    }

#### 5.1.4. `igraph_destroy` — Frees the memory allocated for a graph object.

    void igraph_destroy(igraph_t *graph);

This function should be called for every graph object exactly once.

This function invalidates all iterators (of course), but the iterators of a graph should be destroyed before the graph itself anyway.

Arguments:

`graph`: | Pointer to the graph to free.

Time complexity: operating system specific.

### 5.2. Basic query operations

#### 5.2.1. `igraph_vcount` — The number of vertices in a graph.

    igraph_int_t igraph_vcount(const igraph_t *graph);

Arguments:

`graph`: | The graph.

Returns:

`` | Number of vertices.

Time complexity: O(1)

#### 5.2.2. `igraph_ecount` — The number of edges in a graph.

    igraph_int_t igraph_ecount(const igraph_t *graph);

Arguments:

`graph`: | The graph.

Returns:

`` | Number of edges.

Time complexity: O(1)

#### 5.2.3. `igraph_is_directed` — Is this a directed graph?

    igraph_bool_t igraph_is_directed(const igraph_t *graph);

Arguments:

`graph`: | The graph.

Returns:

`` | Boolean value, `true` if the graph is directed, `false` otherwise.

Time complexity: O(1)

Example 4.3. File `examples/simple/igraph_is_directed.c`

    #include <igraph.h>

    int main(void) {

        igraph_t g;

        /* Initialize the library. */
        igraph_setup();

        igraph_empty(&g, 0, 0);
        if (igraph_is_directed(&g)) {
            return 1;
        }
        igraph_destroy(&g);

        igraph_empty(&g, 0, 1);
        if (!igraph_is_directed(&g)) {
            return 2;
        }
        igraph_destroy(&g);

        return 0;
    }

#### 5.2.4. `igraph_edge` — Returns the head and tail vertices of an edge.

    igraph_error_t igraph_edge(
        const igraph_t *graph, igraph_int_t eid,
        igraph_int_t *from, igraph_int_t *to
    );

Arguments:

`graph`: | The graph object.
`eid`: | The edge ID.
`from`: | Pointer to an igraph_int_t. The tail (source) of the edge will be placed here.
`to`: | Pointer to an igraph_int_t. The head (target) of the edge will be placed here.

Returns:

`` | Error code.

See also:

`` | `igraph_get_eid()` for the opposite operation; `igraph_edges()` to get the endpoints of several edges; `IGRAPH_TO()`, `IGRAPH_FROM()` and `IGRAPH_OTHER()` for a faster but non-error-checked version.

Added in version 0.2.

Time complexity: O(1).

#### 5.2.5. `igraph_edges` — Gives the head and tail vertices of a series of edges.

    igraph_error_t igraph_edges(
        const igraph_t *graph, igraph_es_t eids, igraph_vector_int_t *edges,
        igraph_bool_t bycol
    );

Arguments:

`graph`: | The graph object.
`eids`: | Edge selector, the series of edges.
`edges`: | Pointer to an initialized vector. The start and endpoints of each edge will be placed here.
`bycol`: | Boolean constant. If true, the edges will be returned columnwise, e.g. the first edge is `res[0]->res[|E|]`, the second is `res[1]->res[|E|+1]`, etc. Supply false to get the edge list in a format compatible with `igraph_add_edges()`.

Returns:

`` | Error code.

See also:

`` | `igraph_get_eids()` for the opposite operation; `igraph_edge()` for getting the endpoints of a single edge; `IGRAPH_TO()`, `IGRAPH_FROM()` and `IGRAPH_OTHER()` for a faster but non-error-checked method.

Time complexity: O(k) where k is the number of edges in the selector.

#### 5.2.6. `IGRAPH_FROM` — The source vertex of an edge.

    #define IGRAPH_FROM(graph,eid)

Faster than `igraph_edge()`, but no error checking is done: `eid` is assumed to be valid.

Arguments:

`graph`: | The graph.
`eid`: | The edge ID.

Returns:

`` | The source vertex of the edge.

See also:

`` | `igraph_edge()` if error checking is desired.

#### 5.2.7. `IGRAPH_TO` — The target vertex of an edge.

    #define IGRAPH_TO(graph,eid)

Faster than `igraph_edge()`, but no error checking is done: `eid` is assumed to be valid.

Arguments:

`graph`: | The graph object.
`eid`: | The edge ID.

Returns:

`` | The target vertex of the edge.

See also:

`` | `igraph_edge()` if error checking is desired.

#### 5.2.8. `IGRAPH_OTHER` — The other endpoint of an edge.

    #define IGRAPH_OTHER(graph,eid,vid)

Typically used with undirected edges when one endpoint of the edge is known, and the other endpoint is needed. No error checking is done: `eid` and `vid` are assumed to be valid.

Arguments:

`graph`: | The graph object.
`eid`: | The edge ID.
`vid`: | The vertex ID of one endpoint of an edge.

Returns:

`` | The other endpoint of the edge.

See also:

`` | `IGRAPH_TO()` and `IGRAPH_FROM()` to get the source and target of directed edges.

#### 5.2.9. `igraph_get_eid` — Get the edge ID from the endpoints of an edge.

    igraph_error_t igraph_get_eid(const igraph_t *graph, igraph_int_t *eid,
                       igraph_int_t from, igraph_int_t to,
                       igraph_bool_t directed, igraph_bool_t error);

For undirected graphs `from` and `to` are exchangeable.

Arguments:

`graph`: | The graph object.
`eid`: | Pointer to an integer, the edge ID will be stored here. If `error` is false and no edge was found, `-1` will be returned.
`from`: | The starting point of the edge.
`to`: | The end point of the edge.
`directed`: | Boolean, whether to search for directed edges in a directed graph. Ignored for undirected graphs.
`error`: | Boolean, whether to report an error if the edge was not found. If it is false, then `-1` will be assigned to `eid`. Note that invalid vertex IDs in input arguments (`from` or `to`) always trigger an error, regardless of this setting.

Returns:

`` | Error code.

See also:

`` | `igraph_edge()` for the opposite operation, `igraph_get_all_eids_between()` to retrieve all edge IDs between a pair of vertices.

Time complexity: O(log (d)), where d is smaller of the out-degree of `from` and in-degree of `to` if `directed` is true. If `directed` is false, then it is O(log(d)+log(d2)), where d is the same as before and d2 is the minimum of the out-degree of `to` and the in-degree of `from`.

Example 4.4. File `examples/simple/igraph_get_eid.c`

    #include <igraph.h>

    int main(void) {
        igraph_t graph;
        igraph_int_t eid;
        igraph_vector_int_t hist;

        /* Initialize the library. */
        igraph_setup();

        /* DIRECTED */

        igraph_star(&graph, 10, IGRAPH_STAR_OUT, 0);

        igraph_vector_int_init(&hist, 9);

        for (igraph_int_t i = 1; i < 10; i++) {
            igraph_get_eid(&graph, &eid, 0, i, IGRAPH_DIRECTED, /*error=*/ true);
            VECTOR(hist)[eid] = 1;
        }

        igraph_vector_int_print(&hist);

        igraph_vector_int_destroy(&hist);
        igraph_destroy(&graph);

        /* UNDIRECTED */

        igraph_star(&graph, 10, IGRAPH_STAR_UNDIRECTED, 0);

        igraph_vector_int_init(&hist, 9);

        for (igraph_int_t i = 1; i < 10; i++) {
            igraph_get_eid(&graph, &eid, 0, i, IGRAPH_UNDIRECTED, /*error=*/ true);
            VECTOR(hist)[eid] += 1;
            igraph_get_eid(&graph, &eid, i, 0, IGRAPH_DIRECTED, /*error=*/ true);
            VECTOR(hist)[eid] += 1;
        }
        igraph_vector_int_print(&hist);

        igraph_vector_int_destroy(&hist);
        igraph_destroy(&graph);

        return 0;
    }

#### 5.2.10. `igraph_get_eids` — Return edge IDs based on the adjacent vertices.

    igraph_error_t igraph_get_eids(const igraph_t *graph, igraph_vector_int_t *eids,
                        const igraph_vector_int_t *pairs,
                        igraph_bool_t directed, igraph_bool_t error);

The pairs of vertex IDs for which the edges are looked up are taken consecutively from the `pairs` vector, i.e. `VECTOR(pairs)[0]` and `VECTOR(pairs)[1]` specify the first pair, `VECTOR(pairs)[2]` and `VECTOR(pairs)[3]` the second pair, etc.

If you have a sequence of vertex IDs that describe a path on the graph, use `igraph_expand_path_to_pairs()` to convert them to a list of vertex pairs along the path.

If the `error` argument is true, then it is an error to specify pairs of vertices that are not connected. Otherwise -1 is reported for vertex pairs without at least one edge between them.

If there are multiple edges in the graph, then these are ignored; i.e. for a given pair of vertex IDs, igraph always returns the same edge ID, even if the pair appears multiple times in `pairs`.

Arguments:

`graph`: | The input graph.
`eids`: | Pointer to an initialized vector, the result is stored here. It will be resized as needed.
`pairs`: | Vector giving pairs of vertices to fetch the edges for.
`directed`: | Boolean, whether to consider edge directions in directed graphs. This is ignored for undirected graphs.
`error`: | Boolean, whether it is an error to supply non-connected vertices. If false, then -1 is returned for non-connected pairs.

Returns:

`` | Error code.

Time complexity: O(n log(d)), where n is the number of queried edges and d is the average degree of the vertices.

See also:

`` | `igraph_get_eid()` for a single edge.

Example 4.5. File `examples/simple/igraph_get_eids.c`

    #include <igraph.h>
    #include <stdlib.h>

    int main(void) {
        igraph_t g;
        igraph_int_t nodes = 100;
        igraph_int_t edges = 1000;
        igraph_real_t p = 3.0 / nodes;
        igraph_int_t runs = 10;
        igraph_int_t r, e, ecount;
        igraph_vector_int_t eids, pairs, path;

        /* Initialize the library. */
        igraph_setup();

        igraph_rng_seed(igraph_rng_default(), 42); /* make tests deterministic */

        igraph_vector_int_init(&pairs, edges * 2);
        igraph_vector_int_init(&path, 0);
        igraph_vector_int_init(&eids, 0);

        for (r = 0; r < runs; r++) {
            igraph_vector_int_resize(&pairs, edges * 2);
            igraph_vector_int_clear(&path);
            igraph_vector_int_clear(&eids);

            igraph_erdos_renyi_game_gnp(&g, nodes, p, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED);
            ecount = igraph_ecount(&g);
            for (e = 0; e < edges; e++) {
                igraph_int_t edge = RNG_INTEGER(0, ecount - 1);
                VECTOR(pairs)[2 * e] = IGRAPH_FROM(&g, edge);
                VECTOR(pairs)[2 * e + 1] = IGRAPH_TO(&g, edge);
            }
            igraph_get_eids(&g, &eids, &pairs, IGRAPH_UNDIRECTED, /*error=*/ true);
            for (e = 0; e < edges; e++) {
                igraph_int_t edge = VECTOR(eids)[e];
                igraph_int_t from1 = VECTOR(pairs)[2 * e];
                igraph_int_t to1 = VECTOR(pairs)[2 * e + 1];
                igraph_int_t from2 = IGRAPH_FROM(&g, edge);
                igraph_int_t to2 = IGRAPH_TO(&g, edge);
                igraph_int_t min1 = from1 < to1 ? from1 : to1;
                igraph_int_t max1 = from1 < to1 ? to1 : from1;
                igraph_int_t min2 = from2 < to2 ? from2 : to2;
                igraph_int_t max2 = from2 < to2 ? to2 : from2;
                if (min1 != min2 || max1 != max2) {
                    return 11;
                }
            }

            igraph_diameter(&g, /*weights=*/ NULL, /*res=*/ NULL, /*from=*/ NULL, /*to=*/ NULL, &path, NULL,
                            IGRAPH_UNDIRECTED, /*unconn=*/ true);
            igraph_vector_int_update(&pairs, &path);
            igraph_expand_path_to_pairs(&pairs);
            igraph_get_eids(&g, &eids, &pairs, /* directed= */ false, /*error=*/ true);
            for (e = 0; e < igraph_vector_int_size(&path) - 1; e++) {
                igraph_int_t edge = VECTOR(eids)[e];
                igraph_int_t from1 = VECTOR(path)[e];
                igraph_int_t to1 = VECTOR(path)[e + 1];
                igraph_int_t from2 = IGRAPH_FROM(&g, edge);
                igraph_int_t to2 = IGRAPH_TO(&g, edge);
                igraph_int_t min1 = from1 < to1 ? from1 : to1;
                igraph_int_t max1 = from1 < to1 ? to1 : from1;
                igraph_int_t min2 = from2 < to2 ? from2 : to2;
                igraph_int_t max2 = from2 < to2 ? to2 : from2;
                if (min1 != min2 || max1 != max2) {
                    return 12;
                }
            }

            igraph_destroy(&g);
        }

        igraph_vector_int_destroy(&path);
        igraph_vector_int_destroy(&pairs);
        igraph_vector_int_destroy(&eids);

        return 0;
    }

#### 5.2.11. `igraph_get_all_eids_between` — Returns all edge IDs between a pair of vertices.

    igraph_error_t igraph_get_all_eids_between(
        const igraph_t *graph, igraph_vector_int_t *eids,
        igraph_int_t source, igraph_int_t target, igraph_bool_t directed
    );

For undirected graphs `source` and `target` are exchangeable.

Arguments:

`graph`: | The input graph.
`eids`: | Pointer to an initialized vector, the result is stored here. It will be resized as needed.
`source`: | The ID of the source vertex
`target`: | The ID of the target vertex
`directed`: | Boolean, whether to consider edge directions in directed graphs. This is ignored for undirected graphs.

Returns:

`` | Error code.

Time complexity: TODO

See also:

`` | `igraph_get_eid()` for a single edge.

#### 5.2.12. `igraph_neighbors` — Adjacent vertices to a vertex.

    igraph_error_t igraph_neighbors(
        const igraph_t *graph, igraph_vector_int_t *neis, igraph_int_t pnode,
        igraph_neimode_t mode, igraph_loops_t loops, igraph_bool_t multiple
    );

Arguments:

`graph`: | The graph to work on.
`neis`: | This vector will contain the result. The vector should be initialized beforehand and will be resized. Starting from igraph version 0.4 this vector is always sorted, the vertex IDs are in increasing order. If one neighbor is connected with multiple edges, the neighbor will be returned multiple times.
`pnode`: | The id of the node for which the adjacent vertices are to be searched.
`mode`: | Defines the way adjacent vertices are searched in directed graphs. It can have the following values: `IGRAPH_OUT`, vertices reachable by an edge from the specified vertex are searched; `IGRAPH_IN`, vertices from which the specified vertex is reachable are searched; `IGRAPH_ALL`, both kinds of vertices are searched. This parameter is ignored for undirected graphs.
`loops`: | Specifies how to treat loop edges. `IGRAPH_NO_LOOPS` removes loop edges from the result. `IGRAPH_LOOPS_ONCE` makes each loop edge appear only once in the result. `IGRAPH_LOOPS_TWICE` makes loop edges appear twice in the result if the graph is undirected or `mode` is set to `IGRAPH_ALL` (and once otherwise as returning them twice does not make sense for directed graphs).
`multiple`: | Specifies how to treat multiple (parallel) edges. `IGRAPH_NO_MULTIPLE` collapses parallel edges into a single one; `IGRAPH_MULTIPLE` keeps the multiplicities of parallel edges so the same neighbor will appear as many times in the result as the number of parallel edges going between the two vertices.

Returns:

`` | Error code: `IGRAPH_EINVVID`: invalid vertex ID. `IGRAPH_EINVMODE`: invalid mode argument. `IGRAPH_ENOMEM`: not enough memory.

Time complexity: O(d), d is the number of adjacent vertices to the queried vertex.

Example 4.6. File `examples/simple/igraph_neighbors.c`

    #include <igraph.h>

    int main(void) {
        igraph_t g;
        igraph_vector_int_t v;

        /* Initialize the library. */
        igraph_setup();

        igraph_vector_int_init(&v, 0);
        igraph_small(&g, 4, IGRAPH_DIRECTED, 0,1, 1,2, 2,3, 2,2, -1);

        igraph_neighbors(&g, &v, 2, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE);
        igraph_vector_int_sort(&v);
        igraph_vector_int_print(&v);

        igraph_neighbors(&g, &v, 2, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE);
        igraph_vector_int_sort(&v);
        igraph_vector_int_print(&v);

        igraph_neighbors(&g, &v, 2, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE);
        igraph_vector_int_sort(&v);
        igraph_vector_int_print(&v);

        igraph_vector_int_destroy(&v);
        igraph_destroy(&g);
        return 0;
    }

#### 5.2.13. `igraph_incident` — Gives the incident edges of a vertex.

    igraph_error_t igraph_incident(
        const igraph_t *graph, igraph_vector_int_t *eids, igraph_int_t pnode,
        igraph_neimode_t mode, igraph_loops_t loops
    );

Arguments:

`graph`: | The graph object.
`eids`: | An initialized vector. It will be resized to hold the result.
`pnode`: | A vertex ID.
`mode`: | Specifies what kind of edges to include for directed graphs. `IGRAPH_OUT` means only outgoing edges, `IGRAPH_IN` means only incoming edges, `IGRAPH_ALL` means both. This parameter is ignored for undirected graphs.
`loops`: | Specifies how to treat loop edges. `IGRAPH_NO_LOOPS` removes loop edges from the result. `IGRAPH_LOOPS_ONCE` makes each loop edge appear only once in the result. `IGRAPH_LOOPS_TWICE` makes loop edges appear twice in the result if the graph is undirected or `mode` is set to `IGRAPH_ALL` (and once otherwise as returning them twice does not make sense for directed graphs).

Returns:

`` | Error code: `IGRAPH_EINVVID`: invalid vertex ID. `IGRAPH_EINVMODE`: invalid mode argument. `IGRAPH_ENOMEM`: not enough memory.

Time complexity: O(d), the number of incident edges to `pnode`.

#### 5.2.14. `igraph_degree` — The degree of some vertices in a graph.

    igraph_error_t igraph_degree(
        const igraph_t *graph, igraph_vector_int_t *res, const igraph_vs_t vids,
        igraph_neimode_t mode, igraph_loops_t loops
    );

This function calculates the in-, out- or total degree of the specified vertices.

This function returns the result as a vector of `igraph_int_t` values. In applications where `igraph_real_t` is desired, use `igraph_strength()` with `NULL` weights.

Arguments:

`graph`: | The graph.
`res`: | Integer vector, this will contain the result. It should be initialized and will be resized to be the appropriate size.
`vids`: | Vertex selector, giving the vertex IDs of which the degree will be calculated.
`mode`: | Defines the type of the degree for directed graphs. Valid modes are: `IGRAPH_OUT`, out-degree; `IGRAPH_IN`, in-degree; `IGRAPH_ALL`, total degree (sum of the in- and out-degree). This parameter is ignored for undirected graphs.
`loops`: | Constant of type `igraph_loops_t`, specifies how to treat loop edges when calculating the degree. `IGRAPH_NO_LOOPS` ignores loop edges; `IGRAPH_LOOPS_ONCE` counts each loop edge only once; `IGRAPH_LOOPS_TWICE` counts each loop edge twice in undirected graphs and once in directed graphs.

Returns:

`` | Error code: `IGRAPH_EINVVID`: invalid vertex ID. `IGRAPH_EINVMODE`: invalid mode argument.

Time complexity: O(v) if `loops` is `true`, and O(v*d) otherwise. v is the number of vertices for which the degree will be calculated, and d is their (average) degree.

See also:

`` | `igraph_strength()` for the version that takes into account edge weights; `igraph_degree_1()` to efficiently compute the degree of a single vertex; `igraph_maxdegree()` if you only need the largest degree.

Example 4.7. File `examples/simple/igraph_degree.c`

    #include <igraph.h>

    igraph_bool_t handshaking_lemma(igraph_t *g, igraph_vector_int_t *v) {
        /* Consistency check of the handshaking lemma:
         * If d is the sum of all vertex degrees, then d = 2|E|. */
        return igraph_vector_int_sum(v) == 2 * igraph_ecount(g);
    }

    int main(void) {
        igraph_t g;
        igraph_vector_int_t v;
        igraph_vector_int_t seq;
        igraph_int_t mdeg;

        /* Initialize the library. */
        igraph_setup();

        /* Create graph */
        igraph_vector_int_init(&v, 8);
        igraph_small(&g, 4, IGRAPH_DIRECTED, 0,1, 1,2, 2,3, 2,2, -1);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_OUT, IGRAPH_NO_LOOPS);
        igraph_vector_int_print(&v);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS);
        igraph_vector_int_print(&v);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_IN, IGRAPH_NO_LOOPS);
        igraph_vector_int_print(&v);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS);
        igraph_vector_int_print(&v);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS);
        igraph_vector_int_print(&v);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS);
        igraph_vector_int_print(&v);

        if (!handshaking_lemma(&g, &v)) {
            exit(3);
        }

        igraph_destroy(&g);

        igraph_small(&g, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,3, 2,2, -1);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_OUT, IGRAPH_NO_LOOPS);
        igraph_vector_int_print(&v);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS);
        igraph_vector_int_print(&v);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_IN, IGRAPH_NO_LOOPS);
        igraph_vector_int_print(&v);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS);
        igraph_vector_int_print(&v);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS);
        igraph_vector_int_print(&v);

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS);
        igraph_vector_int_print(&v);

        if (!handshaking_lemma(&g, &v)) {
            exit(4);
        }

        /* Degree of the same vertex multiple times */

        igraph_vector_int_init(&seq, 3);
        VECTOR(seq)[0] = 2;
        VECTOR(seq)[1] = 0;
        VECTOR(seq)[2] = 2;
        igraph_degree(&g, &v, igraph_vss_vector(&seq), IGRAPH_ALL, IGRAPH_LOOPS);
        igraph_vector_int_print(&v);

        igraph_destroy(&g);
        igraph_vector_int_destroy(&seq);

        /* Maximum degree */

        igraph_ring(&g, 10, IGRAPH_UNDIRECTED, /* mutual */ false, /* circular */ false);
        igraph_maxdegree(&g, &mdeg, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS);
        if (mdeg != 2) {
            exit(5);
        }

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS);
        if (! handshaking_lemma(&g, &v)) {
            exit(6);
        }
        igraph_destroy(&g);

        igraph_full(&g, 10, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS);
        igraph_maxdegree(&g, &mdeg, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS);
        if (mdeg != 9) {
            exit(7);
        }

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS);
        if (! handshaking_lemma(&g, &v)) {
            exit(8);
        }
        igraph_destroy(&g);

        igraph_star(&g, 10, IGRAPH_STAR_OUT, /* center */ 0);
        igraph_maxdegree(&g, &mdeg, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS);
        if (mdeg != 9) {
            exit(9);
        }
        igraph_maxdegree(&g, &mdeg, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS);
        if (mdeg != 1) {
            exit(10);
        }
        igraph_maxdegree(&g, &mdeg, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS);
        if (mdeg != 9) {
            exit(11);
        }

        igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS);
        if (! handshaking_lemma(&g, &v)) {
            exit(12);
        }
        igraph_destroy(&g);

        igraph_vector_int_destroy(&v);

        return 0;
    }

#### 5.2.15. `igraph_degree_1` — The degree of of a single vertex in the graph.

    igraph_error_t igraph_degree_1(
        const igraph_t *graph, igraph_int_t *deg, igraph_int_t vid,
        igraph_neimode_t mode, igraph_loops_t loops
    );

This function calculates the in-, out- or total degree of a single vertex. For a single vertex, it is more efficient than calling `igraph_degree()`.

Arguments:

`graph`: | The graph.
`deg`: | Pointer to the integer where the computed degree will be stored.
`vid`: | The vertex for which the degree will be calculated.
`mode`: | Defines the type of the degree for directed graphs. Valid modes are: `IGRAPH_OUT`, out-degree; `IGRAPH_IN`, in-degree; `IGRAPH_ALL`, total degree (sum of the in- and out-degree). This parameter is ignored for undirected graphs.
`loops`: | Boolean, gives whether the self-loops should be counted.

Returns:

`` | Error code.

See also:

`` | `igraph_degree()` to compute the degree of several vertices at once.

Time complexity: O(1) if `loops` is `true`, and O(d) otherwise, where d is the degree.

### 5.3. Adding and deleting vertices and edges

#### 5.3.1. `igraph_add_edge` — Adds a single edge to a graph.

    igraph_error_t igraph_add_edge(igraph_t *graph, igraph_int_t from, igraph_int_t to);

For directed graphs the edge points from `from` to `to`.

Note that if you want to add many edges to a big graph, then it is inefficient to add them one by one, it is better to collect them into a vector and add all of them via a single `igraph_add_edges()` call.

Arguments:

`graph`: | The graph.
`from`: | The id of the first vertex of the edge.
`to`: | The id of the second vertex of the edge.

Returns:

`` | Error code.

See also:

`` | `igraph_add_edges()` to add many edges, `igraph_delete_edges()` to remove edges and `igraph_add_vertices()` to add vertices.

Time complexity: O(|V|+|E|), the number of edges plus the number of vertices.

#### 5.3.2. `igraph_add_edges` — Adds edges to a graph object.

    igraph_error_t igraph_add_edges(
        igraph_t *graph, const igraph_vector_int_t *edges,
        const igraph_attribute_record_list_t *attr
    );

The edges are given in a vector, the first two elements define the first edge (the order is `from`, `to` for directed graphs). The vector should contain even number of integer numbers between zero and the number of vertices in the graph minus one (inclusive). If you also want to add new vertices, call `igraph_add_vertices()` first.

Arguments:

`graph`: | The graph to which the edges will be added.
`edges`: | The edges themselves.
`attr`: | The attributes of the new edges. You can supply a null pointer here if you do not need edge attributes.

Returns:

`` | Error code: `IGRAPH_EINVAL`: invalid (odd) edges vector length, `IGRAPH_EINVVID`: invalid vertex ID in edges vector.

This function invalidates all iterators.

Time complexity: O(|V|+|E|) where |V| is the number of vertices and |E| is the number of edges in the new, extended graph.

Example 4.8. File `examples/simple/creation.c`

    #include <igraph.h>
    #include <assert.h>

    int main(void) {
        igraph_t graph;
        igraph_vector_int_t edges;

        /* Initialize the library. */
        igraph_setup();

        /* Create a directed graph with no vertices or edges. */
        igraph_empty(&graph, 0, IGRAPH_DIRECTED);

        /* Add 5 vertices. Vertex IDs will range from 0 to 4, inclusive. */
        igraph_add_vertices(&graph, 5, NULL);

        /* Add 5 edges, specified as 5 consecutive pairs of vertex IDs
         * stored in an integer vector. */
        igraph_vector_int_init_int(&edges, 10,
                                   0,1, 0,2, 3,1, 2,1, 0,4);
        igraph_add_edges(&graph, &edges, NULL);

        igraph_vector_int_destroy(&edges);

        /* Now the graph has 5 vertices and 5 edges. */
        assert(igraph_vcount(&graph) == 5);
        assert(igraph_ecount(&graph) == 5);

        igraph_destroy(&graph);

        return 0;
    }

#### 5.3.3. `igraph_add_vertices` — Adds vertices to a graph.

    igraph_error_t igraph_add_vertices(
        igraph_t *graph, igraph_int_t nv, const igraph_attribute_record_list_t *attr
    );

This function invalidates all iterators.

Arguments:

`graph`: | The graph object to extend.
`nv`: | Non-negative integer specifying the number of vertices to add.
`attr`: | The attributes of the new vertices. You can supply a null pointer here if you do not need vertex attributes.

Returns:

`` | Error code: `IGRAPH_EINVAL`: invalid number of new vertices.

Time complexity: O(|V|) where |V| is the number of vertices in the new, extended graph.

Example 4.9. File `examples/simple/creation.c`

    #include <igraph.h>
    #include <assert.h>

    int main(void) {
        igraph_t graph;
        igraph_vector_int_t edges;

        /* Initialize the library. */
        igraph_setup();

        /* Create a directed graph with no vertices or edges. */
        igraph_empty(&graph, 0, IGRAPH_DIRECTED);

        /* Add 5 vertices. Vertex IDs will range from 0 to 4, inclusive. */
        igraph_add_vertices(&graph, 5, NULL);

        /* Add 5 edges, specified as 5 consecutive pairs of vertex IDs
         * stored in an integer vector. */
        igraph_vector_int_init_int(&edges, 10,
                                   0,1, 0,2, 3,1, 2,1, 0,4);
        igraph_add_edges(&graph, &edges, NULL);

        igraph_vector_int_destroy(&edges);

        /* Now the graph has 5 vertices and 5 edges. */
        assert(igraph_vcount(&graph) == 5);
        assert(igraph_ecount(&graph) == 5);

        igraph_destroy(&graph);

        return 0;
    }

#### 5.3.4. `igraph_delete_edges` — Removes edges from a graph.

    igraph_error_t igraph_delete_edges(igraph_t *graph, igraph_es_t edges);

The edges to remove are specified as an edge selector.

This function cannot remove vertices; vertices will be kept even if they lose all their edges.

This function invalidates all iterators.

Arguments:

`graph`: | The graph to work on.
`edges`: | The edges to remove.

Returns:

`` | Error code.

Time complexity: O(|V|+|E|) where |V| and |E| are the number of vertices and edges in the original graph, respectively.

Example 4.10. File `examples/simple/igraph_delete_edges.c`

    #include <igraph.h>

    int main(void) {

        igraph_t g;
        igraph_es_t es;

        /* Initialize the library. */
        igraph_setup();

        igraph_small(&g, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, 2,3, -1);

        igraph_es_pairs_small(&es, IGRAPH_DIRECTED, 3, 2, -1);
        igraph_delete_edges(&g, es);
        if (igraph_ecount(&g) != 3) {
            return 1;
        }

        igraph_es_destroy(&es);
        igraph_destroy(&g);

        return 0;
    }

#### 5.3.5. `igraph_delete_vertices` — Removes some vertices (with all their edges) from the graph.

    igraph_error_t igraph_delete_vertices(igraph_t *graph, const igraph_vs_t vertices);

This function changes the IDs of the vertices (except in some very special cases, but these should not be relied on anyway).

This function invalidates all iterators.

Arguments:

`graph`: | The graph to work on.
`vertices`: | The IDs of the vertices to remove, in a vector. The vector may contain the same ID more than once.

Returns:

`` | Error code: `IGRAPH_EINVVID`: invalid vertex ID.

Time complexity: O(|V|+|E|), |V| and |E| are the number of vertices and edges in the original graph.

Example 4.11. File `examples/simple/igraph_delete_vertices.c`

    #include <igraph.h>

    int main(void) {
        igraph_t g;

        /* without edges */
        igraph_small(&g, 15, IGRAPH_UNDIRECTED, -1);

        igraph_delete_vertices(&g, igraph_vss_1(2));
        if (igraph_vcount(&g) != 14)  {
            return 2;
        }
        igraph_destroy(&g);

        /* with edges */
        igraph_small(&g, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,3, 2,2, -1);
        igraph_delete_vertices(&g, igraph_vss_1(2));
        if (igraph_vcount(&g) != 3) {
            return 3;
        }
        if (igraph_ecount(&g) != 1) {
            return 4;
        }

        igraph_destroy(&g);

        return 0;
    }

#### 5.3.6. `igraph_delete_vertices_map` — Removes some vertices (with all their edges) from the graph.

    igraph_error_t igraph_delete_vertices_map(
        igraph_t *graph, const igraph_vs_t vertices, igraph_vector_int_t *map,
        igraph_vector_int_t *invmap
    );

This function changes the IDs of the vertices (except in some very special cases, but these should not be relied on anyway). You can use the `map` argument to obtain the mapping from old vertex IDs to the new ones, and the `newmap` argument to obtain the reverse mapping.

This function invalidates all iterators.

Arguments:

`graph`: | The graph to work on.
`vertices`: | The IDs of the vertices to remove, in a vector. The vector may contain the same ID more than once.
`map`: | An optional pointer to a vector that provides the mapping from the vertex IDs before the removal to the vertex IDs after the removal. You can supply `NULL` here if you are not interested.
`invmap`: | An optional pointer to a vector that provides the mapping from the vertex IDs after the removal to the vertex IDs before the removal. You can supply `NULL` here if you are not interested.

Returns:

`` | Error code: `IGRAPH_EINVVID`: invalid vertex ID.

Time complexity: O(|V|+|E|), |V| and |E| are the number of vertices and edges in the original graph.

## 6. Miscellaneous macros and helper functions

### 6.1. `IGRAPH_VCOUNT_MAX` — The maximum number of vertices supported in igraph graphs.

    #define IGRAPH_VCOUNT_MAX

The value of this constant is one less than `IGRAPH_INTEGER_MAX` . When igraph is compiled in 32-bit mode, this means that you are limited to 231 – 2 (about 2.1 billion) vertices. In 64-bit mode, the limit is 263 – 2 so you are much more likely to hit out-of-memory issues due to other reasons before reaching this limit.

### 6.2. `IGRAPH_ECOUNT_MAX` — The maximum number of edges supported in igraph graphs.

    #define IGRAPH_ECOUNT_MAX

The value of this constant is half of `IGRAPH_INTEGER_MAX` . When igraph is compiled in 32-bit mode, this means that you are limited to approximately 230 (about 1.07 billion) vertices. In 64-bit mode, the limit is approximately 262 so you are much more likely to hit out-of-memory issues due to other reasons before reaching this limit.

### 6.3. `IGRAPH_UNLIMITED` — Constant for "do not limit results".

    #define IGRAPH_UNLIMITED

A constant signifying that no limitation should be used with various cutoff, size limit or result set size parameters, such as minimum or maximum clique size, number of results returned, cutoff for path lengths, etc. Currently defined to `-1`.

### 6.4. `igraph_expand_path_to_pairs` — Helper function to convert a sequence of vertex IDs describing a path into a "pairs" vector.

    igraph_error_t igraph_expand_path_to_pairs(igraph_vector_int_t* path);

This function is useful when you have a sequence of vertex IDs in a graph and you would like to retrieve the IDs of the edges between them. The function duplicates all but the first and the last elements in the vector, effectively converting the path into a vector of vertex IDs that can be passed to `igraph_get_eids()`.

Arguments:

`path`: | the input vector. It will be modified in-place and it will be resized as needed. When the vector contains less than two vertex IDs, it will be cleared.

Returns:

`` | Error code: `IGRAPH_ENOMEM` if there is not enough memory to expand the vector.

### 6.5. `igraph_invalidate_cache` — Invalidates the internal cache of an igraph graph.

    void igraph_invalidate_cache(const igraph_t* graph);

igraph graphs cache some basic properties about themselves in an internal data structure. This function invalidates the contents of the cache and forces a recalculation of the cached properties the next time they are needed.

You should not need to call this function during normal usage; however, we might ask you to call this function explicitly if we suspect that you are running into a bug in igraph's cache handling. A tell-tale sign of an invalid cache entry is that the result of a cached igraph function (such as `igraph_is_dag()` or `igraph_is_simple()`) is different before and after a cache invalidation.

Arguments:

`graph`: | The graph whose cache is to be invalidated.

Time complexity: O(1).

### 6.6. `igraph_is_same_graph` — Are two graphs identical as labelled graphs?

    igraph_error_t igraph_is_same_graph(const igraph_t *graph1, const igraph_t *graph2, igraph_bool_t *res);

Two graphs are considered to be the same if they have the same vertex and edge sets. Graphs which are the same may have multiple different representations in igraph, hence the need for this function.

This function verifies that the two graphs have the same directedness, the same number of vertices, and that they contain precisely the same edges (regardless of their ordering) when written in terms of vertex indices. Graph attributes are not taken into account.

This concept is different from isomorphism. For example, the graphs `0-1, 2-1` and `1-2, 0-1` are considered the same because they only differ in the ordering of their edge lists and the ordering of vertices in an undirected edge. However, they are not the same as `0-2, 1-2`, even though they are isomorphic to it. Note that this latter graph contains the edge `0-2` while the former two do not — thus their edge sets differ.

Arguments:

`graph1`: | The first graph object.
`graph2`: | The second graph object.
`res`: | The result will be stored here.

Returns:

`` | Error code.

Time complexity: O(E), the number of edges in the graphs.

See also:

`` | `igraph_isomorphic()` to test if two graphs are isomorphic.