Previous Next Contents

Chapter 7:   The Structure of the CMAC Class:

7.1   The Makefile: MakeLibrary:

                               
                
# -------------------------- #
# paths for this simulation  #
# -------------------------- #
INCLUDE_PATH_UTILITY = /home/peterson/programs/utility
INCLUDE_PATH_CMAC = /home/peterson/programs/cmacclass
INCLUDE_PATH_CONSTANTS = /home/peterson/Xheaders
INCLUDES = -I$(INCLUDE_PATH_UTILITY) -I$(INCLUDE_PATH_CMAC) \
        -I$(INCLUDE_PATH_CONSTANTS)

LIBRARY_PATH_UTILITY = /home/peterson/programs/utility
LIBRARY_PATH_CMAC = /home/peterson/programs/cmacclass
SIMLIBS = -lutility

# ----------- #
# Definitions #
# ----------- #
CC = CC
LIBS        = -ll -lm -lgen
COMPILE_FLAGS_LINK = -g $(INCLUDES)
COMPILE_FLAGS_SOURCES = -c -g $(INCLUDES)
LIBRARY_FLAGS_SOURCES = -L$(LIBRARY_PATH_UTILITY) 

RANLIB = touch

# ------------- #
# Define target #
# ------------- #
TARGET = libcmac

LIB_FILES_OBJ = echo.o \
        gettrainsize.o \
        gettestsize.o \
        getcmac.o \
        cmacinit.o \
        getrms.o \
        virtmem.o \
        func.o \
        cmacfile.o \
        config.o \
        constructor.o \
        default_constructor.o \
        copy_constructor.o \
        equal.o \
        cmacfree.o \
        readtr.o \
        readte.o \
        writew.o \
        readw.o \
        train.o \
        number.o \
        cmaceval.o \
        getlevels.o \
        getinputsize.o \
        getoutputsize.o \
        getworkingaddress.o \
        getnumberintervals.o \
        getminx.o \
        getmaxx.o \
        getwidth.o \
        loadinputsize.o \
        loadoutputsize.o \
        loadtrainsize.o \
        loadtestsize.o

SOURCES = $(LIB_FILES_OBJ:.o=.c)

$(TARGET).a: $(LIB_FILES_OBJ) 
        rm -f $(TARGET).a
        ar cr $(TARGET).a $(LIB_FILES_OBJ)
        $(RANLIB) $(TARGET).a

# --------------------------------------------- #
# Dependencies of source files  on header files #
# --------------------------------------------- #
$(LIB_FILES_OBJ):        
        $(CC) $(COMPILE_FLAGS_SOURCES) $(LIBRARY_FLAGS_SOURCES) \
        $(SIMLIBS) $(LIBS) $*.c

echo.c: cmac.h 
        @touch $@
gettrainsize.c: cmac.h 
        @touch $@
gettestsize.c: cmac.h 
        @touch $@
cmacinit.c: cmac.h 
        @touch $@
getrms.c: cmac.h 
        @touch $@
virtmem.c: cmac.h 
        @touch $@
func.c: cmac.h 
        @touch $@
cmacfile.c: cmac.h 
        @touch $@
getcmac.c: cmac.h 
        @touch $@
config.c: cmac.h 
        @touch $@
cmacfree.c: cmac.h 
        @touch $@
readtr.c: cmac.h 
        @touch $@
readte.c: cmac.h 
        @touch $@
writew.c: cmac.h 
        @touch $@
readw.c: cmac.h 
        @touch $@
train.c: cmac.h 
        @touch $@
number.c: cmac.h 
        @touch $@
cmaceval.c: cmac.h 
        @touch $@
constructor.c: cmac.h
        @touch $@
default_constructor.c: cmac.h 
        @touch $@
copy_constructor.c: cmac.h
        @touch $@ 
equal.c: cmac.h 
        @touch $@
getlevels.c: cmac.h 
        @touch $@
getinputsize.c: cmac.h 
        @touch $@
getoutputsize.c: cmac.h 
        @touch $@
getworkingaddress.c: cmac.h 
        @touch $@
getnumberintervals.c: cmac.h 
        @touch $@
getminx.c: cmac.h 
        @touch $@
getmaxx.c: cmac.h 
        @touch $@
getwidth.c: cmac.h 
        @touch $@
loadinputsize.c: cmac.h 
        @touch $@
loadoutputsize.c: cmac.h 
        @touch $@
loadtrainsize.c: cmac.h 
        @touch $@
loadtestsize.c: cmac.h 
        @touch $@



7.2   The CMAC Class:

7.2.1   The CMAC Class header:

                               
                
The header file for the C++ class is given in this section. A few auxiliary functions, primarily for allocating and deallocating two--dimensional arrays of various data types, are defined outside of the class. Their prototype information is in the file utility.h and indeed that source is used in an archived library form with title utility.a.

The CMAC class is designed to use a hashing function to calculate the working memory addresses from the virtual addresses. The code allows for a separate hash function to be used on each level for each output architecture. However, in practice, a single hash function is used to initialize all the individual hash functions in the CMAC architecture. The prototype for this function is given using a standard typedef mechanism below. Note that, at the present time, the hash function utilizes four arguments and returns type int.


#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "utility.h"
#include "myconstants.h"

typedef float *pfloat;
typedef int (* fhash)(int size,int *subinterval,
                      int *num_interval,int hash_size);
typedef char file_word[MY_MESSAGE_SIZE];

The parameters required to complete the configuration of the CMAC architecture are stored in the CMAC configuration file.


typedef struct{
  char startup_file[MY_MESSAGE_SIZE];  
  char cmac_file[MY_MESSAGE_SIZE];
  char training_file[MY_MESSAGE_SIZE];
  char testing_file[MY_MESSAGE_SIZE];
  char initfile[MY_MESSAGE_SIZE];
  char wtfile[MY_MESSAGE_SIZE];
  int training_set_size;
  int testing_set_size;
  int in_dimensions;
  int out_dimensions;
  float learning_rate;
  float *min_x;
  float *max_x;
  file_word *file;
  int autosize;
  int echo_input;
  int do_training;
  int read_in_wts;
  float inflate_bottom;
  float inflate_top;
  float common_start;
  float common_end;
  } CMACINPUT;

The meaning of the given structure elements is explained below (see also the explanations in Chapter 6). The value of MY_MESSAGE_SIZE is set in cmac.h to be 256.

char startup_file[MY_MESSAGE_SIZE]:
This is the startup file for the CMAC object instantiation, typically named name.cfg.
char cmac_file[MY_MESSAGE_SIZE]:
This is the file typically called nameinit.txt. It is the name of the file which contains the names of the files which configure each output CMAC architecture (see also the documentation for the class function cmac_files()).
char training_file[MY_MESSAGE_SIZE]:
This is the file that stores the training data.
char testing_file[MY_MESSAGE_SIZE]:
This is the file that stores the testing data.
char initfile[MY_MESSAGE_SIZE]:
This is the file that stores the parameters to initialize a CMAC object for use.
char wtfile[MY_MESSAGE_SIZE]:
This is the file in which the CMAC objects parameters are stored, typically after they have been altered by the CMAC training process.
int training_set_size:
This is the number of data items in the training set.
int testing_set_size:
This is the number of data items in the testing set.
int in_dimensions:
This is the dimension of the input space.
int out_dimensions:
This is the dimension of the output space.
float learning_rate:
This is the learning rate used in the CMAC training algorithm.
float *min_x:
The CMAC architecture focuses on a portion of the ith input which will be denoted by [ai,bi]. The exact values of ai and bi are determined both by the training data and whether we autosize the data or use a common start and end value. This procedure has been detailed in Chapter 6. Once these values are set, we store ai in min_x[i].
float *max_x:
The CMAC architecture focuses on a portion of the ith input which will be denoted by [ai,bi]. The exact values of ai and bi are determined both by the training data and whether we autosize the data or use a common start and end value. This procedure has been detailed in Chapter 6. Once these values are set, we store bi in max_x[i].
file_word *file:
This vector stores the names of the files that determine the configuration of the IOMAP structures for each output dimension. See the discussion for the class function cmac_files() for more information.
int autosize:
This variable determines how we determine the focus interval [ai,bi] for the ith input dimension. Setting autosize to 1 allows for a stretching or shrinking of the actual values obtained via the training data. Setting autosize to 0 will force a common focus interval [a,b] to be used.
int echo_input:
Setting this variable to 1 allows for an automatic echoing of CMAC object parameters to the screen.
int do_training:
Setting this flag variable to 1 allows us to train the CMAC architecture on the given training data.
int read_in_wts:
Setting this flag variable to 1 allows us to read in CMAC object parameters from the external file setup.initfile in order to initialize the CMAC object.
float inflate_bottom:
This is a fudge factor to expand or shrink the ai value of the ith inputs focus interval.
float inflate_top:
This is a fudge factor to expand or shrink the bi value of the ith inputs focus interval.
float common_start:
If we decide to use a constant focus interval [a,b] for the input intervals, we set a to common_start.
float common_end:
If we decide to use a constant focus interval [a,b] for the input intervals, we set b to common_end.
The basic computational unit of the CMAC architecture is the IOMAP structure. You need one IOMAP structure for each output dimension in the CMAC architecture. Hence, the IOMAP structure contains the information about number of levels, sensor characteristics such as offset and receptive field width, etc.


