|
|
//+-------------------------------------------------------------------------
//
// 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; }
|