//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1997 - 1998 // // File: recomend.cpp // //-------------------------------------------------------------------------- // // recomend.cpp: Fix-or-repair planning recommendations // #include #include #include #include "algos.h" #include "recomend.h" #include "parmio.h" #ifdef _DEBUG //#define DUMP // Uncomment for copious diagnostic output #endif const PROB probTiny = 1e-6; // General probability tolerance static ostream & operator << ( ostream & os, GPNDDDIST & gpnddist ) { os << "GPNDDDIST: "; if ( gpnddist.Pgnd() ) { os << gpnddist.Pgnd()->ZsrefName().Szc() << ", distribution: " << gpnddist.Dist(); } else { os << ""; } return os; } static ostream & operator << ( ostream & os, GNODEREFP & gndref ) { assert( gndref.Pgndd() ); os << "GNODEREFP: " << gndref.Gndd().ZsrefName().Szc() << " (obs = " << gndref.CostObserve() << ", fix = " << gndref.CostFix() << ", util = " << gndref.Util() << ", lbl = " << PROPMGR::SzcLbl( gndref.ELbl() ) << ")"; return os; } static ostream & operator << ( ostream & os, GNODERECWORK & gnrw ) { os << "GNODERECWORK: " << gnrw.Gndref() << ", p/c = " << gnrw.PbOverCost() << ", p(fault) = " << gnrw.PbFault(); return os; } static inline bool BIsUnity( const REAL & r ) { return 1.0 - probTiny < r && r < 1.0 + probTiny; } static inline bool BEqual ( const REAL & ra, const REAL & rb ) { //return fabs( ra - rb ) <= probTiny; return ra != 0.0 ? BIsUnity( rb / ra ) : rb == 0.0; } // // Ordering routines for arrays of GNODERECWORKs // typedef binary_function SORTGNODERECWORK; // The greater the prob-over-cost, the lower the sort order class SRTGNW_SgnProbOverCost : public SORTGNODERECWORK { public: bool operator () (const GNODERECWORK & gnwa, const GNODERECWORK & gnwb) const { PROB pra = gnwa.PbOverCost(); PROB prb = gnwb.PbOverCost(); return pra > prb; } }; // The greater the prob fault, the lower the sort order class SRTGNW_SgnProb : public SORTGNODERECWORK { public: bool operator () (const GNODERECWORK & gnwa, const GNODERECWORK & gnwb) const { // Force leak terms to sort high int iLeak = 0; if ( ! gnwa->BLeak() && gnwb->BLeak() ) iLeak = -1; // Unleak < leak else if ( gnwa->BLeak() && ! gnwb->BLeak() ) iLeak = 1; // Leak > Unleak if ( iLeak != 0 ) return iLeak; PROB pra = gnwa.PbFault(); PROB prb = gnwb.PbFault(); return pra > prb; } }; // The lower the cost-to-observe, the lower the sort order class SRTGNW_SgnNegCost : public SORTGNODERECWORK { public: bool operator () (const GNODERECWORK & gnwa, const GNODERECWORK & gnwb) const { COST costa = gnwa.CostObsIfFixable(); COST costb = gnwb.CostObsIfFixable(); return costa < costb; } }; // The higher the utility, the lower the sort order class SRTGNW_SgnUtil : public SORTGNODERECWORK { public: bool operator () (const GNODERECWORK & gnwa, const GNODERECWORK & gnwb) const { COST utila = gnwa.Gndref().Util(); COST utilb = gnwb.Gndref().Util(); return utila > utilb; } }; // // Construct a node reference object. Extract properties, etc. // GNODEREFP :: GNODEREFP ( PROPMGR & propMgr, GNODEMBND * pgndd ) :_pgndd(pgndd), _costObserve(0.0), _costFix(0.0), _costUtil(0.0), _eLbl(ESTDLBL_other) { ASSERT_THROW( pgndd, EC_NULLP, "invalid GNOEREFP construction" ); PROPMBN * pprop = propMgr.PFind( *pgndd, ESTDP_cost_fix ); if ( pprop ) _costFix = pprop->Real(); pprop = propMgr.PFind( *pgndd, ESTDP_cost_observe ); if ( pprop ) _costObserve = pprop->Real(); pprop = propMgr.PFind( *pgndd, ESTDP_label ); if ( pprop ) _eLbl = (ESTDLBL) propMgr.IUserToLbl( pprop->Real() ); _bLeak = pgndd->BFlag( EIBF_Leak ); // If it's unobservable, use cost-to-fix as cost-to-observe if ( _eLbl == ESTDLBL_fixunobs && _costObserve == 0.0 ) { _costObserve = _costFix; _costFix = 0.0; } } // Initialize a work record from a node reference object and its fault probability void GNODERECWORK :: Init ( GNODEREFP * pgndref, PROB pbFault ) { _pgndref = pgndref; _pbFault = pbFault; _pbOverCost = 0.0; if ( BFixable() ) { COST costObserve = _pgndref->CostObserve(); if ( costObserve != 0.0 ) _pbOverCost = _pbFault / costObserve; assert( _finite( _pbOverCost ) ); } } // Initialize a work record from a node reference object void GNODERECWORK :: Init ( MBNET_RECOMMENDER & mbnRecom, GNODEREFP * pgndref ) { MDVCPD mdv; _pgndref = pgndref; mbnRecom.InferGetBelief( _pgndref->Pgndd(), mdv ); Init( pgndref, 1.0 - mdv[0] ); } void VGNODERECWORK :: InitElem ( GNODEREFP * pgndref, int index /* = -1 */ ) { // Grow the array as needed if ( index < 0 ) index = size(); if ( index >= size() ) resize( index+1 ); // Initialize the element self[index].Init( MbnRec(), pgndref ); } void VGNODERECWORK :: InitElem ( GNODEMBND * pgndd, int index ) { // Find the node reference record in the recommendations object's array VPGNODEREFP & vpgndref = MbnRec().Vpgndref(); int indref = vpgndref.ifind( pgndd ); ASSERT_THROW( indref >= 0, EC_INTERNAL_ERROR, "node ref not found during recommendations" ); // Initialize using that reference InitElem( vpgndref[indref], index ); } COST VGNODERECWORK :: CostService () const { return MbnRec().CostService(); } void VGNODERECWORK :: Sort ( ESORT esort ) { iterator ibeg = begin(); iterator iend = end(); switch ( esort ) { case ESRT_ProbOverCost: { sort( ibeg, iend, SRTGNW_SgnProbOverCost() ); break; } case ESRT_SgnProb: { sort( ibeg, iend, SRTGNW_SgnProb() ); break; } case ESRT_NegCost: { sort( ibeg, iend, SRTGNW_SgnNegCost() ); break; } case ESRT_SgnUtil: { sort( ibeg, iend, SRTGNW_SgnUtil() ); break; } default: { THROW_ASSERT( EC_INTERNAL_ERROR, "invalid sort selector in recommendations" ); break; } } } ///////////////////////////////////////////////////////////////////////////////////////// // // class INFOPLAN: // Encloses an array of VGNODERECWORKs, each of which is a fix-and-repair // sequence corresponding to a particular state of an informational node. // ///////////////////////////////////////////////////////////////////////////////////////// class INFOPLAN { public: INFOPLAN ( MBNET_RECOMMENDER & mbnRec, // The recommendations object GNODEMBND & gndInfo, // The information node VGNODERECWORK & vgndrwFixRepair ); // The existing f-r sequence // Compute the cost of the sequence COST Cost(); // Return true if all plans are equivalent bool BSameSequence() { return _bSameSequence; }; protected: MBNET_RECOMMENDER & _mbnRec; // The recommendations object GNODEMBND & _gndInfo; // The info node represented MDVCPD _dd; // Unconditional probability distribution VVGNODERECWORK _vvgndrw; // Array of plan arrays bool _bSameSequence; // True if all plans are equivalent }; INFOPLAN :: INFOPLAN ( MBNET_RECOMMENDER & mbnRec, GNODEMBND & gndInfo, VGNODERECWORK & vgndrwFixRepair ) : _mbnRec(mbnRec), _gndInfo(gndInfo), _bSameSequence(false) { #ifdef DUMP cout << "\nINFOPLAN::INFOPLAN: info node " << gndInfo.ZsrefName().Szc(); #endif CLAMP clampInfo; // State of info node at call time _mbnRec.InferGetEvidence( & _gndInfo, clampInfo ); assert( ! clampInfo.BActive() ); assert( _mbnRec.ELbl( _gndInfo ) == ESTDLBL_info ); // Get setup information GNODEMBND * pgnddPDAbnormal = _mbnRec.PgnddProbDefAbnormal(); assert( pgnddPDAbnormal ); COST costService = _mbnRec.CostService(); // Get beliefs under this state of information _mbnRec.InferGetBelief( & _gndInfo, _dd ); // Resize and initialize the array of fix/repair sequences int cStates = _gndInfo.CState(); _vvgndrw.resize( cStates ); for ( int iplan = 0; iplan < cStates; iplan++ ) { _vvgndrw[iplan].PmbnRec() = & _mbnRec; } _bSameSequence = true; VGPNDDDIST vgndddFixRelevant; // Array of relevant fixable nodes for ( iplan = 0; iplan < cStates; iplan++ ) { // If this state is impossible, ignore it PROB pbPlan = _dd[iplan]; if ( pbPlan == 0.0 ) continue; #ifdef DUMP cout << "\nINFOPLAN clamp " << gndInfo.ZsrefName().Szc() << " to state = " << iplan << ", prob = " << _dd[iplan]; #endif // Clamp this info node to this state CLAMP clamp( true, iplan, true ); _mbnRec.InferEnterEvidence( & _gndInfo, clamp ); // Determine which nodes are relevant given this state of information _mbnRec.DetermineRelevantFixableNodes( vgndddFixRelevant, true, & _gndInfo ); // If there are no relevant fixables then the configuration is impossible if ( vgndddFixRelevant.size() == 0 ) continue; // Collect and sequence the relevant fixable nodes accordingly _mbnRec.ComputeFixSequence( vgndddFixRelevant, _vvgndrw[iplan] ); // See if this is a new sequence if ( _bSameSequence ) _bSameSequence = vgndrwFixRepair.BSameSequence( _vvgndrw[iplan] ); } // Restore the info node to its entry state _mbnRec.InferEnterEvidence( & _gndInfo, clampInfo ); #ifdef DUMP cout << "\nINFOPLAN::INFOPLAN: END info node " << gndInfo.ZsrefName().Szc(); #endif } COST INFOPLAN :: Cost () { VPGNODEREFP & vpgndref = _mbnRec.Vpgndref(); int indref = vpgndref.ifind( & _gndInfo ); assert( indref >= 0 ); COST cost = vpgndref[indref]->CostObserve(); ASSERT_THROW( cost != 0.0, EC_INTERNAL_ERROR, "missing observation cost for info node" ); // Rescale the probabilities of each planning state based upon removal of the // impossible states and renormalization. PROB pbTotal = 0.0; for ( int iplan = 0; iplan < _gndInfo.CState(); iplan++ ) { if ( _vvgndrw[iplan].size() > 0 ) pbTotal += _dd[iplan]; } assert( pbTotal > 0.0 ); for ( iplan = 0; iplan < _gndInfo.CState(); iplan++ ) { // Get the rescaled probability of this state of the info node PROB pbPlan = _dd[iplan]; VGNODERECWORK & vgndrw = _vvgndrw[iplan]; if ( vgndrw.size() == 0 ) { // The plan is zero length; in other words, no fixables were relevant // and the plan is impossible pbPlan = 0.0; } pbPlan /= pbTotal; COST costPlan = _vvgndrw[iplan].Cost(); cost += costPlan * pbPlan; } return cost; } // Rescale the probabilities for the fix list. This routine sets the // array bounds to ignore everything from the first unfixable node and beyond. // Fault probabilities for the list are renormalized against the cumulative // probability of all the faults in the array. Since there should be no fixable // nodes of significance after the first unfixable node, the "probLeak" value // should be very small. void VGNODERECWORK :: Rescale () { // Accumulate totals of all fault probabilities PROB probTot = 0.0; for ( int ind = 0; ind < size(); ind++ ) { probTot += self[ind].PbFault(); } PROB probLeak = 1.0; // Renormalized leak (residual) probability int i1stUnfix = size(); // Index of 1st unfixable node for ( ind = 0; ind < size(); ind++ ) { GNODERECWORK & gndrw = self[ind]; if ( ! gndrw.BFixable() ) { i1stUnfix = ind; break; } //modified to fix the problem //gndrw.SetPbFault( gndrw.PbFault()/probTot); PROB pbTemp = gndrw.PbFault(); if(probTot>0.0) pbTemp /= probTot; gndrw.SetPbFault( pbTemp ); probLeak -= gndrw.PbFault(); } ASSERT_THROW( probLeak >= - probTiny, EC_INTERNAL_ERROR, "fix/repair recommendations rescaling: residual probability too large" ); #ifdef _DEBUG // Verify that there are no fixable nodes of signifcance beyond the new end point int cBeyond = 0; for ( ; ind < size(); ind++ ) { GNODERECWORK & gndrw = self[ind]; if ( gndrw.PbFault() < probTiny ) continue; // highly unlikely to be significant if ( ! gndrw.BFixable() ) continue; } assert( cBeyond == 0 ); #endif // Resize to discard unfixable nodes resize( i1stUnfix ); } ///////////////////////////////////////////////////////////////////////////////////////// // VGNODERECWORK::Cost() // // purpose: // calculate cost of a fix sequence (aka ECR(E)), given by // Cost = Co1 + p1 * Cr1 + (1 - p1) * Co2 + p2 * Cr2 + ... + (1 - sum_i^N pi) Cservice // // The 'ielemFirst' argument, if non-zero, is the index of the element to treat as first. // The 'piMinK' argument, if present, is set to the minimum K value computed. ///////////////////////////////////////////////////////////////////////////////////////// COST VGNODERECWORK :: Cost ( int ielemFirst, // Element to consider as first in array int * piMinK ) // Location to store minimum k { COST cost = 0.0; PROB prob = 1.0; const COST costService = MbnRec().CostService(); COST costK = costService * prob; assert( _iFixedK == -1 || _iFixedK < size() ); int ielem = 0; int iMinK = ielemFirst; const COST costObsProbDef = MbnRec().CostObsProbDef(); int cSize = size(); #ifdef DUMP cout << "\n\nVGNODERECWORK::Cost(" << ielemFirst << "), _iFixedK = " << _iFixedK; #endif for ( int iel = 0; iel < cSize; iel++ ) { // Select the array location, using ielemFirst (if present) as starting point, // and skipping ielemFirst as necessary later. ielem = iel == 0 ? ielemFirst : (iel - (ielemFirst > 0 && iel <= ielemFirst)); // Access the next element in the array GNODERECWORK & gndrw = self[ielem]; GNODEREFP & gndref = gndrw.Gndref(); // If the node is unfixable, ignore it if ( ! gndrw.BFixable() ) continue; const PROB probFault = gndrw.PbFault(); COST costDelta = prob * gndref.CostObserve() + probFault * (gndref.CostFix() + costObsProbDef); #ifdef DUMP cout << "\n\t" << gndrw; cout << "\n\t(iel=" << iel << ",ielem=" << ielem << ",size=" << cSize << ")\n\t\tcostDelta(" << costDelta << ") = prob(" << prob << ") * costObs(" << gndref.CostObserve() << ") + probFault(" << probFault << ") * costFix(" << gndref.CostFix() << ")" ; #endif cost += costDelta; prob -= probFault; // Compute the cost of the sequence if service is inserted here COST costNow = cost + prob * costService; #ifdef DUMP cout << "\n\t\tcostPrior(" << costK << "), costNow(" << costNow << ") = cost(" << cost << ") + prob(" << prob << ") * costService(" << costService << "), (prob =" << prob << ")"; cout.flush(); #endif // Were we better off at the last step? Or is K fixed at this point? if ( costNow < costK || iel == _iFixedK ) { costK = costNow; iMinK = ielem; if ( iel == _iFixedK ) break; // We've reached the fixed point, so stop } ASSERT_THROW( prob >= - probTiny, EC_INTERNAL_ERROR, "fix/repair recommendations costing: probability underflow" ); } #ifdef DUMP cout << "\n\t** ielem=" << ielem << ", first element = " << ielemFirst; if ( _iFixedK < 0 ) cout << ", minimum k = " << iMinK; else cout << ", fixed k = " << _iFixedK; cout << ", cost = " << costK << " (residual prob = " << prob << ")"; #endif if ( _iFixedK < 0 ) { if ( piMinK ) *piMinK = iMinK; } return costK; } // Set the cost of each node in the sequence void VGNODERECWORK :: SetSequenceCost () { // Reset any prior minimum fixed K _iFixedK = -1; // If "fixPlan", compute the minimum K only on the first cycle, // then enforce it thereafter. int iFixedK = -1; for ( int ind = 0; ind < size(); ind++ ) { // Compute the cost of the sequence with this node as first COST cost = Cost( ind, & iFixedK ); // If not "fixplan", reset K for complete search next cycle if ( MbnRec().ErcMethod() != MBNET_RECOMMENDER::ERCM_FixPlan ) iFixedK = -1; else // Else, if first cycle, fix K for remaining cycles. if ( ind == 0 ) _iFixedK = iFixedK; self[ind].SetCost( cost ); #ifdef DUMP cout << "\nSetSequenceCost: " << self[ind]->Gndd().ZsrefName().Szc() << " = " << cost; #endif } _iFixedK = -1; _bSeqSet = true; } bool VGNODERECWORK :: BSameSequence ( const VGNODERECWORK & vgnw ) { if ( size() != vgnw.size() ) return false; for ( int ind = 0; ind < size(); ind++ ) { if ( self[ind].Gndref() != vgnw[ind].Gndref() ) return false; } return true; } MBNET_RECOMMENDER :: MBNET_RECOMMENDER ( GOBJMBN_CLIQSET & inferEng, ERCMETHOD ercm ) : MBNET_NODE_RANKER( inferEng.Model() ), _inferEng( inferEng ), _propMgr( inferEng.Model() ), _ercm(ercm), _err(EC_OK), _pgnddPDAbnormal(NULL), _costService(0.0), _costObsProbDef(0.0), _bReady(false) { } MBNET_RECOMMENDER :: ~ MBNET_RECOMMENDER () { } // // Return true if the network is in a proper state for recommendations // Note that we don't check whether the network has been expanded or not. // Since there must already be an inference engine, it's assumed that the // network is in its correct state. // bool MBNET_RECOMMENDER :: BReady () { MODEL::MODELENUM mdlenum( Model() ); _err = EC_OK; _costService = CostServiceModel(); if ( _costService == 0.0 ) { _err = EC_VOI_MODEL_COST_FIX; return false; } // Clear the structure _vpgnddFix.clear(); // Prepare to collect fixable nodes _vpgndref.clear(); // Clear node reference array // Iterate over the nodes in the network, checking constraints. GELEMLNK * pgelm; GNODEMBND * pgndd; CLAMP clamp; int cProbDefSet = 0; // # of instantiated PD nodes int cFixSetAbnorm = 0; // # of fixables set to "abnormal" int cInfo = 0; // # of info nodes int cFixWithParents = 0; // # of fixables with parents while ( pgelm = mdlenum.PlnkelNext() ) { // Check only nodes if ( pgelm->EType() != GOBJMBN::EBNO_NODE ) continue; // We only support discrete nodes for now DynCastThrow( pgelm, pgndd ); // See if it has a label ESTDLBL eLbl = ELbl( *pgndd ); bool bRef = false; switch ( eLbl ) { case ESTDLBL_info: cInfo++; bRef = true; break; case ESTDLBL_problem: InferGetEvidence( pgndd, clamp ); if ( clamp.BActive() && clamp.Ist() != istNormal ) { cProbDefSet++; // Problem defining node set abnormal _pgnddPDAbnormal = pgndd; PROPMBN * ppropCostObs = _propMgr.PFind( *pgndd, ESTDP_cost_observe ); if ( ppropCostObs ) _costObsProbDef = ppropCostObs->Real(); } break; case ESTDLBL_fixobs: case ESTDLBL_fixunobs: case ESTDLBL_unfix: // Collect fixable nodes _vpgnddFix.push_back( pgndd ); // Check that it's not set abnormal InferGetEvidence( pgndd, clamp ); if ( clamp.BActive() && clamp.Ist() != istNormal ) cFixSetAbnorm++; // Fixable node set abnormal bRef = true; if ( pgndd->CParent() > 0 ) cFixWithParents++; // Fixable node with parents break; default: break; } // If necessary, create a reference item for this node if ( bRef ) { _vpgndref.push_back( new GNODEREFP( _propMgr, pgndd ) ); } } if ( cProbDefSet != 1 ) _err = EC_VOI_PROBDEF_ABNORMAL; // One and only one PD node must be abnormal else if ( cFixWithParents > 0 ) _err = EC_VOI_FIXABLE_PARENTS; // Some fixable node(s) has parents else if ( cFixSetAbnorm > 0 ) _err = EC_VOI_FIXABLE_ABNORMAL; // No fixable nodes can be abnormal return _bReady = (_err == EC_OK); } // Interface to inference engine void MBNET_RECOMMENDER :: InferGetBelief ( GNODEMBND * pgndd, MDVCPD & mdvBel ) { InferEng().GetBelief( pgndd, mdvBel ); } void MBNET_RECOMMENDER :: InferGetEvidence ( GNODEMBND * pgndd, CLAMP & clamp ) { InferEng().GetEvidence( pgndd, clamp ); } void MBNET_RECOMMENDER :: InferEnterEvidence ( GNODEMBND * pgndd, const CLAMP & clamp ) { InferEng().EnterEvidence( pgndd, clamp ); } bool MBNET_RECOMMENDER :: BInferImpossible () { return InferEng().BImpossible(); } void MBNET_RECOMMENDER :: PrintInstantiations () { #ifdef DUMP GELEMLNK * pgelm; GNODEMBND * pgndd; CLAMP clamp; cout << "\n\tInstantiations:"; MODEL::MODELENUM mdlenum( Model() ); while ( pgelm = mdlenum.PlnkelNext() ) { // Check only nodes if ( pgelm->EType() != GOBJMBN::EBNO_NODE ) continue; // We only support discrete nodes for now DynCastThrow( pgelm, pgndd ); InferGetEvidence( pgndd, clamp ); if ( clamp.BActive() ) { cout << "\n\t\tnode " << pgndd->ZsrefName().Szc() << " is instantiated to state " << clamp.Ist() << ", " << pgndd->VzsrStates()[clamp.Ist()].Szc(); } } cout << "\n\tInstantiations end."; #endif } COST MBNET_RECOMMENDER :: CostServiceModel () { // Get the model's cost-to-fix as service cost. PROPMBN * ppropFixCost = _propMgr.PFind( ESTDP_cost_fix ); COST costService = ppropFixCost ? ppropFixCost->Real() : 0.0; return costService; } // Look up the label property of a node; convert to standard enumeration value. ESTDLBL MBNET_RECOMMENDER :: ELbl ( GNODEMBN & gnd ) { PROPMBN * propLbl = PropMgr().PFind( gnd, ESTDP_label ); if ( ! propLbl ) return ESTDLBL_other; int iUserLbl = propLbl->Real(); int iLbl = PropMgr().IUserToLbl( propLbl->Real() ); return iLbl < 0 ? ESTDLBL_other : (ESTDLBL) iLbl; } // Enter evidence for a troubleshooting model. // // If the node is a fixable node being "set" to "normal", uninstantiate all // information nodes downstream from it. // void MBNET_RECOMMENDER :: EnterEvidence ( GNODEMBND * pgndd, const CLAMP & clamp, bool bSet ) { if ( bSet ) { ESTDLBL eLbl = ELbl( *pgndd ); switch ( eLbl ) { case ESTDLBL_unfix: case ESTDLBL_fixobs: case ESTDLBL_fixunobs: { // This is a fixable node if ( ! clamp.BActive() ) break; // Node is being unset if ( clamp.Ist() != istNormal ) break; // Node is not being fixed // Find all downstream information nodes which are instantiated. VPGNODEMBND vpgndd; vpgndd.push_back(pgndd); ExpandDownstream(vpgndd); CLAMP clampInfo; for ( int ind = 0; ind < vpgndd.size(); ind++ ) { GNODEMBND * pgnddInfo = vpgndd[ind]; ESTDLBL l = ELbl( *pgnddInfo ); if ( l != ESTDLBL_info ) continue; InferGetEvidence( pgnddInfo, clampInfo ); if ( ! clampInfo.BActive() ) continue; // This is a clamped information node downstream from the fixable // node being repaired. Unset its instantiation. InferEnterEvidence( pgnddInfo, CLAMP() ); } break; } default: break; } } InferEnterEvidence( pgndd, clamp ); } // // Compute the probability distribution of the node and compare it to // the stored distribution. Return true If it has changed. // bool MBNET_RECOMMENDER :: BProbsChange ( GPNDDDIST & gpndddist ) { MDVCPD mdv; // The the distribution given the current state of evidence InferGetBelief( gpndddist.Pgnd(), mdv ); // Compare it to the other distribution MDVCPD & mdvo = gpndddist.Dist(); int cprob = mdvo.first.size(); assert( mdv.first.size() == cprob ); for ( int i = 0; i < cprob; i++ ) { #ifdef DUMP cout << "\n\t\tBProbsChange, state = " << i << ", old = " << mdvo[i] << ", new = " << mdv[i]; #endif if ( ! BEqual( mdv[i], mdvo[i] ) ) { return true; } } return false; } // Add to the given array all nodes which are downstream from members void MBNET_RECOMMENDER :: ExpandDownstream ( VPGNODEMBND & vpgndd ) { Model().ClearNodeMarks(); // Mark all nodes downstream of every given node for ( int i = 0; i < vpgndd.size(); i++ ) { vpgndd[i]->Visit(false); } // Collect those nodes MODEL::MODELENUM mdlenum( Model() ); GELEMLNK * pgelm; GNODEMBND * pgndd; while ( pgelm = mdlenum.PlnkelNext() ) { if ( pgelm->EType() != GOBJMBN::EBNO_NODE ) continue; // We only support discrete nodes for now DynCastThrow( pgelm, pgndd ); // Add marked nodes which are not already present if ( pgndd->IMark() ) { appendset( vpgndd, pgndd ); } } } void MBNET_RECOMMENDER :: DetermineRelevantFixableNodes ( VGPNDDDIST & vgndddFixRelevant, bool bUsePriorList, GNODEMBND * pgnddInfoPlan /* = NULL */ ) { assert( _vpgnddFix.size() > 0 ); assert( _pgnddPDAbnormal != NULL ); #ifdef DUMP cout << "\nRecommendations, DetermineRelevantFixableNodes: abnormal PD node is " << _pgnddPDAbnormal->ZsrefName().Szc(); if ( bUsePriorList ) cout << " (secondary invocation)"; #endif /* If 'bUsePriorList' is false: Find all the relevant fixable nodes; i.e., those fixable nodes which linked to the Problem node and which are not clamped. If unfixed, (that is, not repaired and not "unfixable"), accumulate them for a search of relevant info nodes. First, visit the problem defining node which is instantiated to an abnormal state and mark all upstream links to it. Else, if 'bUsePriorList' is true: Use the relevant fixable list previously accumulated */ vgndddFixRelevant.clear(); // clear the result array int cfix = 0; // count of fixables to search if ( bUsePriorList ) { // Use the original list of relevant fixables cfix = _vgndddFixRelevant.size(); } else { // Fill in a new list of releveant fixables Model().ClearNodeMarks(); _pgnddPDAbnormal->Visit(); cfix = _vpgnddFix.size(); } // Accumulate the list of relevant, available (unfixed) fixable nodes, to // which downstream info nodes will be added VPGNODEMBND vpgnddDownstreamFromRelevantFixable; int irel = 0; for ( int ifix = 0; ifix < cfix; ifix++ ) { GNODEMBND * pgnddFix; if ( bUsePriorList ) { // Use prior list element pgnddFix = _vgndddFixRelevant[ifix].Pgnd(); } else { // See if this node was marked by "visit" above pgnddFix = _vpgnddFix[ifix]; if ( pgnddFix->IMark() == 0 ) continue; // unconnected to current problem CLAMP clampFix; InferGetEvidence( pgnddFix, clampFix ); if ( clampFix.BActive() ) continue; // Fixable node has been fixed; irrelevant } // This is an unfixed, fixable node involved in the problem; // append it to the list vgndddFixRelevant.resize(irel+1); GPNDDDIST & gpnddd = vgndddFixRelevant[irel++]; gpnddd.Pgnd() = pgnddFix; // Get its current PD and save it InferGetBelief( gpnddd.Pgnd(), gpnddd.Dist() ); // If fixable, add it to the list for accumulation of relevant info nodes ESTDLBL eLbl = ELbl( *pgnddFix ); if ( eLbl == ESTDLBL_fixobs || eLbl == ESTDLBL_fixunobs ) { vpgnddDownstreamFromRelevantFixable.push_back( pgnddFix ); } } #ifdef DUMP cout << "\n\tInstantiations before relevance check"; PrintInstantiations(); #endif // Uninstantiate the info nodes which are downstream from any // RELEVANT UNFIXED fixable nodes. The first step, which is to gather such // relevant fixable nodes, has been done above. // // Note that this is NOT done for the info node being used for INFOPLAN (ECO) // generation. Since INFOPLAN::INFOPLAN precesses this node through its states, // it's pointless to uninstantiate it here. // // Next, find all info nodes downstream from the relevant unfixed fixables. // Finally, temporarily rescind the instantiations of those info nodes. VPNDD_IST vpnddIstReset; // remember pairs of node pointers and ISTs to reset later // Number of unfixed fixables so far int cUnfixedNodes = vpgnddDownstreamFromRelevantFixable.size(); // Expand the collection to include all downstream nodes ExpandDownstream( vpgnddDownstreamFromRelevantFixable ); // Get number of relevant info nodes int cInfoNodes = vpgnddDownstreamFromRelevantFixable.size() - cUnfixedNodes; CLAMP clampInfo; CLAMP clampReset; int ireset = 0; #ifdef DUMP cout << "\n\t" << cUnfixedNodes << " fixable nodes are upstream of PD, " << cInfoNodes << " nodes are downstream from them"; #endif for ( int iinfo = cUnfixedNodes; iinfo < vpgnddDownstreamFromRelevantFixable.size(); iinfo++ ) { GNODEMBND * pgnddInfo = vpgnddDownstreamFromRelevantFixable[iinfo]; if ( ELbl( *pgnddInfo ) != ESTDLBL_info ) continue; // Not an info node if ( pgnddInfo == pgnddInfoPlan ) continue; // The info node we're planning for InferGetEvidence( pgnddInfo, clampInfo ); if ( ! clampInfo.BActive() ) continue; // Not clamped #ifdef DUMP cout << "\n\tinfo node " << pgnddInfo->ZsrefName().Szc() << " is being unclamped from state " << clampInfo.Ist(); #endif // Instantiated info node. Save its ptr and current state for later. vpnddIstReset.push_back( PNDD_IST( pgnddInfo, clampInfo.Ist() ) ); // Unclamp it for relevance check InferEnterEvidence( pgnddInfo, clampReset ); } // Walk the list of relevant fixables accumulated so far and determine those // which are probabilistically relevant. Move those which are to the front // of the relevance array, then chop the stragglers off the end. // Get the current state of the PD node CLAMP clampProblem; InferGetEvidence( _pgnddPDAbnormal, clampProblem ); IST istProblemSet = clampProblem.Ist(); #ifdef DUMP cout << "\n\tInstantiations during relevance check"; PrintInstantiations(); #endif // Iterate over all open (non-evidenced) states of the problem defining node. int cNodeFix = vgndddFixRelevant.size(); int cRelevant = 0; for ( IST istProblem = 0; istProblem < _pgnddPDAbnormal->CState(); istProblem++ ) { // If we've already stored every possible relevant fixable node, quit if ( cRelevant == cUnfixedNodes ) break; // If this is the current problem state, skip it if ( istProblem == istProblemSet ) continue; // Temporarily instantiate the PD node to this alternative state InferEnterEvidence( _pgnddPDAbnormal, CLAMP(true, istProblem, true) ); // If state of evidence is impossible, continue if ( BInferImpossible() ) continue; // Iterate over the remaining relevant fixable nodes. As they are found to be // relevant, the nodes are moved to the front of the array and not checked again. for ( int inode = cRelevant; inode < cNodeFix; inode++ ) { GPNDDDIST & gpndddist = vgndddFixRelevant[inode]; GNODEMBND * pgnddFix = gpndddist.Pgnd(); CLAMP clampFix; InferGetEvidence( pgnddFix, clampFix ); if ( clampFix.BActive() && clampFix.Ist() == istNormal ) continue; // This fixable node has been fixed and is irrelevant // If the PD of this fixable node changes for this problem instantiation, // it's relevant; move it to front of array. if ( BProbsChange( gpndddist ) ) { #ifdef DUMP cout << "\n\tfixable node " << pgnddFix->ZsrefName().Szc() << " is probabilistically relevant "; #endif vswap( vgndddFixRelevant, cRelevant++, inode ); } #ifdef DUMP else { cout << "\n\tfixable node " << pgnddFix->ZsrefName().Szc() << " is NOT probabilistically relevant "; } #endif } } // Resize the computed array to chop off the irrelevant nodes vgndddFixRelevant.resize( cRelevant ); // Reset the probdef node back to its current instantiation InferEnterEvidence( _pgnddPDAbnormal, clampProblem ); // Reset the uninstantiated info nodes back to their prior states for ( ireset = 0; ireset < vpnddIstReset.size(); ireset++ ) { IST ist = vpnddIstReset[ireset].second; GNODEMBND * pgndd = vpnddIstReset[ireset].first; CLAMP clampReset(true, ist, true); InferEnterEvidence( pgndd, clampReset ); } #ifdef DUMP if ( cRelevant ) { cout << "\nRecommendations, DetermineRelevantFixableNodes: relevant fixables are: " ; for ( int ifx = 0; ifx < vgndddFixRelevant.size(); ifx++ ) { cout << "\n\tnode " << vgndddFixRelevant[ifx].Pgnd()->ZsrefName().Szc() << " is relevant fixable #" << ifx; } } else { cout << "\nRecommendations, DetermineRelevantFixableNodes: there are NO relevant fixables " ; } #endif } void MBNET_RECOMMENDER :: ComputeFixSequence ( VGPNDDDIST & vgndddFixRelevant, // IN: Relevant fixable nodes VGNODERECWORK & vgnrwFix ) // OUT: Ordered fix/repair sequence { // Using the array of node references and the array of relevant fixable nodes, // initialize the fix/repair sequence array. vgnrwFix.resize( vgndddFixRelevant.size() ) ; for ( int ind = 0; ind < vgnrwFix.size(); ind++ ) { GNODEMBND * pgndd = vgndddFixRelevant[ind].Pgnd(); vgnrwFix.InitElem( pgndd, ind ); } VGNODERECWORK::ESORT esort = VGNODERECWORK::ESRT_ProbOverCost; switch ( _ercm ) { case ERCM_MostLikely: esort = VGNODERECWORK::ESRT_SgnProb; break; case ERCM_Cheap: esort = VGNODERECWORK::ESRT_NegCost; break; } vgnrwFix.Sort( esort ); vgnrwFix.Rescale(); #ifdef DUMP cout << "\nRecommendations, ComputeFixSequence: fix/repair sequence is:"; for ( ind = 0; ind < vgnrwFix.size(); ind++ ) { GNODEREFP & gndref = vgnrwFix[ind].Gndref(); cout << "\n\tnode " << ind << " is " << gndref.Gndd().ZsrefName().Szc() << ", p/c = " << vgnrwFix[ind].PbOverCost() << ", utility = " << gndref.Util(); } #endif } // Identify the relevant info nodes and compute their costs. // Formerly "BxComputeCosts()" void MBNET_RECOMMENDER :: DetermineRelevantInfoNodes ( VGNODERECWORK & vgnrwFix, VGNODERECWORK & vgnrwInfo ) { assert( _pgnddPDAbnormal != NULL ); CLAMP clampInfo; vgnrwInfo.clear(); #ifdef DUMP cout << "\nRecommendations, DetermineRelevantInfoNodes:"; #endif for ( int ind = 0; ind < _vpgndref.size(); ind++ ) { GNODEREFP * pgndref = _vpgndref[ind]; assert( pgndref ); if ( pgndref->ELbl() != ESTDLBL_info ) continue; InferGetEvidence( pgndref->Pgndd(), clampInfo ); // Instantiated info nodes are irrelevant if ( clampInfo.BActive() ) continue; // Create an array of fix/repair plans for all states of this info node INFOPLAN infoplan( self, pgndref->Gndd(), vgnrwFix ); // If all plans result in the same sequence, it's irrelevant if ( infoplan.BSameSequence() ) { #ifdef DUMP cout << "\n\tinfo node " << pgndref->Gndd().ZsrefName().Szc() << " is NOT relevant; all plans are the same"; #endif } else { // Add this info node to the array vgnrwInfo.InitElem( pgndref->Pgndd() ); // Set the utility to be the negative of the plan cost COST cost = infoplan.Cost(); pgndref->Util() = - cost; #ifdef DUMP cout << "\n\tinfo node " << pgndref->Gndd().ZsrefName().Szc() << " is relevant, utility = " << pgndref->Util(); #endif } } } void MBNET_RECOMMENDER :: operator () () { // If BReady() has not been called yet, do it now. if ( ! _bReady ) { if ( ! BReady() ) throw GMException( _err, "network state invalid for recommendations" ); } #ifdef DUMP cout.precision(8); #endif // Clear the "ready" flag; i.e., force subsequent call to BReady(). Unready(); if ( _ercm != ERCM_FixPlan ) throw GMException( EC_NYI, "only fix/plan recommendations supported" ); assert( _pgnddPDAbnormal ); // Array of fixable nodes VGNODERECWORK vgnrwFix( this ); // Array of informational nodes VGNODERECWORK vgnrwInfo( this ); // Collect the relevant fixable nodes DetermineRelevantFixableNodes( _vgndddFixRelevant, false, NULL ); // Collect and order the relevant fixable node information, // sorted according to planning method and rescaled. ComputeFixSequence( _vgndddFixRelevant, vgnrwFix ); // Compute ECR, the expected cost of repair. vgnrwFix.SetSequenceCost(); // If information nodes are relevant, determine the set of them. if ( _ercm == ERCM_FixPlan || _ercm == ERCM_FixPlanOnly ) { // Compute ECO, the expected cost of the Observation-Repair sequence. DetermineRelevantInfoNodes( vgnrwFix, vgnrwInfo ); } // Collect all relevant fixables and infos and sort them VGNODERECWORK vgnrwRecom( this ); vgnrwRecom.resize( vgnrwFix.size() + vgnrwInfo.size() ); // Add fixables... for ( int ind = 0; ind < vgnrwFix.size(); ind++ ) { vgnrwRecom[ind] = vgnrwFix[ind]; } // Add infos... int indStart = ind; for ( ind = 0; ind < vgnrwInfo.size(); ind++ ) { vgnrwRecom[indStart + ind] = vgnrwInfo[ind]; } // Sort by negative utility vgnrwRecom.Sort( VGNODERECWORK::ESRT_SgnUtil ); // Copy information to the output areas, ordered by lowest cost. // First, determine how many are more expensive than a service call // since we discard those. int cRecom = vgnrwRecom.size(); int iRecom = 0; if ( _costService != 0.0 ) { for ( iRecom = 0; iRecom < cRecom; iRecom++ ) { COST cost = vgnrwRecom[iRecom].Gndref().Util(); if ( cost >= _costService ) break; } cRecom = iRecom; } _vzsrNodes.resize(cRecom); _vlrValues.resize(cRecom); for ( iRecom = 0; iRecom < cRecom; iRecom++ ) { GNODEREFP & gndref = vgnrwRecom[iRecom].Gndref(); // Add the node name to the list _vzsrNodes[iRecom] = gndref.Gndd().ZsrefName(); // and give its score (utility) _vlrValues[iRecom] = gndref.Util(); #ifdef DUMP cout << "\nRecommendation # " << iRecom << ", node " << _vzsrNodes[iRecom].Szc() << " = " << _vlrValues[iRecom]; cout.flush(); #endif } }