typedef struct{
  int levels;
  int Hash;
  float offset_size;
  float width;
  float **offset;
  float **receptive_field_width;
  int *hash_size;
  fhash *hash;
  int **num_interval;
  int *subinterval;
  int *working_address;
  float **working_memory;
  float output;
  } IOMAP;


The individual field elements have the following meaning:

int levels:
This is the number of levels L of offsetted sensors we use in architecture.
int Hash:
This is the hash size H we use in the construction of the working memory.
float offset_size:
Each level of offsetted sensors is offset from the previous level by the constant value O = W/L.
float width:
This is the width W of sensor fields for this architecture.
float **offset:
We need a potentially different offset for each level i and each input j. Hence, we need a two--dimensional array of size L × N, where N is the dimension of the input space. Thus, iomap.offset[i][j] is the offset value for the i level and j input.
float **receptive_field_width:
We need a potentially different field width for each level i and each input j. Hence, we need a two--dimensional array of size L × N. Thus, iomap.receptive_field_width[i][j] is the field width value for the i level and j input.
int *hash_size:
We may have a different hash size for each level. Hence, iomap.hash_size is size L and iomap.hash_size[i] is the hash size for the ith level.
fhash *hash:
We can have a different hash function for each level. Thus iomap.hash[i] is the hash function for the ith level.
int **num_interval:
We compute the number of possible sensors for each level i and input j. Thus, iomap.num_interval is size L × N and iomap.num_interval[i][j] gives the number of sensors for level i and input j.
int *subinterval:
This auxiliary vector stores the active sensor list for a given data vector x as the array [a0,...,aN-1], where aj is the index of the active sensor for input j.
int *working_address:
Once the working address for data x for level i is computed via hashing, we store this address in the vector iomap.working_address. Hence, iomap.working_address has size L and iomap.working_address[i] is the hashed address of data x for level i.
float **working_memory:
This array stores the values that are used to compute the output of the iomap architecture for a given data x. Hence, iomap.working_memory has L rows, where each row stands for a level. Row i corresponds then to level i and has size iomap.hash_size[i]. Thus, iomap.working_memory[i][j] gives the value of the CMAC weight for level i and memory position j.
float output:
This is the scalar ouput of the CMAC IOMAP architecture.
The CMAC class functions appear below. Note that the CMAC is instantiated using as private elements a CMACINPUT structure and a pointer to the IOMAP structure.

class CMAC{
  public:
   CMAC(char *direc_file);
   CMAC(void);
   CMAC(const CMAC&);
   CMAC& CMAC::operator=(const CMAC&);
   ~CMAC(void);
   CMAC& config(char* direc_file);
   CMAC& cmac_files(void);
   CMAC& getcmac(void);
   CMAC& initialize(void);
   CMAC& virtual_memory(int k,float *x);
   CMAC& read_training_data(float ***in,float ***out);
   CMAC& read_testing_data(float ***in,float ***out);
   CMAC& write_wts(void);
   CMAC& read_wts(void);
   CMAC& echo(void);
   CMAC& getinputsize(int *input_size);
   CMAC& getoutputsize(int *output_size);
   CMAC& gettrainsize(int *trainsize);
   CMAC& gettestsize(int *testsize);
   CMAC& getlevels(int *levels,int which_output);
   CMAC& getnumberintervals(int which_output,int which_level,
                            int which_input,int *number);
   CMAC& getminx(int which_input,float *min);
   CMAC& getmaxx(int which_input,float *max);
   CMAC& getwidth(int which_output,int which_level,
                  int which_input,float *width);
   CMAC& getworkingaddress(int which_output,int *address);
   CMAC& loadinputsize(int input_size);
   CMAC& loadoutputsize(int output_size);
   CMAC& loadtrainsize(int trainsize);
   CMAC& loadtestsize(int testsize);
   CMAC& number(void);
   float compute_rms(int sample_size,float **in,float **out);
   float *cmaceval(int do_address,float *x);
   CMAC& train(int do_address,float DESIRED_TOL,int sample_size,
               float **in,float **out);
   CMAC& matrix(float **intr,float **outtr);
  private:
    CMACINPUT setup;
    IOMAP *iomap;
  };


The last item listed is the prototype for the common hash function used in the instantiation of the architecture. It would be nice to include this function as a class function, but we haven't yet been able to figure out how to do that.


int hhash(int size,int *subinterval,int *num_interval,
          int hash_size);





7.3   Construction of the CMAC Object:

7.3.1   Explicit Constructor: constructor.c:

                               
                
#include "cmac.h"

This function is the explicit constructor for the CMAC class. To use this function, you would type a source line like


CMAC T(application.cfg}
and all of the information stored in the file application.cfg would be used in the instantiation of the CMAC object T. The function simply calls the configuration function CMAC& CMAC::config().



CMAC::CMAC(char *direc_file)
{
  config(direc_file);
}

7.3.2   Default Constructor: default_constructor.c:

                               
                
#include "cmac.h"

This function is the default constructor for the CMAC class. To use this function, you may type a source line like


CMAC T[4];
and at compile time the default constructor is called. In the case above, an example of the code use is as follows:


CMAC default(application.cfg);
CMAC T[4];

for(i=0;i<4;++i){
  T[i] = default;
  }
Let's explain this code line by line:
  1. The default constructor is used to initialize the CMAC object default using the information in the file application.cfg. The file name application.cfg is stored as part of the CMAC object creation process in default.setup.startup_file.
  2. The overloaded class equal is then used to build the CMAC object T[i] by first copying the startup file default.setup.startup_file into T[i].setup.startup_file. Then the startup file, which is simply application.cfg, is used in the class function config() to construct the new CMAC object T[i] that will share the values of default. The overloaded = is automatically invoked by the syntax of this line.
Another possibility is to use code of the form


CMAC *T;
An example of such code use is as follows:


CMAC default(application.cfg);
CMAC *T;

T = new CMAC[4];

for(i=0;i<4;++i){
  T[i] = default;
  }
Let's explain this code line by line as well:
  1. The default constructor is used to initialize the CMAC object default using the information in the file application.cfg. The file name application.cfg is stored as part of the CMAC object creation process in default.setup.startup_file.
  2. The global new operator is then used to allocate storage for four CMAC objects.
  3. The overloaded class = is then used to build the CMAC object T[i] by first copying the startup file default.setup.startup_file into T[i].setup.startup_file. Then the startup file, which is simply application.cfg, is used in the class function config() to construct the new CMAC object T[i] that will share the values of default. The overloaded = is automatically invoked by the syntax of this line.
There are some subtle points here. When we are training the CMAC architecture, critical parameters are periodically written into a file whose name is stored in setup.wtfile. Also, if the CMAC architecture parameters are to be initialized using values stored in a file, the name of such an initialization file is stored in setup.initfile. The names of these files are simply copied when the class = is invoked. Hence, since it is not likely that different CMAC architectures should share commom weight files and/or initialization files, we are forced to provide a class function to set the names of these files as desired.


CMAC::CMAC(void)
{ 
  iomap = NULL;
}




7.3.3   Copy Constructor: copy_constructor.c:

                               
                
#include "cmac.h"
#include <string.h>

This function is the class copy constructor. The design decision here is that, if we use the source code


