windows-nt/Source/XPSP1/NT/enduser/troubleshoot/bn/bntest.cpp

761 lines
17 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 1997 - 1997
//
// File: bntest.cpp
//
//--------------------------------------------------------------------------
//
// BNTEST.CPP
//
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <float.h>
#include "bnparse.h" // Parser class
#include "bnreg.h" // Registry management
#include "testinfo.h" // Output test file generation
#include "distdense.hxx" // Distribution classes
#include "distsparse.h"
#ifdef TIME_DYN_CASTS
// Global variable containing count of calls to all forms of DynCastThrow function template
int g_cDynCasts = 0;
#endif
enum EFN // File name in file name array
{
EFN_IN, // input DSC file
EFN_OUT, // output DSC file
EFN_INFER // output inference test file (see testinfo.cpp for format)
};
static
inline
double RNan ()
{
double rnan = sqrt(-1.0);
#ifndef NTALPHA
assert( _isnan( rnan ) );
#endif
return rnan;
}
static
inline
bool BFlag ( ULONG fCtl, ULONG fFlag )
{
return (fCtl & fFlag) > 0;
}
static
void usage ()
{
cout << "\nBNTEST: Belief Network Test program"
<< "\nCommand line:"
<< "\n\tbntest [options] <input.DSC> [/s <output.DSC>] [/p <output.DMP>]"
<< "\nOptions:"
<< "\n\t/v\t\tverbose output"
<< "\n\t/c\t\tclique the network"
<< "\n\t/e\t\ttest CI network expansion"
<< "\n\t/inn\t\ttest inference; nn = iterations (default 1)"
<< "\n\t/p <filename>\twrite inference output (.dmp) file (sets /i)"
<< "\n\t/s <filename>\trewrite input DSC into output file"
<< "\n\t/t\t\tdisplay start and stop times"
<< "\n\t/x\t\tpause at various stages (for memory measurement)"
<< "\n\t/n\t\tuse symbolic names in inference output (default is full)"
<< "\n\t/y\t\tclone the network (write cloned version if /s)"
<< "\n\t/u\t\tinclude entropic utility records in /p output"
<< "\n\t/b\t\tinclude troubleshooting recommendations in /p output"
<< "\n\t/r\t\tstore property types in Registry for persistence"
<< "\n\t/b\t\tcompute troubleshooting recommendations"
<< "\n\t/z\t\tshow inference engine statistics"
<< "\n\t/m<nnnnnn>\tset maximum estimated inference engine size"
<< "\n\t/a<n.n>\t\tflag impossible evidence with numeric value"
<< "\n\nInput DSC is read and parsed; errors and warnings go to stderr."
<< "\nParse errors stop testing. If cloning, output file is cloned version."
<< "\nIf CI expansion (/e), output (/s) has pre- and post- expansion versions."
<< "\nInference (/i or /p) takes precedence over CI expansion (/e)."
<< "\nInference output (/p) writes file in common format with DXTEST."
<< "\nCliquing (/c) just creates and destroys junction tree."
<< "\n";
}
static
void die( SZC szcFormat, ... )
{
ZSTR zsMsg;
va_list valist;
va_start(valist, szcFormat);
zsMsg.Vsprintf( szcFormat, valist );
va_end(valist);
cerr << "\nBNTEST error: "
<< zsMsg.Szc()
<< "\n";
exit(1);
}
// Show the debugging build options
static
void showOptions ( ULONG fCtl )
{
bool bComma = false;
ZSTR zs = TESTINFO::ZsOptions( fCtl );
cout << "(options: "
<< zs;
bComma = zs.length() > 0;
// Show DYNAMIC CAST option
if ( bComma )
cout << ",";
cout <<
#ifdef USE_STATIC_CAST
"STATICCAST"
#else
"DYNCAST"
#endif
;
bComma = true;
// Show DUMP option
#ifdef DUMP
if ( bComma )
cout << ",";
cout << "DUMP";
bComma = true;
#endif
// Show DEBUG option
#ifdef _DEBUG
if ( bComma )
cout << ",";
cout << "DEBUG";
bComma = true;
#endif
cout << ")";
}
// Show memory leaks for primary object types, if any
static
void showResiduals ()
{
#ifdef _DEBUG
if (GEDGE::CNew() + GNODE::CNew() + GNODE::CNew() )
{
cout << "\n(GEDGEs = "
<< GEDGE::CNew()
<< ", GNODESs = "
<< GNODE::CNew()
<< ", BNDISTs = "
<< GNODE::CNew()
<< ")";
}
if ( VMARGSUB::CNew() + MARGSUBREF::CNew() )
{
cout << "\n(VMARGSUBs = "
<< VMARGSUB::CNew()
<< ", MARGSUBREFs = "
<< MARGSUBREF::CNew()
<< ")";
}
#endif
}
static
void printResiduals ()
{
#ifdef _DEBUG
showResiduals();
#endif
#ifdef TIME_DYN_CASTS
cout << "\ntotal number of dynamic casts was "
<< g_cDynCasts;
#endif
}
// Display the message and pause if the "pause" option is active
inline
static
void pauseIf ( ULONG fCtl, SZC szcMsg )
{
if ( (fCtl & fPause) == 0 )
return;
showResiduals();
char c;
cout << "\n"
<< szcMsg
<< " (pause)"
;
cin.get(c);
}
// Display the phase message and, optionally, the time
typedef DWORD CTICKS;
inline
static
CTICKS showPhase ( ULONG fCtl, SZC szcPhase, CTICKS * ptmLast = NULL )
{
// Display the phase message
cout << "\n" << szcPhase;
CTICKS cticks = 0;
if ( fCtl & fShowTime )
{
// Save the current tick count
cticks = ::GetTickCount();
// Prepare to display the current date/time
time_t timeNow;
time(& timeNow);
ZSTR zsTime = ctime(&timeNow);
int cnl = zsTime.find( '\n' );
if ( cnl != 0 )
zsTime.resize( cnl );
cout << " " << zsTime;
// Display the elapsed time if we know it
if ( ptmLast && *ptmLast != 0 )
{
CTICKS ticksElapsed = cticks - *ptmLast;
cout << " (elapsed time "
<< ticksElapsed
<< " milliseconds)";
}
}
return cticks;
}
static
void testRegistry ( MBNET & mbnet )
{
BNREG bnr;
bnr.StorePropertyTypes( mbnet, true );
}
#ifdef TESTDIST
static void loadDistDenseFromMpcpdd ( DISTDENSE & ddense, const MPCPDD & mpcpdd )
{
ddense.AllocateParams();
CST cstNode = ddense.CstNode();
// Find the default vector in the map or create a uniform vector
const VLREAL * pvlrDefault = mpcpdd.PVlrDefault();
VLREAL vlrDefault;
if ( pvlrDefault )
{
vlrDefault = *pvlrDefault;
}
else
{
vlrDefault.resize( cstNode );
REAL rDefault = 1 / cstNode ;
vlrDefault = rDefault;
}
// Fill the dense array with the default value
UINT cParamgrp = ddense.Cparamgrp();
UINT igrp = 0;
for ( ; igrp < cParamgrp; igrp++ )
{
for ( UINT ist = 0; ist < cstNode; ist++ )
{
ddense.Param(ist, igrp) = vlrDefault[ist];
}
}
// Iterate over the sparse map, storing probabilities as parameters
const VCST & vcstParent = ddense.VcstParent();
VIST vist;
for ( MPCPDD::iterator mpitcpd = mpcpdd.begin();
mpitcpd != mpcpdd.end();
mpitcpd++ )
{
const VIMD & vimd = (*mpitcpd).first;
const VLREAL & vlr = (*mpitcpd).second;
// State vector size must match state space
assert( vlr.size() == cstNode );
// Parent dimensions must match dimension index
assert( vdimchk( vimd, vcstParent ) );
// Convert the vector of unsigneds to a vector of signeds
vdup( vist, vimd );
// Get the parameter group index
UINT igrp = ddense.Iparamgrp( vist );
// Copy the probabilities as parameters
for ( UINT ist = 0; ist < cstNode; ist++ )
{
ddense.Param(ist, igrp) = vlr[ist];
}
}
}
static void testDistDenseWithMpcpdd( DISTDENSE & ddense, const MPCPDD & mpcpdd )
{
}
static void loadDistSparseFromMpcpdd ( DISTSPARSE & dsparse, const MPCPDD & mpcpdd )
{
dsparse.Init( mpcpdd );
}
static void testDistSparseWithMpcpdd ( DISTSPARSE & dsparse, const MPCPDD & mpcpdd )
{
MPCPDD mpcpddNew;
dsparse.Fill( mpcpddNew );
assert( mpcpddNew == mpcpdd );
}
#endif
// Bind the model's distibutions and verify behavior of the DISTSPARSE
// and DISTDENSE classes.
static
void testDistributions ( MBNETDSC & mbnetdsc, ULONG fCtl )
{
#ifdef TESTDIST
// Bind the distributions
mbnetdsc.BindDistributions();
GOBJMBN * pgmobj;
for ( MBNETDSC::ITER mbnit( mbnetdsc, GOBJMBN::EBNO_NODE );
pgmobj = *mbnit ;
++mbnit)
{
ZSREF zsrName = mbnit.ZsrCurrent();
GNODEMBND * pgndd;
DynCastThrow( pgmobj, pgndd );
// Convert this node's distribution to a DISTDENSE and
// a DISTSPARSE, then compare them to the original
assert( pgndd->BHasDist() );
const BNDIST & bndist = pgndd->Bndist();
assert( bndist.BSparse() );
const MPCPDD & mpcpdd = bndist.Mpcpdd();
// Get the parent list for this node; convert to a state count vector
VPGNODEMBN vpgndParents;
VIMD vimdParents;
if ( ! pgndd->BGetVimd( vimdParents ) )
continue; // Skip non-discrete ensembles
VCST vcstParents;
vdup( vcstParents, vimdParents );
CST cStates = pgndd->CState();
DISTDENSE ddense( cStates, vcstParents );
DISTSPARSE dsparse( cStates, vcstParents );
loadDistDenseFromMpcpdd( ddense, mpcpdd );
testDistDenseWithMpcpdd( ddense, mpcpdd );
loadDistSparseFromMpcpdd( dsparse, mpcpdd );
testDistSparseWithMpcpdd( dsparse, mpcpdd );
}
// Release the distributions
mbnetdsc.ClearDistributions();
#endif
}
static
void
showInferStats ( TESTINFO & testinfo )
{
GOBJMBN_INFER_ENGINE * pInferEng = testinfo.Mbnet().PInferEngine();
assert( pInferEng );
GOBJMBN_CLIQSET * pCliqset = dynamic_cast<GOBJMBN_CLIQSET *>(pInferEng);
if ( pCliqset == NULL )
return; // Don't know how to get statistics from this inference engine
CLIQSETSTAT & cqstats = pCliqset->CqsetStat();
cout << "\n\nInference statistics: "
<< "\n\treloads = " << cqstats._cReload
<< "\n\tcollects = " << cqstats._cCollect
<< "\n\tset evidence = " << cqstats._cEnterEv
<< "\n\tget belief = " << cqstats._cGetBel
<< "\n\tprob norm = " << cqstats._cProbNorm
<< "\n"
;
}
static
void testInference ( ULONG fCtl, MBNETDSC & mbnet, SZC szcFnInfer, REAL rImposs )
{
ofstream ofs;
bool bOutput = (fCtl & fOutputFile) > 0 ;
int cPass = fCtl & fPassCountMask;
GOBJMBN_INFER_ENGINE * pInferEng = mbnet.PInferEngine();
assert( pInferEng );
if ( bOutput )
{
if ( szcFnInfer == NULL )
szcFnInfer = "infer.dmp";
ofs.open(szcFnInfer);
}
// Construct the test data container
TESTINFO testinfo( fCtl, mbnet, bOutput ? & ofs : NULL );
testinfo._rImposs = rImposs;
// Run the test
testinfo.InferTest();
if ( bOutput )
ofs.close();
if ( fCtl & fInferStats )
showInferStats( testinfo );
}
static
void testCliquingStart ( ULONG fCtl, MBNETDSC & mbnet, REAL rMaxEstSize = -1.0 )
{
#ifdef DUMP
if ( BFlag( fCtl, fVerbose ) )
{
cout << "\nBNTEST: BEGIN model before cliquing";
mbnet.Dump();
cout << "\nBNTEST: END model before cliquing\n";
}
#endif
mbnet.CreateInferEngine( rMaxEstSize );
#ifdef DUMP
if ( BFlag( fCtl, fVerbose ) )
{
cout << "\nBNTEST: BEGIN model after cliquing";
mbnet.Dump();
cout << "\nBNTEST: END model after cliquing\n";
}
#endif
}
static
void testCliquingEnd ( MBNETDSC & mbnet, ULONG fCtl )
{
GOBJMBN_INFER_ENGINE * pInferEng = mbnet.PInferEngine();
if ( pInferEng == NULL )
return;
mbnet.DestroyInferEngine();
// For testing, nuke the topology
mbnet.DestroyTopology( true );
// Create arcs from the given conditional probability distributions
mbnet.CreateTopology();
// For testing, nuke the topology
mbnet.DestroyTopology( false );
}
static
void testParser (
ULONG fCtl,
SZC rgfn[],
REAL rMaxEstSize = -1.0,
REAL rImposs = -1.0 )
{
SZC szcFn = rgfn[EFN_IN];
SZC szcFnOut = rgfn[EFN_OUT];
SZC szcFnInfer = rgfn[EFN_INFER];
// Instantiate the belief network
MBNETDSC mbnet;
// See if there's an output file to write a DSC into
FILE * pfOut = NULL;
if ( (fCtl & fSaveDsc) > 0 && szcFnOut != NULL )
{
pfOut = fopen(szcFnOut,"w");
if ( pfOut == NULL )
die("error creating output DSC file \'%s\'", szcFnOut);
}
// Input file wrapper object
PARSIN_DSC flpIn;
// Output file wrapper object
PARSOUT_STD flpOut(stderr);
// Construct the parser; errors go to 'stderr'
DSCPARSER parser(mbnet, flpIn, flpOut);
UINT cError, cWarning;
try
{
// Attempt to open the file
if ( ! parser.BInitOpen( szcFn ) )
die("unable to access input file");
pauseIf( fCtl, "input DSC file open" );
// Parse the file
if ( ! parser.BParse( cError, cWarning ) )
die("parse failure; %d errors, %d warnings", cError, cWarning);
if ( cWarning )
cout << "\nBNTEST: file "
<< szcFn
<< " had "
<< cWarning
<< " warnings\n";
if ( BFlag( fCtl, fReg ) )
testRegistry( mbnet );
pauseIf( fCtl, "DSC file read and processed" );
if ( BFlag( fCtl, fDistributions ) )
{
testDistributions( mbnet, fCtl );
}
// If requested, test cloning
if ( BFlag( fCtl, fClone ) )
{
MBNETDSC mbnetClone;
mbnetClone.Clone( mbnet );
if ( pfOut )
mbnetClone.Print( pfOut );
}
else
// If requested, write out a DSC file
if ( pfOut )
{
mbnet.Print( pfOut );
}
// Test cliquing if requested (/c) or required (/i)
if ( BFlag( fCtl, fCliquing ) || BFlag( fCtl, fInference ) )
{
testCliquingStart( fCtl, mbnet, rMaxEstSize );
pauseIf( fCtl, "Cliquing completed" );
if ( BFlag( fCtl, fInference ) )
{
// Generate inference results (/i)
testInference( fCtl, mbnet, szcFnInfer, rImposs );
pauseIf( fCtl, "Inference output generation completed" );
}
testCliquingEnd( mbnet, fCtl ) ;
pauseIf( fCtl, "Cliquing and inference completed" );
}
else
// Test if CI expansion requested (/e)
if ( BFlag( fCtl, fExpand ) )
{
// Perform CI expansion on the network.
mbnet.ExpandCI();
pauseIf( fCtl, "Network expansion complete" );
// If output file generation, do "before" and "after" expansion and reversal
if ( pfOut )
{
fprintf( pfOut, "\n\n//////////////////////////////////////////////////////////////" );
fprintf( pfOut, "\n// Network After Expansion //" );
fprintf( pfOut, "\n//////////////////////////////////////////////////////////////\n\n" );
mbnet.Print( pfOut );
}
// Undo the expansion
mbnet.UnexpandCI();
if ( pfOut )
{
fprintf( pfOut, "\n\n//////////////////////////////////////////////////////////////" );
fprintf( pfOut, "\n// Network After Expansion Reversal //" );
fprintf( pfOut, "\n//////////////////////////////////////////////////////////////\n\n" );
mbnet.Print( pfOut );
}
}
// For testing, nuke the topology
mbnet.DestroyTopology();
}
catch ( GMException & exbn )
{
die( exbn.what() );
}
if ( pfOut )
fclose( pfOut );
}
int main (int argc, char * argv[])
{
int iArg ;
short cPass = 1;
int cFile = 0 ;
const int cFnMax = 10 ;
SZC rgfn [cFnMax+1] ;
ULONG fCtl = 0;
REAL rMaxEstSize = -1.0;
REAL rImposs = RNan();
for ( int i = 0 ; i < cFnMax ; i++ )
{
rgfn[i] = NULL ;
}
for ( iArg = 1 ; iArg < argc ; iArg++ )
{
switch ( argv[iArg][0] )
{
case '/':
case '-':
{
char chOpt = toupper( argv[iArg][1] ) ;
switch ( chOpt )
{
case 'V':
// Provide verbose output
fCtl |= fVerbose;
break;
case 'C':
// Perform cliquing
fCtl |= fCliquing;
break;
case 'E':
// Test network CI expansion
fCtl |= fExpand;
break;
case 'I':
// Exercise inference and optionally write the results in a standard form
{
int c = atoi( & argv[iArg][2] );
if ( c > 0 )
{
fCtl |= fMulti;
cPass = c;
}
fCtl |= fInference;
break;
}
case 'P':
// Get the name of the inference output file
fCtl |= fOutputFile | fInference;
if ( ++iArg == argc )
die("no output inference result file name given");
rgfn[EFN_INFER] = argv[iArg];
break;
case 'S':
// Write the input DSC file as an output file
fCtl |= fSaveDsc;
if ( ++iArg == argc )
die("no output DSC file name given");
rgfn[EFN_OUT] = argv[iArg];
break;
case 'T':
// Display start and stop times
fCtl |= fShowTime;
break;
case 'X':
// Pause at times during execution to allow the user to measure
// memory usage
fCtl |= fPause;
break;
case 'Y':
// Clone the network after loading
fCtl |= fClone;
break;
case 'N':
// Write the symbolic name into the inference exercise output file
// instead of the default full name.
fCtl |= fSymName;
break;
case 'U':
// Compute utilities using inference
fCtl |= fUtil | fInference;
break;
case 'B':
// Compute troubleshooting utilities using inference
fCtl |= fTSUtil | fInference;
break;
case 'R':
fCtl |= fReg;
break;
case 'Z':
fCtl |= fInferStats;
break;
case 'M':
{ // Get the maximum estimated clique tree size
float f = atof( & argv[iArg][2] );
if ( f > 0.0 )
rMaxEstSize = f;
break;
}
case 'A':
{
if ( strlen( & argv[iArg][2] ) > 0 )
{
rImposs = atof( & argv[iArg][2] );
}
fCtl |= fImpossible;
break;
}
case 'D':
fCtl |= fDistributions;
break;
default:
die("unrecognized option") ;
break ;
}
}
break;
default:
if ( cFile == 0 )
rgfn[cFile++] = argv[iArg] ;
else
die("too many file names given");
break ;
}
}
fCtl |= fPassCountMask & cPass;
if ( cFile == 0 )
{
usage();
return 0;
}
// Display options and the debugging build mode
showOptions( fCtl );
// Display the start message
CTICKS tmStart = showPhase( fCtl, "BNTEST starts" );
if ( rMaxEstSize > 0.0 )
cout << "\nMaximum clique tree size estimate is " << rMaxEstSize;
// Test the parser and everything else
testParser( fCtl, rgfn, rMaxEstSize, rImposs );
// Display the stop message
showPhase( fCtl, "BNTEST completed", & tmStart );
// Print memory leaks of primary objects, if any
printResiduals();
cout << "\n";
return 0;
}