1711 lines
41 KiB
C++
1711 lines
41 KiB
C++
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 1997 - 1998
|
|
//
|
|
// File: clique.cpp
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
//
|
|
// clique.cpp
|
|
//
|
|
|
|
#include <basetsd.h>
|
|
#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<GOBJMBN> 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<GOBJMBN> 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<GNODEMBN> 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<GOBJMBN> 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<GOBJMBN_CLIQUE> 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<GOBJMBN_CLIQUE> 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<GOBJMBN> 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
|