Skip to content

Generative Rules

generative_rules

Generative rules for determining connection affinities in network models.

This subpackage provides various generative rules that determine how nodes in a network form connections based on different topological properties. These rules produce affinity matrices that represent the likelihood of connections forming between pairs of nodes.

The rules fall into several categories:

  • Homophily-based rules (MatchingIndex, Neighbours): Nodes connect based on shared neighborhoods
  • Clustering-based rules: Nodes connect based on clustering coefficient relationships
  • Degree-based rules: Nodes connect based on node degree relationships
  • Geometric rule: A baseline rule where all connections have equal affinity

Each rule produces an affinity matrix that can be used within a generative network model to determine connection probabilities along with other factors such as distance.

gnm.generative_rules.GenerativeRule

Bases: ABC

Abstract base class for generative rules.

Generative rules compute affinity factors between nodes in a network, which determine how likely nodes are to connect to each other. These affinity factors can be based on various network properties such as shared neighbors, clustering coefficients, or node degrees.

This base class provides common functionality for input validation and output processing, ensuring that all rule implementations follow consistent behavior.

Concrete classes inheriting from this abstract base must implement the `_rule' method.

See Also

__call__(adjacency_matrix)

Apply the generative rule to compute affinity matrices.

This method handles the complete process of validating inputs, applying the specific rule implementation, and processing outputs.

Parameters:

Name Type Description Default
adjacency_matrix Float[Tensor, '... num_nodes num_nodes']

Batch of adjacency matrices with shape [..., num_nodes, num_nodes]

required

Returns:

Type Description
Float[Tensor, '... num_nodes num_nodes']

Affinity matrices with the same shape

Source code in src/gnm/generative_rules/generative_rules.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
@jaxtyped(typechecker=typechecked)
def __call__(
    self, adjacency_matrix: Float[torch.Tensor, "... num_nodes num_nodes"]
) -> Float[torch.Tensor, "... num_nodes num_nodes"]:
    r"""Apply the generative rule to compute affinity matrices.

    This method handles the complete process of validating inputs,
    applying the specific rule implementation, and processing outputs.

    Args:
        adjacency_matrix:
            Batch of adjacency matrices with shape [..., num_nodes, num_nodes]

    Returns:
        Affinity matrices with the same shape
    """
    self.input_checks(adjacency_matrix)
    affinity_matrix = self._rule(adjacency_matrix)
    affinity_matrix = self.output_processing(affinity_matrix)
    return affinity_matrix

_rule(adjacency_matrix) abstractmethod

Apply the specific generative rule.

This abstract method must be implemented by subclasses to define the specific rule for computing affinities between nodes.

Parameters:

Name Type Description Default
adjacency_matrix Float[Tensor, '... num_nodes num_nodes']

Batch of adjacency matrices with shape [..., num_nodes, num_nodes]

required

Returns:

Type Description
Float[Tensor, '... num_nodes num_nodes']

Affinity matrices with the same shape

Source code in src/gnm/generative_rules/generative_rules.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
@abstractmethod
@jaxtyped(typechecker=typechecked)
def _rule(
    self, adjacency_matrix: Float[torch.Tensor, "... num_nodes num_nodes"]
) -> Float[torch.Tensor, "... num_nodes num_nodes"]:
    r"""Apply the specific generative rule.

    This abstract method must be implemented by subclasses to define
    the specific rule for computing affinities between nodes.

    Args:
        adjacency_matrix:
            Batch of adjacency matrices with shape [..., num_nodes, num_nodes]

    Returns:
        Affinity matrices with the same shape
    """
    pass

Homophily-Based Rules

gnm.generative_rules.MatchingIndex(divisor='mean')

Bases: GenerativeRule

Compute affinity based on shared neighborhoods between nodes.

The matching index measures the similarity between the neighborhoods of two nodes, which is a form of homophily. Nodes with many common neighbors will have a high matching index, making them more likely to connect.

Two calculation methods are supported: 1. "mean" - Normalizes by the average size of the two neighborhoods 2. "union" - Normalizes by the size of the union of the two neighborhoods

Let \(N(u)\) be the neighborhood of node \(u\).

When the divisor is set to 'mean', the matching index is computed as: $$ K(u,v) = \frac{ | N(u) \cap N(v) | }{ ( |N(u) - {v}| + |N(v) - {u}| ) /2 }. $$

When the divisor is set to 'union', the matching index is computed as: $$ K(u,v) = \frac{ | N(u) \cap N(v) | }{ | N(u) \cup N(v) - {u,v}| }. $$

When \(N(u) - \{v\}\) and \(N(v) - \{u\}\) are both empty, the matching index is zero.

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import MatchingIndex
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply matching index with mean divisor
>>> rule = MatchingIndex(divisor="mean")
>>> affinity_matrix = rule(network)
>>> # Apply matching index with union divisor
>>> rule_union = MatchingIndex(divisor="union")
>>> affinity_matrix_union = rule_union(network)
See Also

Parameters:

Name Type Description Default
divisor str

Which division mode to use: 'union' or 'mean'. The 'union' mode normalises by the size of the union of neighborhoods, while 'mean' normalises by the average size of the two neighborhoods. Defaults to "mean".

'mean'

Raises:

Type Description
AssertionError

If divisor is not one of "mean" or "union".

Source code in src/gnm/generative_rules/generative_rules.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
def __init__(self, divisor: str = "mean"):
    r"""
    Args:
        divisor:
            Which division mode to use: 'union' or 'mean'. The 'union' mode
            normalises by the size of the union of neighborhoods, while 'mean'
            normalises by the average size of the two neighborhoods. Defaults to "mean".

    Raises:
        AssertionError: If divisor is not one of "mean" or "union".
    """
    self.divisor = divisor
    assert self.divisor in [
        "mean",
        "union",
    ], f"Divisor must be one of 'mean' or 'union'. Recieved {self.divisor}."

gnm.generative_rules.Neighbours

Bases: GenerativeRule

Compute affinity based on the number of shared neighbors.

This rule is a simpler version of the matching index rule. Instead of normalising by neighborhood sizes, it normalises by the total number of nodes in the network.

The affinity factor is computed as: $$ K(u,v) = | N(u) \cap N(v) | / |V|, $$ where \(|V|\) is the number of nodes in the graph.

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import Neighbours
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply neighbors rule
>>> rule = Neighbours()
>>> affinity_matrix = rule(network)
See Also

Geometric Rule

gnm.generative_rules.Geometric

Bases: GenerativeRule

A baseline generative rule that assigns equal affinity to all node pairs.

This rule creates a constant affinity matrix, meaning all potential connections have equal probability from the perspective of this rule. When used in a generative model, other factors like distance will determine connection formation.

The affinity factor is constant: $$ K(u,v) = 1. $$

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import Geometric
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply geometric rule
>>> rule = Geometric()
>>> affinity_matrix = rule(network)
>>> # All non-diagonal elements should be 1
>>> torch.all(affinity_matrix[0][~torch.eye(network.shape[0], dtype=bool)] == 1)
tensor(True)
See Also

Clustering-Based Rules

gnm.generative_rules.ClusteringRule

Bases: GenerativeRule, ABC

Base class for clustering coefficient-based generative rules.

Clustering-based rules use the clustering coefficient of nodes to determine connection affinities. The clustering coefficient of each node measures how connected the neighbourhood of that node is.

The clustering coefficient is computed as: $$ c_u = \frac{t_u}{k_u(k_u - 1)}, $$ where \(k_u\) is the degree of node \(u\), and \(t_u\) is the number of (directed) triangles around node \(u\), computed as: $$ t_u = \sum_{v,w} A_{uv}A_{vw}A_{wu}. $$

Classes that inherit from this base class use the clustering coefficients to form various affinity factors based on different relationships between node pairs.

See Also

gnm.generative_rules.ClusteringAverage

Bases: ClusteringRule

Compute affinity based on the average clustering coefficient of node pairs.

This rule creates affinities based on the average of the clustering coefficients of the two nodes being considered for connection. Higher average clustering tends to create more locally clustered networks.

The affinity factor is computed as the average of the clustering coefficients of the two nodes: $$ K(u,v) = (c_u + c_v) / 2. $$

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import ClusteringAverage
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply clustering average rule
>>> rule = ClusteringAverage()
>>> affinity_matrix = rule(network)
See Also

gnm.generative_rules.ClusteringMax

Bases: ClusteringRule

Compute affinity based on the maximum clustering coefficient between nodes.

This rule creates affinities based on the maximum of the clustering coefficients of the two nodes being considered for connection. It tends to favor connections where at least one node has high clustering.

The affinity factor is computed as the maximum of the clustering coefficients: $$ K(u,v) = \max(c_u, c_v). $$

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import ClusteringMax
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply clustering max rule
>>> rule = ClusteringMax()
>>> affinity_matrix = rule(network)
See Also

gnm.generative_rules.ClusteringMin

Bases: ClusteringRule

Compute affinity based on the minimum clustering coefficient between nodes.

This rule creates affinities based on the minimum of the clustering coefficients of the two nodes being considered for connection. It tends to favor connections where both nodes have reasonably high clustering.

The affinity factor is computed as the minimum of the clustering coefficients: $$ K(u,v) = \min(c_u, c_v). $$

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import ClusteringMin
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply clustering min rule
>>> rule = ClusteringMin()
>>> affinity_matrix = rule(network)
See Also

gnm.generative_rules.ClusteringDifference

Bases: ClusteringRule

Compute affinity based on the difference in clustering coefficients between nodes.

This rule creates affinities based on how different the clustering coefficients of two nodes are. It can be used to connect nodes with either similar (when using negative exponents in the generative model) or dissimilar (with positive exponents) clustering properties.

The affinity factor is computed as the absolute difference of the clustering coefficients: $$ K(u,v) = |c_u - c_v|. $$

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import ClusteringDifference
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply clustering difference rule
>>> rule = ClusteringDifference()
>>> affinity_matrix = rule(network)
See Also

gnm.generative_rules.ClusteringProduct

Bases: ClusteringRule

Compute affinity based on the product of clustering coefficients between nodes.

This rule creates affinities based on the product of the clustering coefficients of the two nodes being considered for connection. It strongly favors connections between nodes that both have high clustering.

The affinity factor is computed as the product of the clustering coefficients: $$ K(u,v) = c_u \times c_v. $$

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import ClusteringProduct
>>> # Load a default binary network
>>> network = get_binary_network()[0]  # Get first network from batch
>>> # Apply clustering product rule
>>> rule = ClusteringProduct()
>>> affinity_matrix = rule(network.unsqueeze(0))
See Also

Degree-Based Rules

gnm.generative_rules.DegreeRule

Bases: GenerativeRule, ABC

Base class for degree-based generative rules.

Degree-based rules use the degree (number of connections) of nodes to determine connection affinities. These rules can create various network structures by favoring connections between nodes with specific degree relationships.

The (normalised) degree of a node \(u\) is computed as: $$ k_u = \frac{1}{|V|} \sum_{v} A_{uv}. $$

The division by \(|V|\) (number of nodes) ensures that the degree is between 0 and 1.

Classes which inherit from this base class use the normalised degrees to form the affinity factor using different relationships between node pairs.

See Also

gnm.generative_rules.DegreeAverage

Bases: DegreeRule

Compute affinity based on the average degree of node pairs.

This rule creates affinities based on the average of the degrees of the two nodes being considered for connection. It tends to create networks with more uniform degree distributions.

The affinity factor is computed as the average of the normalised degrees: $$ K(u,v) = (k_u + k_v) / 2. $$

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import DegreeAverage
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply degree average rule
>>> rule = DegreeAverage()
>>> affinity_matrix = rule(network)
See Also

gnm.generative_rules.DegreeMax

Bases: DegreeRule

Compute affinity based on the maximum degree between nodes.

This rule creates affinities based on the maximum of the degrees of the two nodes being considered for connection. It tends to favor connections where at least one node has high degree.

The affinity factor is computed as the maximum of the normalised degrees: $$ K(u,v) = \max(k_u,k_v). $$

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import DegreeMax
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply degree max rule
>>> rule = DegreeMax()
>>> affinity_matrix = rule(network)
See Also

gnm.generative_rules.DegreeMin

Bases: DegreeRule

Compute affinity based on the minimum degree between nodes.

This rule creates affinities based on the minimum of the degrees of the two nodes being considered for connection. It tends to favor connections where both nodes have reasonably high degree.

The affinity factor is computed as the minimum of the normalised degrees: $$ K(u,v) = \min(k_u,k_v). $$

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import DegreeMin
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply degree min rule
>>> rule = DegreeMin()
>>> affinity_matrix = rule(network)
See Also

gnm.generative_rules.DegreeDifference

Bases: DegreeRule

Compute affinity based on the difference in degrees between nodes.

This rule creates affinities based on how different the degrees of two nodes are. It can be used to connect nodes with either similar (when using negative exponents in the generative model) or dissimilar (with positive exponents) connectivity patterns.

The affinity factor is computed as the absolute difference of the normalised degrees: $$ K(u,v) = |k_u - k_v|. $$

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import DegreeDifference
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply degree difference rule
>>> rule = DegreeDifference()
>>> affinity_matrix = rule(network)
See Also

gnm.generative_rules.DegreeProduct

Bases: DegreeRule

Compute affinity based on the product of degrees between nodes.

This rule creates affinities based on the product of the degrees of the two nodes being considered for connection. It strongly favors connections between nodes that both have high degree, potentially leading to rich-club structures.

The affinity factor is computed as the product of the normalised degrees: $$ K(u,v) = k_u \times k_v. $$

Examples:

>>> import torch
>>> from gnm.defaults import get_binary_network
>>> from gnm.generative_rules import DegreeProduct
>>> # Load a default binary network
>>> network = get_binary_network()
>>> # Apply degree product rule
>>> rule = DegreeProduct()
>>> affinity_matrix = rule(network)
See Also