CMAC T(application.cfg);
CMAC S = T;
then the following sequence of events takes place:

  1. The standard constructor is used to build the CMAC object T using the information in the file application.cfg. The file name application.cfg is stored as part of the CMAC object creation process in the T.setup.startup_file.
  2. The copy constructor is used to build the CMAC object S by first copying the startup file T.setup.startup_file into S.setup.startup_file. Then the startup file, which is simply application.cfg, is used in the class function config() to construct the new CMAC object S that will share the values of T. The copy constructor is automatically invoked by the syntax of this line.
Again, there are some subtle points here. When we are training the CMAC architecture, critical parameters are periodically written into a file whose name is stored in setup.wtfile. Also, if the CMAC architecture parameters are to be initialized using values stored in a file, the name of such an initialization file is stored in setup.initfile. The names of these files are simply copied when the copy constructor is invoked. Hence, since it is not likely that different CMAC architectures should share commom weight files and/or initialization files, we are forced to provide a class function to set the names of these files as desired.


CMAC::CMAC(const CMAC& b)
{  
  strcpy(setup.startup_file,b.setup.startup_file);
  config(setup.startup_file);
}

7.3.4   Configuration Function: config.c:

                               
                
#include "cmac.h"
#include <string.h>

This function is the core of the constructors for the CMAC class. When this function is called, the appropriate allocations and initializations are performed.

CMAC& CMAC::config(char *direc_file)
{
FILE *fd2;
char message[MESSAGE_SIZE];

//store the name of the startup file in the IOMAP structure
strcpy(setup.startup_file,direc_file);
printf("startup_file = %s\n",setup.startup_file);

if((fd2=fopen(direc_file,"r")) == NULL){
  printf("\nCan't open file %s\n",direc_file);
  exit(0);
  }

// read in name of file containing configuration
// information for all the IOMAP structures that will
// comprise the full CMAC 
get_my_text(setup.cmac_file,fd2);

// read in name of training file  
get_my_text(setup.training_file,fd2);

// read in name of testing file 
get_my_text(setup.testing_file,fd2);

// read in name of the file that initializes the
// CMAC tunable parameters  
get_my_text(setup.initfile,fd2);

// read in name of file to which the CMAC tunable
// paramters will be written to during the training process 
get_my_text(setup.wtfile,fd2);

// training set size 
fscanf(fd2,"%d",&(setup.training_set_size));
  printf("setup.training_set_size = %d  ",setup.training_set_size);
  get_my_text(message,fd2);

// testing set size 
fscanf(fd2,"%d",&(setup.testing_set_size));
  printf("setup.testing_set_size = %d  ",setup.testing_set_size);
  get_my_text(message,fd2);

// number of input dimensions 
fscanf(fd2,"%d",&(setup.in_dimensions));
  printf("setup.in_dimensions = %d  ",setup.in_dimensions);
  get_my_text(message,fd2);

// number of output dimensions 
fscanf(fd2,"%d",&(setup.out_dimensions));
  printf("setup.out_dimensions = %d  ",setup.out_dimensions);
  get_my_text(message,fd2);

// learning rate 
fscanf(fd2,"%f",&(setup.learning_rate));
  printf("setup.learning_rate = %f  ",setup.learning_rate);
  get_my_text(message,fd2);

// echo input 
fscanf(fd2,"%d",&(setup.echo_input));
  printf("setup.echo_input = %d  ",setup.echo_input);
  get_my_text(message,fd2);

// do_training
fscanf(fd2,"%d",&(setup.do_training));
  printf("setup.do_training = %d  ",setup.do_training);
  get_my_text(message,fd2);

// read_in_weights 
fscanf(fd2,"%d",&(setup.read_in_wts));
  printf("setup.read_in_wts = %d  ",setup.read_in_wts);
  get_my_text(message,fd2);

// autosize 
fscanf(fd2,"%d",&(setup.autosize));
  printf("setup.autosize = %d  ",setup.autosize);
  get_my_text(message,fd2);

if(setup.autosize==0){
  fscanf(fd2,"%f",&setup.common_start);
    printf("setup.common_start = %d  ",setup.common_start);
    get_my_text(message,fd2);
  fscanf(fd2,"%f",&setup.common_end);
    printf("setup.common_end = %d  ",setup.common_end);
    get_my_text(message,fd2);
  }
else{
  // inflate_bottom 
  fscanf(fd2,"%f",&(setup.inflate_bottom));
    printf("setup.inflate_bottom = %f  ",setup.inflate_bottom);
    get_my_text(message,fd2);
  // inflate_top
  fscanf(fd2,"%f",&(setup.inflate_top));
    printf("setup.inflate_top = %f  ",setup.inflate_top);
    get_my_text(message,fd2);
  }

// allocate space for vector containing
// minimum input dimensions  
setup.min_x = new float[setup.in_dimensions];

// allocate space for vector containing
// maximum input dimensions 
setup.max_x = new float[setup.in_dimensions];

// allocate memory for setup.out_dimensions files
// to contain each output components CMAC configuration information 
setup.file = new file_word[setup.out_dimensions];

// allocate space for setup.out_dimensions IOMAP structures 
iomap = new IOMAP[setup.out_dimensions];

cmac_files();
fclose(fd2);
return *this;
}

7.3.5   Get Configuration Files: cmacfile.c:

                               
                
#include "cmac.h"

For each output dimension n in the CMAC architecture, there is a separate IOMAP structure. The individual field elements of this structure are set using information stored in a configuration file whose name is stored in the private CMAC class element setup.file[n] of structure CMACINPUT. The main configuration file for the CMAC architecture is usually called name.cfg, where name is chosen to be pertinent to the application at hand. The first item inside this file is the name of the file which itself contains the names of the files used to initialize the IOMAP structures. This file is typically called nameinit.txt. The name nameinit.txt has already been stored in the private CMAC class element setup.cmac_file. Inside this file setup.cmac_file is a list of files with names of the form setname0.cfg, setname1.cfg and so forth. The total number of files is the same as the output dimension of the CMAC architecture. Hence, the file containing the information to initialize the nth IOMAP structure is called setnamen.cfg.

The
cmac_file class function's purpose is to open the main configuration file setup.cmac_file and extract the individual IOMAP initialization file names. These file names are then stored in the field elements setup.file[].

CMAC& CMAC::cmac_files(void)
{
int i;
FILE *fd2;

if((fd2=fopen(setup.cmac_file,"r")) == NULL){
  printf("\nCan't open file %s\n",setup.cmac_file);
  exit(0);
  }

// get configuration file for IOMAP for ith output dimension 
for(i=0;i<setup.out_dimensions;++i)
  get_my_text(setup.file[i],fd2);

fclose(fd2);
return *this;
}

7.3.6   Internal Allocation: getcmac.c:

                               
                
#include "cmac.h"
This function handles the primary allocation of memory for the CMAC object. The individual allocation tasks are carefully documented in the code below.


