//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1997 - 1998 // // File: mbnet.cpp // //-------------------------------------------------------------------------- // // mbnet.cpp: Belief network model member functions // #include #include "basics.h" #include "algos.h" #include "gmprop.h" #include "gmobj.h" #include "cliqset.h" #include "clique.h" #include "expand.h" MBNET :: MBNET () :_inmFree(0), _iInferEngID(0) { } MBNET :: ~ MBNET () { PopModifierStack( true ); // Clear all modifiers from the network // Clear the node-index-to-name information _inmFree = 0; _vzsrNames.clear(); } // // Clone this belief network from another. Note that the contents // of the modifier stack (inference engines, expanders, etc.) are // NOT cloned. // void MBNET :: Clone ( MODEL & model ) { // This must be a truly empty structure ASSERT_THROW( _vpModifiers.size() == 0 && _vzsrNames.size() == 0, EC_INVALID_CLONE, "cannot clone into non-empty structure" ); MODEL::Clone( model ); MBNET * pmbnet; DynCastThrow( & model, pmbnet ); MBNET & mbnet = *pmbnet; { // Build the name table by iterating over the contents and // allocating a slot for each node GELEMLNK * pgelm; MODELENUM mdlenumNode( mbnet ); while ( pgelm = mdlenumNode.PlnkelNext() ) { // Check that it's a node (not an edge) if ( ! pgelm->BIsEType( GELEM::EGELM_NODE ) ) continue; GOBJMBN * pgobjmbn; DynCastThrow( pgelm, pgobjmbn ); _vzsrNames.push_back( pgobjmbn->ZsrefName() ); } _inmFree = _vzsrNames.size(); } // Clone the distribution map _mppd.Clone( _mpsymtbl, mbnet._mppd ) ; // Check the topology if it's supposed to be present #ifdef _DEBUG if ( mbnet.BFlag( EIBF_Topology ) ) VerifyTopology(); #endif } // // Iterate over the distributions, matching them to the nodes they belong to. // void MBNET :: VerifyTopology () { for ( MPPD::iterator itpd = Mppd().begin(); itpd != Mppd().end(); itpd++ ) { const VTKNPD & vtknpd = (*itpd).first; const BNDIST * pbndist = (*itpd).second; // Guarantee that the descriptor is of the form "p(X|...)" if ( vtknpd.size() < 2 || vtknpd[0] != TKNPD(DTKN_PD) || ! vtknpd[1].BStr() ) throw GMException( EC_INV_PD, "invalid token descriptor on PD"); // Get the name of the node whose distribution this is SZC szc = vtknpd[1].Szc(); assert( szc ) ; // Find that named thing in the graph GOBJMBN * pbnobj = Mpsymtbl().find( szc ); assert( pbnobj && pbnobj->EType() == GOBJMBN::EBNO_NODE ); // Guarantee that it's a node GNODEMBN * pgndbn = dynamic_cast (pbnobj); ASSERT_THROW( pgndbn, EC_INV_PD, "token on PD references non-node"); // Verify the node's distribution if ( ! pgndbn->BMatchTopology( *this, vtknpd ) ) { throw GMException( EC_TOPOLOGY_MISMATCH, "topology mismatch between PD and network"); } } } MBNET_MODIFIER * MBNET :: PModifierStackTop () { return _vpModifiers.size() > 0 ? _vpModifiers[ _vpModifiers.size() - 1 ] : NULL; } void MBNET :: PushModifierStack ( MBNET_MODIFIER * pmodf ) { assert( pmodf ); pmodf->Create(); _vpModifiers.push_back( pmodf ); } void MBNET :: PopModifierStack ( bool bAll ) { int iPop = _vpModifiers.size(); while ( iPop > 0 ) { MBNET_MODIFIER * pmodf = _vpModifiers[ --iPop ]; assert ( pmodf ); // NOTE: Deleting the object should be all that's necessary; // object's destructor should call its Destroy() function. delete pmodf; if ( ! bAll ) break; } if ( iPop == 0 ) _vpModifiers.clear(); else _vpModifiers.resize(iPop); } // Find the named object by index GOBJMBN * MBNET :: PgobjFindByIndex ( int inm ) { ZSREF zsMt; if ( inm >= _vzsrNames.size() || _vzsrNames[inm] == zsMt ) return NULL; return Mpsymtbl().find( _vzsrNames[inm] ); } int MBNET :: INameIndex ( ZSREF zsr ) { return ifind( _vzsrNames, zsr ); } int MBNET :: INameIndex ( const GOBJMBN * pgobj ) { return INameIndex( pgobj->ZsrefName() ); } int MBNET :: CreateNameIndex ( const GOBJMBN * pgobj ) { int ind = -1; if ( _inmFree >= _vzsrNames.size() ) { // No free slots; grow the array ind = _vzsrNames.size(); _vzsrNames.push_back( pgobj->ZsrefName() ); _inmFree = _vzsrNames.size(); } else { // Use the given free slot, find the next _vzsrNames[ind = _inmFree] = pgobj->ZsrefName(); ZSREF zsMt; for ( ; _inmFree < _vzsrNames.size() ; _inmFree++ ) { if ( zsMt == _vzsrNames[_inmFree] ) break; } } return ind; } void MBNET :: DeleteNameIndex ( int inm ) { ASSERT_THROW( inm < _vzsrNames.size(), EC_INTERNAL_ERROR, "MBNET name index out of range" ); _vzsrNames[inm] = ZSREF(); if ( inm < _inmFree ) _inmFree = inm; } void MBNET :: DeleteNameIndex ( const GOBJMBN * pgobj ) { int inm = INameIndex( pgobj ); if ( inm >= 0 ) DeleteNameIndex(inm); } // Add a named object to the graph and symbol table void MBNET :: AddElem ( SZC szcName, GOBJMBN * pgelm ) { if ( szcName == NULL || ::strlen(szcName) == 0 ) { MODEL::AddElem( pgelm ); // empty name } else { MODEL::AddElem( szcName, pgelm ); assert( INameIndex( pgelm ) < 0 ); // guarantee no duplicates CreateNameIndex( pgelm ); } } void MBNET :: DeleteElem ( GOBJMBN * pgobj ) { DeleteNameIndex( pgobj ); MODEL::DeleteElem( pgobj ); } /* Iterator has moved into the MODEL class... I've left the code here in case MBNET needs its own iterator. (Max, 05/12/97) MBNET::ITER :: ITER ( MBNET & bnet, GOBJMBN::EBNOBJ eType ) : _eType(eType), _bnet(bnet) { Reset(); } void MBNET::ITER :: Reset () { _pCurrent = NULL; _itsym = _bnet.Mpsymtbl().begin(); BNext(); } bool MBNET::ITER :: BNext () { while ( _itsym != _bnet.Mpsymtbl().end() ) { _pCurrent = (*_itsym).second.Pobj(); _zsrCurrent = (*_itsym).first; _itsym++; if ( _pCurrent->EType() == _eType ) return true; } _pCurrent = NULL; return false; } */ void MBNET :: CreateTopology () { if ( BFlag( EIBF_Topology ) ) return; // Walk the map of distributions. For each one, extract the node // name and find it. Then add arcs for each parent. #ifdef _DEBUG UINT iCycleMax = 2; #else UINT iCycleMax = 1; #endif UINT iIter = 0; for ( UINT iCycle = 0 ; iCycle < iCycleMax ; iCycle++ ) { for ( MPPD::iterator itpd = Mppd().begin(); itpd != Mppd().end(); itpd++, iIter++ ) { const VTKNPD & vtknpd = (*itpd).first; const BNDIST * pbndist = (*itpd).second; // Guarantee that the descriptor is of the form "p(X|...)" if ( vtknpd.size() < 2 || vtknpd[0] != TKNPD(DTKN_PD) || ! vtknpd[1].BStr() ) throw GMException( EC_INV_PD, "invalid token descriptor on PD"); // Get the name of the node whose distribution this is SZC szcChild = vtknpd[1].Szc(); assert( szcChild ) ; // Find that named thing in the graph GOBJMBN * pbnobjChild = Mpsymtbl().find( szcChild ); assert( pbnobjChild && pbnobjChild->EType() == GOBJMBN::EBNO_NODE ); // Guarantee that it's a node GNODEMBN * pgndbnChild = dynamic_cast (pbnobjChild); ASSERT_THROW( pgndbnChild, EC_INV_PD, "token on PD references non-node"); UINT cParents = 0; UINT cChildren = pgndbnChild->CChild(); for ( int i = 2; i < vtknpd.size(); i++ ) { if ( ! vtknpd[i].BStr() ) continue; SZC szcParent = vtknpd[i].Szc(); assert( szcParent) ; GOBJMBN * pbnobjParent = Mpsymtbl().find( szcParent ); assert( pbnobjParent && pbnobjParent->EType() == GOBJMBN::EBNO_NODE ); GNODEMBN * pgndbnParent = (GNODEMBN *) pbnobjParent; UINT cPrChildren = pgndbnParent->CChild(); if ( iCycle == 0 ) { AddElem( new GEDGEMBN_PROB( pgndbnParent, pgndbnChild ) ); } cParents++; if ( iCycle == 0 ) { UINT cChNew = pgndbnChild->CChild(); UINT cPrNew = pgndbnChild->CParent(); UINT cPrChNew = pgndbnParent->CChild(); assert( cPrChNew = cPrChildren + 1 ); assert( cChildren == cChNew ); } } if ( iCycle ) { UINT cPrNew = pgndbnChild->CParent(); assert( cParents == cPrNew ); } if ( iCycle == 0 ) { #ifdef _DEBUG if ( ! pgndbnChild->BMatchTopology( *this, vtknpd ) ) { throw GMException( EC_TOPOLOGY_MISMATCH, "topology mismatch between PD and network"); } #endif } } } BSetBFlag( EIBF_Topology ); } DEFINEVP(GEDGEMBN); void MBNET :: DestroyTopology ( bool bDirectedOnly ) { // Size up an array to hold pointers to all the edges VPGEDGEMBN vpgedge; int cItem = Grph().Chn().Count(); vpgedge.resize(cItem); // Find all the arcs/edges int iItem = 0; GELEMLNK * pgelm; MODELENUM mdlenum( self ); 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 ( bDirectedOnly && pgelm->EType() != GEDGEMBN::ETPROB ) continue; GEDGEMBN * pgedge; DynCastThrow( pgelm, pgedge ); vpgedge[iItem++] = pgedge; } // Delete all the accumulated edges for ( int i = 0; i < iItem; ) { GEDGEMBN * pgedge = vpgedge[i++]; delete pgedge; } assert( Grph().Chn().Count() + iItem == cItem ); BSetBFlag( EIBF_Topology, false ); } // // Bind distributions to nodes. If they're already bound, exit. // If the node has a distribution already, leave it. // void MBNET :: BindDistributions ( bool bBind ) { bool bDist = BFlag( EIBF_Distributions ); if ( ! (bDist ^ bBind) ) return; ITER itnd( self, GOBJMBN::EBNO_NODE ); for ( ; *itnd ; itnd++ ) { GNODEMBND * pgndd = dynamic_cast(*itnd); if ( pgndd == NULL ) continue; if ( ! bBind ) { pgndd->ClearDist(); } else if ( ! pgndd->BHasDist() ) { pgndd->SetDist( self ); } } BSetBFlag( EIBF_Distributions, bBind ); } void MBNET :: ClearNodeMarks () { ITER itnd( self, GOBJMBN::EBNO_NODE ); for ( ; *itnd ; itnd++ ) { GNODEMBN * pgndbn = NULL; DynCastThrow( *itnd, pgndbn ); pgndbn->IMark() = 0; } } void MBNET :: TopSortNodes () { ClearNodeMarks(); ITER itnd( self, GOBJMBN::EBNO_NODE ); for ( ; *itnd ; itnd++ ) { GNODEMBN * pgndbn = NULL; DynCastThrow( *itnd, pgndbn ); pgndbn->Visit(); } itnd.Reset(); for ( ; *itnd ; itnd++ ) { GNODEMBN * pgndbn = NULL; DynCastThrow( *itnd, pgndbn ); pgndbn->ITopLevel() = pgndbn->IMark(); } } void MBNET :: Dump () { TopSortNodes(); UINT iEntry = 0; for ( MPSYMTBL::iterator itsym = Mpsymtbl().begin(); itsym != Mpsymtbl().end(); itsym++ ) { GOBJMBN * pbnobj = (*itsym).second.Pobj(); if ( pbnobj->EType() != GOBJMBN::EBNO_NODE ) continue; // It's not a node GNODEMBN * pgndbn; DynCastThrow(pbnobj,pgndbn); int iNode = INameIndex( pbnobj ); assert( iNode == INameIndex( pbnobj->ZsrefName() ) ); cout << "\n\tEntry " << iEntry++ << ", inode " << iNode << " "; pgndbn->Dump(); } } GOBJMBN_INFER_ENGINE * MBNET :: PInferEngine () { GOBJMBN_INFER_ENGINE * pInferEng = NULL; for ( int iMod = _vpModifiers.size(); --iMod >= 0; ) { MBNET_MODIFIER * pmodf = _vpModifiers[iMod]; pInferEng = dynamic_cast ( pmodf ); if ( pInferEng ) break; } return pInferEng; } void MBNET :: ExpandCI () { PushModifierStack( new GOBJMBN_MBNET_EXPANDER( self ) ); } void MBNET :: UnexpandCI () { MBNET_MODIFIER * pmodf = PModifierStackTop(); if ( pmodf == NULL ) return; if ( pmodf->EType() == GOBJMBN::EBNO_MBNET_EXPANDER ) PopModifierStack(); } // Return true if an edge is allowed between these two nodes bool MBNET :: BAcyclicEdge ( GNODEMBN * pgndSource, GNODEMBN * pgndSink ) { ClearNodeMarks(); pgndSink->Visit( false ); return pgndSource->IMark() == 0; }