// file: $isip/class/numeric/Sigmoid/sigm_02.cc
// version: $Id: sigm_02.cc 7473 2001-11-13 15:30:23Z gao $
//

// isip include files
//
#include "Sigmoid.h"
#include <MatrixFloat.h>
#include <Console.h>

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

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

  // output the class name
  //
  if (level_a > Integral::NONE) {
    SysString 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();
  }

  // do a quick test of constructors, destructors, and memory management
  // methods
  //
  Sigmoid::setGrowSize(5);
  Sigmoid* sig0 = new Sigmoid;
  Sigmoid* sig1 = new Sigmoid(1.5);
  Sigmoid* sig2 = new Sigmoid(1.5, 2.5);
  Sigmoid* sig3 = new Sigmoid(1.5, 2.5, 3.5);
  Sigmoid* sig3a = new Sigmoid(1.5, 2.5, 3.5, 4.5);
  Sigmoid* sig4 = new Sigmoid[10];

  // clear pointers
  //
  delete sig0;
  delete [] sig4;

  // verify that the constructors worked
  //
  if ((sig1->gain_d != (Float)1.5) || (sig1->slope_d != (Float)DEF_SLOPE) ||
      (sig1->xoffset_d != (Float)DEF_XOFFSET) ||
      (sig1->yoffset_d != (Float)DEF_YOFFSET)) {
    sig1->debug(L"sig1");
    return Error::handle(name(), L"constructor", Error::TEST,
			 __FILE__, __LINE__);
  }

  if ((sig2->gain_d != (Float)1.5) || (sig2->slope_d != (Float)2.5) ||
      (sig2->xoffset_d != DEF_XOFFSET) || (sig2->yoffset_d != DEF_YOFFSET)) {
    sig2->debug(L"sig2");
    return Error::handle(name(), L"constructor", Error::TEST,
			 __FILE__, __LINE__);
  }

  if ((sig3->gain_d != (Float)1.5) || (sig3->slope_d != (Float)2.5) ||
      (sig3->xoffset_d != (Float)3.5) ||
      (sig3->yoffset_d != (Float)DEF_YOFFSET)) {
    sig3->debug(L"sig3");
    return Error::handle(name(), L"constructor", Error::TEST,
			 __FILE__, __LINE__);
  }

  if ((sig3a->gain_d != (Float)1.5) || (sig3a->slope_d != (Float)2.5) ||
      (sig3a->xoffset_d != (Float)3.5) || (sig3a->yoffset_d != (Float)4.5)) {
    sig3a->debug(L"sig3a");
    return Error::handle(name(), L"constructor", Error::TEST,
			 __FILE__, __LINE__);
  }

  // test the setDebug method
  //
  setDebug(debug_level_d);
  
  // test the copy constructor, assign, and eq methods
  //
  delete sig1;
  delete sig2;
  sig1 = new Sigmoid(*sig3a);
  sig2 = new Sigmoid;
  sig2->assign(*sig3a);

  if ((sig1->gain_d != (Float)1.5) || (sig1->slope_d != (Float)2.5) ||
      (sig1->xoffset_d != (Float)3.5) || (sig1->yoffset_d != (Float)4.5)) {
    sig1->debug(L"sig1");
    return Error::handle(name(), L"copy constructor", Error::TEST,
			 __FILE__, __LINE__);
  }

  if ((sig2->gain_d != (Float)1.5) || (sig2->slope_d != (Float)2.5) ||
      (sig2->xoffset_d != (Float)3.5) || (sig2->yoffset_d != (Float)4.5)) {
    sig2->debug(L"sig2");
    return Error::handle(name(), L"assign", Error::TEST, __FILE__, __LINE__);
  }

  if (!sig2->eq(*sig3a)) {
    sig2->debug(L"sig2");
    sig3a->debug(L"sig3a");
    return Error::handle(name(), L"eq", Error::TEST, __FILE__, __LINE__);
  }

  // test the clear method
  //
  delete sig1;
  sig1 = new Sigmoid;
  sig2->clear();
  if (!sig2->eq(*sig1)) {
    sig1->debug(L"sig1");
    sig2->debug(L"sig2");
    return Error::handle(name(), L"clear", Error::TEST, __FILE__, __LINE__);
  }

  // test the operator= method
  //
  *sig2 = *sig3a;
  if (!sig2->eq(*sig3a)) {
    sig2->debug(L"sig2");
    sig3a->debug(L"sig3a");
    return Error::handle(name(), L"operator=", Error::TEST, __FILE__,__LINE__);
  }

  // clear the pointers
  //
  delete sig1;
  delete sig2;
  delete sig3;
  delete sig3a;  

  // test i/o methods
  //
  Sigmoid sig5(1.7, -1.3, 4.5, 9.0);
  Sigmoid sig6(2.9, -13.3, 95.3, 9.0);  

  // 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);

  sig5.write(tmp_file0, 0);
  sig6.write(tmp_file0, 1);

  sig5.write(tmp_file1, 0);
  sig6.write(tmp_file1, 1);

  // 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 sigmoids from the text file
  //
  Sigmoid sig7;
  Sigmoid sig8;
  sig7.read(tmp_file0, 0);
  sig8.read(tmp_file0, 1);

  if (!sig7.eq(sig5) || !sig8.eq(sig6)) {
    sig5.debug(L"sig5");
    sig6.debug(L"sig6");
    sig7.debug(L"sig7");
    sig8.debug(L"sig8");
    return Error::handle(name(), L"read/write text", Error::TEST,
			 __FILE__, __LINE__);
  }

  // read the sigmoids from the binary file
  //
  sig7.clear();
  sig8.clear();
  sig7.read(tmp_file1, 0);
  sig8.read(tmp_file1, 1);

  if (!sig7.eq(sig5) || !sig8.eq(sig6)) {
    sig5.debug(L"sig5");
    sig6.debug(L"sig6");
    sig7.debug(L"sig7");
    sig8.debug(L"sig8");
    return Error::handle(name(), L"read/write binary", Error::TEST,
			 __FILE__, __LINE__);
  }

  // close and  delete the temporary files
  //
  tmp_file0.close();
  tmp_file1.close();
  
  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  //---------------------------------------------------------------------------
  //
  // 2. class-specific public methods:
  //     set/get methods
  //
  //---------------------------------------------------------------------------

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

  // test the parameter set methods
  //
  sig5.setGain(-4.9);
  sig5.setSlope(3.7);
  sig5.setXOffset(-2.1);
  sig5.setYOffset(14.3);
  sig6.set(-4.9, 3.7, -2.1, 14.3);

  if ((sig5.gain_d != (float)-4.9) ||
      (sig5.slope_d != (float)3.7) ||
      (sig5.xoffset_d != (float)-2.1) ||
      (sig5.yoffset_d != (float)14.3) ||
      !sig5.eq(sig6)) {
    sig5.debug(L"sig5");
    sig6.debug(L"sig6");
    return Error::handle(name(), L"set methods", Error::TEST,
			 __FILE__, __LINE__);
  }

  // test the parameter get methods
  //
  if ((sig5.getGain() != (float)-4.9) || (sig5.getSlope() != (float)3.7) ||
      (sig5.getXOffset() != (float)-2.1) ||
      (sig5.getYOffset() != (float)14.3)) {
    sig5.debug(L"sig5");
    return Error::handle(name(), L"get methods", Error::TEST,
			 __FILE__, __LINE__);
  }

  float tmp_gain = 0;
  float tmp_slope = 0;
  float tmp_xoffset = 0;
  float tmp_yoffset = 0;

  sig5.get(tmp_gain, tmp_slope, tmp_xoffset, tmp_yoffset);
  if ((tmp_gain != (float)-4.9) || (tmp_slope != (float)3.7) ||
      (tmp_xoffset != (float)-2.1) || (tmp_yoffset != (float)14.3)) {
    sig5.debug(L"sig5");
    return Error::handle(name(), L"get method", Error::TEST,
			 __FILE__, __LINE__);
  }
  
  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  //---------------------------------------------------------------------------
  //
  // 3. class-specific public methods:
  //     computation methods
  //
  //---------------------------------------------------------------------------

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

  // create value vectors for testing
  //
  VectorFloat gains(L"1, -5, 10");
  VectorFloat slopes(L"1, -5, 10");
  VectorFloat xoffsets(L"0, -1, 5");
  VectorFloat yoffsets(L"0, -1, 5");
  VectorFloat inputs(L"0, -1.3, 17");
  VectorFloat outputs;
  VectorFloat expected_values;
  
  long num_inputs = inputs.length();
  long num_configurations = gains.length();

  MatrixFloat expect(num_configurations, num_inputs,
		     L"0.500000, 0.214165, 0.999999, -1.033464, -5.087872, -1.000000, 5.000000, 5.000000, 15.000000", Integral::FULL);
  MatrixFloat expectdydx(num_configurations, num_inputs,
                         L"0.250000, 0.168298, 4.139937e-08, 0.166201, 3.728661, 2.048503e-38, 1.928749e-20, 4.359610e-26, 7.667648e-72", Integral::FULL);
  MatrixFloat expectdydgain(num_configurations, num_inputs,
			     L"0.500000, 0.214165, 0.999999, 0.006693, 0.817574, 8.194013e-40, 1.928749e-22, 4.359610e-28, 1.000000", Integral::FULL);  
  MatrixFloat expectdydslope(num_configurations, num_inputs,
			     L"0.000000, -0.218788, 7.037894e-07, -0.0332403, 0.22371968, -7.374611e-38, 0.000000, -5.667493e-27, 1.303500e-50", Integral::FULL);
  MatrixFloat expectdydxoffset(num_configurations, num_inputs,
			       L"-0.250000, -0.168298, -4.139937e-08, -0.166201, -3.728661, -2.048503e-38, -9.643749e-21, -2.746554e-26, 9.2011777e-51", Integral::FULL);
  MatrixFloat expectdydyoffset(num_configurations, num_inputs,
			       L"1, 1, 1, 1, 1, 1, 1, 1, 1", Integral::FULL);
  
  // loop over sigmoid configurations, the vector compute calls internally
  // call the base methods except for derivativeOffset
  //
  Sigmoid sig_test;
  for (long j = 0; j < num_configurations; j++) {

    sig_test.set(gains(j), slopes(j), xoffsets(j), yoffsets(j));

    // compute the sigmoid
    //
    sig_test.compute(outputs, inputs);
    expect.getRow(expected_values, j);
    if (!outputs.almostEqual(expected_values)) {
      outputs.debug(L"actual");
      expected_values.debug(L"expected");
      Error::handle(name(), L"compute", Error::TEST, __FILE__, __LINE__);
    }

    // compute dy/dx
    //
    sig_test.derivative(outputs, inputs);
    expectdydx.getRow(expected_values, j);
    if (!outputs.almostEqual(expected_values)) {
      outputs.debug(L"actual");
      expected_values.debug(L"expected");
      Error::handle(name(), L"derivative", Error::TEST, __FILE__, __LINE__);
    }

    // compute dy/dgain
    //
    sig_test.derivativeGain(outputs, inputs);
    expectdydgain.getRow(expected_values, j);
    if (!outputs.almostEqual(expected_values)) {
      outputs.debug(L"actual");
      expected_values.debug(L"expected");
      Error::handle(name(), L"derivativeGain", Error::TEST, __FILE__,
		    __LINE__);
    }

    // compute dy/dslope
    //
    sig_test.derivativeSlope(outputs, inputs);
    expectdydslope.getRow(expected_values, j);
    if (!outputs.almostEqual(expected_values)) {
      outputs.debug(L"actual");
      expected_values.debug(L"expected");
      Error::handle(name(), L"derivativeSlope", Error::TEST, __FILE__,
		    __LINE__);
    }

    // compute dy/dxoffset
    //
    sig_test.derivativeXOffset(outputs, inputs);
    expectdydxoffset.getRow(expected_values, j);
    if (!outputs.almostEqual(expected_values)) {
      outputs.debug(L"actual");
      expected_values.debug(L"expected");
      Error::handle(name(), L"derivativeXOffset", Error::TEST, __FILE__,
		    __LINE__);
    }

    // compute dy/doffset
    //
    sig_test.derivativeYOffset(outputs, inputs);
    expectdydyoffset.getRow(expected_values, j);
    if (!outputs.almostEqual(expected_values)) {
      outputs.debug(L"actual");
      expected_values.debug(L"expected");
      Error::handle(name(), L"derivativeYOffset", Error::TEST, __FILE__,
		    __LINE__);
    }
  }

  // test the single value derivativeOffset method
  //
  float actual_tmp = 0;
  sig_test.derivativeYOffset(actual_tmp, (float)inputs(0));
  if (!Integral::almostEqual(actual_tmp, 1)) {
      Error::handle(name(), L"derivativeOffset", Error::TEST, __FILE__,
		    __LINE__);
  }

  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  //---------------------------------------------------------------------------
  //
  // 4. print completion message
  //
  //---------------------------------------------------------------------------

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

  // exit gracefully
  //
  return true;
}
