Skip to content

Network superposition model of Erdős-Rényi subgraphs

The network superposition model, a union of Erdős-Rényi graphs whose linking probabilities are drawn.

  • Name: network_superposition
  • Field: mathematics/networks
  • Formalism: ordinary differential equations
  • References: Abe and Thurner 2005; Thurner et al. 2018

Select this system

from skcomplex.systems import create_simulation, create_system, describe

system = create_system('network_superposition')
entry = describe('network_superposition')
trajectory = create_simulation('network_superposition').prepare().run().trajectory_

Factory

@register_system(
    field='mathematics/networks',
    tags=('network', 'superposition', 'random graph', 'superstatistics', 'degree distribution'),
    references=('Abe and Thurner 2005', 'Thurner et al. 2018'),
    defaults={'until': 1, 'dt': 1},
)
def network_superposition(
    n_nodes: int = 100,
    *,
    n_subgraphs: int = 20,
    exponent: float = 2.0,
    low: float = 0.005,
    high: float = 0.1,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the network superposition model, a union of Erdős-Rényi graphs whose linking probabilities are drawn.

    Every one of `n_subgraphs` subgraphs of `n_nodes` nodes draws one linking probability `p` from
    `Q(p) ~ p^-exponent` on `[low, high]` and links each pair of its nodes with probability `p`, and the network is
    their union (Thurner et al. 2018, section 4.5.2). Its degrees mix the Poisson laws of the subgraphs,
    `P(k) = ∫ dp P(k | N p) Q(p)`, Eq. 4.34, so a power law of the probabilities gives a power law of the degrees up
    to the largest mean degree `N high`. The book draws `p` for every attempted link, which gives one Erdős-Rényi graph
    of the mean probability, so the probability is drawn once per subgraph, the reading of Eq. 4.34, like the cells of
    superstatistics.

    Args:
        n_nodes:
            The number of nodes of every subgraph.

        n_subgraphs:
            The number of subgraphs.

        exponent:
            The exponent of the distribution `Q(p)` of the linking probabilities.

        low:
            The smallest linking probability.

        high:
            The largest linking probability.

        random_state:
            The seed or generator of the linking probabilities and the links.

    Returns:
        system:
            The system, with the group `nodes` and their states `degree` and `clustering`.

    Raises:
        ValidationError:
            The bounds of the linking probabilities are not `0 < low <= high <= 1`.

    Examples:
        >>> from skcomplex.systems.mathematics.networks import network_superposition
        >>> system = network_superposition(10, n_subgraphs=3)
        >>> system.elements[0].values['degree'].size
        30
    """
    if not 0 < low <= high <= 1:
        error_msg = f'The linking probabilities lie in [{low}, {high}]. Give bounds with 0 < low <= high <= 1.'
        raise ValidationError(error_msg)
    rng = np.random.default_rng(random_state)
    graph = nx.Graph()
    for index, chance in enumerate(_linking_probabilities(n_subgraphs, exponent, (low, high), rng)):
        subgraph = nx.fast_gnp_random_graph(n_nodes, float(chance), seed=rng)
        graph.update(nx.relabel_nodes(subgraph, {node: index * n_nodes + node for node in subgraph}))
    clustering = nx.clustering(graph)
    nodes = GroupElement(
        'nodes',
        element=SuperposedNodeElement,
        size=graph.number_of_nodes(),
        values={
            'degree': np.asarray([graph.degree(node) for node in graph], dtype=float),
            'clustering': np.asarray([clustering[node] for node in graph], dtype=float),
        },
        location=list(graph),
    )
    return System(elements=[nodes], space=NetworkSpace(graph))

Components

Elements:

  • SuperposedNodeElement, from Element
class SuperposedNodeElement(Element):
    """Node of a superposed network, with its degree and its clustering.

    Args:
        label:
            The label of the node group.

        visual:
            How the element is drawn, or `None` for `default_visual_`.

        degree:
            The number of edges of the node.

        clustering:
            The share of the pairs of neighbours of the node that are joined.

    Examples:
        >>> from skcomplex.systems.mathematics.networks import SuperposedNodeElement
        >>> SuperposedNodeElement('nodes').clustering
        0.0
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = DEGREE_VISUAL,
        *,
        degree: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
        clustering: Annotated[float, StateSpec(domain=RealSet.closed(0, 1))] = 0.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.degree = degree
        self.clustering = clustering

Equations

nodes.degree[i]: diff(degree_nodes(t), t) == 0
nodes.clustering[i]: diff(clustering_nodes(t), t) == 0