WindowsXP-SP1/enduser/troubleshoot/bn/recomend.cpp
2020-09-30 16:53:49 +02:00

1426 lines
38 KiB
C++

//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 1997 - 1998
//
// File: recomend.cpp
//
//--------------------------------------------------------------------------
//
// recomend.cpp: Fix-or-repair planning recommendations
//
#include <basetsd.h>
#include <math.h>
#include <float.h>
#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 << "<NULL>";
}
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<const GNODERECWORK &, const GNODERECWORK &, bool> 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
}
}