//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1997 - 1998 // // File: cliqwork.cpp // //-------------------------------------------------------------------------- // // cliqwork.cpp // #include #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 { 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; }