Source code for graph_tool.inference.overlap_blockmodel

#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# graph_tool -- a general graph manipulation python module
#
# Copyright (C) 2006-2026 Tiago de Paula Peixoto <tiago@skewed.de>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from .. import _prop, Graph, libcore, _get_rng, PropertyMap

import numpy as np

from .. import group_vector_property, ungroup_vector_property

from .. dl_import import dl_import
dl_import("from . import libgraph_tool_inference as libinference")

from . blockmodel import *
from . layered_blockmodel import *

[docs] @entropy_state_signature class OverlapBlockState(BlockState): r"""The overlapping (mixed-membership) stochastic block model state of a given graph. Parameters ---------- g : :class:`~graph_tool.Graph` Graph to be modelled. b : :class:`~graph_tool.EdgePropertyMap` (optional, default: ``None``) Initial block labels on the **half-edges**, i.e two values per edge. clabel : :class:`~graph_tool.EdgePropertyMap` (optional, default: ``None``) Constraint labels on the half-edges. If supplied, vertices with different label values will not be clustered in the same group. pclabel : :class:`~graph_tool.EdgePropertyMap` (optional, default: ``None``) Partition constraint labels on the half-edges. This has the same interpretation as ``clabel``, but will be used to compute the partition description length. bfield : :class:`~graph_tool.EdgePropertyMap` (optional, default: ``None``) Local field acting as a prior for the node partition. This should be a vector property map of type ``vector<double>``, and contain the log-probability for each half-edge to be placed in each group. vweight : :class:`~graph_tool.EdgePropertyMap` (optional, default: ``None``) Half-edge multiplicities. gen_overlap : ``bool`` (optional, default: ``True``) If ``True``, the generalized overlap partition prior will be used. Otherwise, a simplified (but faster) prior will be used instead. **kwargs : ``(keywork parameters)`` (optional, default: ``(none)``) Parameters to be passed to :class:`~graph_tool.inference.BlockState`. """ @kwargs_wrap def __init__(self, g, b=None, clabel=None, pclabel=None, bfield=None, vweight=None, gen_overlap=True, **kwargs): BlockState.__init__(self, g, **kwargs) if pclabel is None: pclabel = g.new_ep("vector<int64_t>", val=[0, 0]) self.pclabel = pclabel if clabel is None: clabel = pclabel self.clabel = clabel if b is None: b = clabel.copy() self.b = b if vweight is None: vweight = g.new_ep("vector<int64_t>", val=[1, 1]) self._vweight = vweight if bfield is None: bfield = (g.new_ep("vector<double>"), g.new_ep("vector<double>"), g._Graph__graph) self.bfield = bfield self.gen_overlap = gen_overlap if type(self) is OverlapBlockState: with self._mk_state(): self._state = libinference.make_overlap_block_model_state(self) @property def vweight(self): r""":class:`~graph_tool.VertexPropertyMap` with node multiplicities""" if self._mk_override: return self.g return self._vweight @property def B(self): r"""Nominal number of groups""" b_s, b_t = ungroup_vector_property(self.b) return max(b_s.fa.max(), b_t.fa.max()) + 1 @property def wr(self): r"""Group sizes""" if self._mk_override: return self._wr wr = self.bg.new_vp("int64_t") self._state.get_wr(wr) return wr def _repr_extra(self): return " with generalized overlapping partition," if self.gen_overlap else "" def _get_block_state_type(self): return BlockState def _get_vertices(self, vals=None): if vals is None: vertices = [] else: vertices = vals return vertices def _get_b_min(self): sidx = self.g.edge_index.copy("int64_t") tidx = sidx.copy() tidx.fa += sidx.fa.max() + 1 return group_vector_property([sidx, tidx]) def _get_b_max(self): zs = self.g.new_ep("int64_t") return group_vector_property([zs, zs]) def _check_clabel(self, b=None, clabel=None): return True # FIXME: add implementation
[docs] def get_N(self): r"Returns the total number of nodes." return self.g.num_vertices()
def _get_bweight(self): return self.bg.new_vp("int64_t", self.wr.a > 0)
[docs] def get_bclabel(self, clabel=None): r"""Returns a :class:`~graph_tool.VertexPropertyMap` corresponding to constraint labels for the block graph.""" bclabel = self.bg.new_vertex_property("int64_t") b_s, b_t = ungroup_vector_property(self.b) b = np.concatenate([b_s.fa, b_t.fa]) reverse_map(b, bclabel) if clabel is None: clabel = self.clabel c_s, c_t = ungroup_vector_property(clabel) c = np.concatenate([c_s.fa, c_t.fa]) pmap(bclabel, c) return bclabel
[docs] def get_overlap_groups(self): r"""Returns the mixed membership of each vertex. Returns ------- bv : :class:`~graph_tool.VertexPropertyMap` A vector-valued vertex property map containing the block memberships of each node. bc_in : :class:`~graph_tool.VertexPropertyMap` The labelled in-degrees of each node, i.e. how many in-edges belong to each group, in the same order as the ``bv`` property above. bc_out : :class:`~graph_tool.VertexPropertyMap` The labelled out-degrees of each node, i.e. how many out-edges belong to each group, in the same order as the ``bv`` property above. bc_total : :class:`~graph_tool.VertexPropertyMap` The labelled total degrees of each node, i.e. how many incident edges belong to each group, in the same order as the ``bv`` property above. """ bv = self.g.new_vertex_property("vector<int64_t>") bc_in = self.g.new_vertex_property("vector<int64_t>") bc_out = self.g.new_vertex_property("vector<int64_t>") bc_total = self.g.new_vertex_property("vector<int64_t>") self._state.get_overlap_groups(bv, bc_in, bc_out, bc_total) return bv, bc_in, bc_out, bc_total
[docs] def get_majority_groups(self): r"""Returns a scalar-valued vertex property map with the majority block membership of each node.""" bv = self.get_overlap_groups() bv, bc = bv[0], bv[-1] b = self.g.new_vertex_property("int64_t") self._state.get_maj_groups(bv, bc, b) return b
[docs] def draw(self, **kwargs): r"""Convenience wrapper to :func:`~graph_tool.draw.graph_draw` that draws the state of the graph as colors on the vertices and edges.""" bv, bc_in, bc_out, bc_total = self.get_overlap_groups() pie_fractions = bc_total.copy("vector<double>") return DrawBlockState.draw(self, vertex_fill_color=None, vertex_color=None, vertex_shape=kwargs.get("vertex_shape", "pie"), vertex_pie_colors=kwargs.get("vertex_pie_colors", bv), vertex_pie_fractions=kwargs.get("vertex_pie_fractions", pie_fractions), **kwargs)
[docs] @entropy_state_signature class WeightedOverlapBlockState(WeightedBlockState, OverlapBlockState): r"""The weighted overlapping stochastic block model state of a given graph. Parameters ---------- g : :class:`~graph_tool.Graph` Graph to be modelled. **kwargs : ``(keywork parameters)`` (optional, default: ``(none)``) Parameters to be passed to :class:`~graph_tool.inference.WeightedBlockState` and :class:`~graph_tool.inference.OverlapBlockState`. """ @kwargs_wrap def __init__(self, g, **kwargs): WeightedBlockState.__init__(self, g, **kwargs) OverlapBlockState.__init__(self, g, **kwargs) if type(self) is WeightedOverlapBlockState: with self._mk_state(): self._state = libinference.make_weighted_overlap_block_model_state(self) def _get_block_state_type(self): return WeightedBlockState
[docs] @entropy_state_signature class LayeredOverlapBlockState(OverlapBlockState, LayeredBlockState): r"""The layered overlapping stochastic block model state of a given graph. Parameters ---------- g : :class:`~graph_tool.Graph` Graph to be modelled. **kwargs : ``(keywork parameters)`` (optional, default: ``(none)``) Parameters to be passed to :class:`~graph_tool.inference.LayeredBlockState` and :class:`~graph_tool.inference.OverlapBlockState`. """ @kwargs_wrap def __init__(self, g, **kwargs): OverlapBlockState.__init__(self, g, **kwargs) LayeredBlockState.__init__(self, g, **kwargs) if type(self) is LayeredOverlapBlockState: with self._mk_state(): self._state = libinference.make_layered_overlap_block_model_state(self) @property def cvweight(self): r""":class:`~graph_tool.VertexPropertyMap` with half-edge multiplicities""" if self._mk_override: return self.g return self._cvweight @property def vweight(self): r""":class:`~graph_tool.VertexPropertyMap` with half-edge multiplicities on each layer""" if self._mk_override: return self.g vweight = self.g.new_ep("vector<int64_t>") self._state.get_vweight(vweight) return vweight def _repr_extra(self): return LayeredBlockState._repr_extra(self) + \ OverlapBlockState._repr_extra(self) def _get_block_state_type(self): return LayeredBlockState
[docs] @entropy_state_signature class LayeredWeightedOverlapBlockState(OverlapBlockState, LayeredWeightedBlockState): r"""The layered weigthed overlapping stochastic block model state of a given graph. Parameters ---------- g : :class:`~graph_tool.Graph` Graph to be modelled. **kwargs : ``(keywork parameters)`` (optional, default: ``(none)``) Parameters to be passed to :class:`~graph_tool.inference.LayeredWeightedBlockState` and :class:`~graph_tool.inference.OverlapBlockState`. """ @kwargs_wrap def __init__(self, g, **kwargs): OverlapBlockState.__init__(self, g, **kwargs) LayeredWeightedBlockState.__init__(self, g, **kwargs) if type(self) is LayeredWeightedOverlapBlockState: with self._mk_state(): self._state = libinference.make_layered_weighted_overlap_block_model_state(self) @property def cvweight(self): r""":class:`~graph_tool.VertexPropertyMap` with half-edge multiplicities""" if self._mk_override: return self.g return self._cvweight def _repr_extra(self): return LayeredWeightedBlockState._repr_extra(self) + \ OverlapBlockState._repr_extra(self) def _get_block_state_type(self): return LayeredWeightedBlockState
[docs] def get_block_edge_gradient(g, be, cmap=None): r"""Get edge gradients corresponding to the block membership at the endpoints of the edges given by the ``be`` edge property map. Parameters ---------- g : :class:`~graph_tool.Graph` The graph. be : :class:`~graph_tool.EdgePropertyMap` Vector-valued edge property map with the block membership at each endpoint. cmap : :class:`matplotlib.colors.Colormap` (optional, default: ``default_cm``) Color map used to construct the gradient. Returns ------- cp : :class:`~graph_tool.EdgePropertyMap` A vector-valued edge property map containing a color gradient. """ if cmap is None: from .. draw import default_cm cmap = default_cm cp = g.new_edge_property("vector<double>") rg = [np.inf, -np.inf] for e in g.edges(): s, t = be[e] rg[0] = min((s, rg[0])) rg[0] = min((t, rg[0])) rg[1] = max((s, rg[1])) rg[1] = max((t, rg[1])) for e in g.edges(): if int(e.source()) < int(e.target()) or g.is_directed(): s, t = be[e] else: t, s = be[e] cs = cmap((s - rg[0]) / max((rg[1] - rg[0], 1))) ct = cmap((t - rg[0]) / max((rg[1] - rg[0], 1))) cp[e] = [0] + list(cs) + [1] + list(ct) return cp