//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1997 - 1998 // // File: clique.cpp // //-------------------------------------------------------------------------- // // clique.cpp // #include #include "cliqset.h" #include "clique.h" #include "cliqwork.h" #include "parmio.h" #ifdef _DEBUG // In debug mode only... #define CONSISTENCY // Do complete consistency checking on sepsets // #define DUMP // Perform general dumping of objects // #define DUMPCLIQUESET // Dump extensive tables from clique tree // #define INFERINIT // Full initial tree balancing #endif //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// // GEDGEMBN_CLIQ: Edges between cliques and member nodes //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// GEDGEMBN_CLIQ :: GEDGEMBN_CLIQ ( GOBJMBN_CLIQUE * pgnSource, GNODEMBN * pgndSink, int iFcqlRole ) : GEDGEMBN( pgnSource, pgndSink ), _iFcqlRole( iFcqlRole ), _iMark( pgndSink->IMark() ), _bBuilt( false ) { } void GEDGEMBN_CLIQ :: Build () { if ( ! BBuilt() ) { GNODEMBND * pgndd; DynCastThrow( PgndSink(), pgndd ); // If role is "Family", this edge is used for marginalization of belief // and creating joint distribution in clique if ( BFamily() ) { ReorderFamily( pgndd, _vimdFamilyReorder ); // Build the reordered marginals table for the node MargCpd().CreateOrderedCPDFromNode( pgndd, _vimdFamilyReorder ); // Build an iterator between the CPD and the clique joint MiterLoadClique().Build( PclqParent()->Marginals(), MargCpd() ); // Build the belief marginalization structure MiterNodeBelief().Build( PclqParent()->Marginals(), pgndd ); } _bBuilt = true; } } void GEDGEMBN_CLIQ :: LoadCliqueFromNode () { assert( _bBuilt ); MiterLoadClique().MultiplyBy( MargCpd() ); } GEDGEMBN_CLIQ :: ~ GEDGEMBN_CLIQ() { } GOBJMBN_CLIQUE * GEDGEMBN_CLIQ :: PclqParent() { GOBJMBN * pobj = PobjSource(); GOBJMBN_CLIQUE * pclq; DynCastThrow( pobj, pclq ); return pclq; } GNODEMBN * GEDGEMBN_CLIQ :: PgndSink() { GOBJMBN * pobj = PobjSink(); GNODEMBN * pgnd; DynCastThrow( pobj, pgnd ); return pgnd; } // Using the topological renumber of the nodes, produce // an array correlating the old family to the new order. // In other words, vimd[0] will be the family index of // the node which had the lowest topological order; vimd[1] // will be the family index of the next lowest, etc. // // Note that node itself is always last in either ordering. void GEDGEMBN_CLIQ :: ReorderFamily ( GNODEMBN * pgnd, VIMD & vimd ) { VPGNODEMBN vpgndFamily; // Get the family (parents & self) pgnd->GetFamily( vpgndFamily ); int cFam = vpgndFamily.size(); vimd.resize( cFam ); for ( int i = 0; i < cFam; i++ ) { int iLow = INT_MAX; int iFam = INT_MAX; // Find the lowest unrecorded family member for ( int j = 0; j < cFam; j++ ) { GNODEMBN * pgndFam = vpgndFamily[j]; if ( pgndFam == NULL ) continue; if ( pgndFam->IMark() < iLow ) { iLow = pgndFam->IMark(); iFam = j; } } assert( iLow != INT_MAX ); vimd[i] = iFam; vpgndFamily[iFam] = NULL; } } //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// // GEDGEMBN_SEPSET: A separator marginal //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// GEDGEMBN_SEPSET :: GEDGEMBN_SEPSET ( GOBJMBN_CLIQUE * pgnSource, GOBJMBN_CLIQUE * pgnSink ) : GEDGEMBN( pgnSource, pgnSink ), _pmargOld( new MARGINALS ), _pmargNew( new MARGINALS ) { } GEDGEMBN_SEPSET :: ~ GEDGEMBN_SEPSET() { delete _pmargOld; delete _pmargNew; } void GEDGEMBN_SEPSET :: ExchangeMarginals () { pexchange( _pmargOld, _pmargNew ); } GOBJMBN_CLIQUE * GEDGEMBN_SEPSET :: PclqParent() { GOBJMBN * pobj = PobjSource(); GOBJMBN_CLIQUE * pclq; DynCastThrow( pobj, pclq ); return pclq; } GOBJMBN_CLIQUE * GEDGEMBN_SEPSET :: PclqChild() { GOBJMBN * pobj = PobjSink(); GOBJMBN_CLIQUE * pclq; DynCastThrow( pobj, pclq ); return pclq; } void GEDGEMBN_SEPSET :: GetMembers ( VPGNODEMBN & vpgnode ) { GOBJMBN_CLIQUE * pclqSource = PclqParent(); GOBJMBN_CLIQUE * pclqSink = PclqChild(); VPGNODEMBN vpgndSink; VPGNODEMBN vpgndSource; pclqSource->GetMembers( vpgndSource ); pclqSink->GetMembers( vpgndSink ); assert( vpgndSink.size() > 0 ); assert( vpgndSource.size() > 0 ); // Fill the given array with the intersection of the two clique // member node arrays. Since we cannot sort them into cliqing order // anymore (IMark() is unreliable after cliquing), we just search // one against the other in order to guarantee that the intersection // result set has the same node ordering as the original sets. int ibLast = -1; for ( int ia = 0; ia < vpgndSink.size(); ia++ ) { GNODEMBN * pa = vpgndSink[ia]; for ( int ib = ibLast+1; ib < vpgndSource.size(); ib++ ) { GNODEMBN * pb = vpgndSource[ib]; if ( pa == pb ) { vpgnode.push_back(pa); ibLast = ib; break; } } } #ifdef DUMP if ( vpgnode.size() == 0 ) { cout << "\nSEPSET INTERSECTION NULL: source clique:"; pclqSource->Dump(); cout << "\n\t\tsink clique:"; pclqSink->Dump(); cout << "\n"; cout.flush(); } #endif assert( vpgnode.size() > 0 ); } void GEDGEMBN_SEPSET :: CreateMarginals () { VPGNODEMBN vpgnd; GetMembers( vpgnd ); MarginalsOld().Init( vpgnd ); MarginalsNew().Init( vpgnd ); } void GEDGEMBN_SEPSET :: InitMarginals () { assert( VerifyMarginals() ); MarginalsOld().Clear( 1.0 ); MarginalsNew().Clear( 1.0 ); if ( ! _miterParent.BBuilt() ) _miterParent.Build( PclqParent()->Marginals(), MarginalsOld() ); if ( ! _miterChild.BBuilt() ) _miterChild.Build( PclqChild()->Marginals(), MarginalsOld() ); } bool GEDGEMBN_SEPSET :: VerifyMarginals () { VPGNODEMBN vpgnd; GetMembers( vpgnd ); VIMD vimd = MARGINALS::VimdFromVpgnd( vpgnd ); return vimd == Marginals().Vimd(); } void GEDGEMBN_SEPSET :: UpdateRatios () { MarginalsOld().UpdateRatios( MarginalsNew() ); } void GEDGEMBN_SEPSET :: AbsorbClique ( bool bFromParentToChild ) { MARGSUBITER * pmiterFrom; MARGSUBITER * pmiterTo; if ( bFromParentToChild ) { pmiterFrom = & _miterParent; pmiterTo = & _miterChild; } else { pmiterFrom = & _miterChild; pmiterTo = & _miterParent; } // Marginalize "from" probs into the "new" marginals table pmiterFrom->MarginalizeInto( MarginalsNew() ); // Absorb the changes into the "old" marginals table UpdateRatios(); // Multiply the table into the "to"'s marginals pmiterTo->MultiplyBy( MarginalsOld() ); // Finally, exchange the marginals tables ExchangeMarginals(); } void GEDGEMBN_SEPSET :: BalanceCliquesCollect () { // Use the "new" table as a work area. // Marginalize the child into the work area _miterChild.MarginalizeInto( MarginalsNew() ); // Update the parent with those values _miterParent.MultiplyBy( MarginalsNew() ); // Invert each value, so we're really dividing MarginalsNew().Invert(); // Update the child marginals by dividing by the marginals _miterChild.MultiplyBy( MarginalsNew() ); // Clear the "new" marginals back to 1.0. MarginalsNew().Clear( 1.0 ); } void GEDGEMBN_SEPSET :: BalanceCliquesDistribute () { // Set the old marginals to the parent clique's values _miterParent.MarginalizeInto( MarginalsOld() ); // Update the child marginals by those values _miterChild.MultiplyBy( MarginalsOld() ); // "Old" marginals are left as they are } void GEDGEMBN_SEPSET :: UpdateParentClique () { AbsorbClique( false ); } void GEDGEMBN_SEPSET :: UpdateChildClique () { AbsorbClique( true ); } void GEDGEMBN_SEPSET :: Dump () { GOBJMBN_CLIQUE * pclqParent = PclqParent(); GOBJMBN_CLIQUE * pclqChild = PclqChild(); cout << "\n=== Sepset between parent clique " << pclqParent->IClique() << " and child clique " << pclqChild->IClique() << ", \n\n\tOld marginals:"; _pmargOld->Dump(); cout << "\n\n\tNew marginals:"; _pmargNew->Dump(); cout << "\n\n"; } bool GEDGEMBN_SEPSET :: BConsistent () { // Get the sepset member list for creation of temporary marginals VPGNODEMBN vpgnd; GetMembers( vpgnd ); // Create the marginals for the parent clique GOBJMBN_CLIQUE * pclqParent = PclqParent(); MARGINALS margParent; margParent.Init( vpgnd ); pclqParent->Marginals().Marginalize( margParent ); // Create the marginals for the child clique GOBJMBN_CLIQUE * pclqChild = PclqChild(); MARGINALS margChild; margChild.Init( vpgnd ); pclqChild->Marginals().Marginalize( margChild ); // Are they equivalent? bool bOK = margParent.BEquivalent( margChild, 0.00000001 ); #ifdef DUMP if ( ! bOK ) { cout << "\nGEDGEMBN_SEPSET::BConsistent: cliques are NOT consistent, parent clique " << pclqParent->IClique() << ", child " << pclqChild->IClique(); MARGINALS::Iterator itParent(margParent); MARGINALS::Iterator itChild(margChild); cout << "\n\tparent marginals: " << itParent; cout << "\n\tchild marginals: " << itChild << "\n"; cout.flush(); } #endif #ifdef NEVER MARGINALS margParent2; margParent2.Init( vpgnd ); _miterParent.Test( margParent2 ); _miterParent.MarginalizeInto( margParent2 ); bOK = margParent.BEquivalent( margParent2, 0.00000001 ); #endif ASSERT_THROW( bOK, EC_INTERNAL_ERROR, "inconsistent cliques" ); return bOK; } //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// // GOBJMBN_CLIQUE: A Clique //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// GOBJMBN_CLIQUE :: GOBJMBN_CLIQUE ( int iClique, int iInferEngID ) : _iClique( iClique ), _iInferEngID( iInferEngID ), _bRoot(false), _bCollect(false) { } GOBJMBN_CLIQUE :: ~ GOBJMBN_CLIQUE() { } // Initialize a clique by finding all edges leading from "family" // arcs and initializing the marginals from there. void GOBJMBN_CLIQUE :: LoadMarginals () { GNODEMBND * pgnddSink; GEDGEMBN_CLIQ * pgedgeMbr; // Prepare to enumerate child member arcs GNODENUM benumMembers(false); benumMembers.SetETypeFollow( GEDGEMBN::ETCLIQUE ); // Enumerate child member arcs, reloading the marginals for nodes for which this // clique is their "self" clique. for ( benumMembers.Set( this ); benumMembers.PnodeCurrent(); benumMembers++ ) { DynCastThrow( benumMembers.PgedgeCurrent(), pgedgeMbr ); pgedgeMbr->Build(); if ( pgedgeMbr->BFamily() ) pgedgeMbr->LoadCliqueFromNode(); } // Enumerate child member arcs, entering evidence (clamped state) for nodes for which this // clique is their "self" for ( benumMembers.Set( this ); benumMembers.PnodeCurrent(); benumMembers++ ) { DynCastThrow( benumMembers.PgedgeCurrent(), pgedgeMbr ); if ( ! pgedgeMbr->BSelf() ) continue; DynCastThrow( benumMembers.PnodeCurrent(), pgnddSink ); // Note: ClampNode is benign when node is unclamped. Marginals().ClampNode( pgnddSink, pgedgeMbr->Clamp() ); } SetCollect(); } void GOBJMBN_CLIQUE :: GetMembers ( VPGNODEMBN & vpgnode ) { GNODENUM benumMembers(false); benumMembers.SetETypeFollow( GEDGEMBN::ETCLIQUE ); for ( benumMembers.Set( this ); benumMembers.PnodeCurrent(); benumMembers++ ) { GOBJMBN * pgobj = *benumMembers; GNODEMBN * pgnd; DynCastThrow( pgobj, pgnd ); vpgnode.push_back( pgnd ); } assert( vpgnode.size() > 0 ); } void GOBJMBN_CLIQUE :: CreateMarginals () { VPGNODEMBN vpgnd; GetMembers( vpgnd ); Marginals().Init( vpgnd ); } void GOBJMBN_CLIQUE :: InitMarginals () { assert( VerifyMarginals() ); Marginals().Clear( 1.0 ); } bool GOBJMBN_CLIQUE :: VerifyMarginals () { VPGNODEMBN vpgnd; GetMembers( vpgnd ); VIMD vimd = MARGINALS::VimdFromVpgnd( vpgnd ); return vimd == Marginals().Vimd(); } void GOBJMBN_CLIQUE :: Dump () { cout << "\n=== Clique " << _iClique << ", tree ID: " << _iInferEngID << ", root = " << _bRoot; _marg.Dump(); cout << "\n\n"; } void GOBJMBN_CLIQUE :: GetBelief ( GNODEMBN * pgnd, MDVCPD & mdvBel ) { GNODEMBND * pgndd; DynCastThrow( pgnd, pgndd ); Marginals().Marginalize( pgndd, mdvBel ); } //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// // GOBJMBN_CLIQSET: The graphical model junction tree //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// GOBJMBN_CLIQSET :: GOBJMBN_CLIQSET ( MBNET & model, REAL rMaxEstimatedSize, int iInferEngID ) : GOBJMBN_INFER_ENGINE( model, rMaxEstimatedSize, iInferEngID ) { Clear() ; } void GOBJMBN_CLIQSET :: Clear () { _eState = CTOR; _cCliques = 0; _cCliqueMemberArcs = 0; _cSepsetArcs = 0; _cUndirArcs = 0; _probNorm = 1.0; _bReset = true; _bCollect = true; _cqsetStat.Clear(); }; GOBJMBN_CLIQSET :: ~ GOBJMBN_CLIQSET () { #ifdef DUMP Dump(); #endif Destroy(); } bool GOBJMBN_CLIQSET :: BImpossible () { return ProbNorm() == 0.0; } // Add an undirected arc iff there isn't one already. bool GOBJMBN_CLIQSET :: BAddUndirArc ( GNODEMBN * pgndbnSource, GNODEMBN * pgndbnSink ) { if ( pgndbnSource->BIsNeighbor( pgndbnSink ) ) return false; #ifdef DUMP cout << "\n\t\tADD undirected arc from " << pgndbnSource->ZsrefName().Szc() << " to " << pgndbnSink->ZsrefName().Szc(); #endif Model().AddElem( new GEDGEMBN_U( pgndbnSource, pgndbnSink ) ); ++_cUndirArcs; return true; } void GOBJMBN_CLIQSET :: CreateUndirectedGraph ( bool bMarryParents ) { if ( EState() >= MORAL ) return; int cDirArcs = 0; int cUndirArcs = 0; int cNodes = 0; GELEMLNK * pgelm; #ifdef DUMP cout << "\n\n***** MORALIZE GRAPH"; #endif if ( EState() < MORAL ) { // Create an undirected arc for every directed arc. MODEL::MODELENUM mdlenum( Model() ); while ( pgelm = mdlenum.PlnkelNext() ) { // Check that it's an edge if ( ! pgelm->BIsEType( GELEM::EGELM_EDGE ) ) continue; // Check that it's a directed probabilistic arc if ( pgelm->EType() != GEDGEMBN::ETPROB ) continue; GEDGEMBN * pgedge; DynCastThrow( pgelm, pgedge ); GNODEMBN * pgndbnSource; GNODEMBN * pgndbnSink; DynCastThrow( pgedge->PnodeSource(), pgndbnSource ); DynCastThrow( pgedge->PnodeSink(), pgndbnSink ); // If the sink (child) node has been expanded, // consider only Expansion parents if ( pgndbnSink->BFlag( EIBF_Expanded ) && ! pgndbnSource->BFlag( EIBF_Expansion ) ) continue; cDirArcs++; cUndirArcs += BAddUndirArc( pgndbnSource, pgndbnSink ); } assert( cDirArcs == cUndirArcs ) ; // Undirected graph has been created _eState = UNDIR; } if ( !bMarryParents ) return; #ifdef DUMP cout << "\n\n***** MARRY PARENTS"; #endif MODEL::MODELENUM mdlenum( Model() ); GNODENUM benumparent(true); benumparent.SetETypeFollow( GEDGEMBN::ETPROB ); GNODEMBN * pgndmbn; VPGNODEMBN vpgnd; while ( pgelm = mdlenum.PlnkelNext() ) { if ( pgelm->EType() != EBNO_NODE ) continue; DynCastThrow( pgelm, pgndmbn ); // Collect the parents vpgnd.resize(0); pgndmbn->GetParents( vpgnd ); // Marry them int cParent = vpgnd.size(); for ( int iParent = 0; iParent < cParent - 1; iParent++ ) { for ( int ip2 = iParent+1; ip2 < cParent ; ip2++ ) { BAddUndirArc( vpgnd[iParent], vpgnd[ip2] ); } } } // Graph is now moral _eState = MORAL; } // // Return the number of neighbors of this node which are unlinked // int GOBJMBN_CLIQSET :: CNeighborUnlinked ( GNODEMBN * pgndmbn, bool bLinkNeighbors ) { int cNeighborUnlinked = 0; // Get the array of neighbors VPGNODEMBN vpgnode; pgndmbn->GetNeighbors( vpgnode ); #ifdef DUMP cout << "\n\t\tCNeighborUnlinked, called for node " << pgndmbn->ZsrefName().Szc(); #endif for ( int inbor = 0; inbor < vpgnode.size(); inbor++ ) { GNODEMBN * pgndNbor = vpgnode[inbor]; #ifdef DUMP cout << "\n\t\t\t" << pgndNbor->ZsrefName().Szc(); int cUnlinked = 0; #endif if ( pgndNbor->IMark() ) continue; // Node has been eliminated already // Check it against all other neighbors. for ( int inbor2 = inbor + 1; inbor2 < vpgnode.size(); inbor2++ ) { GNODEMBN * pgndNbor2 = vpgnode[inbor2]; // See if node has been eliminated already or is already a neighbor if ( pgndNbor2->IMark() ) continue; if ( pgndNbor->BIsNeighbor( pgndNbor2 ) ) { assert( pgndNbor2->BIsNeighbor( pgndNbor ) ); continue; } #ifdef DUMP cUnlinked++; #endif ++cNeighborUnlinked; if ( bLinkNeighbors ) { BAddUndirArc( pgndNbor, pgndNbor2 ); #ifdef DUMP cout << " (" << pgndNbor->ZsrefName().Szc() << " <-> " << pgndNbor2->ZsrefName().Szc() << ") "; #endif } } #ifdef DUMP if ( cUnlinked ) cout << " <-- unlinked to " << cUnlinked << " neighbors"; #endif } #ifdef DUMP cout << "\n\t\t---- total unlinked = " << cNeighborUnlinked; #endif return cNeighborUnlinked; } void GOBJMBN_CLIQSET :: Eliminate ( GNODEMBN * pgndmbn, CLIQSETWORK & clqsetWork ) { #ifdef DUMP cout << "\n\n***** ELIMINATE " << pgndmbn->ZsrefName().Szc(); #endif // Add another array to the clique set and fill it with the clique menbers clqsetWork._vvpgnd.push_back( VPGNODEMBN() ); VPGNODEMBN & vpgndClique = clqsetWork._vvpgnd[ clqsetWork._vvpgnd.size() - 1 ]; // Complete the elimination of this node and its neighbors. CNeighborUnlinked( pgndmbn, true ); pgndmbn->IMark() = ++clqsetWork._iElimIndex; // Start the clique with this entry. vpgndClique.push_back( pgndmbn ); // Iterate over the neighbors, adding the unmarked ones GNODENUM_UNDIR gnenumUndir; for ( gnenumUndir = pgndmbn; gnenumUndir.PnodeCurrent(); gnenumUndir++ ) { GNODEMBN * pgndmbNeighbor = *gnenumUndir; if ( pgndmbNeighbor->IMark() == 0 ) vpgndClique.push_back( pgndmbNeighbor ); } #ifdef DUMP cout << "\n\t\tNEW CLIQUE: "; clqsetWork.DumpClique( clqsetWork._vvpgnd.size() - 1 ); #endif assert( pgndmbn->IMark() > 0 ); } void GOBJMBN_CLIQSET :: GenerateCliques ( CLIQSETWORK & clqsetWork ) { // Reset marks in all nodes Model().ClearNodeMarks(); clqsetWork._vvpgnd.clear(); #ifdef DUMP cout << "\n\n***** GENERATE CLIQUES"; #endif for(;;) { // Find the node that requires the fewest edges to turn into a clique. GNODEMBN * pgndmbnMin = NULL; int cNeighborMin = INT_MAX; MODEL::MODELENUM mdlenum( Model() ); GELEMLNK * pgelm; while ( pgelm = mdlenum.PlnkelNext() ) { if ( pgelm->EType() != EBNO_NODE ) continue; GNODEMBN * pgndmbn; DynCastThrow( pgelm, pgndmbn ); if ( pgndmbn->IMark() ) continue; // Node has been eliminated already int cNeighborUnlinked = CNeighborUnlinked( pgndmbn ); if ( cNeighborMin > cNeighborUnlinked ) { pgndmbnMin = pgndmbn; if ( (cNeighborMin = cNeighborUnlinked) == 0 ) break; // zero is as few neighbors as possible } } if ( pgndmbnMin == NULL ) break; // Mark the node for elimination and assign an elimination order to it. This // number is crucial for the construction of the strong junction tree. #ifdef DUMP cout << "\nGenerateCliques: Eliminate " << pgndmbnMin->ZsrefName().Szc() << ", which has " << cNeighborMin << " unlinked neighbors"; #endif Eliminate( pgndmbnMin, clqsetWork ); } #ifdef DUMP cout << "\n\n"; #endif } // // Create the junction tree. // void GOBJMBN_CLIQSET :: Create () { Model().CreateTopology(); ASSERT_THROW( EState() == CTOR, EC_INTERNAL_ERROR, "GOBJMBN_CLIQSET:Create already called" ); // If it hasn't been done already, create the undirected graph and moralize it. CreateUndirectedGraph(true); CLIQSETWORK clqsetWork(self); clqsetWork._iElimIndex = 1; // Triangulate the undirected graph, eliminating nodes and accumulating cliques // along the way. GenerateCliques( clqsetWork ); if ( clqsetWork._vvpgnd.size() == 0 ) return; _eState = CLIQUED; #ifdef DUMP clqsetWork.DumpCliques(); #endif // Provide a total ordering over the nodes based upon topological level // MSRDEVBUG: What happened to the elimination index? Koos doesn't use it; will we? // Renumbering here overwrites the elimination order. clqsetWork.RenumberNodesForCliquing(); // Build the cliques clqsetWork.BuildCliques(); // Set clique membership and topological information clqsetWork.SetTopologicalInfo(); // Check that the running intersection property holds ASSERT_THROW( clqsetWork.BCheckRIP(), EC_INTERNAL_ERROR, "GOBJMBN_CLIQSET::Create: junction tree failed RIP test" ); // See if the resulting memory allocation size would violate the size estimate if ( _rEstMaxSize > 0.0 ) { REAL rSizeEstimate = clqsetWork.REstimatedSize(); if ( rSizeEstimate > _rEstMaxSize ) throw GMException( EC_OVER_SIZE_ESTIMATE, "Clique tree size violates estimated size limit" ); } // Create the topology-- all the trees in the forest clqsetWork.CreateTopology(); // Nuke the moral graph DestroyDirectedGraph(); // Bind the known distributions to their target nodes; _model.BindDistributions(); // Reset/initialize the "lazy" switches SetReset(); // Create the marginals in the cliques and sepsets CreateMarginals(); _eState = BUILT; // Load and initialize the tree Reload(); // Release the distributions from their target nodes _model.ClearDistributions(); } DEFINEVP(GELEMLNK); // // Destroy the junction tree. Allow the GOBJMBN_CLIQSET object to be reused // for another cliquing operation later. // void GOBJMBN_CLIQSET :: Destroy () { if ( ! Model().Pgraph() ) return; int cCliques = 0; int cCliqueMemberArcs = 0; int cSepsetArcs = 0; int cUndirArcs = 0; int cRootCliqueArcs = 0; VPGELEMLNK vpgelm; GELEMLNK * pgelm; MODEL::MODELENUM mdlenum( Model() ); while ( pgelm = mdlenum.PlnkelNext() ) { bool bDelete = false; int eType = pgelm->EType(); if ( pgelm->BIsEType( GELEM::EGELM_EDGE ) ) { GEDGEMBN * pgedge; DynCastThrow( pgelm , pgedge ); int eType = pgedge->EType(); switch ( eType ) { case GEDGEMBN::ETPROB: break; case GEDGEMBN::ETCLIQUE: // Clique membership arcs will go away automatically because // cliques will be deleted. ++cCliqueMemberArcs; break; case GEDGEMBN::ETJTREE: // Junction tree arcs will go away automatically because // cliques will be deleted. ++cSepsetArcs; break; case GEDGEMBN::ETUNDIR: // Undirected arcs must be deleted explicitly bDelete = true; ++cUndirArcs; break; case GEDGEMBN::ETCLIQSET: ++cRootCliqueArcs; break; default: THROW_ASSERT( EC_INTERNAL_ERROR, " GOBJMBN_CLIQSET::Destroy: Unrecognized edge object in graph" ); break; } } else if ( pgelm->BIsEType( GELEM::EGELM_NODE ) ) { GOBJMBN * pgobj; DynCastThrow( pgelm , pgobj ); switch ( eType ) { case GOBJMBN::EBNO_CLIQUE: { ++cCliques; bDelete = true; break; } case GOBJMBN::EBNO_CLIQUE_SET: case GOBJMBN::EBNO_NODE: case GOBJMBN::EBNO_PROP_TYPE: case GOBJMBN::EBNO_USER: break; default: THROW_ASSERT( EC_INTERNAL_ERROR, " GOBJMBN_CLIQSET::Destroy: Unrecognized node object in graph" ); break; } } else { THROW_ASSERT( EC_INTERNAL_ERROR, " GOBJMBN_CLIQSET::Destroy: Unrecognized object in graph" ); } if ( bDelete ) vpgelm.push_back( pgelm ); } assert( cCliques == _cCliques && cCliqueMemberArcs == _cCliqueMemberArcs && cSepsetArcs == _cSepsetArcs && cUndirArcs == _cUndirArcs ); for ( int i = 0; i < vpgelm.size(); ) { delete vpgelm[i++]; } Clear(); } void GOBJMBN_CLIQSET :: DestroyDirectedGraph () { int cUndirArcs = 0; VPGELEMLNK vpgelm; GELEMLNK * pgelm; MODEL::MODELENUM mdlenum( Model() ); while ( pgelm = mdlenum.PlnkelNext() ) { if ( pgelm->BIsEType( GELEM::EGELM_EDGE ) ) { GEDGEMBN * pgedge; DynCastThrow( pgelm , pgedge ); int eType = pgedge->EType(); switch ( eType ) { case GEDGEMBN::ETUNDIR: vpgelm.push_back( pgelm ); ++cUndirArcs; break; default: break; } } } assert( cUndirArcs == _cUndirArcs ); _cUndirArcs = 0; for ( int i = 0; i < vpgelm.size(); ) { delete vpgelm[i++]; } } // Create and initialize all marginals tables void GOBJMBN_CLIQSET :: CreateMarginals () { assert( _eState == CLIQUED ) ; //MSRDEVBUG: The class name qualifier should not be necessary here and below. WalkTree( true, & GOBJMBN_CLIQSET::BCreateClique, & GOBJMBN_CLIQSET::BCreateSepset ); } // Reset the entire tree by reloading all marginals tables void GOBJMBN_CLIQSET :: LoadMarginals () { assert( _eState == BUILT ) ; WalkTree( true, & GOBJMBN_CLIQSET::BLoadClique, & GOBJMBN_CLIQSET::BLoadSepset ); _cqsetStat._cReload++; } // Apply the given member function(s) to every clique tree in the forest. int GOBJMBN_CLIQSET :: WalkTree ( bool bDepthFirst, // Depth first or breadth first? PFNC_JTREE pfJtree, // Function to apply to each clique PFNC_SEPSET pfSepset ) // Function to apply to each sepset { int cClique = 0; // Don't count the clique set object int cWalk = 0; // Return count of cliques visited GNODENUM benumChildren(false); benumChildren.SetETypeFollow( GEDGEMBN::ETCLIQSET ); for ( benumChildren.Set( this ); benumChildren.PnodeCurrent(); benumChildren++ ) { GOBJMBN * pgobj = *benumChildren; assert( pgobj->EType() == GNODEMBN::EBNO_CLIQUE ); GOBJMBN_CLIQUE * pCliqueTreeRoot; DynCastThrow( pgobj, pCliqueTreeRoot ); cWalk = bDepthFirst ? WalkDepthFirst( pCliqueTreeRoot, pfJtree, pfSepset ) : WalkBreadthFirst( pCliqueTreeRoot, pfJtree, pfSepset ); if ( cWalk < 0 ) return -1; cClique += cWalk; } assert( cClique < 0 || cClique == _cCliques ); return cClique; } // // Recursive depth-first walk down the tree. // // Apply the given member function(s), depth first from this clique. // If application function call returns false, walk is aborted and // -1 is returned; otherwise, count of cliques traversed is returned. int GOBJMBN_CLIQSET :: WalkDepthFirst ( GOBJMBN_CLIQUE * pClique, // Starting point PFNC_JTREE pfJtree, // Function to apply to each clique PFNC_SEPSET pfSepset ) // Function to apply to each sepset { assert( pClique ) ; assert( pClique->IInferEngID() == IInferEngID() ) ; if ( pfJtree ) { // Call the application function on the way down if ( ! (self.*pfJtree)( *pClique, true ) ) return -1; } int cWalks = 1; // Count the clique we just processed above int cWalk = 0; // Return count of cliques visited GNODENUM benumChildren(false); benumChildren.SetETypeFollow( GEDGEMBN::ETJTREE ); for ( benumChildren.Set( pClique ); benumChildren.PnodeCurrent(); benumChildren++ ) { GOBJMBN_CLIQUE * pCliqueChild = NULL; GEDGEMBN_SEPSET * pgedge = NULL; if ( pfSepset ) { // Call the application function on the way down DynCastThrow( benumChildren.PgedgeCurrent(), pgedge ); if ( ! (self.*pfSepset)( *pgedge, true ) ) return -1; } DynCastThrow( benumChildren.PnodeCurrent(), pCliqueChild ); cWalk = WalkDepthFirst( pCliqueChild, pfJtree, pfSepset ); if ( cWalk < 0 ) return -1; cWalks += cWalk; if ( pfSepset ) { assert( pgedge ); // Call the application function on the way up if ( ! (self.*pfSepset)( *pgedge, false ) ) return -1; } } if ( pfJtree ) { // Call the application function on the way up if ( ! (self.*pfJtree)( *pClique, false ) ) return -1; } return cWalks; } // // Non-recursive breadth-first walk down the tree. // No "up" actions are called using the function pointers. // int GOBJMBN_CLIQSET :: WalkBreadthFirst ( GOBJMBN_CLIQUE * pClique, // Starting point PFNC_JTREE pfJtree, // Function to apply to each clique PFNC_SEPSET pfSepset ) // Function to apply to each sepset { assert( pClique ) ; assert( pClique->IInferEngID() == IInferEngID() ) ; VPGEDGEMBN_SEPSET vpgedgeThis; VPGEDGEMBN_SEPSET vpgedgeNext; VPGEDGEMBN_SEPSET * pvpgedgeThis = & vpgedgeThis; VPGEDGEMBN_SEPSET * pvpgedgeNext = & vpgedgeNext; VPGEDGEMBN_SEPSET * pvpgedgeTemp = NULL; GOBJMBN_CLIQUE * pgobjClique = NULL; GEDGEMBN_SEPSET * pgedgeSepset = NULL; // Count the cliques we process, including this one int cWalk = 1; // Starting clique is a special case; process it now if ( pfJtree ) { // Call the application function on the way down if ( ! (self.*pfJtree)( *pClique, true ) ) return -1; } // Prepare an enumerator for child cliques GNODENUM benumChildren(false); benumChildren.SetETypeFollow( GEDGEMBN::ETJTREE ); // Since we don't have the edge that led us here, put a NULL // in its place to start iteration pvpgedgeNext->push_back(NULL); // While there were entries at the last topological level... while ( pvpgedgeNext->size() ) { // Swap the array pointers and clear next pass array pexchange( pvpgedgeThis, pvpgedgeNext ); pvpgedgeNext->clear(); for ( int iEdge = 0; iEdge < pvpgedgeThis->size(); iEdge++ ) { pgedgeSepset = (*pvpgedgeThis)[iEdge]; pgobjClique = pgedgeSepset == NULL ? pClique // This is the start of iteration : pgedgeSepset->PclqChild(); assert( pgobjClique ); // Accumulate all child cliques of this clique, // processing as necessary for ( benumChildren.Set( pgobjClique ); benumChildren.PnodeCurrent(); benumChildren++ ) { GEDGEMBN_SEPSET * pgedge; DynCastThrow( benumChildren.PgedgeCurrent(), pgedge ); if ( pfSepset ) { // Call the sepset application function on the way down if ( ! (self.*pfSepset)( *pgedge, true ) ) return -1; } if ( pfJtree ) { // Call the clique application function on the way down GOBJMBN_CLIQUE * pCliqueChild = pgedge->PclqChild(); if ( ! (self.*pfJtree)( *pCliqueChild, true ) ) return -1; } cWalk++; pvpgedgeNext->push_back( pgedge ); } } } return cWalk; } // // Terminology: "Create", "Init" and "Load": // // 'Create' means to size the dynamic arrays; // 'Init' means to initialize them to 1.0; // 'Load' means to multiply in the probabilities of the clique members. // bool GOBJMBN_CLIQSET :: BCreateClique ( GOBJMBN_CLIQUE & clique, bool bDownwards ) { if ( ! bDownwards ) return true; clique.CreateMarginals(); return true; } bool GOBJMBN_CLIQSET :: BLoadClique ( GOBJMBN_CLIQUE & clique, bool bDownwards ) { if ( ! bDownwards ) return true; clique.InitMarginals(); clique.LoadMarginals(); return true; } bool GOBJMBN_CLIQSET :: BCreateSepset ( GEDGEMBN_SEPSET & sepset, bool bDownwards ) { if ( ! bDownwards ) return true; sepset.CreateMarginals(); return true; } bool GOBJMBN_CLIQSET :: BLoadSepset ( GEDGEMBN_SEPSET & sepset, bool bDownwards ) { if ( ! bDownwards ) return true; sepset.InitMarginals(); return true; } // Return the "family" or "self" clique for a node GOBJMBN_CLIQUE * GOBJMBN_CLIQSET :: PCliqueFromNode ( GNODEMBN * pgnd, // Node to find clique for bool bFamily, // "family" clique if true, "self" clique if false GEDGEMBN_CLIQ * * ppgedgeClique ) // return pointer to edge if not NULL { GEDGEMBN_CLIQ::FCQLROLE fcqlRole = bFamily ? GEDGEMBN_CLIQ::FAMILY : GEDGEMBN_CLIQ::SELF; // Prepare to iterate over the source arcs GNODENUM benumMembers(true); benumMembers.SetETypeFollow( GEDGEMBN::ETCLIQUE ); for ( benumMembers.Set( pgnd ); benumMembers.PnodeCurrent(); benumMembers++ ) { GEDGEMBN_CLIQ * pgedgeClique; DynCastThrow( benumMembers.PgedgeCurrent(), pgedgeClique ); GOBJMBN_CLIQUE * pgobjClique = pgedgeClique->PclqParent(); if ( pgobjClique->IInferEngID() != IInferEngID() ) continue; // not an edge for this junction tree if ( pgedgeClique->IFcqlRole() & fcqlRole ) { if ( ppgedgeClique ) *ppgedgeClique = pgedgeClique; return pgedgeClique->PclqParent(); } } assert( false ); return NULL; } // // Enter evidence for a node. // void GOBJMBN_CLIQSET :: EnterEvidence ( GNODEMBN * pgnd, const CLAMP & clamp ) { // Get the pointer to the node's "self" clique and the edge leading to it GEDGEMBN_CLIQ * pgedgeClique = NULL; GOBJMBN_CLIQUE * pCliqueSelf = PCliqueFromNode( pgnd, false, & pgedgeClique ); ASSERT_THROW( pCliqueSelf, EC_INTERNAL_ERROR, "GOBJMBN_CLIQSET::EnterEvidence: can\'t find self clique" ); assert( pgedgeClique ); // Update with evidence if it has changed if ( pgedgeClique->Clamp() != clamp ) { // Evidence is NOT the same as the old evidence pgedgeClique->Clamp() = clamp; // Indicate that we must reload the tree SetReset(); pCliqueSelf->SetCollect(); _cqsetStat._cEnterEv++; } } // // Return the evidence "clamp" for a node. It is stored in the edge // between the node and its "self" clique: the highest clique in the tree // of which the node is a member. // void GOBJMBN_CLIQSET :: GetEvidence ( GNODEMBN * pgnd, CLAMP & clamp ) { // Get the pointer to the node's "self" clique and the edge leading to it GEDGEMBN_CLIQ * pgedgeClique = NULL; GOBJMBN_CLIQUE * pCliqueSelf = PCliqueFromNode( pgnd, false, & pgedgeClique ); ASSERT_THROW( pCliqueSelf, EC_INTERNAL_ERROR, "GOBJMBN_CLIQSET::GetEvidence: can\'t find self clique" ); assert( pgedgeClique ); clamp = pgedgeClique->Clamp(); } void GOBJMBN_CLIQSET :: GetBelief ( GNODEMBN * pgnd, MDVCPD & mdvBel ) { GEDGEMBN_CLIQ * pgedgeClique = NULL; GOBJMBN_CLIQUE * pCliqueFamily = PCliqueFromNode( pgnd, true, & pgedgeClique ); ASSERT_THROW( pCliqueFamily, EC_INTERNAL_ERROR, "GOBJMBN_CLIQSET::GetBelief: can\'t find family clique" ); // Perform inference if necessary Infer(); // Marginalize the clique down to one node GNODEMBND * pgndd; DynCastThrow( pgnd, pgndd ); pgedgeClique->MiterNodeBelief().MarginalizeBelief( mdvBel, pgndd ); _cqsetStat._cGetBel++; } PROB GOBJMBN_CLIQSET :: ProbNorm () { // MSRDEVBUG /* Reset(); CollectEvidence(); */ Infer(); _cqsetStat._cProbNorm++; return _probNorm; } // // Reload all marginals, reset the trees // void GOBJMBN_CLIQSET :: Reload () { SetReset( true ); Reset(); } // // Reset all marginals, restore all clamped evidence and // perform the initial inference pass. // void GOBJMBN_CLIQSET :: Reset () { assert( EState() >= BUILT ); if ( ! _bReset ) return; _probNorm = 1.0; LoadMarginals(); SetReset( false ); // Initialize the entire tree for inference #ifdef INFERINIT InferInit(); #endif SetCollect(true); } // Perform an inference cycle if necessary void GOBJMBN_CLIQSET :: Infer () { Reset(); // Reloads the tree if necessary if ( ! BCollect() ) return; #ifdef DUMPCLIQUESET cout << "\n\n==============================================================="; cout << "\n============= Dump of clique tree before inference ===============\n"; Dump(); cout << "\n========= End Dump of clique tree before inference ==============="; cout << "\n===============================================================\n\n"; cout << "\n\nGOBJMBN_CLIQSET::Infer: begin."; #endif CollectEvidence(); DistributeEvidence(); #ifdef CONSISTENCY CheckConsistency(); #endif SetCollect( false ); #ifdef DUMPCLIQUESET cout << "\n\n==============================================================="; cout << "\n============= Dump of clique tree after inference ===============\n"; Dump(); cout << "\n========= End Dump of clique tree after inference ==============="; cout << "\n===============================================================\n\n"; cout << "\nGOBJMBN_CLIQSET::Infer: end.\n\n"; #endif } // Perform initial inference collect/distribute cycle void GOBJMBN_CLIQSET :: InferInit () { #ifdef DUMPCLIQUESET cout << "\n\n==============================================================="; cout << "\n============= Dump of clique tree before inference INIT ======\n"; Dump(); cout << "\n========= End Dump of clique tree before inference INIT ======"; cout << "\n===============================================================\n\n"; cout << "\n\nGOBJMBN_CLIQSET::InferInit: begin."; #endif CollectEvidenceInit(); DistributeEvidenceInit(); #ifdef DUMPCLIQUESET cout << "\n\n==============================================================="; cout << "\n============= Dump of clique tree after inference INIT =======\n"; Dump(); cout << "\n========= End Dump of clique tree after inference INIT ========"; cout << "\n================================================================\n\n"; cout << "\nGOBJMBN_CLIQSET::InferInit: end.\n\n"; #endif } void GOBJMBN_CLIQSET :: CollectEvidence() { WalkTree( true, BCollectEvidenceAtRoot, BCollectEvidenceAtSepset ); _cqsetStat._cCollect++; } void GOBJMBN_CLIQSET :: DistributeEvidence() { WalkTree( true, BDistributeEvidenceAtRoot, BDistributeEvidenceAtSepset ); } void GOBJMBN_CLIQSET :: CollectEvidenceInit () { WalkTree( true, BCollectInitEvidenceAtRoot, BCollectInitEvidenceAtSepset ); } void GOBJMBN_CLIQSET :: DistributeEvidenceInit () { WalkTree( true, BDistributeInitEvidenceAtRoot, BDistributeInitEvidenceAtSepset ); } void GOBJMBN_CLIQSET :: CheckConsistency () { WalkTree( true, NULL, BConsistentSepset ); } bool GOBJMBN_CLIQSET :: BConsistentSepset ( GEDGEMBN_SEPSET & sepset, bool bDownwards ) { if ( ! bDownwards ) return true; return sepset.BConsistent(); } // When the collection cycle has completed for a tree, recompute the // "prob norm" value. bool GOBJMBN_CLIQSET :: BCollectEvidenceAtRoot ( GOBJMBN_CLIQUE & clique, bool bDownwards ) { if ( bDownwards || ! clique.BRoot() ) return true; // This is a root clique at the end of the collection cycle. // Normalize the clique and maintain the norm of the the probability // of the tree. // MSRDEVBUG: (Explain this better!) REAL rProb = clique.Marginals().RSum(); _probNorm *= rProb; if ( rProb != 0.0 ) { rProb = 1.0 / rProb; clique.Marginals().Multiply( rProb ); } #ifdef DUMPCLIQUESET cout << "\nCollect Evidence (root), clique " << clique._iClique << ", root = " << int(clique._bRoot) << ", prob norm = " << _probNorm; #endif return true; } bool GOBJMBN_CLIQSET :: BDistributeEvidenceAtRoot ( GOBJMBN_CLIQUE & clique, bool bDownwards ) { if ( ! bDownwards || ! clique.BRoot() ) return true; #ifdef DUMPCLIQUESET cout << "\nDistribute Evidence (root), clique " << clique._iClique << ", root = " << int(clique._bRoot); #endif return true; } bool GOBJMBN_CLIQSET :: BCollectEvidenceAtSepset ( GEDGEMBN_SEPSET & sepset, bool bDownwards ) { GOBJMBN_CLIQUE * pCliqueChild = sepset.PclqChild(); GOBJMBN_CLIQUE * pCliqueParent = sepset.PclqParent(); if ( bDownwards ) return true; #ifdef DUMPCLIQUESET cout << "\nCollect Evidence (sepset), clique " << pCliqueChild->_iClique << ", root = " << int(pCliqueChild->_bRoot) << ", parent = " << pCliqueParent->_iClique ; cout.flush(); #endif if ( ! pCliqueChild->BCollect() ) return true; pCliqueParent->SetCollect(); sepset.UpdateParentClique(); SetCollect( false ); return true; } bool GOBJMBN_CLIQSET :: BDistributeEvidenceAtSepset ( GEDGEMBN_SEPSET & sepset, bool bDownwards ) { if ( ! bDownwards ) return true; #ifdef DUMPCLIQUESET GOBJMBN_CLIQUE * pCliqueChild = sepset.PclqChild(); GOBJMBN_CLIQUE * pCliqueParent = sepset.PclqParent(); cout << "\nDistribute Evidence (sepset), clique " << pCliqueParent->_iClique << ", root = " << int(pCliqueParent->_bRoot) << ", child = " << pCliqueChild->_iClique ; cout.flush(); #endif sepset.UpdateChildClique(); return true; } bool GOBJMBN_CLIQSET :: BCollectInitEvidenceAtSepset ( GEDGEMBN_SEPSET & sepset, bool bDownwards ) { if ( bDownwards ) return true; #ifdef DUMPCLIQUESET GOBJMBN_CLIQUE * pCliqueChild = sepset.PclqChild(); GOBJMBN_CLIQUE * pCliqueParent = sepset.PclqParent(); cout << "\nCollect Initial Evidence (sepset), clique " << pCliqueChild->_iClique << ", root = " << int(pCliqueChild->_bRoot) << ", parent = " << pCliqueParent->_iClique ; cout.flush(); #endif sepset.BalanceCliquesCollect(); return true; } bool GOBJMBN_CLIQSET :: BDistributeInitEvidenceAtSepset ( GEDGEMBN_SEPSET & sepset, bool bDownwards ) { if ( ! bDownwards ) return true; #ifdef DUMPCLIQUESET GOBJMBN_CLIQUE * pCliqueParent = sepset.PclqParent(); GOBJMBN_CLIQUE * pCliqueChild = sepset.PclqChild(); cout << "\nDistribute Initial Evidence (sepset), clique " << pCliqueParent->_iClique << ", root = " << int(pCliqueParent->_bRoot) << ", child = " << pCliqueChild->_iClique ; cout.flush(); #endif sepset.BalanceCliquesDistribute(); return true; } bool GOBJMBN_CLIQSET :: BCollectInitEvidenceAtRoot ( GOBJMBN_CLIQUE & clique, bool bDownwards ) { if ( bDownwards || ! clique.BRoot() ) return true; #ifdef DUMPCLIQUESET cout << "\nCollect Initial Evidence at root, clique " << clique._iClique << ", root = " << int(clique._bRoot); #endif clique.Marginals().Normalize(); return true; } bool GOBJMBN_CLIQSET :: BDistributeInitEvidenceAtRoot ( GOBJMBN_CLIQUE & clique, bool bDownwards ) { return true; } void GOBJMBN_CLIQSET :: Dump () { WalkTree( true, BDumpClique, BDumpSepset ); MARGSUBITER::Dump(); } bool GOBJMBN_CLIQSET :: BDumpSepset ( GEDGEMBN_SEPSET & sepset, bool bDownwards ) { if ( ! bDownwards ) return true; sepset.Dump(); return true; } bool GOBJMBN_CLIQSET :: BDumpClique ( GOBJMBN_CLIQUE & clique, bool bDownwards ) { if ( ! bDownwards ) return true; clique.Dump(); return true; } // End of CLIQUE.CPP