// hopfield.c           Section: 13.2
//      Copyright 1994, Center for Computer Vision and Visualization,
//      University of Florida.  All rights reserved.
// Here we define the main Hopfield functions and data structures.
//

#include "hopfield.h"
#include "make-examplars.h"


/////////////////////////////////////////////////////////////////////////
// Hopfield_hard_limiter()
// This function implements the hard limiter function  f()  as
//    defined for the Hopfield algorithm.
// a_lp_t == linear_product( a, t )
//
IA_Image<IA_Point<int>,int>
Hopfield_hard_limiter( const IA_Image<IA_Point<int>,int> & a_lp_t,
		       const IA_Image<IA_Point<int>,int> & a      ) {

    IA_Image<IA_Point<int>,int>
	tau_eq_zero( chi_eq( a_lp_t, 0 ) ),
	tau_gt_zero( chi_gt( a_lp_t, 0 ) ),
	tau_lt_zero( chi_lt( a_lp_t, 0 ) );

    return
	a * tau_eq_zero + tau_gt_zero - tau_lt_zero;
}



/////////////////////////////////////////////////////////////////////////
// class column2D
// Convert 1D points to 2D points.
// This class is given a column index at construction time.
// Then when evaluated with  operator()  returns a 2D point
//    which always is in the same column.
// E.g.
//    c4 = column2D( 4 ); // column index == 4
//    .....
//    ... c4( 2 ) ..;            // Yields point <  2,4>
//    ... c4( -17 ) ..;          // Yields point <-17,4>

class  column2D: public IA_Closure<IA_Point<int>,IA_Point<int> > {
private:
    int column;
public:
    column2D( int c ): column( c ) {}

    IA_Point<int> operator() ( const IA_Point<int> & rowPt ) const {
	return concat( rowPt, column );
    }

    IA_Closure<IA_Point<int>,IA_Point<int> > *
    clone_self() const {
	return new column2D( *this );
    }
};



/////////////////////////////////////////////////////////////////////////
// calculate_hopfield_weights()
// This function calculates the weight array, which is of size NxN
//
// NOTE: Ref: The C++ Programming Language, p. 129
void
calculate_hopfield_weights(
    const IA_Image<IA_Point<int>,int> & examplars,
    int ** w, // w is NxN
    const unsigned int N ) {

    // Extract the number of examplars
    int K( examplars.domain().sup()( 0 )
	   - examplars.domain().inf()( 0 ) + 1 );

    IA_Set<IA_Point<int> >
	K_1D( IA_boxy_pset( IA_Point<int>( 0 ), IA_Point<int>( K-1 ) ) );

    int i, j, wij;
    int * wi = (int *) w;  // Claim w is just a simple pointer
    for( i=0; i<N; i++ ) {
	// The diagonal should be zero
	wi[ i*N+i ] = 0;

	// Now fill in the lower and upper half, which is symmetric
	for( j=0; j<i; j++ ) {
	    column2D  fi( i ), fj( j );  // Declare column functions

	    // Don\'t you just love multi-dimensional arrays ?
	    wij = sum( compose( examplars, fi, K_1D )
		       * compose( examplars, fj, K_1D ) );
	    wi[ i*N+j ] = wij;
	    wi[ j*N+i ] = wij;
	}
    }
    // That\'s all folks !
}


/////////////////////////////////////////////////////////////////////////
// class HopfieldTemplate
// Declare the Hopfield template, which is based on the examplars.
// We assume the examplars are all of the same cardinality.
//

// Define the copy constructor
HopfieldTemplate::
HopfieldTemplate( const HopfieldTemplate & ht )
:IA_ClosureDT<IA_Image<IA_Point<int>,int> >( ht.domain() ),
 N( ht.N ),
 weights_p( (int **) new int[ht.N * ht.N] ) {

    int * wp  = (int *) weights_p;

    int * htwp = (int *) ht.weights_p;

    for( int i=0; i<N*N; i++ ) {
	*(wp++) = *(htwp++);
    }
}



// Initialize the template with the examplars
HopfieldTemplate::
HopfieldTemplate( IA_Image<IA_Point<int>,int> * e_array,
		  unsigned int                  e_array_size )
:IA_ClosureDT<IA_Image<IA_Point<int>,int> >( e_array[0].domain() ) {

    N = e_array[0].domain().card();

    // Initialize the weights
    weights_p = (int **) new int[N*N];

    if ( weights_p == 0 ) {
	ia_throw( IA::INVALID_OPERATION, __FILE__, __LINE__ );
	N = 0;
    }

    calculate_hopfield_weights(
	make_examplars_image( e_array, e_array_size ),
	weights_p, N );
}


HopfieldTemplate::
HopfieldTemplate( const IA_Image<IA_Point<int>,int>  & weight_image_ )
:IA_ClosureDT<IA_Image<IA_Point<int>,int> >(
    // Our domain is 1D, the width of the weight_image_,
    //    which had better have domain() = {<0,0>..<N-1,N-1>}
    IA_boxy_pset( IA_Point<int>( 0 ),
		  IA_Point<int>( weight_image_.domain().sup()( 0 ) ) ) ) {

    N = weight_image_.domain().sup()( 0 ) + 1;
    calculate_hopfield_weights( weight_image_, weights_p, N );
}



// We have to remember to delete the storage allocated by this guy
HopfieldTemplate::
~HopfieldTemplate() { delete[] weights_p; }



// Return a 1D image which is of cardinality  N
IA_Image<IA_Point<int>,int>
HopfieldTemplate::operator() ( const IA_Point<int> & oneDpoint ) const {

    // The image we want to return is the row indicated by oneDpoint.
    // The image will have be defined over the set { <0>..<N-1> }

    // We could assume that we know how the data vector is arranged in
    //    the weight image, but it is safer to make our own vector
    //    and hand that off to the the image constructor.

    int i = oneDpoint(0);
    int * values = &( ((int *) weights_p)[ i*N ] );

    return
	IA_Image<IA_Point<int>,int>(
	    IA_boxy_pset( IA_Point<int>( 0 ),
			  IA_Point<int>( N - 1 ) ),
	    values, N );
}


IA_ClosureDT<IA_Image<IA_Point<int>,int> > *
HopfieldTemplate::clone_self() const {
    return
	new HopfieldTemplate( *this );
}


// Return the weight_image we are based on.
IA_Image<IA_Point<int>,int>
HopfieldTemplate::weight_image() const {

    return
	IA_Image<IA_Point<int>,int>(
	    IA_boxy_pset( IA_Point<int>(   0,   0 ),
			  IA_Point<int>( N-1, N-1 ) ),
	    (int *) weights_p,  N*N );
}