CMAC& CMAC::getcmac(void)
{
FILE *fd2;
char message[MESSAGE_SIZE];
int i,j;

// The IOMAP for the ith output dimension is configured using
// the file name stored in setup.file[i].  The entries in
// setup.file[] are read in using the class function cmac_file() 
for(j=0;j<setup.out_dimensions;++j){
  if((fd2=fopen(setup.file[j],"r")) == NULL){
    printf("\nCan't open file %s\n",setup.file[j]);
    exit(0);
    }
  // Number of Levels
  fscanf(fd2,"%d",&(iomap[j].levels));
    get_my_text(message,fd2);

  // Hash size 
  fscanf(fd2,"%d",&(iomap[j].Hash));
    get_my_text(message,fd2);

  // offset
  iomap[j].offset_size = 1.0/(float)iomap[j].levels;

  // width 
  fscanf(fd2,"%f",&(iomap[j].width));
    get_my_text(message,fd2);

  //done with the file, so close it
  fclose(fd2);

  // allocate space for iomap[j].offset, an array 
  // containing offsets for each level in iomap[j].
  // This is a 2D matrix having iomap[j].levels rows and
  // setup.in_dimensions columns.
  get_2dmatrix_float(&(iomap[j].offset),iomap[j].levels,
                     setup.in_dimensions);

  // allocate space for iomap[j].receptive_field_width, an array 
  // containing receptive field widths for each level in iomap[j].
  // This is a 2D matrix having iomap[j].levels rows and
  // setup.in_dimensions columns.
  get_2dmatrix_float(&(iomap[j].receptive_field_width),iomap[j].levels,
                     setup.in_dimensions);

  // allocate space for vector containing hash_size for 
  // each level of iomap[j] 
  iomap[j].hash_size = new int[iomap[j].levels];
  for(i=0;i<iomap[j].levels;++i)
    iomap[j].hash_size[i] = iomap[j].Hash;

  // allocate space for vector containing hash functions
  // for each level of iomap[j]
  iomap[j].hash = new fhash[iomap[j].levels];

  // allocate space for iomap[j].working_memory, a 2D array 
  // storing working memory for each level in iomap[j].
  // This is a 2D matrix having iomap[j].levels rows and
  // each row is length iomap[j].hash_size[i]
  iomap[j].working_memory = new pfloat[iomap[j].levels];
  for(i=0;i<iomap[j].levels;++i)
    iomap[j].working_memory[i] = new float[iomap[j].hash_size[i]];

  // allocate space for a a vector to temporarily store
  // the computed address for iomap[j]
  iomap[j].working_address = new int[iomap[j].levels];

  // allocate space for iomap[j].num_interval, a 2D array 
  // storing the number of intervals for each level and
  // each input component in iomap[j].
  // This is a 2D matrix having iomap[j].levels rows and
  // setup.in_dimensions columns
  get_2dmatrix_int(&(iomap[j].num_interval),iomap[j].levels,
                   setup.in_dimensions);

  // allocate space for iomap[j].subinterval, a vector 
  // storing the number of intervals per input component 
  iomap[j].subinterval = new int[setup.in_dimensions];

  }//output dimension loop
return *this;
}

7.3.7   Initialization: cmacinit.c:

                               
                
#include "cmac.h"

This function sets the initial values of many of the important CMAC object parameters. For each output dimension i, there is a corresponding IOMAP structure, iomap[i]. The architecture specified by iomap[i] consists of Li = iomap[i].levels. In principle, there can be a distinct hash function for each level; in this code, we are more pragmatic and a common hash function is used to initialize all the level hash functions. The common hash function is denoted by hhash().

Each
IOMAP structure iomap[i] is essentially a CMAC architecture to model a scalar valued input/output mapping from an input space of dimension N = setup.in_dimensions. Each of these architectures consists of Li levels, and on each of these levels the portion of the input space on which the CMAC architecture focuses is divided up into hypercubes that function as binary sensors. Each of the hypercubes on level j is constructed from sides of length wjk = iomap[i].receptive_field_width[j][k].

Each of the receptive field widths can be set in several ways. Currently the code sets this width using a common value stored in the
IOMAP structure, W = iomap[i].width. This is certainly not the best solution for all applications. This approach subdivides the input space into hypercubes of fixed side length. So, the user must think about the side length before the application is run. Another way is to set the widths to be a fraction of each input variables focus. If the focus of input variable k is the interval (a,b) = (setup.max_x[k]-setup.min_x[k]), an alternate approach is to set the width using the common value iomap[i].width and then scale the result using wjk *= (b-a). This second approach can be activated by setting SCALED to 1 in the code and recompiling.

The hypercubes for each level
j and input k are staggered using an offset strategy. We begin by setting all the offsets to a common value: Ojk = iomap[i].offset[j][k] = O = iomap[i].offset_size. Currently, the numerical value of the common offset parameter is set to be the reciprocal of the the number of levels L. We then stagger the offsets for the kth input as follows:

O0k = 0.0
O1k =
w1k ×
1

L
=
W

L
O2k =
2 × w2k ×
1

L
=
2W

L
O3k =
3 × w3k ×
1

L
=
3W

L
  ·
·
·
 
OL-1,k =
(L-1) × wL-1,k ×
1

L
=
(L-1)W

L

Hence, an additional offset term would give an offset value of W and we begin repeating the same offset strategy.


#define SCALED (0)

CMAC& CMAC::initialize(void)
{
int i,j,k;

getcmac();
for(i=0;i<setup.out_dimensions;++i){ 
  for(j=0;j<iomap[i].levels;++j){

    //Initialize the hash function 
    //for each level of iomap[i]
    iomap[i].hash[j] = hhash;

    //Initialize receptive field widths
    for(k=0;k<setup.in_dimensions;++k){

      // field width for level j, input k 
      iomap[i].receptive_field_width[j][k] = iomap[i].width;
      if(SCALED)
        iomap[i].receptive_field_width[j][k] *= 
          (setup.max_x[k]-setup.min_x[k]);

      // offset for level j and input k
      iomap[i].offset[j][k] = iomap[i].offset_size;
      iomap[i].offset[j][k] *= (float)(j)* 
        (iomap[i].receptive_field_width[j][k]);

      }//input loop
    }// level loop
  }//output loop

If the option to read in existing weights from an already trained CMAC architecture is set, then the read_wts() function is called; otherwise, all of the CMAC weights are initialized to 0.0.

// load weights
if(setup.read_in_wts==0){
  for(i=0;i<setup.out_dimensions;++i){ 
    for(j=0;j<iomap[i].levels;++j){
      for(k=0;k<iomap[i].hash_size[j];++k){
        iomap[i].working_memory[j][k] = 0.0;
        }
      }
    }
  }
else{
  read_wts();
  }
if(setup.echo_input)
  echo();
return *this;
}

7.4   Destruction of the CMAC Object:

7.4.1   Destructor: cmacfree.c:

                               
                
#include "cmac.h"

This function is the default destructor for the CMAC object. Note that the private elements of a CMAC object contain only one pointer, IOMAP *iomap. This pointer is explicitly freed in the destructor

CMAC::~CMAC(void)
{
  delete iomap;
}

7.5   Evaluation Functions for the CMAC Object:

7.5.1   Evaluation: cmaceval.c:

                               
                
#include "cmac.h"

This function evaluates the output of the CMAC architecture for a given input x. Note that if the working_memory structure holds current addressing information, we can pass in the value of 0 for the argument do_address and the address calculation function virtmem() will not be called. If address information is required, setting the argument do_address to 1 enables the address calculation call to virtmem(). In addition, there is the ability to enable diagnostic prints for debugging purposes by setting the #defined variable diagnostic.

#define diagnostic (0)

float * CMAC::cmaceval(int do_address,float *x)
{
 int i,p,base;
 static float *value;

 value = new float[setup.out_dimensions];

 if(diagnostic==1){
   for(i=0;i<setup.in_dimensions;++i)
     printf("x[%d] = %12.6f\n",i,x[i]);
   }
 for(i=0;i<setup.out_dimensions;++i){ 
   if(do_address==1)
     virtual_memory(i,x); 
   iomap[i].output = 0.0;
   for(p=0;p<iomap[i].levels;++p){
     base = iomap[i].working_address[p];
     iomap[i].output += iomap[i].working_memory[p][base];
     }// level loop
   value[i]=iomap[i].output;
   }//output dimension loop
if(diagnostic==1){
  for(i=0;i<setup.out_dimensions;++i)
    printf("iomap[%d].output = %5.1f\n",i,iomap[i].output);
  }
return(value);
}

7.5.2   Hashed Memory Computations: virtmem.c:

                               
                
#include "cmac.h"

This function computes the hashed address used to find CMAC weight values in the working memory array.

In our class implementation, let
N be the dimension of the input space and ai and bi denote the initial and final value, respectively, of the focus interval for input i. Then define the auxiliary variables:

offset = O :
the offset for output component k, level j and input i, given by iomap[k].offset[j][i]
width = W:
the field width for output component k, level j and input i, given by
iomap[k].receptive_field_width[j][i]
init = I:
the ai value for the focus interval for input i, given by setup.min_x[i]
final = F:
the bi value for the focus interval for input i, given by setup.max_x[i]
For a given output component k, level j and input i we interpret the first sensor as covering the interval (-¥, I + O) or (-¥, I + W) depending on whether the offset is nonzero. Hence, if the ith component of x, xi, satisfies xi < I, it is covered by the first sensor field. Similarly, the last sensor is interpreted as covering out to +¥, so if xi > F, it is covered by the last sensor. We will let OFFSET_ZERO denote the value under which we will treat the offset as zero.

