//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1997 - 1998 // // File: expand.cpp // //-------------------------------------------------------------------------- // // expand.cpp: CI expansion // #include #include "basics.h" #include "algos.h" #include "expand.h" /* The Causal Independence model expansion. In all cases, the zeroth state is considered the "normal" state; all other states are "abnormal" in some sense. For each CI node, new "expansion" nodes and arcs are created. All generated nodes have the same state space as the original CI node. 1) For each parent, a new intermediate node is created. 2) A "leak" node is created for the CI node, and one for each of the parents except the last. 3) The nodes are linked in a chain, such as: (A) (B) (A) (B) | | becomes | | \ / (Pca) (Pcb) \ / | | (C) (Lc) -- (La) -- (C') 4) In other words, the intermediate nodes are between the original parents and the CI leak nodes or the final, modified C node (labeled C'). 5) Probabilities for C given abnormal states of each parent are moved to the intermediate parent nodes (labeled Pca and Pcb above). 6) Probabilities of the primary leak node (Lc) are set to the "leak" probabilties of the original node; namely, the pvector representing all parents in a normal state (0,0,...). 7) The replacement node for C (labeled C') is just another "leak" node for the original node given its final parent. (This is a topological optimization.) 8) All of the leak nodes are deterministic; i.e., their ptables contain only 0 or 1.0 in every entry. Topological consistency is maintained as follows: 1) All newly generated nodes and arcs are marked with the "Expansion" bit flag. 2) The target node is marked as "Expanded". Its distribution reference is replaced with a reference to a distribution created to represent the expanded distribution. 3) New nodes are added for leak and expansion parents; they are marked accordingly as "Expansion" and "Leak". 3) New arcs are added between "Expanded" (modified) nodes and their new expansion parents, as well as between expansion nodes. These are marked as "Expansion" arcs. Note that the ordering of the parents of a node cannot change as a result of CI expansion. During cliquing and inference, if a node is marked as "Expanded", only its "Expansion" arcs are considered true parents. During expansion tear-down (in Destroy()), all "Expansion" by-products are deleted. "Expanded" flags are cleared from all remaining nodes and arcs. This must be a complete "undo" of all that expansion accomplished. Note that generated distributions (which are not recorded in the model's distribution map) will be automatically deleted */ GOBJMBN_MBNET_EXPANDER :: GOBJMBN_MBNET_EXPANDER ( MBNET & model ) : MBNET_MODIFIER(model), _propmgr(model), _cNodesExpanded(0), _cNodesCreated(0), _cArcsCreated(0) { } GOBJMBN_MBNET_EXPANDER :: ~ GOBJMBN_MBNET_EXPANDER () { Destroy(); } // Return true if no modidfications were performed. bool GOBJMBN_MBNET_EXPANDER :: BMoot () { return _cNodesExpanded == 0; } // Perform any creation-time operations void GOBJMBN_MBNET_EXPANDER :: Create () { // Test whether network has already been CI-expanded ASSERT_THROW( ! _model.BFlag( EIBF_Expanded ), EC_INTERNAL_ERROR, "network expansion called on expanded network" ); // Create the topology if necessary _model.CreateTopology(); // Connect the nodes to their distributions _model.BindDistributions(); // Collect the expandable nodes GOBJMBN * pgmobj; VPGNODEMBND vpgndd; MBNET::ITER mbnit( _model, GOBJMBN::EBNO_NODE ); for ( ; pgmobj = *mbnit ; ++mbnit) { ZSREF zsrName = mbnit.ZsrCurrent(); GNODEMBN * pbnode; DynCastThrow( pgmobj, pbnode ); assert( zsrName == pbnode->ZsrefName() ); assert( ! pbnode->BFlag( EIBF_Expanded ) ); assert( ! pbnode->BFlag( EIBF_Expansion ) ); // For now, this routine only handles discrete nodes GNODEMBND * pbnoded; DynCastThrow( pbnode, pbnoded ); // Does this node have any parents? // Is this a CI node? assert( pbnoded->BHasDist() ); BNDIST::EDIST ed = pbnoded->Bndist().Edist() ; if ( ed <= BNDIST::ED_SPARSE ) continue; ASSERT_THROW( ed == BNDIST::ED_CI_MAX, EC_NYI, "attempt to expand non-MAX CI node" ); vpgndd.push_back( pbnoded ); } // Expand them for ( int ind = 0; ind < vpgndd.size(); ) { Expand( *vpgndd[ind++] ); _cNodesExpanded++; } _model.BSetBFlag( EIBF_Expanded ); } // Perform any special destruction void GOBJMBN_MBNET_EXPANDER :: Destroy () { ASSERT_THROW( _model.BFlag( EIBF_Expanded ), EC_INTERNAL_ERROR, "network expansion undo called on unexpanded network" ); int cNodesExpanded = 0; int cNodesCreated = 0; int cArcsCreated = 0; VPGNODEMBN vpgnd; GELEMLNK * pgelm; MODEL::MODELENUM mdlenum( Model() ); while ( pgelm = mdlenum.PlnkelNext() ) { // See if it's an expansion-generated edge if ( pgelm->BIsEType( GELEM::EGELM_EDGE ) ) { GEDGEMBN * pgedge; DynCastThrow( pgelm , pgedge ); if ( pgedge->EType() == GEDGEMBN::ETPROB ) { GNODEMBN * pgndSource = dynamic_cast ( pgedge->PobjSource() ); GNODEMBN * pgndSink = dynamic_cast ( pgedge->PobjSink() ); if ( pgndSource && pgndSink ) { // Count this edge if either end connects to an expansion by-product if ( pgndSource->BFlag( EIBF_Expansion ) || pgndSink->BFlag( EIBF_Expansion ) ) { // This arc was created during expansion; it will be deleted along with // the expansion node(s) it connects to. cArcsCreated++; } } } } else if ( pgelm->BIsEType( GELEM::EGELM_NODE ) ) { GNODEMBND * pgndd = dynamic_cast(pgelm); if ( pgndd ) { if ( pgndd->BFlag( EIBF_Expansion ) ) { // Expansion node; kill it vpgnd.push_back( pgndd ); cNodesCreated++; } else if ( pgndd->BFlag( EIBF_Expanded ) ) { // Expanded node; zap the generated distribution, clear all flags pgndd->ClearDist(); pgndd->BSetBFlag( EIBF_Expanded, false ); cNodesExpanded++; } } } } assert( cNodesCreated == _cNodesCreated && cArcsCreated == _cArcsCreated && cNodesExpanded == _cNodesExpanded ); for ( int i = 0; i < vpgnd.size(); ) { _model.DeleteElem( vpgnd[i++] ) ; } // Disconnect the nodes from their distributions. Note that this destroys distributions // generated during expansion, since their reference counts will go to zero. _model.ClearDistributions(); // Unmark the network _model.BSetBFlag( EIBF_Expanded, false ); } // // Perform the expansion operation against a node. // // This creates: // // A parentless "leak" node for the ensemble, marked as "expansion" // // A "causal" node for each original parent, marked as "expansion" // // An "expand/leak" node for each original parent but the last. The given // node is (reversably) modified for reuse as the last in the chain. These // nodes are marked as "expanded" and "expansion", so that expansion // arcs will be considered real parents by GNODEMBN::GetParents(). // void GOBJMBN_MBNET_EXPANDER :: Expand ( GNODEMBND & gndd ) { // Guarantee that the node to be expanded has a sparse distribution assert( ! gndd.BFlag( EIBF_Expanded ) ); assert( gndd.BHasDist() ); assert( gndd.Bndist().BSparse() ); // Get the array of parents VPGNODEMBN vpgndParents; gndd.GetParents( vpgndParents ); int cParent = vpgndParents.size(); VIMD vimd1Dim(1); // Useful 1-dimensional subscript vector // Build a leak distribution to use either on the leak // node or on this node if the node has no parents. BNDIST * pbndistLeak = new BNDIST(); { // Locate the leak vector const VLREAL * pvlrLeak = gndd.Bndist().PVlrLeak(); ASSERT_THROW( pvlrLeak, EC_INTERNAL_ERROR, "node CI expansion cannot locate leak/default vector" ); assert( pvlrLeak->size() == gndd.CState() ); // Build a leak distribution assert( pvlrLeak->size() == gndd.CState() ); vimd1Dim[0] = gndd.CState(); pbndistLeak->SetDense( vimd1Dim ); MDVCPD & mdvLeak = pbndistLeak->Mdvcpd(); mdvLeak = *pvlrLeak; } if ( cParent == 0 ) { // CI node has no parents; use leak distribution. gndd.SetDist( pbndistLeak ); } // Use the special "internal symbol" character char chMark = _model.ChInternal(); SZC szcNode = gndd.ZsrefName().Szc(); // Start the CI expansion chain with a node representing the "leak" or // background event. ZSTR zsName; // Format the name "$Leak$Nodename" zsName.Format( "%cLeak%c%s", chMark, chMark, szcNode ); // Create the node, initialize it and add it to the network GNODEMBND * pgnddLeak = new GNODEMBND; pgnddLeak->BSetBFlag( EIBF_Leak ); pgnddLeak->BSetBFlag( EIBF_Expansion ); pgnddLeak->SetStates( gndd.VzsrStates() ); _model.AddElem( zsName, pgnddLeak ); _cNodesCreated++; pgnddLeak->SetDist( pbndistLeak ); // Prepare to iterate over the parents // Dense dimensioned subscript vector for causal parent VIMD vimdCausal(2); // Dense dimensioned subscript vector for leak/expansion parent VIMD vimdLeak(3); // Sparse dimensioned subscript vector for real parent VIMD vimdTarget( gndd.Bndist().VimdDim().size() - 1 ); // Set up a "normal" vector for causal parents VLREAL vlrNormal( gndd.CState() ); vlrNormal = 0.0; vlrNormal[0] = 1.0; // Sparse map for this node's distribution. Note that the last cycle through // the loop will replace the distribution on this node. However, the // reference object will increment the reference count on the // distribution object, and all created distributions will disappear when // the expansion is reversed. REFBNDIST refbndThis = gndd.RefBndist(); const MPCPDD & dmap = refbndThis->Mpcpdd(); for ( int iParent = 0; iParent < cParent; iParent++ ) { // Set to create a new node if this isn't the last parent bool bNew = iParent+1 < cParent; GNODEMBND * pgnddParent; DynCastThrow( vpgndParents[iParent], pgnddParent ); SZC szcParent = pgnddParent->ZsrefName().Szc(); // Create a new leak node if this isn't last parent GNODEMBND * pgnddLeakNew = NULL; if ( bNew ) { // Format the name "$Expand$Child$Parent" zsName.Format( "%cExpand%c%s%c%s", chMark, chMark, szcNode, chMark, szcParent ); _model.AddElem( zsName, pgnddLeakNew = new GNODEMBND ); _cNodesCreated++; pgnddLeakNew->SetStates( gndd.VzsrStates() ); pgnddLeakNew->BSetBFlag( EIBF_Expansion ); } else { pgnddLeakNew = & gndd; } pgnddLeakNew->BSetBFlag( EIBF_Expanded ); // Create a "causal" node for each parent to contain the probabilities // for that parent's abnormal states. GNODEMBND * pgnddCausal = new GNODEMBND; pgnddCausal->BSetBFlag( EIBF_Expansion ); pgnddCausal->SetStates( gndd.VzsrStates() ); // Format the name "$Causal$Child$Parent" zsName.Format( "%cCausal%c%s%c%s", chMark, chMark, szcNode, chMark, szcParent ); _model.AddElem( zsName, pgnddCausal ); _cNodesCreated++; // Add the appropriate edges: // from the original parent to the causal parent _model.AddElem( new GEDGEMBN_PROB( pgnddParent, pgnddCausal) ) ; // from the old leak node to the new leak node _model.AddElem( new GEDGEMBN_PROB( pgnddLeak, pgnddLeakNew ) ); // from the causal to the new "leak" node _model.AddElem( new GEDGEMBN_PROB( pgnddCausal, pgnddLeakNew ) ); _cArcsCreated += 3; // Set the priors for the new "causal" pseudo-parent // p( causal | originalParent ) { BNDIST * pbndist = new BNDIST; vimdCausal[0] = pgnddParent->CState(); vimdCausal[1] = gndd.CState(); pbndist->SetDense( vimdCausal ); MDVCPD & mdvCausal = pbndist->Mdvcpd(); vclear( vimdCausal, 0); vclear( vimdTarget, 0); vimd1Dim[0] = 0; // Zero vector is deterministic based on parent being in normal state mdvCausal.UpdatePartial( vimd1Dim, vlrNormal ); for ( int iAbnorm = 0; ++iAbnorm < pgnddParent->CState(); ) { // Look up priors in original node for each abnormal state of parent vimd1Dim[0] = iAbnorm; assert( iParent < vimdTarget.size() ); vimdTarget[iParent] = iAbnorm; MPCPDD::const_iterator itdm = dmap.find(vimdTarget); ASSERT_THROW( itdm != dmap.end(), EC_MDVECT_MISUSE, "cannot locate abnormal parent probs" ); mdvCausal.UpdatePartial( vimd1Dim, (*itdm).second ); } // Bind the distribution to the causal node pgnddCausal->SetDist( pbndist ); } // Set the priors for the new "leak" node // p( newLeakExpand | oldLeakExpand, causal ) { BNDIST * pbndist = new BNDIST; int cValue = gndd.CState(); assert( cValue == pgnddCausal->CState() && cValue == pgnddLeak->CState() ); vclear( vimdLeak, cValue ); pbndist->SetDense( vimdLeak ); MDVCPD & mdvLeak = pbndist->Mdvcpd(); for ( int il = 0; il < cValue; il++ ) { vimdLeak[0] = il; for ( int ic = 0; ic < cValue; ic++ ) { vimdLeak[1] = ic; for ( int iself = 0; iself < cValue; iself++ ) { vimdLeak[2] = iself; int ivalue = il >= ic ? il : ic; assert( ivalue < cValue ); REAL r = ivalue == iself ? 1.0 : 0.0; mdvLeak[vimdLeak] = r; } } } pgnddLeakNew->SetDist( pbndist ); } // Verify that the dimensions of the created nodes match their // created dense distributions assert( pgnddCausal->BCheckDistDense() ); assert( pgnddLeakNew->BCheckDistDense() ); pgnddLeak = pgnddLeakNew; } }