// slp.c                Section: 13.5 Single Layer Perceptron (SLP)
//      Copyright 1994, Center for Computer Vision and Visualization,
//      University of Florida.  All rights reserved.
//

#include "slp.h"
#include "ImageIter.h"

// An uninitialized Single Layer Perceptron
// Initialize weights to random values
SLP::SLP( double nu, unsigned int m ) {

    this->nu = nu;  // The learning rate

    slp_weight = extend_to_point( 0.0, m+1 );  // create the point

    // Initialize the point with random values
    for( int i=0; i<=m; i++ ) {
	slp_weight[i] = drand48();
    }
}


// Classification of a point
int
SLP::operator() (const IA_Point<double> & pattern ) const {

    // The pattern should be of one less dimension than slp_weight
    if ( slp_weight.dim() != (pattern.dim() + 1) ) {
	ia_throw( IA::POINT_INDEX_MISMATCH, __FILE__, __LINE__ );
	return 0;
    }

    // Extend the pattern by pre-pending a <1>
    //  and multiple by the weight.  

    return
	SLP::f( sum( concat( 1.0, pattern ) * slp_weight ) );
};


// Return a copy of this SLP
IA_Closure<IA_Point<double>,int> *
SLP::clone_self() const {

    return
	new SLP( *this );
};


// Limiter function
unsigned int
SLP::f( double x ) {

    return
	( x > 0 ) ? 1 : 0;
};



// Adjust the weight of the SLP for a single pattern
void
SLP::adjust_weight(const IA_Point<double> & pattern,
		   const IA_Bit           & y_class ) {

    double  y_prime = (*this)( pattern );  // Compute the class number

    // STEP 3.  Adjust weights.
    
    slp_weight += nu * ( double(y_class) - y_prime ) *
	concat( 1.0, pattern );

};


// Training function, does NOT reset weights to random values.
// An implication of this fact is that the SLP can be partially
//    trained, examined, and then trained some more.
//
// Each point in the domain of the image represents a pattern,
//    the value of the image at each point is the classification
//    of the pattern the point represents.
// The number of times patterns is examined is returned.
int
SLP::train( const IA_Image<IA_Point<double>,IA_Bit> & patterns,
	    unsigned int max_iterations ) {

    // STEP 1.  SLP weight has already be initialized.

    // STEP 2.  Present each pattern and compute it\'s class number.
    //          Loop until convergence or until max_iterations.
    IA_IPIter<IA_Point<double>,IA_Bit>
//	ipIter( patterns );
	ipIter( (const IA_CoreImage<IA_Point<double>,IA_Bit>&) patterns );

    IA_Point<double>
	pattern,
	v = extend_to_point( 0.0, slp_weight.dim() );
    IA_Bit y_class;
    unsigned int count = 0;

    while( (v != this->weight()) && (count++ < max_iterations) ) {

	v = this->weight();
	while( ipIter( pattern, y_class ) ) {
	    adjust_weight( pattern, y_class );
	};
	ipIter.reset();  // Prepare for next iteration

    };

    return count;
};


// Read the stream for the training patterns.
// Assume the stream has one <class> <pattern values> pair per line.
// Max_iterations is the maximum number of times the stream values
//    should be consulted to train to convergence.
// The number of times the stream is constulted is returned.
int
SLP::train( istream & training_stream,
       unsigned int max_iterations ) {

    int training_count = 0;
    int done = 0;

    streampos  start_of_stream = training_stream.tellg();

    unsigned int m_value = this->slp_weight.dim() - 1;
    IA_Point<double>
	v = extend_to_point( 0.0, this->slp_weight.dim() );

    while( (v != this->weight()) &&
	   (training_count < max_iterations) ) {
	// Position at the start of the stream.
	training_stream.seekg( start_of_stream );
	if ( !training_stream.good() ) {
	    cerr << "Could not position to start of stream." << endl;
	    return training_count;
	}

	// Remember the current weights
	IA_Point<double>
	    v = this->weight();

	// Train ourself based on the stream
	unsigned int  aClassification;
	training_count ++;
	while( ! training_stream.eof() ) {

	    // Read the classification of this pattern
	    training_stream >> aClassification;

	    // Read the pattern
	    IA_Point<double>
		aPattern = extend_to_point( 0.0, m_value );
	    for( unsigned int m=0; m < m_value; m++ ) {
		training_stream >> aPattern[ m ];
	    }

	    cerr << "Adjust weight: " << aClassification
		 << " " << aPattern << endl;
	    // Adjust the weight based on this pattern
	    adjust_weight( aPattern,
			   IA_Bit( aClassification ) );
	}
    }

    return training_count;
}


// Answer with the current weight vector
double
SLP::learning_rate() const {
    return nu;
};



// Answer with the current weight vector
IA_Point<double>
SLP::weight() const {
    return slp_weight;
  };