We then have two cases:
O > OFFSET_ZERO:


The subintervals are then organized as follows:
[I, I+O], [I+O, I+O+W], [I+O+W, I+O+2W], ..., [I+O+(N-1)W, I+O+NW] giving N+1 sensors.

O £ OFFSET_ZERO:
The subintervals are then organized as follows: [I, I+W], [I+W, I+2W], [I+2W, I+3W], ..., [I+(M-1)W, I+MW] giving M sensors.
So for a given component xi, first we need to find which sensor is active on each level. Fixing the level, the determined active sensor will be stored in ai = iomap[k].subinterval[i]. The active sensor can be determined by using ceil() and floor() calculations according to the algorithm below:

if(O > OFFSET_ZERO) {
   if( xi < I+O)
     ai = 0
   else
     ai = ceil(xi-I-O/W)
   }
else {
   ai = floor(xi-I/W)
   }

Once the full active sensor vector [a0,a1,...,aN-1] has been calculated, we then call the hash function for level j, iomap[k].hash[j], to compute the working address for data x.

A stripped version of the code without a substantial amount of diagnostic checking code is listed below. We enter this function to find the virtual memory address for output component
k that corresponds to data x.


#define OFFSET_ZERO (1.0e-6)

CMAC& CMAC::virtual_memory(int k,float *x)
{
int i,j;
float offset,width,init,final;

for(j=0;j<iomap[k].levels;++j){ // level loop
  for(i=0;i<setup.in_dimensions;++i){ //input loop   
    offset = iomap[k].offset[j][i];
    width = iomap[k].receptive_field_width[j][i];
    init = setup.min_x[i];
    final = setup.max_x[i];

    if(offset>OFFSET_ZERO){
      if(x[i]<(init+offset))
        iomap[k].subinterval[i] = 0;
      else
        iomap[k].subinterval[i] = (int)(ceil( (x[i]-init-offset)/width));
      }
    else{
      iomap[k].subinterval[i] = (int)(floor( (x[i]-init)/width));
      }
    if(iomap[k].subinterval[i]<0){
      printf("BIG ERROR--NEGATIVE SUBINTERVAL CALCULATION\n");
      exit(1);
      }
    }// level loop
  iomap[k].working_address[j] 
      = iomap[k].hash[j](setup.in_dimensions,iomap[k].subinterval,
                         iomap[k].num_interval[j],iomap[k].hash_size[j]);
  }// j loop: level loop for kth iomap
return *this;
}
The full code is listed below; this code has built-in diagnostic and check code which check to see if the active sensor calculations are correct. For speed, these diagnostics are typically turned off. If the value of the #defined variable check is set to 1, the built in checking will be activated. This will require a recompile of the CMAC class.


#define OFFSET_ZERO (1.0e-6)
#define diagnostic (0)
#define check (0)

CMAC& CMAC::virtual_memory(int k,float *x)
{
int i,j;
float offset,width,init,final;
float down,up;

// we enter this routine to find the virtual memory address 
// for output component k that corresponds to input vector x. 


for(j=0;j<iomap[k].levels;++j){ // level loop
  for(i=0;i<setup.in_dimensions;++i){ //input loop   
    offset = iomap[k].offset[j][i];
    width = iomap[k].receptive_field_width[j][i];
    init = setup.min_x[i];
    final = setup.max_x[i];

    if(offset>OFFSET_ZERO){
      if(x[i]<(init+offset))
        iomap[k].subinterval[i] = 0;
      else
        iomap[k].subinterval[i] = (int)(ceil( (x[i]-init-offset)/width));
      }
    else{
      iomap[k].subinterval[i] = (int)(floor( (x[i]-init)/width));
      }
    if(check==1){
      /* check subinterval calculation:

         should have 

         1. O>OFFSET_ZERO  ==> s=0   I<=x<I+O
                             s>0   I+O+(s-1)W<=x<I+O+sW
         2. O<=OFFSET_ZERO ==>       I+sW<=x<I+(s+1)W         */
      if(offset>OFFSET_ZERO){
        if(iomap[k].subinterval[i]==0){
          if(!(x[i]<init+offset+1.0e-6)){
            printf("x[%d] = %12.8e\n",i,x[i]);
            printf("offset = %12.8e\n",offset);
            printf("init = %12.8e\n",init);
            printf("final = %12.8e\n",final);
            printf("width = %12.8e\n",width);
            printf("num_interval[level=%3d][input=%3d] = %3d\n",
              j,i,iomap[k].num_interval[j][i]);
            printf("subinterval[%3d] = %3d\n",i,iomap[k].subinterval[i]);
            printf("[%12.8e,%12.8e,%12.8e]\n",init,x[i],init+offset);
            exit(1);
            }
          }
        else{
          down = offset+(iomap[k].subinterval[i]-1)*width;
          up = down+width;
          if(!(  (init+down-1.0e-6)<=x[i]
               &&x[i]<(init+up+1.0e-6))){
            printf("x[%d] = %12.8e\n",i,x[i]);
            printf("offset = %12.8e\n",offset);
            printf("init = %12.8e\n",init);
            printf("final = %12.8e\n",final);
            printf("width = %12.8e\n",width);
            printf("num_interval[level=%3d][input=%3d] = %3d\n",
              j,i,iomap[k].num_interval[j][i]);
            printf("subinterval[%3d] = %3d\n",i,iomap[k].subinterval[i]);
            printf("[%12.8e,%12.8e,%12.8e]\n",
              init+offset+(iomap[k].subinterval[i]-1)*width,x[i],
              init+offset+iomap[k].subinterval[i]*width);
            exit(1);
            }
          }
        }// offset < OFFSET_ZERO
      else{
        down = offset+iomap[k].subinterval[i]*width;
        up = down + width;
        if(iomap[k].subinterval[i]==0){  
          if(!(x[i]<init+up+1.0e-6)){
            printf("x[%d] = %12.8e\n",i,x[i]);
            printf("offset = %12.8e\n",offset);
            printf("init = %12.8e\n",init);
            printf("final = %12.8e\n",final);
            printf("width = %12.8e\n",width);
            printf("num_interval[level=%3d][input=%3d] = %3d\n",
              j,i,iomap[k].num_interval[j][i]);
            printf("subinterval[%3d] = %3d\n",i,iomap[k].subinterval[i]);
            printf("[%12.8e,%12.8e,%12.8e]\n",
              init+offset+iomap[k].subinterval[i]*width,x[i],
              init+offset+(iomap[k].subinterval[i]+1)*width);
            exit(1);
            }
          }
        else{
          if(!(  init+down-1.0e-6<=x[i]
               &&x[i]<init+up+1.0e-6)){
            printf("x[%d] = %12.8e\n",i,x[i]);
            printf("offset = %12.8e\n",offset);
            printf("init = %12.8e\n",init);
            printf("final = %12.8e\n",final);
            printf("width = %12.8e\n",width);
            printf("num_interval[level=%3d][input=%3d] = %3d\n",
              j,i,iomap[k].num_interval[j][i]);
            printf("subinterval[%3d] = %3d\n",i,iomap[k].subinterval[i]);
            printf("[%12.8e,%12.8e,%12.8e]\n",
              init+offset+iomap[k].subinterval[i]*width,x[i],
              init+offset+(iomap[k].subinterval[i]+1)*width);
            exit(1);
            }
          }
        }
      }// check subinterval calculations?
    if(diagnostic==1)
      printf("subinterval[%3d] = %3d\n",i,iomap[k].subinterval[i]);
    if(iomap[k].subinterval[i]<0){
      printf("BIG ERROR--NEGATIVE SUBINTERVAL CALCULATION\n");
      exit(1);
      }
    }/* input loop */
  iomap[k].working_address[j] 
      = iomap[k].hash[j](setup.in_dimensions,iomap[k].subinterval,
                         iomap[k].num_interval[j],iomap[k].hash_size[j]);
  }// j loop: level loop for kth iomap
return *this;
}

7.5.3   CMAC Training Algorithm: train.c:

                               
                
#include "cmac.h"

#define diagnostic (0)

