Source code for graph_tool.inference.layered_blockmodel
#! /usr/bin/env python# -*- coding: utf-8 -*-## graph_tool -- a general graph manipulation python module## Copyright (C) 2006-2025 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,GraphView,libcore,_get_rng,openmp_contextimportnumpyimportcopyimportwarningsfrom..importgroup_vector_property,ungroup_vector_property,perfect_prop_hashfrom..dl_importimportdl_importdl_import("from . import libgraph_tool_inference as libinference")from..generationimportgraph_mergefrom.base_statesimport_bm_testfrom.blockmodelimport*from.overlap_blockmodelimport*
[docs]classLayeredBlockState(OverlapBlockState,BlockState):r"""The (possibly overlapping) block state of a given graph, where the edges are divided into discrete layers. Parameters ---------- g : :class:`~graph_tool.Graph` Graph to be modelled. ec : :class:`~graph_tool.EdgePropertyMap` Edge property map containing discrete edge covariates that will split the network in discrete layers. recs : list of :class:`~graph_tool.EdgePropertyMap` instances (optional, default: ``[]``) List of real or discrete-valued edge covariates. rec_types : list of edge covariate types (optional, default: ``[]``) List of types of edge covariates. The possible types are: ``"real-exponential"``, ``"real-normal"``, ``"discrete-geometric"``, ``"discrete-poisson"`` or ``"discrete-binomial"``. rec_params : list of ``dict`` (optional, default: ``[]``) Model hyperparameters for edge covariates. This should a list of ``dict`` instances. See :class:`~graph_tool.inference.BlockState` for more details. eweight : :class:`~graph_tool.EdgePropertyMap` (optional, default: ``None``) Edge multiplicities (for multigraphs or block graphs). vweight : :class:`~graph_tool.VertexPropertyMap` (optional, default: ``None``) Vertex multiplicities (for block graphs). b : :class:`~graph_tool.VertexPropertyMap` or :class:`numpy.ndarray` (optional, default: ``None``) Initial block labels on the vertices or half-edges. If not supplied, it will be randomly sampled. B : ``int`` (optional, default: ``None``) Number of blocks (or vertex groups). If not supplied it will be obtained from the parameter ``b``. clabel : :class:`~graph_tool.VertexPropertyMap` (optional, default: ``None``) Constraint labels on the vertices. If supplied, vertices with different label values will not be clustered in the same group. pclabel : :class:`~graph_tool.VertexPropertyMap` (optional, default: ``None``) Partition constraint labels on the vertices. This has the same interpretation as ``clabel``, but will be used to compute the partition description length. layers : ``bool`` (optional, default: ``False``) If ``layers == True``, the "independent layers" version of the model is used, instead of the "edge covariates" version. deg_corr : ``bool`` (optional, default: ``True``) If ``True``, the degree-corrected version of the blockmodel ensemble will be assumed, otherwise the traditional variant will be used. overlap : ``bool`` (optional, default: ``False``) If ``True``, the overlapping version of the model will be used. """def__init__(self,g,ec,eweight=None,vweight=None,recs=[],rec_types=[],rec_params=[],b=None,B=None,clabel=None,pclabel=None,layers=False,deg_corr=True,overlap=False,**kwargs):kwargs=kwargs.copy()self.g=gifkwargs.pop("ec_done",False)orecisNone:self.ec=ecelse:self.ec=ec=perfect_prop_hash([ec],"int64_t")[0]ifecisnotNone:self.C=ec.fa.max()+1else:self.C=len(kwargs.get("gs"))self.layers=layersif"dense_bg"inkwargs:delkwargs["dense_bg"]dense_bg=FalseifvweightisNone:vweight=g.new_vp("int64_t",1)ifeweightisNone:eweight=g.new_ep("int64_t",1)self.Lrecdx=kwargs.pop("Lrecdx",[])whilelen(self.Lrecdx)<self.C+1:self.Lrecdx.append(libcore.Vector_double(1))self.Lrecdx[-1][0]=-1ifnotoverlap:kwargs=dmask(kwargs,["base_g","node_index","eindex","half_edges"])ldegs=kwargs.pop("degs",None)ifldegsisnotNone:tdegs=libinference.get_mapped_block_degs(self.g._Graph__graph,ldegs,0,_prop("v",self.g,self.g.vertex_index.copy("int64_t")))else:tdegs=Noneagg_state=BlockState(g,b=b,B=B,eweight=eweight,vweight=vweight,recs=recs,rec_types=rec_types,rec_params=rec_params,clabel=clabel,pclabel=pclabel,deg_corr=deg_corr,dense_bg=dense_bg,Lrecdx=self.Lrecdx[0],use_rmap=True,**dmask(kwargs,["lweights","gs"]))else:ldegs=Noneagg_state=OverlapBlockState(g,b=b,B=B,recs=recs,rec_types=rec_types,rec_params=rec_params,clabel=clabel,pclabel=pclabel,deg_corr=deg_corr,dense_bg=dense_bg,Lrecdx=self.Lrecdx[0],**dmask(kwargs,["lweights","gs","bfield"]))self.base_g=agg_state.base_gself.g=agg_state.geweight=self.g.new_ep("int64_t",1)vweight=self.g.new_vp("int64_t",1)kwargs=dmask(kwargs,["base_g","node_index","eindex","half_edges"])self.agg_state=agg_stateifoverlapandself.ecisnotNone:self.base_ec=self.base_g.own_property(ec.copy())ec=agg_state.g.new_ep("int64_t")eindex=self.base_g.edge_index.copy()ec.fa=self.ec.a[eindex.fa]self.ec=ecself.eweight=eweightself.vweight=vweightifnotoverlap:self.is_weighted=agg_state.is_weightedelse:self.is_weighted=Falseself.rec=agg_state.recself.drec=agg_state.drecself.rec_types=agg_state.rec_typesself.rec_params=agg_state.rec_paramsself.epsilon=agg_state.epsilonself.b=agg_state.bself.clabel=agg_state.clabelself.pclabel=agg_state.pclabelself.bclabel=agg_state.bclabelself.hclabel=agg_state.hclabelifnotoverlap:self.bfield=agg_state.bfieldelse:self.bfield=Noneself.deg_corr=deg_corrself.overlap=overlapself.vc=self.g.new_vp("vector<int64_t>")self.vmap=self.g.new_vp("vector<int64_t>")self.gs=kwargs.pop("gs",[])self.block_map=libinference.bmap_t()lweights=kwargs.pop("lweights",self.g.new_vp("vector<int64_t>"))iflen(self.gs)==0:forlinrange(0,self.C):u=Graph(directed=g.is_directed())u.vp["b"]=u.new_vp("int64_t")u.vp["weight"]=u.new_vp("int64_t")u.ep["weight"]=u.new_ep("int64_t")u.gp["rec"]=u.new_gp("object",val=[u.new_ep("double")foriinrange(len(self.rec))])u.gp["drec"]=u.new_gp("object",val=[u.new_ep("double")foriinrange(len(self.drec))])u.vp["brmap"]=u.new_vp("int64_t")u.vp["vmap"]=u.new_vp("int64_t")self.gs.append(u)libinference.split_layers(self.g._Graph__graph,_prop("e",self.g,self.ec),_prop("v",self.g,self.b),[_prop("e",self.g,x)forxinself.rec],[_prop("e",self.g,x)forxinself.drec],_prop("e",self.g,self.eweight),_prop("v",self.g,self.vweight),_prop("v",self.g,self.vc),_prop("v",self.g,self.vmap),_prop("v",self.g,lweights),[u._Graph__graphforuinself.gs],[_prop("v",u,u.vp["b"])foruinself.gs],[[_prop("e",u,x)forxinu.gp["rec"]]foruinself.gs],[[_prop("e",u,x)forxinu.gp["drec"]]foruinself.gs],[_prop("e",u,u.ep["weight"])foruinself.gs],[_prop("v",u,u.vp["weight"])foruinself.gs],self.block_map,[_prop("v",u,u.vp["brmap"])foruinself.gs],[_prop("v",u,u.vp["vmap"])foruinself.gs])else:libinference.split_groups(_prop("v",self.g,self.b),_prop("v",self.g,self.vc),_prop("v",self.g,self.vmap),[u._Graph__graphforuinself.gs],[_prop("v",u,u.vp["b"])foruinself.gs],[_prop("v",u,u.vp["weight"])foruinself.gs],self.block_map,[_prop("v",u,u.vp["brmap"])foruinself.gs],[_prop("v",u,u.vp["vmap"])foruinself.gs])ifself.g.get_vertex_filter()isnotNone:foruinself.gs:u.set_vertex_filter(u.new_vp("bool",True))self.master=notself.layersself.layer_states=[]self.dense_bg=dense_bgself.bg=agg_state.bgself.wr=agg_state.wrself.mrs=agg_state.mrsself.mrp=agg_state.mrpself.mrm=agg_state.mrmself.brec=agg_state.brecself.bdrec=agg_state.bdrecself.rec_params=agg_state.rec_paramsself.wparams=agg_state.wparamsself.epsilon=agg_state.epsilonself._entropy_args=agg_state._entropy_argsself.recdx=agg_state.recdxself._coupled_state=Noneself.alayer_states=libcore.Vector_any()self.ablock_rmaps=libcore.Vector_any()forl,uinenumerate(self.gs):state=self.__gen_state(l,u,ldegs)self.layer_states.append(state)self.alayer_states.append(state._state.get_any())self.ablock_rmaps.append(state.block_rmap._get_any())ifecisNone:self.ec=self.g.new_ep("int64_t")ifnotself.overlap:self._state= \
libinference.make_layered_block_state(agg_state._state,self)else:self._state= \
libinference.make_layered_overlap_block_state(agg_state._state,self)ifecisNone:self.ec=Noneif_bm_test():assertself.mrs.fa.sum()==self.eweight.fa.sum(),"inconsistent mrs!"kwargs.pop("recs",None)kwargs.pop("drec",None)kwargs.pop("rec_params",None)kwargs.pop("Lrecdx",None)kwargs.pop("epsilon",None)kwargs.pop("bfield",None)self._entropy_args=self.agg_state._entropy_args.copy()iflen(kwargs)>0:warnings.warn("unrecognized keyword arguments: "+str(list(kwargs.keys())))
[docs]defget_N(self):r"Returns the total number of edges."returnself.agg_state.get_N()
[docs]defget_E(self):r"Returns the total number of nodes."returnself.agg_state.get_E()
[docs]defget_B(self):r"Returns the total number of blocks."returnself.agg_state.get_B()
[docs]defget_nonempty_B(self):r"Returns the total number of nonempty blocks."returnself.agg_state.get_nonempty_B()
[docs]defcopy(self,g=None,eweight=None,vweight=None,b=None,B=None,deg_corr=None,clabel=None,pclabel=None,bfield=None,overlap=None,layers=None,ec=None,**kwargs):r"""Copies the block state. The parameters override the state properties, and have the same meaning as in the constructor."""lweights=self.g.new_vp("vector<int>")ifnotself.overlap:libinference.get_lweights(self.g._Graph__graph,_prop("v",self.g,self.vc),_prop("v",self.g,self.vmap),_prop("v",self.g,lweights),[_prop("v",state.g,state.vweight)forstateinself.layer_states])gs=[u.copy()foruinself.gs]ifecisNoneelse[]ec=self.ecifecisNoneelseeciflen(gs)>0:libinference.get_rvmap(self.g._Graph__graph,_prop("v",self.g,self.vc),_prop("v",self.g,self.vmap),[_prop("v",u,u.vp.vmap)foruings])foruings:u.gp.rec=[u.own_property(x.copy())forxinu.gp.rec]ifu.gp.drecisnotNone:u.gp.drec=[u.own_property(x.copy())forxinu.gp.drec]state=LayeredBlockState(self.gifgisNoneelseg,ec=ec,gs=gs,eweight=self.eweightifeweightisNoneelseeweight,vweight=self.vweightifvweightisNoneelsevweight,recs=kwargs.pop("recs",self.rec),drec=kwargs.pop("drec",self.drec),rec_types=kwargs.pop("rec_types",self.rec_types),rec_params=kwargs.pop("rec_params",self.rec_params),b=self.bifbisNoneelseb,B=(self.get_B()ifbisNoneelseNone)ifBisNoneelseB,clabel=self.clabel.faifclabelisNoneelseclabel,pclabel=self.pclabelifpclabelisNoneelsepclabel,bfield=self.bfieldifbfieldisNoneelsebfield,deg_corr=self.deg_corrifdeg_corrisNoneelsedeg_corr,overlap=self.overlapifoverlapisNoneelseoverlap,layers=self.layersiflayersisNoneelselayers,base_g=self.base_gifself.overlapelseNone,half_edges=self.agg_state.half_edgesifself.overlapelseNone,node_index=self.agg_state.node_indexifself.overlapelseNone,eindex=self.agg_state.eindexifself.overlapelseNone,ec_done=ecisnotNone,lweights=lweights,Lrecdx=kwargs.pop("Lrecdx",[x.copy()forxinself.Lrecdx]),epsilon=kwargs.pop("epsilon",self.epsilon.copy()),**kwargs)ifself._coupled_stateisnotNone:state._couple_state(state.get_block_state(b=state.get_bclabel(),copy_bg=False,vweight="nonempty",Lrecdx=state.Lrecdx),self._coupled_state[1])returnstate
def__repr__(self):return"<LayeredBlockState object with %d%sblocks, %d%s,%s%s for graph %s, at 0x%x>"% \
(self.get_B(),"overlapping "ifself.overlapelse"",self.C,"layers"ifself.layerselse"edge covariates"," degree-corrected,"ifself.deg_correlse"",((" with %d edge covariate%s,"%(len(self.rec_types),"s"iflen(self.rec_types)>1else""))iflen(self.rec_types)>0else""),str(self.base_gifself.overlapelseself.g),id(self))
[docs]defget_bg(self):r"""Returns the block graph."""bg=Graph(directed=self.g.is_directed())mrs=bg.new_ep("int64_t")ec=bg.new_ep("int64_t")rec=bg.new_edge_property("vector<double>")drec=bg.new_edge_property("vector<double>")forlinrange(self.C):u=GraphView(self.g,efilt=self.ec.a==l)ug=get_block_graph(u,self.get_B(),self.b,self.vweight,self.eweight,rec=self.rec,drec=self.drec)uec=ug.new_ep("int64_t")uec.a=liflen(ug.gp.rec)>0:urec=group_vector_property(ug.gp.rec)udrec=group_vector_property(ug.gp.drec)else:urec=ug.new_ep("vector<double>")udrec=ug.new_ep("vector<double>")withopenmp_context(nthreads=1):bg,props=graph_merge(bg,ug,props={"set":[(mrs,ug.ep["count"]),(ec,uec),(rec,urec),(drec,udrec)]},multiset=True,in_place=True)mrs,ec,rec,drec=props["set"]rec=ungroup_vector_property(rec,range(len(self.rec)))drec=ungroup_vector_property(drec,range(len(self.drec)))returnbg,mrs,ec,rec,drec
[docs]defget_block_state(self,b=None,vweight=False,deg_corr=False,overlap=False,layers=None,**kwargs):r"""Returns a :class:`~graph_tool.inference.LayeredBlockState` corresponding to the block graph. The parameters have the same meaning as the in the constructor. """copy_bg=kwargs.pop("copy_bg",True)ifcopy_bg:bg,mrs,ec,brec,bdrec=self.get_bg()gs=[]else:gs=[]forl,sinenumerate(self.layer_states):u=GraphView(s.bg)u.ep.weight=s.mrsu.vp.vmap=u.own_property(s.g.vp.brmap).copy()u.vp.b=u.new_vp("int64_t")ifvweight==True:u.vp.weight=u.own_property(s.wr)else:u.vp.weight=u.new_vp("int64_t",s.wr.a>0)u.vp.brmap=u.new_vp("int64_t")u.gp.rec=u.new_gp("object",val=s.brec)u.gp.drec=u.new_gp("object",val=s.bdrec)gs.append(u)bg=self.agg_state.bgmrs=self.agg_state.mrsec=Nonebrec=self.brecbdrec=self.bdreclweights=bg.new_vp("vector<int>")ifnotoverlapandvweight==True:libinference.get_blweights(self.g._Graph__graph,_prop("v",self.g,self.b),_prop("v",self.g,self.vc),_prop("v",self.g,self.vmap),_prop("v",bg,lweights),[_prop("v",state.g,state.vweight)forstateinself.layer_states])copy_coupled=Falserecs=Falseifvweight=="nonempty":vweight=bg.new_vp("int64_t",self.wr.a>0)layers=TrueiflayersisNoneelselayerselifvweight=="unity":vweight=bg.new_vp("int64_t",1)layers=TrueiflayersisNoneelselayerselifvweight==True:ifcopy_bg:vweight=bg.own_property(self.wr.copy())else:vweight=self.wrrecs=Truecopy_coupled=Truekwargs["Lrecdx"]=kwargs.get("Lrecdx",[x.copy()forxinself.Lrecdx])else:vweight=Nonelayers=TrueiflayersisNoneelselayersifrecs:rec_types=kwargs.pop("rec_types",self.rec_types)recs=kwargs.pop("recs",brec)drec=kwargs.pop("drec",bdrec)rec_params=kwargs.pop("rec_params",self.rec_params)else:recs=[]drec=Noneforuings:u.gp.drec=Noneu.gp.rec=[]rec_types=[]rec_params=[]fori,(rt,rp,r)inenumerate(zip(self.rec_types,self.wparams,brec)):ifrt==libinference.rec_type.count:recs.append(bg.new_ep("double",mrs.fa>0))forl,uinenumerate(gs):u.gp.rec.append(u.new_ep("double",u.ep.weight.fa>0))rec_types.append(rt)rec_params.append("microcanonical")elifnumpy.isnan(rp.a).sum()==0:continueelifrtin[libinference.rec_type.discrete_geometric,libinference.rec_type.discrete_binomial,libinference.rec_type.discrete_poisson]:recs.append(r)forl,uinenumerate(gs):u.gp.rec.append(self.layer_states[l].brec[i])rec_types.append(libinference.rec_type.discrete_geometric)rec_params.append("microcanonical")elifrt==libinference.rec_type.real_exponential:recs.append(r)forl,uinenumerate(gs):u.gp.rec.append(self.layer_states[l].brec[i])rec_types.append(rt)rec_params.append("microcanonical")elifrt==libinference.rec_type.real_normal:recs.append(r)forl,uinenumerate(gs):u.gp.rec.append(self.layer_states[l].brec[i])rec_types.append(rt)rec_params.append("microcanonical")rec_params=kwargs.pop("rec_params",rec_params)state=LayeredBlockState(bg,ec,eweight=mrs,vweight=vweight,gs=gs,rec_types=rec_types,recs=recs,drec=drec,rec_params=rec_params,b=bg.vertex_index.copy("int64_t")ifbisNoneelseb,deg_corr=deg_corr,overlap=overlap,dense_bg=self.dense_bg,layers=self.layersiflayersisNoneelselayers,ec_done=True,lweights=lweights,clabel=kwargs.pop("clabel",self.agg_state.get_bclabel()),pclabel=kwargs.pop("pclabel",self.agg_state.get_bpclabel()),epsilon=kwargs.pop("epsilon",self.epsilon.copy()),**kwargs)ifcopy_coupledandself._coupled_stateisnotNone:state._couple_state(state.get_block_state(b=state.get_bclabel(),copy_bg=False,vweight="nonempty",Lrecdx=state.Lrecdx),self._coupled_state[1])returnstate
def_set_bclabel(self,bstate):BlockState._set_bclabel(self,bstate)#self._state.sync_bclabel()# for s, sn in zip(self.layer_states, bstate.layer_states):# s.bclabel.a = sn.b.a
[docs]defget_edge_blocks(self):r"""Returns an edge property map which contains the block labels pairs for each edge."""ifnotself.overlap:raiseValueError("edge blocks only available if overlap == True")returnself.agg_state.get_edge_blocks()
[docs]defget_overlap_blocks(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. """ifnotself.overlap:raiseValueError("overlap blocks only available if overlap == True")returnself.agg_state.get_overlap_blocks()
[docs]defget_nonoverlap_blocks(self):r"""Returns a scalar-valued vertex property map with the block mixture represented as a single number."""ifnotself.overlap:returnself.b.copy()else:returnself.agg_state.get_nonoverlap_blocks()
[docs]defget_majority_blocks(self):r"""Returns a scalar-valued vertex property map with the majority block membership of each node."""ifnotself.overlap:returnself.b.copy()else:returnself.agg_state.get_majority_blocks()
def_couple_state(self,state,entropy_args):ifstateisNone:self._coupled_state=Noneself._state.decouple_state()else:if_bm_test():assertstate.g.baseisself.bg.baseassertstate.agg_state.g.baseisself.agg_state.bg.baseforl,(s1,s2)inenumerate(zip(state.layer_states,self.layer_states)):asserts1.g.baseiss2.bg.base,(l,s1,s2)self._coupled_state=(state,entropy_args)eargs=state._get_entropy_args(dict(self._entropy_args,**entropy_args))self._state.couple_state(state._state,eargs)#self._set_bclabel(state)def_set_bclabel(self,bstate):BlockState._set_bclabel(self,bstate)fors,bsinzip(self.layer_states,bstate.layer_states):s._set_bclabel(bs)def_check_clabel(self,clabel=None,b=None):ifnotBlockState._check_clabel(self,clabel,b):returnFalse# if self._coupled_state is not None:# for s, bs in zip(self.layer_states,# self._coupled_state[0].layer_states):# b = s.bclabel# mask = bs.vweight.fa > 0# if any(b.fa[mask] != bs.b.fa[mask]):# return FalsereturnTrue
[docs]@copy_state_wrapdefentropy(self,**kwargs):r"""Calculate the entropy associated with the current block partition. The meaning of the parameters are the same as in :meth:`graph_tool.inference.BlockState.entropy`. """returnBlockState.entropy(self,**dict(kwargs,test=False))
def_get_lvertex(self,v,l):i=numpy.searchsorted(self.vc[v].a,l)ifi>=len(self.vc[v])orl!=self.vc[v][i]:raiseValueError("vertex %d not present in layer %d"%(v,l))u=self.vmap[v][i]returnu
[docs]defget_edges_prob(self,missing,spurious=[],entropy_args={}):r"""Compute the joint log-probability of the missing and spurious edges given by ``missing`` and ``spurious`` (a list of ``(source, target, layer)`` tuples, or :meth:`~graph_tool.Edge` instances), together with the observed edges. More precisely, the log-likelihood returned is .. math:: \ln \frac{P(\boldsymbol G + \delta \boldsymbol G | \boldsymbol b)}{P(\boldsymbol G| \boldsymbol b)} where :math:`\boldsymbol G + \delta \boldsymbol G` is the modified graph (with missing edges added and spurious edges deleted). The values in ``entropy_args`` are passed to :meth:`graph_tool.inference.BlockState.entropy()` to calculate the log-probability. """Si=self.entropy(**dict(dict(partition_dl=False),**entropy_args))pos={}nes=[]foreinitertools.chain(missing,spurious):try:u,v=el=self.ec[e]except(TypeError,ValueError):u,v,l=epos[u]=self.b[u]pos[v]=self.b[v]nes.append((u,v,(l,False)))nes.append((self._get_lvertex(u,l),self._get_lvertex(v,l),(l,True)))edge_list=nesself.remove_vertex(pos.keys())agg_state=self.agg_statetry:new_es=[]foriinrange(len(missing)):u,v,l=edge_list[i]ifnotl[1]:state=self.agg_stateelse:state=self.layer_states[l[0]]e=state.g.add_edge(u,v)ifnotl[1]andself.ecisnotNone:self.ec[e]=l[0]ifstate.is_weighted:state.eweight[e]=1new_es.append((e,l))old_es=[]foriinrange(len(spurious)):u,v,l=edge_list[i+len(missing)]ifnotl[1]:state=self.agg_statees=state.g.edge(u,v,all_edges=True)ifself.ecisnotNone:es=[eforeinesifself.ec[e]==l[0]]else:es=list(es)iflen(es)>0:e=es[0]else:e=Noneelse:state=self.layer_states[l[0]]e=state.g.edge(u,v)ifeisNone:raiseValueError("edge not found: (%d, %d, %d)"% \
(int(u),int(v),l[0]))ifstate.is_weighted:state.eweight[e]-=1ifstate.eweight[e]==0:state.g.remove_edge(e)else:state.g.remove_edge(e)old_es.append((u,v,l))self.add_vertex(pos.keys(),pos.values())Sf=self.entropy(**dict(dict(partition_dl=False),**entropy_args))self.remove_vertex(pos.keys())finally:fore,linnew_es:ifnotl[1]:state=self.agg_stateelse:state=self.layer_states[l[0]]state.g.remove_edge(e)foru,v,linold_es:ifnotl[1]:state=self.agg_stateelse:state=self.layer_states[l[0]]ifstate.is_weighted:e=state.g.edge(u,v)ifeisNone:e=state.g.add_edge(u,v)state.eweight[e]=0ifnotl[1]andself.ecisnotNone:self.ec[e]=l[0]state.eweight[e]+=1else:e=state.g.add_edge(u,v)ifnotl[1]andself.ecisnotNone:self.ec[e]=l[0]self.add_vertex(pos.keys(),pos.values())L=Si-Sfif_bm_test():state=self.copy()set_test(False)L_alt=state.get_edges_prob(edge_list,missing=missing,entropy_args=entropy_args)set_test(True)assertmath.isclose(L,L_alt,abs_tol=1e-8), \
"inconsistent missing=%s edge probability (%g, %g): %s, %s"% \
(str(missing),L,L_alt,str(entropy_args),str(edge_list))returnL
[docs]defmcmc_sweep(self,bundled=False,**kwargs):r"""Perform sweeps of a Metropolis-Hastings rejection sampling MCMC to sample network partitions. If ``bundled == True`` and the state is an overlapping one, the half-edges incident of the same node that belong to the same group are moved together. All remaining parameters are passed to :meth:`graph_tool.inference.BlockState.mcmc_sweep`."""self.__bundled=bundledreturnBlockState.mcmc_sweep(self,**kwargs)
[docs]defdraw(self,**kwargs):"""Convenience function to draw the current state. All keyword arguments are passed to :meth:`graph_tool.inference.BlockState.draw` or :meth:`graph_tool.inference.OverlapBlockState.draw`, as appropriate. """self.agg_state.draw(**kwargs)