WindowsXP-SP1/enduser/troubleshoot/bn/cliqwork.cpp

743 lines
21 KiB
C++

//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 1997 - 1998
//
// File: cliqwork.cpp
//
//--------------------------------------------------------------------------
//
// cliqwork.cpp
//
#include <basetsd.h>
#include "cliqset.h"
#include "clique.h"
#include "cliqwork.h"
#ifdef _DEBUG
// #define DUMP
#endif
// Sort helper 'less' function for sorting arrays of node pointers into 'mark' sequence.
class MARKSRTPGND : public binary_function<const GNODEMBN *, const GNODEMBN *, bool>
{
public:
bool operator () (const GNODEMBN * pa, const GNODEMBN * pb) const
{ return pa->IMark() < pb->IMark() ; }
};
#ifdef _DEBUG
static void seqchkVpnodeByMark (const VPGNODEMBN & vpgnd)
{
int imrk = INT_MIN;
int imrk2;
for ( int i = 0; i < vpgnd.size(); i++, imrk = imrk2)
{
imrk2 = vpgnd[i]->IMark();
assert( imrk2 >= 0 );
assert( imrk2 >= imrk );
}
}
#endif
// Sort the clique information array into topological sequence
void CLIQSETWORK :: TopSortNodeCliqueInfo ()
{
sort( _vndcqInfo.begin(), _vndcqInfo.end() );
}
// Sort the given node pointer array in to "mark" (cliquing order) sequence
void CLIQSETWORK :: MarkSortNodePtrArray ( VPGNODEMBN & vpgnd )
{
MARKSRTPGND marksorter;
sort( vpgnd.begin(), vpgnd.end(), marksorter );
#ifdef _DEBUG
seqchkVpnodeByMark( vpgnd );
#endif
}
// Establish an absolute ordering based upon the topological ordering
void CLIQSETWORK :: RenumberNodesForCliquing ()
{
// Perform a topological sort of the network
Model().TopSortNodes();
MODEL::MODELENUM mdlenum( Model() );
GELEMLNK * pgelm;
_vndcqInfo.clear();
// Collect all the nodes into a pointer array
while ( pgelm = mdlenum.PlnkelNext() )
{
if ( pgelm->EType() != GOBJMBN::EBNO_NODE )
continue;
NDCQINFO ndcq;
DynCastThrow( pgelm, ndcq._pgnd );
_vndcqInfo.push_back( ndcq );
}
// Sort the array into topological sequence.
TopSortNodeCliqueInfo();
#ifdef _DEBUG
int iTop = -1;
#endif
// Establish the total ordering based upon topological level.
for ( int i = 0; i < _vndcqInfo.size() ; i++ )
{
GNODEMBN * pgnd = _vndcqInfo[i]._pgnd;
assert( pgnd );
#ifdef _DEBUG
// Check sequence.
assert( iTop <= pgnd->ITopLevel() );
iTop = pgnd->ITopLevel();
#endif
pgnd->IMark() = i;
}
}
void CLIQSETWORK :: PrepareForBuild ()
{
// Resize and initialize the work arrays
int cCliques = _vvpgnd.size();
_viParent.resize( cCliques );
_viOrder.resize( cCliques );
_viCNodesCommon.resize( cCliques );
_viICliqCommon.resize( cCliques );
_viOrdered.clear();
for ( int iClique = 0; iClique < cCliques; iClique++ )
{
MarkSortNodePtrArray( _vvpgnd[iClique] );
_viParent[iClique] = INT_MIN;
_viOrder[iClique] = INT_MIN;
_viCNodesCommon[iClique] = INT_MIN;
_viICliqCommon[iClique] = INT_MIN;
}
}
// Return the number of nodes in common between the two cliques
int CLIQSETWORK :: CNodesCommon ( int iClique1, int iClique2 )
{
assert( iClique1 < _vvpgnd.size() && iClique2 < _vvpgnd.size() );
return CNodesCommon( _vvpgnd[iClique1], _vvpgnd[iClique2] );
}
// Return the number of nodes in common between the two node lists
int CLIQSETWORK :: CNodesCommon ( const VPGNODEMBN & vpgnd1, const VPGNODEMBN & vpgnd2 )
{
MARKSRTPGND marksorter;
#ifdef _DEBUG
seqchkVpnodeByMark( vpgnd1 );
seqchkVpnodeByMark( vpgnd2 );
#endif
int cCommon = count_set_intersection( vpgnd1.begin(),
vpgnd1.end(),
vpgnd2.begin(),
vpgnd2.end(),
marksorter );
return cCommon;
}
// Return the ordered index of a clique or -1 if not in the tree yet.
inline
int CLIQSETWORK :: IOrdered ( int iClique )
{
return ifind( _viOrdered, iClique );
}
// Update the "most common clique" info of iClique1 based upon iClique2. This is
// used to count the number of nodes in common between a candidate clique and a
// clique already in the tree.
void CLIQSETWORK :: SetCNodeMaxCommon ( int iClique1, int iCliqueOrdered2 )
{
assert( iCliqueOrdered2 < _viOrdered.size() );
int iClique2 = _viOrdered[iCliqueOrdered2];
int cCommon = CNodesCommon( iClique1, iClique2 );
if ( cCommon > _viCNodesCommon[iClique1] )
{
_viCNodesCommon[iClique1] = cCommon;
_viICliqCommon[iClique1] = iCliqueOrdered2;
}
}
//
// Completely update the "most common clique" information for this clique.
// This is necessary because cliques can change membership due to subsumption
// during generation of the clique tree.
// Return true if there is any overlap with a clique already in the tree.
//
bool CLIQSETWORK :: BUpdateCNodeMaxCommon ( int iClique )
{
assert( _viOrder[iClique] == INT_MIN );
int & cNodesCommon = _viCNodesCommon[iClique];
int & iCliqCommon = _viICliqCommon[iClique];
cNodesCommon = INT_MIN;
iCliqCommon = INT_MIN;
for ( int iord = 0; iord < _viOrdered.size(); iord++ )
{
SetCNodeMaxCommon( iClique, iord );
}
return cNodesCommon > 0;
}
// Return true if clique 1 has more nodes in common with a clique that is already in
// the tree than clique2. If they have the same number of nodes in common, return
// true if clique 1 has fewer nodes than clique2.
bool CLIQSETWORK :: BBetter ( int iClique1, int iClique2 )
{
assert( _viCNodesCommon[iClique1] >= 0 );
assert( _viCNodesCommon[iClique2] >= 0 );
if ( _viCNodesCommon[iClique1] != _viCNodesCommon[iClique2] )
return _viCNodesCommon[iClique1] > _viCNodesCommon[iClique2];
return _vvpgnd[iClique1].size() < _vvpgnd[iClique2].size();
}
// After building the cliques, topologically sort them and anchor each node
// to the highest clique in the tree to which it belongs.
void CLIQSETWORK :: SetTopologicalInfo ()
{
#ifdef DUMP
DumpTree();
#endif
// First, set up the ordered parent information array
int cCliqueOrdered = _viOrdered.size();
assert( cCliqueOrdered > 0 );
int cClique = _viOrder.size();
_viParentOrdered.resize(cCliqueOrdered);
for ( int icq = 0; icq < cCliqueOrdered; ++icq )
{
int iClique = _viOrdered[icq];
assert( iClique < cClique && iClique >= 0 );
int iCliqueParent = _viParent[iClique];
assert( iCliqueParent < cClique && iCliqueParent >= 0 );
assert( CNodesCommon( iClique, iCliqueParent ) > 0 );
int iCliqueParentOrdered = IOrdered( iCliqueParent );
assert( iCliqueParentOrdered < cCliqueOrdered && iCliqueParentOrdered >= 0 );
_viParentOrdered[icq] = iCliqueParentOrdered;
}
// Next, follow each ordered clique's parentage to compute its topological level
_viTopLevelOrdered.resize(cCliqueOrdered);
int cTrees = 0;
for ( icq = 0; icq < cCliqueOrdered; ++icq )
{
int icqParent = icq;
// Follow until we get to a (the) root clique
for ( int itop = 0; icqParent != _viParentOrdered[icqParent]; ++itop )
{
assert( itop < cCliqueOrdered );
icqParent = _viParentOrdered[icqParent];
}
if ( itop == 0 )
cTrees++ ;
_viTopLevelOrdered[icq] = itop;
}
assert( cTrees == _cTrees );
// Next, find each node's "family" clique. This is the smallest clique containing
// it and its parents.
VPGNODEMBN vpgnd;
for ( int ind = 0 ; ind < _vndcqInfo.size(); ind++ )
{
NDCQINFO & ndcq = _vndcqInfo[ind];
vpgnd.clear();
// Get the "family" set and sort it for matching other cliques.
ndcq._pgnd->GetFamily( vpgnd );
MarkSortNodePtrArray( vpgnd );
int cFamily = vpgnd.size();
int cCommonSize = INT_MAX;
int iCqCommon = -1;
// Find the smallest clique containing the family
for ( icq = 0; icq < cCliqueOrdered; ++icq )
{
const VPGNODEMBN & vpgndClique = _vvpgnd[ _viOrdered[icq] ];
int cCqCommon = CNodesCommon( vpgnd, vpgndClique );
// See if this clique contains the family and is smaller than any other.
if ( cCqCommon == cFamily && vpgndClique.size() < cCommonSize )
{
iCqCommon = icq;
}
}
assert( iCqCommon >= 0 );
ndcq._iCliqOrdFamily = iCqCommon;
// Now, find the highest clique in the tree containing this node.
int itop = INT_MAX;
int iCqTop = -1;
for ( icq = 0; icq < cCliqueOrdered; ++icq )
{
const VPGNODEMBN & vpgndClique = _vvpgnd[ _viOrdered[icq] ];
int ind = ifind( vpgndClique, ndcq._pgnd );
if ( ind >= 0 && _viTopLevelOrdered[icq] < itop )
{
iCqTop = icq;
itop = _viTopLevelOrdered[icq];
}
}
assert( iCqTop >= 0 );
ndcq._iCliqOrdSelf = iCqTop;
}
#ifdef DUMP
DumpTopInfo();
#endif
}
void CLIQSETWORK :: BuildCliques ()
{
// Prepare tables for junction tree construction
PrepareForBuild() ;
// Choose the zeroth arbitrarily as a starting point; set it as its own parent.
// As we iterate over the array, we assign an ordering to cliques. If the clique has
// already been ordered, its value in _viOrder will either >= 0 (order in clique tree)
//
_cTrees = 1;
_viParent[0] = 0;
_viOrder[0] = 0;
_viOrdered.clear();
_viOrdered.push_back(0);
for (;;)
{
int iCliqueBest = INT_MAX; // Best clique found so far
// Find a new clique that has the largest overlap with any of the cliques already in the tree.
for ( int iClique = 0; iClique < _vvpgnd.size(); iClique++ )
{
int iord = _viOrder[iClique];
if ( iord != INT_MIN )
continue; // Clique has already been ordered or dealt with
// Update the "most common clique already in tree" info between this clique
// and all the cliques in the trees
BUpdateCNodeMaxCommon( iClique );
//MSRDEVBUG: SetCNodeMaxCommon( iClique, _viOrdered.size() - 1 );
if ( iCliqueBest == INT_MAX )
{
// first time through the loop
iCliqueBest = iClique;
}
else
if ( BBetter( iClique, iCliqueBest ) )
{
// This clique has an overlap as large as any other yet found.
iCliqueBest = iClique;
}
}
// See if we're done
if ( iCliqueBest == INT_MAX )
break;
// Get the ordered index and absolute index of the most common clique
int iCliqueCommonOrdered = _viICliqCommon[iCliqueBest];
assert( iCliqueCommonOrdered >= 0 && iCliqueCommonOrdered < _viOrdered.size() );
int iCliqueCommon = _viOrdered[ iCliqueCommonOrdered ];
assert( iCliqueCommon >= 0 );
assert( iCliqueBest != iCliqueCommon );
int cNodesCommon = _viCNodesCommon[iCliqueBest];
assert( cNodesCommon <= _vvpgnd[iCliqueCommon].size() );
assert( cNodesCommon <= _vvpgnd[iCliqueBest].size() );
assert( cNodesCommon == CNodesCommon( iCliqueCommon, iCliqueBest ) ) ;
// Index of clique to be added to ordered clique set
int iCliqueNew = INT_MAX;
// If the candidate clique has the same number of nodes in common with its most
// common clique as that clique has members, then this clique is either identical
// to or a superset of that clique.
if ( cNodesCommon == _vvpgnd[iCliqueCommon].size() )
{
// New clique is superset of its most common clique.
assert( cNodesCommon != 0 );
assert( iCliqueCommon != iCliqueBest );
assert( _vvpgnd[iCliqueCommon].size() < _vvpgnd[iCliqueBest].size() );
// Assign this clique's node set to the previously ordered subset clique
_vvpgnd[iCliqueCommon] = _vvpgnd[iCliqueBest] ;
assert ( _vvpgnd[iCliqueCommon].size() == _vvpgnd[iCliqueBest].size() );
// Leave the parent the same as it was
iCliqueNew = iCliqueCommon;
}
else
if ( cNodesCommon == 0 )
{
// This is the start of a new tree
_cTrees++;
// Self and parent are the same
_viParent[iCliqueBest] = iCliqueNew = iCliqueBest;
_viOrdered.push_back( iCliqueNew );
}
else
if ( cNodesCommon != _vvpgnd[iCliqueBest].size() )
{
// New clique is child of existing clique.
iCliqueNew = iCliqueBest;
_viParent[iCliqueBest] = iCliqueCommon ;
// Keep this clique by adding it to the ordered clique set.
_viOrdered.push_back( iCliqueNew );
}
else
{
// Child is subset of parent; ignore by marking as "subsumed"
iCliqueNew = - iCliqueCommon;
}
// Mark the clique as either ordered or subsumed.
_viOrder[iCliqueBest] = iCliqueNew;
}
#ifdef DUMP
cout << "\n\nBuild cliques; generated " << _cTrees << " clique trees\n\n";
#endif
}
// Verify that the Running Intersection Property holds for this clique tree.
bool CLIQSETWORK :: BCheckRIP ()
{
// Check that topological information has been generated
assert( _viOrdered.size() == _viParentOrdered.size() );
for ( int iCliqueOrdered = 0; iCliqueOrdered < _viOrdered.size(); iCliqueOrdered++ )
{
if ( ! BCheckRIP( iCliqueOrdered ) )
return false;
}
return true;
}
// Verify that the Running Intersection Property holds for this clique.
bool CLIQSETWORK :: BCheckRIP ( int iCliqueOrdered )
{
int iClique = _viOrdered[iCliqueOrdered];
const VPGNODEMBN & vpgndClique = _vvpgnd[iClique];
int iCliqueParent = _viParent[iClique];
const VPGNODEMBN & vpgndCliqueParent = _vvpgnd[iCliqueParent];
bool bRoot = iCliqueParent == iClique;
// For every node in this clique, check that either:
//
// 1) this is a root clique, or
// 2) the node is present in the parent clique.
//
// If this test fails, check that this is the "self" clique,
// which is the highest clique in the tree in which the
// node appears.
//
for ( int iNode = 0; iNode < vpgndClique.size(); iNode++ )
{
// Access the node information for this node
GNODEMBN * pgnd = vpgndClique[iNode];
if ( bRoot || ifind( vpgndCliqueParent, pgnd ) < 0 )
{
NDCQINFO & ndcq = _vndcqInfo[ pgnd->IMark() ];
if ( ndcq._iCliqOrdSelf != iCliqueOrdered )
{
#ifdef _DEBUG
cout << "RIP FAILURE: node "
<< ndcq._pgnd->ZsrefName().Szc()
<< " is in clique "
<< iCliqueOrdered
<< " but absent from "
<< _viParentOrdered[iCliqueOrdered]
<< "("
<< _viParent[iClique]
<< ")"
;
#endif
return false;
}
}
}
return true;
}
// Using the constructed tables, create the clique objects and
// link them to each other and their member nodes.
void CLIQSETWORK :: CreateTopology ()
{
_vpclq.resize( _viOrdered.size() ) ;
for ( int i = 0; i < _vpclq.size(); )
_vpclq[i++] = NULL;
int iInferEngID = _cliqset._iInferEngID;
int ccq = 0; // Total cliques created
// Create all cliques. Iterate in topological order, creating
// the cliques and linking them to their parents.
for ( int itop = 0;; itop++)
{
int ccqLevel = 0; // Number of cliques added at this topological level
for ( int icq = 0; icq < _viOrdered.size(); icq++ )
{
if ( _viTopLevelOrdered[icq] != itop )
continue;
GOBJMBN_CLIQUE * pclqParent = NULL;
GOBJMBN_CLIQUE * pclqThis = NULL;
int iParentOrdered = _viParentOrdered[icq];
if ( iParentOrdered != icq )
{
// Get the parent clique pointer
pclqParent = _vpclq[ iParentOrdered ];
assert( pclqParent );
}
else
{
// Root cliques have toplevel zero
assert( itop == 0 );
}
// Create the new clique and its edge to its parent clique (if any)
pclqThis = _vpclq[icq] = new GOBJMBN_CLIQUE( icq, iInferEngID );
Model().AddElem( pclqThis );
if ( pclqParent )
{
// This is not a root clique; link it to its parent.
Model().AddElem( new GEDGEMBN_SEPSET( pclqParent, pclqThis ) );
}
else
{
// This IS a root clique; mark it and link it to the clique set top.
pclqThis->_bRoot = true;
Model().AddElem( new GEDGEMBN_CLIQSET( & _cliqset, pclqThis ) );
}
++_cliqset._cCliques;
if ( pclqParent )
{
++_cliqset._cSepsetArcs;
}
ccq++;
ccqLevel++;
}
if ( ccqLevel == 0 )
break; // No cliques added at this topological level: we're done
}
assert( ccq == _viOrdered.size() );
// For each of the new cliques, add all members
for ( i = 0; i < _vpclq.size(); i++ )
{
const VPGNODEMBN & vpgndMembers = _vvpgnd[ _viOrdered[i] ];
for ( int ind = 0; ind < vpgndMembers.size(); ind++)
{
// Get the node pointer and the data pointer
GNODEMBN * pgnd = vpgndMembers[ind];
const NDCQINFO & ndcq = _vndcqInfo[ pgnd->IMark() ];
assert( pgnd == ndcq._pgnd );
int fRole = GEDGEMBN_CLIQ::NONE;
if ( ndcq._iCliqOrdSelf == i )
fRole |= GEDGEMBN_CLIQ::SELF;
if ( ndcq._iCliqOrdFamily == i )
fRole |= GEDGEMBN_CLIQ::FAMILY;
Model().AddElem( new GEDGEMBN_CLIQ( _vpclq[i], pgnd, fRole ) );
++_cliqset._cCliqueMemberArcs;
}
}
#ifdef _DEBUG
for ( i = 0; i < _vpclq.size(); i++ )
{
const VPGNODEMBN & vpgndMembers = _vvpgnd[ _viOrdered[i] ];
VPGNODEMBN vpgndMembers2;
_vpclq[i]->GetMembers( vpgndMembers2 );
assert( vpgndMembers2.size() == vpgndMembers.size() );
MarkSortNodePtrArray( vpgndMembers2 );
assert( vpgndMembers2 == vpgndMembers );
// Exercise the topology by locating the "self" and "family" cliques
for ( int imbr = 0; imbr < vpgndMembers.size(); imbr++ )
{
GNODEMBN * pgnd = vpgndMembers[imbr];
GOBJMBN_CLIQUE * pCliqueFamily = _cliqset.PCliqueFromNode( pgnd, false );
GOBJMBN_CLIQUE * pCliqueSelf = _cliqset.PCliqueFromNode( pgnd, false );
assert( pCliqueFamily );
assert( pCliqueSelf );
}
}
#endif
}
void CLIQSETWORK :: DumpClique ( int iClique )
{
cout << "\tClique "
<< iClique
<< ':'
<< _vvpgnd[iClique]
<< "\n";
cout.flush();
}
void CLIQSETWORK :: DumpCliques ()
{
for ( int iClique = 0; iClique < _vvpgnd.size(); ++iClique )
{
DumpClique( iClique );
}
}
void CLIQSETWORK :: DumpTree ()
{
for ( int iCliqueOrd = 0; iCliqueOrd < _viOrdered.size(); ++iCliqueOrd )
{
int iClique = _viOrdered[iCliqueOrd];
cout << "\tTree Clique "
<< iCliqueOrd
<< " ("
<< iClique
<< "), parent "
<< IOrdered( _viParent[iClique] )
<< " ("
<< _viParent[iClique]
<< "): "
<< _vvpgnd[iClique]
<< "\n";
}
cout.flush();
}
void CLIQSETWORK :: DumpTopInfo()
{
for ( int iCliqueOrd = 0; iCliqueOrd < _viOrdered.size(); ++iCliqueOrd )
{
cout << "\tTree Clique "
<< iCliqueOrd
<< " (" << _viOrdered[iCliqueOrd] << ")"
<< ", parent is "
<< _viParentOrdered[iCliqueOrd]
<< " (" << _viOrdered[_viParentOrdered[iCliqueOrd]] << ")"
<< ", top level is "
<< _viTopLevelOrdered[iCliqueOrd]
<< "\n";
}
for ( int ind = 0 ; ind < _vndcqInfo.size(); ind++ )
{
NDCQINFO & ndcq = _vndcqInfo[ind];
cout << "\tNode ";
cout.width( 20 );
cout << ndcq._pgnd->ZsrefName().Szc()
<< "\tfamily is clique "
<< ndcq._iCliqOrdFamily
<< ", self is clique "
<< ndcq._iCliqOrdSelf
<< "\n";
}
cout.flush();
}
//
// Estimate the total size of the structures necessary to support the
// compute clique trees.
//
REAL CLIQSETWORK :: REstimatedSize ()
{
int cClique = 0;
int cSepsetArc = 0;
int cCliqsetArc = 0;
size_t cMbrArc = 0;
int cCliqueEntries = 0;
int cFamEntries = 0;
for ( int icq = 0; icq < _viOrdered.size(); icq++ )
{
cClique++;
if ( icq != _viParentOrdered[icq] )
{
// Clique has a parent
cSepsetArc++;
}
else
{
// Clique is root
cCliqsetArc++;
}
// Account for clique membership arcs
const VPGNODEMBN & vpgndMembers = _vvpgnd[ _viOrdered[icq] ];
int cMbr = vpgndMembers.size();
cMbrArc += vpgndMembers.size();
// Compute the size of the joint table for this clique
VIMD vimd(cMbr);
GNODEMBND * pgndd;
for ( int ind = 0; ind < vpgndMembers.size(); ind++)
{
// Get the discrete node pointer and the data pointer
DynCastThrow( vpgndMembers[ind], pgndd );
// Add to the clique's dimensionality
vimd[ind] = pgndd->CState();
const NDCQINFO & ndcq = _vndcqInfo[ pgndd->IMark() ];
assert( pgndd == ndcq._pgnd );
// If this is the edge to the "family" clique, it will
// contain the reordered discrete conditional probabilities
// for this node, so we must compute it size.
if ( ndcq._iCliqOrdFamily == icq )
{
// This is the edge leading to this node's "family" clique
VPGNODEMBN vpgndFamily; // List of parents and self
pgndd->GetParents( vpgndFamily, true );
GNODEMBND * pgnddFamily;
int cStates = 1;
for ( int ifam = 0; ifam < vpgndFamily.size(); ifam++ )
{
DynCastThrow( vpgndFamily[ifam], pgnddFamily );
cStates *= pgnddFamily->CState();
}
cFamEntries += cStates;
}
}
MDVSLICE mdvs( vimd );
cCliqueEntries += mdvs._Totlen();
}
REAL rcb = 0;
rcb += cClique * sizeof(GOBJMBN_CLIQUE);
rcb += cSepsetArc * sizeof(GEDGEMBN_SEPSET);
rcb += cCliqsetArc * sizeof(GEDGEMBN_CLIQSET);
rcb += cMbrArc * sizeof(GEDGEMBN_CLIQ);
rcb += cCliqueEntries * sizeof(REAL);
rcb += cFamEntries * sizeof(REAL);
#ifdef DUMP
cout << "\nEstimated clique tree memory is " << rcb;
#endif
return rcb;
}