/*****************************************************************************
* *
* FOX Controller Library, Version 1.3.3. *
* *
* Copyright (C) 1998,1999,2000 Russell Smith (rl.smith@auckland.ac.nz) *
* *
* The FOX Controller Library is free software; you can redistribute it *
* and/or modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either version *
* 2 of the License, or (at your option) any later version. *
* *
* The FOX Controller Library is distributed in the hope that it will be *
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with the Fox Controller Library; see the file COPYING.LIB. *
* If not, write to the Free Software Foundation, Inc., 59 Temple Place - *
* Suite 330, Boston, MA 02111-1307, USA. *
* *
*****************************************************************************/
/*
FOXN: CMAC with vector eligibility
----------------------------------
We want to avoid having two or more weight indexes point to the same weight
for any input, so we dont have to do tricky things with the eligibility
computations to take the multiple references into account.
In the "normal" cmac the weight index is hash function of the virtual
address (made up of the input receptive field numbers) and the AU
number. The weight index was between 0..wsize-1. We could instead compute the
weight index as a hash function of the virtual address alone, hashed into the
range 0..wsize-1, PLUS wsize times the AU number, so the weight index is
between 0..(wsize*C)-1. The weight table would be divided into `C'
wsize-blocks. This has the advantage that the weight indexes would always be
distinct.
BUT this scheme is not quite perfect because the basis functions for each AU
will look the same, so a hash collision (i.e. a basis function localized in
more than one useful area) will not "spread itself around" but will instead
be concentrated in the AU basis functions. The solution is to make the AU
number a parameter of the hash function also.
However, as C increased the total number of weights would increase, but
the weight requirement actually decreases. Thus we have the user specify the
desired number of weights per output, wsize is computed so that wsize*C is
close to this number.
The "slow" method of updating eligibilities is to perform the filter
calculations for each weight. For accurate comparisons with the fast method
we need to get the effect of "retiring" weights, so we use the aux[] index
value to store the time at which the weight was last accessed.
The "inactive weight" queue is split into two parts, `buffer1' and `buffer2'.
Buffer 1 stores C weight indexes at each buffer position. Each index is the
index of an inactive weight, or -1 if none.
Buffer 2 stores C eligibility vectors at each buffer position, which
correspond to the non -1 weight indexes. Each vector has size `ny', so C*ny
numbers are stored at each buffer position. The vector is ignored if the
corresponding weight index is -1.
NOTE that the eligibility vectors stored in buffer2 should be unique for each
output (the eligibility filter driving force from matrix B is different for
each output), but we wont worry about that yet as we are currently only
supporting one output.
A weight index can only be in the inactive queue once. The eligibility
vector of an inactive weight says its eligibility the last time it was active.
If a weight is _not_ in the inactive queue then it's eligibility is assumed
to be 0.
We store some auxiliary information about each weight index (NOT for each
weight, i.e. for more outputs there is still the same amount of auxiliary
information). The auxiliary information is an integer `i' : if the weight is
in the inactive queue, this is its position, otherwise the weight's stored
value is correct and `i' is -1.
See cmac.cc for additional comments.
*/
#include
#include "foxstuff.h"
#include "fox-n.h"
//***************************************************************************
// see the .cc file for a description of what this does
char * GetCMACOverlayDisplacementVector (int n, int p);
//***************************************************************************
// Here is a really hashy hash function from "Numerical Recipes in C",
// 2nd Ed, page 300. It does a pseudo-DES hashing of the given 64 bit word
// (lword,irword). Both 32-bit arguments are returned hashed on all bits.
// This does a much better job of nonlinear pseudo-random bit mixing than
// some other hash functions (like mod-prime or linear congruential) but
// at the expense of speed. It's still pretty quick, though.
#define PDES_ITERS 4
static void pdes_hash (unsigned long &lword, unsigned long &irword)
{
unsigned long i,ia,ib,iswap,itmph=0,itmpl=0;
static unsigned long c1 [PDES_ITERS] = {
0xbaa96887L,0x1e17d32cL,0x03bcdc3cL,0x0f33d1b2L};
static unsigned long c2 [PDES_ITERS] = {
0x4b0f3b58L,0xe874f0c3L,0x6955c5a6L,0x55a7ca46L};
// Perform PDES_ITERS iterations of DES logic, using a simpler
// (non-cryptographic) nonlinear function instead of DES's.
for (i=0; i> 16;
ib = itmpl*itmpl + ~(itmph*itmph);
irword = lword^(((ia=(ib>>16)|((ib&0xffff)<<16))^c2[i])+itmpl*itmph);
lword = iswap;
}
}
// Test pdes_hash to make sure the integer arithmetic is being done ok.
// If anything is wrong an error message is printed and the program is halted.
static void test_pdes_hash()
{
unsigned long a,b;
if (sizeof (a) != 4)
ZFatalError ("Internal error: sizeof(unsigned long) must be 4.");
a=99;
b=99;
pdes_hash (a,b);
if ((a != 0xd7f376f0) || (b != 0x59ba89eb))
ZFatalError ("Internal error: pdes_hash integer arithmetic incorrect.");
}
// Automatic pdes_hash tester
static struct automatic_pdes_hash_tester {
automatic_pdes_hash_tester() { test_pdes_hash(); };
} test_pdes_hash_automatically;
//***************************************************************************
// Check that we can set a `cftype' to zero by setting all its bits to zero,
// because we want to use memset() to zero out some large arrays.
static void check_zero_cftype()
{
cftype a;
a = 3.14159265358979323846;
memset (&a,0,sizeof(a));
if (a != 0.0)
ZFatalError ("Internal error: floating point format not supported.");
}
// Automatic check_zero_cftype caller
static struct automatic_check_zero_cftype {
automatic_check_zero_cftype() { check_zero_cftype(); };
} call_check_zero_cftype_automatically;
//***************************************************************************
// FoxN
FoxN::FoxN (int num_inputs, CmacInputInfo *input_info,
int num_weights, int iC, int _ny, cftype *_matA, cftype *_matB,
cftype *_matC, int _history)
{
int i;
unsigned long a;
valid = 0;
// set all pointers to NULL so that the destructor can safely destruct a
// partially constructed object
weights = NULL;
windex = NULL;
buffer1 = NULL;
buffer2 = NULL;
atoi = NULL;
aux = NULL;
trace = NULL;
#ifdef SLOW_METHOD
slow_eli = NULL;
#endif
nin = num_inputs;
C = iC;
checkthat (nin > 0 && nin <= CMAC_MAXIN,"bad number of inputs (%d)",nin);
checkthat (C > 0 && C <= CMAC_MAXC,"bad number of association units (%d)",C);
// setup input info array
for (i=0; i= 2,"resolution for input %d too small",i);
iinfo[i].min = input_info[i].min;
iinfo[i].max = input_info[i].max;
iinfo[i].q = cftype(iinfo[i].res) / (iinfo[i].max - iinfo[i].min);
checkthat (iinfo[i].q > 0,"bad max / min for input %d",i);
iinfo[i].parts = (iinfo[i].res-2)/C + 2;
}
// Check that the receptive field numbers for all inputs can be packed
// into a 32-bit word - multiply them together and check for overflow.
a = iinfo[0].parts;
for (i=1; i= C,"number of weights must be > C");
wsize = num_weights / C;
// Setup weights.
checkmemptr (weights = new cftype [wsize * C]);
memset (weights, 0, wsize*C * sizeof(cftype));
// setup other arrays
checkmemptr (windex = new int [C]);
odv = GetCMACOverlayDisplacementVector (nin, C);
if (!odv) return;
checkmemptr (aux = new int [wsize*C]);
output = 0.0;
old_output = 0.0;
// setup eligibility stuff
ny = _ny;
checkthat (ny >= 1 && ny <= MAX_NY,"ny must be in the range 1..%d",MAX_NY);
memcpy (matA,_matA,ny*ny * sizeof(cftype));
memcpy (matB,_matB,ny*1 * sizeof(cftype));
memcpy (matC,_matC,ny * sizeof(cftype));
elimode = 1;
history = _history;
checkthat (history > 0,"history must be > 0");
bufsize1 = history * C;
bufsize2 = history * C * ny;
checkmemptr (buffer1 = new int [bufsize1]);
checkmemptr (buffer2 = new cftype [bufsize2]);
checkmemptr (atoi = new GetAtoi (ny,matA,history + 2));
if (!atoi->Valid()) return;
checkmemptr (trace = new TraceN (ny,matA,matC,history + 2,atoi));
if (!trace->Valid()) return;
// set all buffer indexes to -1, else Reset() will try to correct the weights
memset (buffer1,~0,sizeof(int) * bufsize1);
#ifdef SLOW_METHOD
checkmemptr (slow_eli = new cftype [wsize * C * ny]);
#endif
Reset();
valid = 1;
}
FoxN::~FoxN()
{
delete[] weights;
delete[] windex;
delete[] buffer1;
delete[] buffer2;
delete atoi;
delete[] aux;
delete trace;
#ifdef SLOW_METHOD
delete[] slow_eli;
#endif
}
void FoxN::SetEligibilityMode (int _elimode)
{
myassert (elimode !=2,"can't change eligibility mode, Train() call pending");
int newelimode = (_elimode != 0);
if (elimode == newelimode) return;
if (elimode == 1 && newelimode == 0) Reset();
elimode = newelimode;
}
void FoxN::Map (cftype *input)
{
MapGate (input,1);
}
void FoxN::MapGate (cftype *input, int gate)
{
int i,j;
Quantize (input);
GetAssociations();
#ifdef FAST_METHOD
// If in mapping mode, all weights will be correct.
if (elimode) {
myassert (elimode == 1,"called Map() but call to Train() pending");
elimode = 2;
// advance buffer head and retire old weights
time++;
head += C;
if (head >= bufsize1) head = 0;
for (i=0; i= 0) && ((time - aux [i]) >= history) ) {
eptr = slow_eli + i*ny;
for (j=0; jNext (k);
#endif
#ifdef SLOW_METHOD
cftype k = error / (cftype (C));
cftype dot,*eli = slow_eli;
for (int i=0; i < (wsize*C); i++) {
dot = 0.0;
for (int j=0; j limit) {
cftype winc = (limit - output)/cftype(C);
for (int i=0; i= bufsize1) head = 0;
for (int i=0; iReset();
// clear out aux array: set indexes to -1
memset (aux,~0,sizeof(int) * wsize*C);
}
void FoxN::ResetAll()
{
Reset();
memset (weights, 0, wsize*C * sizeof(cftype));
}
int FoxN::InRange (cftype *input)
{
int i;
for (i=0; i iinfo[i].max) return 0;
else if (input[i] < iinfo[i].min) return 0;
}
return 1;
}
void FoxN::Quantize (cftype *input)
{
for (int i=0; i= iinfo[i].max) qin[i] = iinfo[i].res-1;
else {
if (input[i] <= iinfo[i].min) qin[i] = 0;
# ifdef USING_HPFLOAT
else {
cftype result = (input[i] - iinfo[i].min) * iinfo[i].q;
qin[i] = (int) result.Double();
}
# else
else qin[i] = int ( (input[i] - iinfo[i].min) * iinfo[i].q );
# endif
}
}
}
int FoxN::Association (int a)
{
int i,r;
unsigned long hh,h; // (hh,h) = 64 bit hash key
int oo; // AU receptive field ("overlay") offset
int addr; // returned address
myassert (a>=0 && a= hd) : (bpos > hd)) t -= history;
myassert (t >= 0 && t <= time && t >= (time-history),
"bad time in GetCorrectWeight");
cftype *eli = buffer2 + aux[i] * ny; // eligibility vector for this weight
// Find increment to weight (for all outputs), and change in eligiblity.
if ((time-t)==1) {
// If weight was active last timestep (as most weights usually are) then
// we can do it the easy way.
cftype dot = 0.0;
for (int k=0; kGetDelta (t);
dot = 0.0;
for (int k=0; kPreMulByAtoPlus (neweli,eli,time-t);
}
aux[i] = -1;
return neweli;
}