CMAC& CMAC::train(int do_address,float DESIRED_TOL,
                  int sample_size,float **in,float **out)
{
  int i,j,k,base;

  for(k=0;k<sample_size;++k){
    //evaluate address and compute cmac output
    cmaceval(1,in[k]);
    for(i=0;i<setup.out_dimensions;++i){
      for(j=0;j<iomap[i].levels;++j){
        base = iomap[i].working_address[j];
        if(fabs(out[k][i]-iomap[i].output)>DESIRED_TOL){
          iomap[i].working_memory[j][base] 
            +=   setup.learning_rate*(out[k][i]-iomap[i].output)
               /(float)iomap[i].levels;
          }//weight update step
        }//levels loop
      }// output component loop
    }// sample loop
  return *this;
}

7.6   Utility Functions for the CMAC Object:

7.6.1   Size Calculations: number.c:

                               
                
#include "cmac.h"

#define OFFSET_ZERO (1.0e-6)
#define diagnostic (0)
#define check (0)
CMAC& CMAC::number(void)
{
int i,j,k,p;
float offset,width,init,final,temp;

/*----------------------------------------------------------------
Case 0: no degeneracies: offset > OFFSET_ZERO

  I----------I+O--------I+O+W------I+O+2W-----/ /----I+O+NW-----F
      
  subintervals:

      0           1            2                   N         N+1

Case 1: offset  <= OFFSET_ZERO

  I=I+O--------I+W--------I+2W----------------/ /---I+MW--------F

      0            1              2             M-1     M */

for(k=0;k<setup.out_dimensions;++k){
  for(j=0;j<iomap[k].levels;++j){
    for(i=0;i<setup.in_dimensions;++i){
      offset = iomap[k].offset[j][i];
      width = iomap[k].receptive_field_width[j][i];
      init = setup.min_x[i];
      final = setup.max_x[i];

      temp = (final-init-offset)/width;
      if(temp<0.0){
        printf("iomap[%d].num_interval calculation negative\n",k);
        printf("final = %12.6e init = %12.6e offset = %12.6e\n",
          final,init,offset);
        }
      iomap[k].num_interval[j][i] = (int)temp;
      if((temp-(int)temp)>0)
        iomap[k].num_interval[j][i] += 1;
      if(offset>OFFSET_ZERO)
        iomap[k].num_interval[j][i] += 1;
      p = iomap[k].num_interval[j][i];
      if(check==1){
        if(offset>OFFSET_ZERO){
          if(!(  init+offset+(p-2)*width-OFFSET_ZERO<=final
               &&final<=init+offset+(p-1)*width+OFFSET_ZERO)){
            printf("init = %12.8e\n",init);
            printf("final = %12.8e\n",final);
            printf("offset = %12.8e\n",offset);
            printf("width = %12.8e\n",width);
            printf("(F-I-O)/W = %12.8e\n",temp);
            printf("(int)temp = %3d\n",(int)temp);
            printf("ceil(temp) = %12.6e\n",ceil(temp));
            printf("floor(temp) = %12.6e\n",floor(temp));
            printf("iomap[%d].num_interval[level=%3d][input=%3d] = %3d\n",
              k,j,i,p);
            printf("[%12.8e,%12.8e,%12.8e]\n",
              init+offset+(p-2)*width,final,init+offset+(p-1)*width);
            exit(1);
            }
          }
        else{
          if(!(  init+offset+(p-1)*width-OFFSET_ZERO<=final
               &&final<=init+offset+(p)*width+OFFSET_ZERO)){
            printf("init = %12.8e\n",init);
            printf("offset = %12.8e\n",offset);
            printf("width = %12.8e\n",width);
            printf("iomap[%d].num_interval[level=%3d][input=%3d] = %3d\n",
              k,j,i,p);
            printf("[%12.8e,%12.8e,%12.8e]\n",
              init+offset+(p-1)*width,final,init+offset+(p)*width);
            exit(1);
            }
          }
        }// check iomap[k].number calculation for level j, input
         // component i
      if(diagnostic==1)
        printf("iomap[%d].num_interval[level = %3d][input = %3d]  = %3d\n",
          k,j,i,p);              
      }// input loop 
    }// level loop
  }// output component loop
return *this;
}

7.6.2   Echo CMAC Class Parameters: echo.c:

                               
                
#include "cmac.h"
This function will echo to the screen the critical parameters of the CMAC architecture. Since there can be many such parameters for each choice of output dimension, this code defaults to printing the required echo for output component 0. This choice can be changed by altering the # defined variable WHICH_OUTPUT_COMPONENT to which ever index is needed. This, of course, implies a recompile of the class.

#define WHICH_OUTPUT_COMPONENT (0)
CMAC& CMAC::echo(void)
{
  int total,i,j,k,m,n;
  float temp;

  printf("setup.startup_file = %s\n",setup.startup_file);
  printf("setup.cmac_file = %s\n",setup.cmac_file);
  printf("setup.training_file = %s\n",setup.training_file);
  printf("setup.testing_file = %s\n",setup.testing_file);
  printf("setup.initfile = %s\n",setup.initfile);
  printf("setup.wtfile = %s\n",setup.wtfile);
  printf("setup.training_set_size = %3d\n",setup.training_set_size);
  printf("setup.testing_set_size = %3d\n",setup.testing_set_size);
  printf("setup.learning_rate = %12.6f\n",setup.learning_rate);
  printf("setup.in_dimensions = %3d\n",setup.in_dimensions);
  printf("setup.out_dimensions = %3d\n",setup.out_dimensions);
  for(i=0;i<setup.in_dimensions;++i){
    printf("input interval[%3d] = [%12.6f,%12.6f]\n",i,
     setup.min_x[i],setup.max_x[i]);
    }
  printf("\n");
  for(i=0;i<setup.out_dimensions;++i){
    printf("setup.file[%3d] = %s\n",i,setup.file[i]);
  
  printf("\n*************************\n");
  printf("CMAC[%3d] parameters\n",WHICH_OUTPUT_COMPONENT);
  printf("*************************\n\n");
  printf("number levels = %3d\n",iomap[WHICH_OUTPUT_COMPONENT].levels);
    printf("hash = %3d\n",iomap[WHICH_OUTPUT_COMPONENT].Hash);
    printf("offset = %12.6f\n",iomap[WHICH_OUTPUT_COMPONENT].offset_size);
    printf("width = %12.6f\n",iomap[WHICH_OUTPUT_COMPONENT].width);
    printf("LEVEL  INPUT   OFFSET       FIELD WIDTH  HASH_SIZE\n\n");
    for(j=0;j<iomap[WHICH_OUTPUT_COMPONENT].levels;++j){
      for(k=0;k<setup.in_dimensions;++k){
        printf(" %3d    %3d    %12.6f   %12.6f   %3d\n",
          j,k,iomap[WHICH_OUTPUT_COMPONENT].offset[j][k],
          iomap[WHICH_OUTPUT_COMPONENT].receptive_field_width[j][k],
          iomap[WHICH_OUTPUT_COMPONENT].hash_size[j]);
        }
      }
    }
  for(i=0;i<setup.out_dimensions;++i){
    for(j=0;j<iomap[i].levels;++j){
      for(k=0;k<setup.in_dimensions;++k){
        printf("sensors[output = %d][level = %d][input = %d] = %d\n",
          i,j,k,iomap[i].num_interval[j][k]);
        }
      }
    }
This function also computes the total number of sensor cells that the CMAC architecture would have if the level structure would be collapsed or ``flattened'' from L levels to one level. Essentially, if there are N output components, then for output component i, there are Li levels in the architecture with a fixed receptive field width Wi and an interval [ai,bi] where the CMAC sensors are active. Thus, the number of sensors on a given level is given by Si = (bi-ai)/Wi and the total number of sensors for the output component i is Ti = Si × Li. This number must be converted to the nearest integer -- call this integer Mi. The total number of flattened sensors for the full CMAC architecture is then å0N-1 Mi.

  total = 0;
  for(i=0;i<setup.out_dimensions;++i){
    n = 1;
    for(k=0;k<setup.in_dimensions;++k){
      temp = (setup.max_x[k]-setup.min_x[k])*iomap[i].levels/iomap[i].width;
      m = (int)temp;
      if((temp - m)>0.0) m +=1;
      printf("flattened sensors[output = %d, levels = %d][%d input] = %d\n",
        i,iomap[i].levels,k,m);
      n *= m;
      }
    printf("flattened sensors[output = %d, levels = %d, input = %d] = %d\n",
      i,iomap[i].levels,k,n);
    total +=n;
    }
  printf("Total flattened sensors for %d outputs = %d\n",
    setup.out_dimensions,total);
return *this;
}

