// file: $isip/class/algo/FourierTransform/ft_02.cc
// version: $Id: ft_02.cc 8307 2002-07-08 00:14:40Z picone $
//

// isip include files
//
#include "FourierTransform.h"
#include <Console.h>

// method: diagnose
//
// arguments:
//  Integral::DEBUG level: (input) debug level for diagnostics.
//
// return: a boolean value indicating status
//
boolean FourierTransform::diagnose(Integral::DEBUG level_a) {

  // ---------------------------------------------------------------------
  //
  // 0. preliminaries
  //
  // ---------------------------------------------------------------------

  // output the class name
  //
  if (level_a > Integral::NONE) {
    String output(L"diagnosing class ");
    output.concat(CLASS_NAME);
    output.concat(L": ");
    Console::put(output);
    Console::increaseIndention();
  } 

  // --------------------------------------------------------------------
  //
  // 1. required public methods
  //
  // --------------------------------------------------------------------

  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing required public methods...\n");
    Console::increaseIndention();
  }

  // test destructor/constructor(s) and memory management
  //
  FourierTransform ft0;
  ft0.setAlgorithm(FourierTransform::DFT);
  ft0.setImplementation(FourierTransform::CONVENTIONAL);
  ft0.setDirection(FourierTransform::FORWARD);
  
  FourierTransform ft1(ft0);
  
  if (!ft1.eq(ft0)) {
    return Error::handle(name(), L"copy constructor", Error::TEST,
			 __FILE__, __LINE__);
  }
  
  // test large allocation construction and deletion
  //
  if (level_a == Integral::ALL) {
    
    Console::put(L"\ntesting large chunk memory allocation and deletion:\n");
    
    // set the memory to a strange block size so we can hopefully catch any
    // frame overrun errors
    //
    FourierTransform::setGrowSize((long)500);
    
    FourierTransform* pft = new FourierTransform();
    
    for (long j = 1; j <= 100; j++) {
      FourierTransform** pfts = new FourierTransform*[j * 100];
      
      // create the objects
      //
      for (long i = 0; i < j * 100; i++) {
	pfts[i] = new FourierTransform();
      }
      
      // delete objects
      //
      for (long i = (j * 100) - 1; i >= 0; i--) {
	delete pfts[i];
      }
      
      delete [] pfts;
    } 
    
    delete pft;
  }

  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  //--------------------------------------------------------------------------
  //
  // 2. required public methods
  //     i/o methods 
  //
  //--------------------------------------------------------------------------
  
  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing required public methods: i/o methods...\n");
    Console::increaseIndention();
  }
  
  // test the i/o methods 
  //
  FourierTransform ft2;
  ft0.set(DCT, TYPE_III, INVERSE, FULL, FIXED, 256);
  
  // we need binary and text sof files
  //
  String tmp_filename0;
  Integral::makeTemp(tmp_filename0);
  String tmp_filename1;
  Integral::makeTemp(tmp_filename1);

  // open files in write mode
  //
  Sof tmp_file0;
  tmp_file0.open(tmp_filename0, File::WRITE_ONLY, File::TEXT);
  Sof tmp_file1;
  tmp_file1.open(tmp_filename1, File::WRITE_ONLY, File::BINARY);

  ft0.write(tmp_file0, (long)0);
  ft0.write(tmp_file1, (long)0);

  // close the files
  //
  tmp_file0.close();
  tmp_file1.close();

  // open the files in read mode
  //
  tmp_file0.open(tmp_filename0);
  tmp_file1.open(tmp_filename1);

  // read the value back
  //
  if (!ft1.read(tmp_file0, (long)0) ||
      (ft1.getAlgorithm() != ft0.getAlgorithm()) ||
      (ft1.getImplementation() != ft0.getImplementation())) {
    return Error::handle(name(), L"diagnose", Error::TEST, __FILE__, __LINE__);
  }

  if (!ft2.read(tmp_file0, (long)0) ||
      (ft2.getAlgorithm() != ft0.getAlgorithm()) ||
      (ft2.getImplementation() != ft0.getImplementation())) {
    return Error::handle(name(), L"diagnose", Error::TEST, __FILE__, __LINE__);
  }

  // close and delete the temporary files
  //
  tmp_file0.close();
  tmp_file1.close();
  
  File::remove(tmp_filename0);
  File::remove(tmp_filename1);

  // test the conditional i/o methods
  //
  Sof tmp_file2;
  String tmp_filename2;
  Integral::makeTemp(tmp_filename2);

  tmp_file2.open(tmp_filename2, File::WRITE_ONLY);
  
  tmp_file2.put(CLASS_NAME, 5, -1);
  String line;

  line.assign(L"implementation = CONVENTIONAL;\n");
  tmp_file2.puts(line);

  // close the file
  //
  tmp_file2.close();

  // open the file in read mode
  //
  tmp_file2.open(tmp_filename2);

  ft0.read(tmp_file2, (long)5);

  if (ft0.getImplementation() != CONVENTIONAL) {
    return Error::handle(name(), L"readData", Error::TEST, __FILE__, __LINE__);
  }
  
  // close and  delete the temporary files
  //
  tmp_file2.close();
  
  File::remove(tmp_filename2);

  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }

  //---------------------------------------------------------------------------
  //
  // 3. class-specific public methods:
  //     set and get methods
  //
  //---------------------------------------------------------------------------
  
  // set indentation
  //
  if (level_a > Integral::NONE) {  
    Console::put(L"testing class-specific public methods: set and get methods...\n");
    Console::increaseIndention();
  }

  // establish an object
  //
  ft0.setAlgorithm(FourierTransform::FFT);
  ft0.setImplementation(FourierTransform::QF);
  ft0.setDirection(FourierTransform::INVERSE);
  ft0.setOutputType(FourierTransform::SYMMETRIC);
  ft0.setResolution(FourierTransform::FIXED);
  ft0.setNormalization(FourierTransform::UNIT_ENERGY);

  // check that the values were set
  //
  if (ft0.algorithm_d != FFT) {
    return Error::handle(name(), L"setAlgorithm", Error::TEST,
			 __FILE__, __LINE__);
  }
  else if (ft0.implementation_d != QF) {
    return Error::handle(name(), L"setImplementation", Error::TEST,
			 __FILE__, __LINE__);
  }
  else if (ft0.direction_d != INVERSE) {
    return Error::handle(name(), L"setDirection", Error::TEST,
			 __FILE__, __LINE__);
  }
  else if (ft0.otype_d != SYMMETRIC) {
    return Error::handle(name(), L"setOutputType", Error::TEST,
			 __FILE__, __LINE__);
  }
  else if (ft0.resolution_d != FIXED) {
    return Error::handle(name(), L"setResolution", Error::TEST,
			 __FILE__, __LINE__);
  }
  else if (ft0.normalization_d != UNIT_ENERGY) {
    return Error::handle(name(), L"setNormalization", Error::TEST,
			 __FILE__, __LINE__);
  }
  
  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  //--------------------------------------------------------------------------
  //
  // 4. class-specific public methods:
  //     computation methods
  //
  //--------------------------------------------------------------------------

  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing required public methods: computation methods...\n");
    Console::increaseIndention();
  }

  // setup variables for loop structure
  //
  ALGORITHM forward_algo[] = { DFT, DFT, FFT, FFT, FFT, FFT, FFT, FFT };
  IMPLEMENTATION forward_impl[] = { CONVENTIONAL, TRIGONOMETRIC, SPLIT_RADIX,
				    RADIX_2, RADIX_4, DITF, FAST_HARTLEY, QF };
  ALGORITHM inverse_algo[] = { DFT, DFT, FFT, FFT, FFT, FFT, FFT, FFT };
  IMPLEMENTATION inverse_impl[] = { CONVENTIONAL, TRIGONOMETRIC, SPLIT_RADIX,
				    RADIX_2, RADIX_4, DITF, FAST_HARTLEY, QF };
  long data_lengths[] = { 16, 64, 256, 1024, 16384 };
  long desired_lengths[] = { 64, 16, 256, 16384, 1024 };
  long num_impls = 8;
  long num_inverse_impls = 8;
  long num_lengths = 5; 

  // if this is a brief debugging mode then only do the bare minimum testing
  //
  if (level_a <= Integral::BRIEF) {
    num_impls = 2;
    num_inverse_impls = 2;
    num_lengths = 3;
    data_lengths[0] = 64;
    data_lengths[1] = 1024;
    data_lengths[2] = 16384;
    desired_lengths[0] = 16384;
    desired_lengths[1] = 64;
    desired_lengths[2] = 1024;
  }

  // setup minimum dB precisions
  //
  double min_float_prec = 110.0;

  ft0.setAlgorithm(DFT);

  // check the assign and equality methods
  //
  FourierTransform ft4(ft0);
  ft1.assign(ft0);

  if (!(ft1.eq(ft0)) || (!ft4.eq(ft0))) {
    Error::handle(name(), L"eq", Error::TEST, __FILE__, __LINE__);
  }
  
  // loop where dft is used rather than zero-padding
  //
  if (level_a > Integral::BRIEF) {
    Console::put(L"\ntesting implementations. running randomized data through a");
    Console::put(L"forward transform and then inverting. if the SNRs");
    Console::put(L"between the input and inverse are less than 80dB for");
    Console::put(L"floats then the test fails. the DFT is used for ");
    Console::put(L"transforms where the size is incompatible\n");      
  }
  
  // create a block for floats
  //
  
  // create a temporary FourierTransform object for computation
  //
  FourierTransform ft5;
  ft5.setResolution(FourierTransform::AUTO);
  
  // setup vectors for holding data
  //
  VectorFloat real_input_vector;
  real_input_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  VectorComplexFloat complex_input_vector;  
  complex_input_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  VectorComplexFloat freq_vector;
  freq_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  VectorComplexFloat inverse_vector;
  inverse_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  VectorFloat real_inverse_vector;
  real_inverse_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  VectorFloat real_diff_vector;
  real_diff_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  VectorComplexFloat complex_diff_vector;
  complex_diff_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  // loop over the different data lengths
  //
  for (long length_ind = 0; length_ind < num_lengths; length_ind++) {
    
    long curr_length = data_lengths[length_ind];
    
    // clear all of the data arrays and start over
    //
    real_input_vector.setLength(curr_length, false);
    complex_input_vector.setLength(curr_length, false);
    
    // loop over zero, constant and random test data type
    //
    for (long test_data_type = 0; test_data_type < 3; test_data_type++) {
      
      // generate zero input data
      //
      if (test_data_type == 0) {
	real_input_vector.assign(curr_length, (float)0.0);
	ComplexFloat c_zero(0.0);
	complex_input_vector.assign(curr_length, c_zero);
      }
      
      // generate const input data
      //
      else if (test_data_type == 1) {
	real_input_vector.assign(curr_length, (float)1.0);
	ComplexFloat c_one(1.0);
	complex_input_vector.assign(curr_length, c_one);
      }
      
      // generate random input data
      //
      else if (test_data_type == 2) {
	real_input_vector.rand();
	complex_input_vector.rand();
      }
      
      // loop over the different implementation types for doing the forward
      // transform
      //
      for (long forward_ind = 0; forward_ind < num_impls; forward_ind++) {
 	
	// set the forward transform algorithm and implementation
	// for this pass
	//
	ALGORITHM curr_forward_algo = forward_algo[forward_ind];
	IMPLEMENTATION curr_forward_impl = forward_impl[forward_ind];
	
	// loop over the different implementation types for doing
	// the inverse transform
	//
	for (long inverse_ind = 0; inverse_ind < num_inverse_impls;
	     inverse_ind++) {
	  
	  // set the inverse transform algorithm and
	  // implementation for this pass
	  //
	  ALGORITHM curr_inverse_algo = inverse_algo[inverse_ind];
	  IMPLEMENTATION curr_inverse_impl = inverse_impl[inverse_ind];
	  
	  // setup the forward real transform
	  //
	  ft5.setAlgorithm(curr_forward_algo);
	  ft5.setImplementation(curr_forward_impl);
	  
	  // output the experiment characteristics
	  //
	  if (level_a >= Integral::DETAILED) {
	    
	    String value;
	    String output;
	    
	    // output the current data precision
	    //
	    value.assign(L"float");
	    output.debugStr(name(), L"forward transform", L"data precision",
			    value);
	    Console::put(output);
	    
	    // output the current order
	    //
	    value.assign(curr_length);
	    output.debugStr(name(), L"forward transform", L"transform order",
			    value);
	    Console::put(output);
	    
	    // output the forward implementation type
	    //
	    output.debugStr(name(), L"forward transform", L"forward impl",
			    IMPL_MAP.getName(ft5.getImplementation()));
	    Console::put(output);
	  }
	  
	  // make sure we aren't doing a dft on a very large order
	  //
	  if (((curr_forward_algo != DFT) && (curr_inverse_algo != DFT)) ||
	      (curr_length <= 1024)) {
	    
	    // compute the forward real transform
	    //
	    ft5.setDirection(FourierTransform::FORWARD);	      
	    if (!ft5.compute(freq_vector, real_input_vector)) {
	      Error::handle(name(), L"compute", Error::TEST,
			    __FILE__, __LINE__);
	    }
	    
	    // now setup the the inverse complex transform
	    //
	    ft5.setAlgorithm(curr_inverse_algo);
	    ft5.setImplementation(curr_inverse_impl);
	    ft5.setDirection(FourierTransform::INVERSE);
	    
	    // output the experiment characteristics
	    //
	    if (level_a >= Integral::DETAILED) {
	      
	      String value;
	      String output;
	      
	      // output the inverse implementation type
	      //
	      output.debugStr(name(), L"inverse transform", L"inverse impl",
			      IMPL_MAP.getName(ft5.getImplementation()));
	      Console::put(output);
	      
	      // output input and output vectors
	      //
	      real_input_vector.debug(L"real_input");
	      freq_vector.debug(L"freq_output");
	    }
	    
	    // compute the inverse complex transform
	    //
	    if (!ft5.compute(inverse_vector, freq_vector)) {
	      Error::handle(name(), L"compute", Error::TEST,
			    __FILE__, __LINE__);
	    }
	    
	    // compute the snr for this operation
	    //
	    // get the sum of the squares for the input
	    //
	    double signal_power = real_input_vector.sumSquare();
	    
	    // get the real part of the inverse vector
	    //
	    inverse_vector.real(real_inverse_vector);
	    
	    // generate the difference signal - for the real transform, we
	    // need to remove the 0.0 imaginary parts - we'll just add them
	    // to the noise component
	    //
	    real_diff_vector.sub(real_inverse_vector, real_input_vector);
	    double noise_power = (double)real_diff_vector.sumSquare();
	    
	    // compute the snr
	    //
	    Double snr = signal_power / noise_power;
	    snr.log10();
	    snr *= Integral::DB_POW_SCALE_FACTOR;
	    
	    // print the snr
	    //
	    if (level_a >= Integral::DETAILED) {
	      
	      String value;
	      String output;
	      
	      // output the current order
	      //
	      value.assign(snr, L"%g");
	      output.debugStr(name(), L"forward to inverse", L"SNR",
			      value);
	      Console::put(output);
	      
	      // output vectors
	      //
	      inverse_vector.debug(L"inverse_output");
	      
	      Float temp_float;
	      temp_float = signal_power;
	      temp_float.debug(L"signal_power");
	      temp_float = noise_power;
	      temp_float.debug(L"noise_power");
	    }
	    
	    // print a separating line
	    //
	    if (level_a >= Integral::DETAILED) {
	      Console::put(L"\n");
	    }
	    
	    // repeat for complex transforms:
	    //  setup the forward complex transform
	    //
	    ft5.setAlgorithm(curr_forward_algo);
	    ft5.setImplementation(curr_forward_impl);
	    
	    // output the experiment characteristics
	    //
	    if (level_a >= Integral::DETAILED) {
	      
	      String value;
	      String output;
	      
	      // output the data precision
	      //
	      value.assign(L"float");
	      output.debugStr(name(), L"forward transform",
			      L"data precision", value);
	      Console::put(output);
	      
	      // output the current order
	      //
	      value.assign(curr_length);
	      output.debugStr(name(), L"forward transform",
			      L"transform order", value);
	      Console::put(output);
	      
	      // output the forward implementation type
	      //
	      output.debugStr(name(), L"forward transform", L"forward impl",
			      IMPL_MAP.getName(ft5.getImplementation()));
	      Console::put(output);
	    }
	    
	    // compute the forward complex transform
	    //
	    ft5.setDirection(FourierTransform::FORWARD);
	    if (!ft5.compute(freq_vector, complex_input_vector)) {
	      Error::handle(name(), L"compute", Error::TEST, __FILE__,
			    __LINE__);
	    }
	    
	    // now setup the the inverse complex transform
	    //
	    ft5.setAlgorithm(curr_inverse_algo);		
	    ft5.setImplementation(curr_inverse_impl);
	    ft5.setDirection(FourierTransform::INVERSE);
	    
	    // output the experiment characteristics
	    //
	    if (level_a >= Integral::DETAILED) {
	      
	      String value;
	      String output;
	      
	      // output the current order
	      //
	      output.debugStr(name(), L"inverse transform", L"inverse impl",
			      IMPL_MAP.getName(ft5.getImplementation()));
	      Console::put(output);
	      
	      // output input and output vectors
	      //
	      complex_input_vector.debug(L"complex_input");
	      freq_vector.debug(L"freq_output");
	    }
	    
	    // compute the inverse complex transform
	    //
	    if (!ft5.compute(inverse_vector, freq_vector)) {
	      Error::handle(name(), L"compute", Error::TEST, __FILE__,
			    __LINE__);
	    }
	    
	    // compute the snr for this operation
	    //  get the sum of the squares for the input
	    //
	    // signal_power = complex_input_vector.sumSquare();
	    //
	    signal_power = 0;
	    for (long i = 0; i < complex_input_vector.length(); i++) {
	      float temp_float = complex_input_vector(i).mag();
	      signal_power += temp_float * temp_float;
	    }
	    
	    // generate the difference signal - for the real transform, we
	    // need to remove the 0.0 imaginary parts - we'll just add them
	    // to the noise component
	    //
	    complex_diff_vector.assign(complex_input_vector);
	    complex_diff_vector.sub(inverse_vector);
	    
	    // noise_power = complex_diff_vector.sumSquare();
	    //
	    noise_power = 0;
	    for (long i = 0; i < complex_diff_vector.length(); i++) {
	      float temp_float = complex_diff_vector(i).mag();
	      noise_power += temp_float * temp_float;
	    }
	    
	    // compute the snr
	    //
	    snr = signal_power / noise_power;
	    snr.log10();
	    snr *= Integral::DB_POW_SCALE_FACTOR;
	    
	    // print the snr
	    //
	    if (level_a >= Integral::DETAILED) {
	      
	      String value;
	      String output;
	      
	      // output the current order
	      //
	      value.assign(snr, L"%g");
	      output.debugStr(name(), L"forward to inverse", L"SNR",
			      value);
	      Console::put(output);
	      
	      // output vectors
	      //
	      inverse_vector.debug(L"inverse_output");
	      
	      Float temp_float;
	      temp_float = signal_power;
	      temp_float.debug(L"signal_power");
	      temp_float = noise_power;
	      temp_float.debug(L"noise_power");
	    }
	    
	    // make sure the snr is sufficient
	    //
	    if (snr < min_float_prec) {
	      Error::handle(name(), L"snr too low", Error::TEST, __FILE__,
			    __LINE__);
	    }
	    
	    // print a separating line
	    //
	    if (level_a >= Integral::DETAILED) {
	      Console::put(L"\n");
	    }
	  }
	  else {
	    if (level_a >= Integral::DETAILED) {	      
	      String tmp(L"skipping DFT test for this length");
	      Console::put(tmp);
	    }
	  }
	} // end loop over inverse transform implementations
      } // end loop over forward transform implementations
    } // end loop over the test_data_type
  } // end of data length loop
  
  // check implementation accuracy for zero-padded data
  //
  if (level_a > Integral::BRIEF) {
    Console::put(L"\ntesting implementations. running randomized data through a");
    Console::put(L"forward transform and then inverting. if the SNRs");
    Console::put(L"between the input and inverse are less than 100dB for");
    Console::put(L"floats the test fails. zero-padding is used for ");
    Console::put(L"transforms where the size is incompatible\n");
  }
  
  // create a block for floats
  //
  
  // create a temporary FourierTransform object for computation
  //
  ft5.setResolution(FourierTransform::FIXED);
  
  // setup vectors for holding data
  //
  real_input_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  complex_input_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  freq_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  inverse_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  real_inverse_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  real_diff_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  complex_diff_vector.setCapacity(data_lengths[num_lengths - 1]);
  
  // loop over the different data lengths
  //
  for (long length_ind = 0; length_ind < num_lengths; length_ind++) {
    
    long curr_length = data_lengths[length_ind];
    long curr_desired_length = desired_lengths[length_ind];
    
    // clear all of the data arrays and start over
    //
    real_input_vector.setLength(curr_length, false);
    complex_input_vector.setLength(curr_length, false);
    
    // generate the input data
    //
    real_input_vector.rand();
    complex_input_vector.rand();
    
    // loop over the different implementation types for doing the forward
    // transform
    //
    for (long forward_ind = 0; forward_ind < num_impls; forward_ind++) {
      
      // set the forward transform implementation for this pass
      //
      ALGORITHM curr_forward_algo = forward_algo[forward_ind];
      IMPLEMENTATION curr_forward_impl = forward_impl[forward_ind];
      
      // loop over the different implementation types for doing the inverse
      // transform
      //
      for (long inverse_ind = 0; inverse_ind < num_inverse_impls;
	   inverse_ind++) {
	
	// set the forward transform implementation for this pass
	//
	ALGORITHM curr_inverse_algo = inverse_algo[inverse_ind];
	IMPLEMENTATION curr_inverse_impl = inverse_impl[inverse_ind];
	
	// setup the forward real transform
	//
	ft5.setImplementation(curr_forward_impl);
	
	// output the experiment characteristics
	//
	if (level_a >= Integral::DETAILED) {
	  
	  String value;
	  String output;
	  
	  // output the data precision
	  //
	  value.assign(L"float");
	  output.debugStr(name(), L"forward transform", L"data precision",
			  value);
	  Console::put(output);
	  
	  // output the current order
	  //
	  value.assign(curr_desired_length);
	  output.debugStr(name(), L"forward transform", L"transform order",
			  value);
	  Console::put(output);
	  
	  // output the data length
	  //
	  value.assign(curr_length);
	  output.debugStr(name(), L"forward transform", L"data length",
			  value);
	  Console::put(output);
	  
	  // output the forward implementation type
	  //
	  output.debugStr(name(), L"forward transform", L"forward impl",
			  IMPL_MAP.getName(ft5.getImplementation()));
	  Console::put(output);
	}
	
	// make sure we aren't doing a dft on a very large order
	//
	if (((curr_forward_algo != DFT) && (curr_inverse_algo != DFT)) ||
	    (curr_desired_length <= 1024)) {
	  
	  // compute the forward real transform
	  //
	  ft5.setAlgorithm(curr_forward_algo);
	  ft5.setImplementation(curr_forward_impl);
	  ft5.setDirection(FourierTransform::FORWARD);
	  ft5.setOrder(curr_desired_length);
	  ft5.setInputLength(curr_desired_length);
	  ft5.setOutputLength(curr_desired_length);	      
	  if (!ft5.compute(freq_vector, real_input_vector)) {
	    Error::handle(name(), L"compute", Error::TEST, __FILE__,
			  __LINE__);
	  }
	  
	  // now setup the the inverse complex transform
	  //
	  ft5.setAlgorithm(curr_inverse_algo);
	  ft5.setImplementation(curr_inverse_impl);
	  ft5.setDirection(FourierTransform::INVERSE);
	  
	  // output the experiment characteristics
	  //
	  if (level_a >= Integral::DETAILED) {
	    
	    String value;
	    String output;
	    
	    // output the current order
	    //
	    output.debugStr(name(), L"inverse transform", L"inverse impl",
			    IMPL_MAP.getName(ft5.getImplementation()));
	    Console::put(output);
	    
	    // output input and output vectors
	    //
	    real_input_vector.debug(L"real_input");
	    freq_vector.debug(L"freq_output");
	  }
	  
	  // compute the inverse complex transform
	  //
	  ft5.setInputLength(freq_vector.length());
	  if (!ft5.compute(inverse_vector, freq_vector)) {
	    Error::handle(name(), L"compute", Error::TEST, __FILE__,
			  __LINE__);
	  }
	  
	  // compute the snr for this operation
	  //
	  // get the sum of the squares for the input
	  //
	  double signal_power = real_input_vector.sumSquare();
	  
	  // get the real part of the inverse vector
	  //
	  inverse_vector.real(real_inverse_vector);
	  
	  // generate the difference signal - for the real transform, we
	  // need to remove the 0.0 imaginary parts - we'll just add them
	  // to the noise component
	  //
	  real_diff_vector.assign(real_input_vector);
	  real_diff_vector.setLength(real_inverse_vector.length(), true);
	  real_diff_vector.sub(real_inverse_vector);
	  double noise_power = (double)real_diff_vector.sumSquare();
	  
	  // compute the snr
	  //
	  Double snr = signal_power / noise_power;
	  snr.log10();
	  snr *= Integral::DB_POW_SCALE_FACTOR;
	  
	  // print the snr
	  //
	  if (level_a >= Integral::DETAILED) {
	    
	    String value;
	    String output;
	    
	    // output the current order
	    //
	    value.assign(snr, L"%g");
	    output.debugStr(name(), L"forward to inverse", L"SNR",
			    value);
	    Console::put(output);
	    
	    // output vectors
	    //
	    inverse_vector.debug(L"inverse_output");
	    
	    Float temp_float;
	    temp_float = signal_power;
	    temp_float.debug(L"signal_power");
	    temp_float = noise_power;
	    temp_float.debug(L"noise_power");
	  }
	  
	  // make sure the snr is sufficient
	  //
	  if (snr < min_float_prec) {
	    real_input_vector.debug(L"real_input");
	    inverse_vector.debug(L"inverse_output");
	    String output, value;
	    value.assign(snr, L"%g");
	    output.debugStr(name(), L"forward to inverse", L"SNR",
			    value);
	    Console::put(output);		
	    Error::handle(name(), L"snr too low", Error::TEST, __FILE__,
			  __LINE__);
	  }

	  // print a separating line
	  //
	  if (level_a >= Integral::DETAILED) {
	    Console::put(L"\n");
	  }
		
	  // output the experiment characteristics
	  //
	  if (level_a >= Integral::DETAILED) {
		
	    String value;
	    String output;
		
	    // output the data precision
	    //
	    value.assign(L"float");
	    output.debugStr(name(), L"forward transform",
			    L"data precision", value);
	    Console::put(output);
		
	    // output the current order
	    //
	    value.assign(curr_desired_length);
	    output.debugStr(name(), L"forward transform",
			    L"transform order", value);
	    Console::put(output);

	    // output the data length
	    //
	    value.assign(curr_length);
	    output.debugStr(name(), L"forward transform",
			    L"data length", value);
	    Console::put(output);

	    // output the forward implementation type
	    //
	    output.debugStr(name(), L"forward transform", L"forward impl",
			    IMPL_MAP.getName(ft5.getImplementation()));
	    Console::put(output);
	  }
	      
	  // compute the forward complex transform
	  //
	  ft5.setAlgorithm(curr_forward_algo);
	  ft5.setImplementation(curr_forward_impl);
	  ft5.setDirection(FourierTransform::FORWARD);
	  ft5.setOrder(curr_desired_length);
	  ft5.setInputLength(curr_desired_length);
	  ft5.setOutputLength(curr_desired_length);	      
	  ft5.setOrder(curr_desired_length);
	  if (!ft5.compute(freq_vector, complex_input_vector)) {
	    Error::handle(name(), L"compute", Error::TEST, __FILE__,
			  __LINE__);
	  }
	      
	  // output the experiment characteristics
	  //
	  if (level_a >= Integral::DETAILED) {
		
	    String value;
	    String output;
		
	    // output the current order
	    //
	    output.debugStr(name(), L"inverse transform", L"inverse impl",
			    IMPL_MAP.getName(ft5.getImplementation()));
	    Console::put(output);

	    // output input and output vectors
	    //
	    complex_input_vector.debug(L"complex_input");
	    freq_vector.debug(L"freq_output");
	  }
	      
	  // compute the inverse complex transform
	  //
	  ft5.setAlgorithm(curr_inverse_algo);
	  ft5.setImplementation(curr_inverse_impl);
	  ft5.setDirection(FourierTransform::INVERSE);
	  ft5.setInputLength(freq_vector.length());
	  if (!ft5.compute(inverse_vector, freq_vector)) {
	    Error::handle(name(), L"compute", Error::TEST, __FILE__,
			  __LINE__);
	  }
	      
	  // compute the snr for this operation
	  //  get the sum of the squares for the input
	  //
	  // signal_power = complex_input_vector.sumSquare();
	  //
	  signal_power = 0;
	  for (long i = 0; i < complex_input_vector.length(); i++) {
	    float temp_float = complex_input_vector(i).mag();
	    signal_power += temp_float * temp_float;
	  }
	      
	  // generate the difference signal - for the complex transform,
	  //
	  complex_diff_vector.assign(complex_input_vector);
	  complex_diff_vector.setLength(inverse_vector.length(),
					true);
	  complex_diff_vector.sub(inverse_vector);

	      // noise_power = complex_diff_vector.sumSquare();
	      //
	  noise_power = 0;
	  for (long i = 0; i < complex_diff_vector.length(); i++) {
	    float temp_float = complex_diff_vector(i).mag();
	    noise_power += temp_float * temp_float;
	  }
	      
	  // compute the snr
	  //
	  snr = signal_power / noise_power;
	  snr.log10();
	  snr *= Integral::DB_POW_SCALE_FACTOR;
	      
	      // print the snr
	      //
	  if (level_a >= Integral::DETAILED) {
		
	    String value;
	    String output;
		
	    // output the current order
	    //
	    value.assign(snr, L"%g");
	    output.debugStr(name(), L"forward to inverse", L"SNR",
			    value);
	    Console::put(output);

	    // output vectors
	    //
	    inverse_vector.debug(L"inverse_output");

	    Float temp_float;
	    temp_float = signal_power;
	    temp_float.debug(L"signal_power");
	    temp_float = noise_power;
	    temp_float.debug(L"noise_power");
	  }

	  // make sure the snr is sufficient
	  //
	  if (snr < min_float_prec) {
	    Error::handle(name(), L"snr too low", Error::TEST, __FILE__,
			  __LINE__);
	  }

	  // print a separating line
	  //
	  if (level_a >= Integral::DETAILED) {
	    Console::put(L"\n");
	  }
	}
	else {
	  if (level_a >= Integral::DETAILED) {
	    String tmp(L"skipping DFT test for this length");
	    Console::put(tmp);
	  }
	}

      } // end loop over inverse transform implementations
    } // end loop over forward transform implementations
  } // end of data length loop
  
  // test FFT algorithm
  //  Case: real spectrum -> complex signal -> complex spectrum
  //
  
  // local variables
  //
  FourierTransform ft6;
  long N = 8;
  VectorFloat real_in(N), real_out, imag_out;
  VectorComplexFloat ft_forward_out, ft_inverse_out;
  real_in.assign(L"1, 2, 6, 8, 3, 7, 5, 4, 4, 5, 7, 3, 8, 6, 2, 1");
  
  // inverse transform
  //
  ft6.set(FourierTransform::FFT, FourierTransform::SPLIT_RADIX,
	 FourierTransform::INVERSE);
    
  ft6.setInputLength(N);

  if (!ft6.compute(ft_inverse_out, real_in)) {
    Error::handle(ft6.name(), L"compute", Error::TEST, __FILE__,
		  __LINE__);
  }
    
  // inverse transform
  //
  ft6.setDirection(FourierTransform::FORWARD);
    
  if (!ft6.compute(ft_forward_out, ft_inverse_out)) {
    Error::handle(ft6.name(), L"compute", Error::TEST, __FILE__,
		  __LINE__);
  }

  // compare inverse output to input
  //
  ft_forward_out.real(real_out);
  ft_forward_out.imag(imag_out);
  real_in.setLength(N);  
  if (!real_out.almostEqual(real_in) || !imag_out.almostEqual(0.0)) {
    imag_out.debug(L"imag output");
    ft_forward_out.debug(L"fft forward result");
    Error::handle(name(), L"fft computation error", Error::TEST, __FILE__,
		  __LINE__);
  }
  

  // test DFT algorithm
  //  Case: real signal -> complex spectrum -> complex signal
  //        input length < output length
  //
  
  // local variables
  //
  FourierTransform ft7;
  N = 7;
  IMPLEMENTATION impl[] = { CONVENTIONAL, TRIGONOMETRIC };
  long num_impl = 2;
  real_in.setCapacity(N + 1);
  real_in.assign(L"1, 2, 6, 8, 3");

  for (int i = 0; i < num_impl; i++) {

    // inverse transform
    //
    ft7.set(FourierTransform::DFT, impl[i], FourierTransform::FORWARD);
      
    ft7.setOutputLength(N);
      
    if (!ft7.compute(ft_forward_out, real_in)) {
      Error::handle(ft7.name(), L"compute", Error::TEST, __FILE__,
		    __LINE__);
    }

    // inverse transform
    //
    ft7.setDirection(FourierTransform::INVERSE);
    ft7.setOutputLength(real_in.length());
    if (!ft7.compute(ft_inverse_out, ft_forward_out)) {
      Error::handle(ft7.name(), L"compute", Error::TEST, __FILE__,
		    __LINE__);
    }

    // compare inverse output to input
    //
    ft_inverse_out.real(real_out);
    ft_inverse_out.imag(imag_out);
      
    if (!real_out.almostEqual(real_in) || !imag_out.almostEqual(0.0)) {
      imag_out.debug(L"imag out");
      ft_inverse_out.debug(L"fft inverse result");
      Error::handle(name(), L"fft computation error", Error::TEST, __FILE__,
		    __LINE__);
    }
  }
  
  // test DCT algorithm
  //

  // local variables
  //
  FourierTransform ft8;    
  N = 16;
  IMPLEMENTATION impl_dct[] = { TYPE_I, TYPE_II, TYPE_III, TYPE_IV };
  num_impl = 4;
  VectorFloat dct_forward_out, dct_inverse_out;
  real_in.setLength(N);  
  for (long i = 0; i < N; i++) {
    real_in(i).cos(2 * Integral::PI * i / N / 4);
  }
    
  for (int i = 0; i < num_impl; i++) {

    // forward transform
    //
    ft8.set(FourierTransform::DCT, impl_dct[i], FourierTransform::FORWARD);
    ft8.setOutputLength(N * 4);

    if (!ft8.compute(dct_forward_out, real_in)) {
      Error::handle(ft8.name(), L"compute", Error::TEST, __FILE__,
		    __LINE__);
    }
      
    // inverse transform
    //
    ft8.setDirection(FourierTransform::INVERSE);
    ft8.setInputLength(N);
      
    if (!ft8.compute(dct_inverse_out, dct_forward_out)) {
      Error::handle(ft8.name(), L"compute", Error::TEST, __FILE__,
		    __LINE__);
    }

    if (level_a >= Integral::DETAILED) {

      ft8.debug(L"DCT");
      real_in.debug(L"real_in");
      dct_forward_out.debug(L"dct_forward_out");
      dct_inverse_out.debug(L"dct_inverse_out");
    }

    // compare inverse output to input
    //
    dct_inverse_out.setLength(N);  
    if (!dct_inverse_out.almostEqual(real_in)) {
      real_in.debug(L"real input");
      dct_inverse_out.debug(L"dct inverse result");
      Error::handle(name(), L"dct computation error", Error::TEST, __FILE__,
		    __LINE__);
    }
  }
  
  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }

  // --------------------------------------------------------------------
  //
  // 5. print completion message
  //
  // --------------------------------------------------------------------

  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  if (level_a > Integral::NONE) {
    String output(L"diagnostics passed for class ");
    output.concat(name());
    output.concat(L"\n");
    Console::put(output);
  }

  // exit gracefully
  //
  return true;
}