7.6.3   Hash Algorithm: func.c:

                               
                
#include "cmac.h"
This function is the generic hash function for the CMAC architecture. The subinterval array coming in represents the coarse encoding the CMAC architecture gives to an input x. In other words, letting N = denote the size of subinterval and ai the active sensor or subinterval in which the ith input component resides, the array subinterval stores the active sensor list [a0,a1,...,aN-1]. The argument size stores the value of N. Let the number of sensors or subintervals for input component i be denoted by Mi. The full list of the number of sensors per input component has been placed in the array num_interval = [M0,M1,...,MN-1]. Then, the list [a0,a1,...,aN-1] represents the location of an element in an N-dimensional space where the ith input component can take on at most Mi values. Alternately, we can visualize an N-dimensional array A where the ith row has Mi elements. The list [a0,a1,...,aN-1] then represents the array element

A
 
a0,a1,...,aN-1

in an array with unequal row sizes. Now this
ragged array A can be converted into an equivalent contiguous block of memory space by linearizing the elements array address in the standard way given below:

define the elements
Ti by

T-1 = 1
Tj =
j
Õ
k=0
Mk

where we see

T-1 = 1
T0 = M0
T1 = M0 × M1
T2 = M0 × M1 × M2
  ·
·
·
 
TN-2 = M0 × M1 × M2 × ··· × MN-2

Then, T1 is the number of elements for input component 1, T2 is the number of possible elements for the input 1 and 2 combined and so forth. The equivalent linear address L is given by

L =
N-1
å
i=0
ai*Ti-1
  = a0 + a1*M0 + a2*M0*M1 +
    ... + aN-1*M0*M1*...*MN-2.

Now the linear address L is usually too large to store in our finite memory computer architectures; hence this large linear address will be converted into a smaller working address W by computing the remainder of the integer division of L by a fixed integer H, the hash size. The argument hash_size stores the value of H.

W =
L

H

This method will result in overflow as often L is too large to store as int or long int.

We now prove some basic facts about arithmetic operations modulo
H.


Proposition 1   Let H be a positive integer greater than two and A and B be arbitrary positive integers. Then

(A+B)%H = (A%H + B%H) % H
(AB)%H = [ (A%H) (B%H) ] % H.



Proof: We know that there are integers s0, s1, t0 and t1 such that

A = s0 H + t0, 0 £ t0 < H
B = s1 H + t1, 0 £ t1 < H.

Thus,

(A+B)%H = [ (s0 + s1) H + t0 + t1] % H
  = (t0 + t1) % H
  = [(A%H) + (B%H)] % H
(AB)%H = [ (s0 s1) H2 + (s0 t1 + s1 t0) H + t0 t1] % H
  = [t0 t1] % H
  = [ (A%H) (B%H) ] % H

                                  Q.E.D.


Proposition 2   Let H be a positive integer greater than two and

V =
N-1
å
i=0
ai Ti-1.

Then, V % H can be computed via the iterative scheme

V-1 = 0
Vi = (Vi-1 % H) MN-i-1 + aN-i-1, 0 £ i £ N-2
VN-1 = (VN-2 % H) M0 + a0
V % H = VN-1 % H.



Proof: We know that there are integers si, ti, ci, di, ai and bi such that for all appropriate indices i

Mi = ci H + di, 0 £ di < H
Ti = si H + ti, 0 £ ti < H
ai = ai H + bi, 0 £ bi < H.

Thus, using Proposition 1 repeatedly,

V0 = aN-1
V1 = (V0 % H) MN-2 + aN-2
  = (aN-1 % H) MN-2 + aN-2
  = (bN-1) (cN-2 H + dN-2) +(aN-2 H + bN-2)
V1 % H = (bN-1 dN-2 + bN-2) % H
  = (aN-1 % H MN-2 % H + aN-2 % H) % H
  = (aN-1 MN-2 + aN-2) % H
V2 = (V1 % H) MN-3 + aN-3
V2 % H = ((V1 % H) MN-3 % H + aN-3 % H) % H
  = ( (aN-1 MN-2 + aN-2) MN-3 + aN-3) % H
  = ( aN-1 MN-2 MN-3 + aN-2 MN-3 + aN-3) % H
V3 = (V2 % H) MN-3 + aN-3
V3 % H = ( aN-1 MN-2 MN-3 MN-4 + aN-2 MN-3 MN-4 + aN-3 MN-4 + aN-4) % H.

Continuing in this way, we see

VN-2 % H =
( aN-1
N-1
Õ
i=1
Mi + aN-2
N-2
Õ
i=1
Mi + ··· + + a2
1
Õ
i=1
Mi + a1) % H
VN-1 = (VN-2 % H) M0 + a0
VN-1 % H =
(
N-1
å
i=0
ai Ti-1 ) % H
  = V % H

                                  Q.E.D.

The code to implement the iterative scheme of Proposition 2 is given in the function hhash.


int hhash(int size,int *subinterval,int *num_interval,
          int hash_size)

{
  int i,work_address,accumulator;
  unsigned long virtual_address;
  
  virtual_address = 0;
  accumulator = 1;
  for(i=0;i<size;++i){
    virtual_address = virtual_address*(unsigned long)accumulator
              +(unsigned long)subinterval[size-1-i];
    virtual_address = virtual_address%(unsigned long)hash_size;
    if(i<size-1)
      accumulator = num_interval[size-i-2];
    }
  work_address = virtual_address%(unsigned long)hash_size;
  return(work_address);
}  


7.6.4   Compute RMS Error: getrms.c:

                               
                
#include "cmac.h"

#define diagnostic (0)

float CMAC::compute_rms(int sample_size,float **in,float **out)
{
int i,k,base;
float rms, big;

  rms = 0.0;
  big = -1.0;
  for(k=0;k<sample_size;++k){
    cmaceval(1,in[k]);
    if(diagnostic==1){
      for(i=0;i<setup.in_dimensions;++i)
        printf("in[%3d][%3d] = %12.6f\n",k,i,in[k][i]);
      }
    for(i=0;i<setup.out_dimensions;++i){
      rms +=  (out[k][i] - iomap[i].output)
             *(out[k][i] - iomap[i].output);
      if(fabs(out[k][i]-iomap[i].output)>big)
        big = fabs(out[k][i]-iomap[i].output);
      }//output dimensions loop
    }// sample loop
rms=sqrt(rms/(float)sample_size/(float)setup.out_dimensions);
return(rms);
}

7.6.5   getworkingaddress.c:

                               
                
#include "cmac.h"

CMAC& CMAC::getworkingaddress(int which_output,int *address)
{
int i;

for(i=0;i<iomap[which_output].levels;++i)
  address[i] = iomap[which_output].working_address[i];

return *this;
}

7.6.6   getlevels.c:

                               
                
#include "cmac.h"

CMAC& CMAC::getlevels(int *levels,int which_output)
{
*levels = iomap[which_output].levels;
return *this;
}

7.6.7   getnumberintervals.c:

                               
                
#include "cmac.h"

CMAC& CMAC::getnumberintervals(int which_output,int which_level,
                            int which_input,int *number)
{
*number = iomap[which_output].num_interval[which_level][which_input];
return *this;
}

7.6.8   getinputsize.c:

                               
                
#include "cmac.h"

CMAC& CMAC::getinputsize(int *input_size)
{
*input_size = setup.in_dimensions;
return *this;
}

7.6.9   getoutputsize.c:

                               
                
#include "cmac.h"

CMAC& CMAC::getoutputsize(int *output_size)
{
*output_size = setup.out_dimensions;
return *this;
}

7.6.10   getminx.c:

                               
                
#include "cmac.h"

CMAC& CMAC::getminx(int which_input,float *min)
{
*min = setup.min_x[which_input];
return *this;
}

7.6.11   getmaxx.c:

                               
                
#include "cmac.h"

CMAC& CMAC::getmaxx(int which_input,float *max)
{
*max = setup.max_x[which_input];
return *this;
}

7.6.12   getwidth.c:

                               
                
#include "cmac.h"

CMAC& CMAC::getwidth(int which_output,int which_level,
                  int which_input,float *width)
{
*width = 
   iomap[which_output].receptive_field_width[which_level][which_input];
return *this;
}

7.6.13   loadinputsize.c:

                               
                
#include "cmac.h"

CMAC& CMAC::loadinputsize(int input_size)
{
setup.in_dimensions = input_size;
return *this;
}

7.6.14   loadoutputsize.c:

                               
                
#include "cmac.h"

CMAC& CMAC::loadoutputsize(int output_size)
{
setup.out_dimensions = output_size;
return *this;
}

7.6.15   loadtrainsize.c:

                               
                
#include "cmac.h"

CMAC& CMAC::loadtrainsize(int trainsize)
{
setup.training_set_size = trainsize;
return *this;
}

7.6.16   loadtestsize.c:

                               
                
#include "cmac.h"

CMAC& CMAC::loadtestsize(int testsize)
{
setup.testing_set_size = testsize;
return *this;
}

7.6.17   gettestsize.c:

                               
                
#include "cmac.h"

CMAC& CMAC::gettestsize(int *testsize)
{
*testsize = setup.testing_set_size;
return *this;
}

7.6.18   gettrainsize.c:

                               
                
#include "cmac.h"

CMAC& CMAC::gettrainsize(int *trainsize)
{
*trainsize = setup.training_set_size;
return *this;
}

7.6.19   Get Test Data: readte.c:

                               
                
#include "cmac.h"

#define INFINITY (1.0e9)
#define NEGATIVE_INFINITY (-1.0e9)

CMAC& CMAC::read_testing_data(float ***in,float ***out)
/*---------------------------------------------------------------

  read and store testing data in vectors in and out

  ---------------------------------------------------------------*/
{
int i,j,insize,outsize,setsize;
float *min,*max;
FILE *fd2;

insize = setup.in_dimensions;
outsize = setup.out_dimensions;
setsize = setup.testing_set_size;

if((fd2=fopen(setup.testing_file,"r")) == NULL){
  printf("\nCan't open input file %s\n",setup.testing_file);
  exit(0);
  }

get_2dmatrix_float(in,setsize+1,insize);
get_2dmatrix_float(out,setsize+1,outsize);

for(j=0;j<setsize;++j){
  // read and store ith component of jth input vector
  for(i=0;i<insize;++i)
    fscanf(fd2,"%f",&(*in)[j][i]);
  // read and store ith component of jth desired output vector 
  for(i=0;i<outsize;++i)
    fscanf(fd2,"%f",&(*out)[j][i]);
  }
fclose(fd2);

min = new float[setup.in_dimensions];
max = new float[setup.in_dimensions];

// find each input coordinate's interval
for(i=0;i<setup.in_dimensions;++i){
  max[i] = NEGATIVE_INFINITY;
  min[i] = INFINITY;
  for(j=0;j<setup.testing_set_size;++j){
    if(max[i]<(*in)[j][i])
      max[i] = (*in)[j][i];
    if(min[i]>(*in)[j][i])
      min[i] = (*in)[j][i];
    }
  printf("Input[%d]::min/max of input widths= %12.6f/%12.6f\n",
    i,min[i],max[i]); 
  if(min[i]<setup.min_x[i] || max[i]>setup.max_x[i]){
    printf("\nError!\n");
    printf("[minTr,minTe,maxTe,maxTr][%d]=[%10.6f,%10.6f,%10.6f,%10.6f]\n",
      i,setup.min_x[i],min[i],max[i],setup.max_x[i]);
    } 
  }
delete min;
delete max;

return *this;
}

7.6.20   Get Training Data: readtr.c:

                               
                
#include "cmac.h"

#define INFINITY (1.0e9)
#define NEGATIVE_INFINITY (-1.0e9)

CMAC& CMAC::read_training_data(float ***in,float ***out)
/*---------------------------------------------------------------

  read and store training data in vectors in and out

  ---------------------------------------------------------------*/
{
int i,j,insize,outsize,setsize;
float *min,*max,temp;
FILE *fd2;

insize = setup.in_dimensions;
outsize = setup.out_dimensions;
setsize = setup.training_set_size;

if((fd2=fopen(setup.training_file,"r")) == NULL){
  printf("\nCan't open input file %s\n",setup.training_file);
  exit(0);
  }

get_2dmatrix_float(in,setsize+1,insize);
get_2dmatrix_float(out,setsize+1,outsize);

for(j=0;j<setsize;++j){
  // read and store ith component of jth input vector 
  for(i=0;i<insize;++i)
    fscanf(fd2,"%f",&(*in)[j][i]);
  // read and store ith component of jth desired output vector
  for(i=0;i<outsize;++i)
    fscanf(fd2,"%f",&(*out)[j][i]);
  }
fclose(fd2);

min = new float[setup.in_dimensions];
max = new float[setup.in_dimensions];

// find each input coordinate's interval
for(i=0;i<insize;++i){
  max[i] = NEGATIVE_INFINITY;
  min[i] = INFINITY;
  for(j=0;j<setsize;++j){
    if(max[i]<(*in)[j][i])
      max[i] = (*in)[j][i];
    if(min[i]>(*in)[j][i])
      min[i] = (*in)[j][i];
    }
  printf("Input[%d]::min/max of input widths= %12.6f/%12.6f\n",
    i,min[i],max[i]);  
  }
for(i=0;i<insize;++i){
  if(setup.autosize==1){
    setup.min_x[i] = min[i]-setup.inflate_bottom*(max[i]-min[i]);
    setup.max_x[i] = max[i]+setup.inflate_top*(max[i]-min[i]);
    }
  else{
    setup.min_x[i] = setup.common_start;
    setup.max_x[i] = setup.common_end;
    }
  }
delete min;
delete max;

return *this;
}

7.6.21   Get Stored Parameters: readw.c:

                               
                
#include "cmac.h"

#define write_to_console (0)
/*-------------------------------------------------------------

    read in weights from file and output to console
    (if desired)

  ------------------------------------------------------------ */
CMAC& CMAC::read_wts(void)
{
FILE *fd2;
int i,j,k;

if((fd2=fopen(setup.initfile,"r")) == NULL){
  printf("\nCan't open input file %s\n",setup.initfile);
  exit(0);
  }

for(i=0;i<setup.out_dimensions;++i){
  if(write_to_console==1)
    printf("\n iomap[%d] weights \n",i);
  for(j=0;j<iomap[i].levels;++j){
   if(write_to_console==1)
     printf("\nLevel %3d\n",j);
    for(k=0;k<iomap[i].hash_size[j];++k){
      fscanf(fd2,"%f\n",&(iomap[i].working_memory[j][k]));
      if(write_to_console==1)
        printf("working_memory[level =%d][element = %d] = %12.6f\n",
          j,k,iomap[i].working_memory[j][k]);
      }//element loop
    }//level loop
  }//output component loop
fclose(fd2);
return *this;
}

7.6.22   Save CMAC Parameters: writew.c:

                               
                
#include "cmac.h"

#define write_to_console (0)

/*-------------------------------------------------------------

  print out weights both to console(if desired)
  and to file weights.dat

  ------------------------------------------------------------ */
CMAC& CMAC::write_wts(void)
{
FILE *fd2;
int i,j,k;

if((fd2=fopen(setup.wtfile,"w")) == NULL){
  printf("\nCan't open file %s\n",setup.wtfile);
  exit(0);
  }

for(i=0;i<setup.out_dimensions;++i){
  if(write_to_console==1)
    printf("\niomap[%d] weights\n",i);
  for(j=0;j<iomap[i].levels;++j){
    if(write_to_console==1)
      printf("\nlevel %3d\n",j);
    for(k=0;k<iomap[i].hash_size[j];++k){
      if(write_to_console==1)
        printf("working_memory[level = %d][element =%d] = %12.6f\n",
          j,k,iomap[i].working_memory[j][k]);
      fprintf(fd2,"%14.10f ",iomap[i].working_memory[j][k]);
      if((k+1)%4==0){
        if(write_to_console==1)
          printf("\n");
        fprintf(fd2,"\n");
        }
      }
    if(write_to_console==1)
      printf("\n");
    fprintf(fd2,"\n");
    }
  }
fclose(fd2);
return *this;
}


Previous Next Contents