💾 Archived View for spam.works › mirrors › textfiles › computers › blum.lst captured on 2024-02-05 at 11:23:58.

View Raw

More Information

⬅️ Previous capture (2023-06-14)

-=-=-=-=-=-=-

BIDIRECTIONAL ASSOCIATIVE MEMORY SYSTEMS IN C++
by Adam Blum


[LISTING ONE]

////////////////////////////////////////////////////////////
// BAM.HPP  Provide vector, matrix, vector pair, matrix, BAM matrix, and 
// BAM system classes and methods to implement BAM system concept.
// Extended note:
// This is an implementation of the concept of Bidirectional
// Associative Memories as developed by Bart Kosko and others. 
// It includes the extended concept introduced by Patrick Simpson
// of the "BAM System". Where reasonable Simpson's notation has been
// been maintained. The presentation benefits greatly from C++ and OOP, in that
// (I believe) it is both easier to understand than a "pseudocode" version, 
// yet more precise (in that it works!)
// Developed with Zortech C++ Version 2.0 -- Copyright (c) Adam Blum, 1989,90

#include<stdlib.h>
#include<io.h>
#include<stdio.h>
#include<string.h>
#include<limits.h>
#include<ctype.h>
#include<stream.hpp>
#include "debug.h" // debugging devices
// where are Zortech's min,max?
#define max(a,b)	(((a) > (b)) ? (a) : (b))
#define min(a,b)	(((a) < (b)) ? (a) : (b))

// will be changed to much higher than these values
const ROWS=16;	// number of rows (length of first pattern)
const COLS=8;	// number of columns (length of second pattern)
const MAXMATS=10; // maximum number of matrices in BAM system
const MAXVEC=16; // default size of vectors

class matrix;
class bam_matrix;
class vec {
	friend class matrix;
	friend class bam_matrix;
	friend class bam_system;
		int n;
		int *v;
	public:
		// see BAM.CPP for implementations of these
		vec(int size=MAXVEC,int val=0); // constructor
		~vec();	// destructor
		vec(vec &v1); // copy-initializer
		int length();
		vec& operator=(const vec& v1); // vector assignment
		vec& operator+(const vec& v1); // vector addition
		vec& operator+=(const vec& v1); // vector additive-assignment
		vec& operator*=(int i); // vector multiply by constant
		// supplied for completeness, but we don't use this now
		int operator*(const vec& v1); // dot product
		vec operator*(int c); // multiply by constant
		// vector transpose multiply needs access to v array
		int operator==(const vec& v1);
		int& operator[](int x);
		friend istream& operator>>(istream& s,vec& v);
		friend ostream& operator<<(ostream& s, vec& v);
}; //vector class

class vecpair;

class matrix {
	protected: 
	// bam_matrix (a derived class) will need to use these members
	// preferred to "friend class", since there may be many derived
	// classes which need to use this 
		int **m; // the matrix representation
		int r,c; // number of rows and columns
	public: 
		// constructors 
		matrix(int n=ROWS,int p=COLS); 
		matrix(const vec& v1,const vec& v2);
		matrix(const vecpair& vp);
		matrix(matrix& m1); // copy-initializer
		~matrix();
		int depth();
		int width();
		matrix& operator=(const matrix& m1);
		matrix& operator+(const matrix& m1);
		matrix& operator+=(const matrix& m1);
		vec colslice(int col); 
		vec rowslice(int row); 
		friend ostream& operator<<(ostream& s,matrix& m1);
}; // matrix class

class vecpair {
	friend class matrix;
	friend class bam_matrix;
	friend class bam_system;
		int flag; // flag signalling whether encoding succeeded
		vec a;
		vec b;
	public: 
		vecpair(int n=ROWS,int p=COLS); // constructor
		vecpair(const vec& A,const vec& B);
		vecpair(const vecpair& AB); // copy initializer
		~vecpair();    
		vecpair& operator=(const vecpair& v1);
		int operator==(const vecpair& v1);
		friend istream& operator>>(istream& s,vecpair& v);
		friend ostream& operator<<(ostream& s,vecpair& v);
		friend matrix::matrix(const vecpair& vp);
};

class bam_matrix: public matrix {
	private: 
		int K; // number of patterns stored in matrix
		vecpair *C; // actual pattern pairs stored
		int feedthru(const vec&A,vec& B);
		int sigmoid(int n); // sigmoid threshold function
	public: 
		bam_matrix(int n=ROWS,int p=COLS);
		~bam_matrix();
		// but we supply it with the actual matrix A|B (W is implied)
		void encode(const vecpair& AB); // self-ref version
		// uncode only necessary for BAM-system
		void uncode(const vecpair& AB); // self-ref version
		vecpair recall(const vec& A);
		int check(); 
		int check(const vecpair& AB);
		// Lyapunov energy function: E=-AWBtranspose
		int energy(const matrix& m1); // Lyapunov energy function
}; // BAM matrix

class bam_system {
		bam_matrix *W[MAXMATS];
		int M; // number of matrices
	public:
		bam_system(int M=1);
		~bam_system();
		void encode(const vecpair& AB);
		vecpair& recall(const vec& A);
		// train equiv. to Simpson's encode of all pairs
		void train(char *patternfile); 
		friend ostream& operator<<(ostream& s,bam_system& b);
}; // BAM system class


[LISTING TWO]

///////////////////////////////////////
// BAM.CPP Provide vector, matrix, vector pair, matrix, BAM matrix, and BAM 
// system classes to implement BAM systems
// Extended note:
// This is an implementation of the concept of Bidirectional
// Associative Memories as developed by Bart Kosko and others. 
// It includes the extended concept introduced by Patrick Simpson
// of the "BAM System". Where reasonable Simpson's notation has been
// been maintained. The presentation benefits greatly from C++ and OOP, in that
// (I believe) it is both easier to understand than a "pseudocode" version, 
// yet more precise (in that it works!)
// Developed with Zortech C++ Version 2.0 -- Copyright (c) 1989,90 Adam Blum

#include"bam.hpp"

///////////////////////////////////
// vector class member functions

vec::vec(int size,int val) { 
	v = new int[size]; 
	n=size;
	for(int i=0;i<n;i++)
		v[i]=0;
} // constructor
vec::~vec() { delete v;} // destructor
vec::vec(vec& v1) // copy-initializer
{
	v=new int[n=v1.n];
	for(int i=0;i<n;i++)
		v[i]=v1.v[i];
}
vec& vec::operator=(const vec& v1)
{
	delete v;
	v=new int[n=v1.n];
	for(int i=0;i<n;i++)
		v[i]=v1.v[i];
	return *this;
}
vec& vec::operator+(const vec& v1)
{
	vec sum(v1.n);
	sum.n=v1.n;
	for(int i=0;i<v1.n;i++)
		sum.v[i]=v1.v[i]+v[i];
	return sum;
}
vec& vec::operator+=(const vec& v1)
{
	for(int i=0;i<v1.n;i++)
		v[i]+=v1.v[i];
	return *this;
}
vec vec::operator*(int c)
{
	vec prod(length());
	for(int i=0;i<prod.n;i++)
		prod.v[i]=v[i]*c;
	return prod;	
}
int vec::operator*(const vec& v1) // dot-product
{
	int sum=0;
	for(int i=0;i<min(n,v1.n);i++)
		sum+=(v1.v[i]*v[i]);
	//D(cout << "dot product " << *this << v1 << sum << "\n";)
	return sum;
}
int vec::operator==(const vec& v1) 
{
	if(v1.n!=n)return 0;
	for(int i=0;i<min(n,v1.n);i++){
		if(v1.v[i]!=v[i]){
			return 0;
		}
	}
	return 1;
}
int& vec::operator[](int x)
{
	if(x<length() && x>=0)
		return v[x];
	else
		cout << "vec index out of range";
}
int vec::length(){return n;} // length method

istream& operator>>(istream& s,vec &v)
// format: list of ints followed by ','
{
	char c;
	v.n=0;
	v.v=new int[MAXVEC];
	for(;;){
		s>>c;
		if(s.eof())return s;		
		if(c==',')return s;
		if(isspace(c))continue;
		v.v[v.n++]=((c!='0')?1:-1);
	}
}
ostream& operator<<(ostream& s, vec& v)
// format: list of ints followed by ','
{
	for(int i=0;i<v.n;i++)
		s << (v.v[i]<0?0:1);
	s << ",";
	return s;
}

///////////////////////////////
// matrix  member functions
matrix::matrix(int n,int p) 
{ 
	//D(cout << "Constructing " << n << " x " << p << " matrix.\n";)
	m=new int *[n];
	for(int i=0;i<n;i++){
		m[i]=new int[p];
		for(int j=0;j<p;j++)
			m[i][j]=0;
	}
	r=n; 
	c=p;
} // constructor
matrix::matrix(const vecpair& vp) 
{
	//D(cout << "Constructing matrix from: " << vp;)
	r=vp.a.length();
	c=vp.b.length();
	m=new int *[r];
	for(int i=0;i<r;i++){
		m[i]=new int[c];
		for(int j=0;j<c;j++)
			m[i][j]=vp.a.v[i]*vp.b.v[j];
	}
}// constructor 
matrix::matrix(const vec& v1,const vec& v2) 
{
	//D(cout << "Constructing matrix from " << v1 << v2 << "\n";)
	r=v1.length();
	c=v2.length();
	m=new int *[r];
	for(int i=0;i<r;i++){
		m[i]=new int[c];
		for(int j=0;j<c;j++)
			m[i][j]=v1.v[i]*v2.v[j];
	}
}// constructor 
matrix::matrix(matrix& m1) // copy-initializer
{
	//D(cout << "matrix copy-initializer\n"; )
	r=m1.r;
	c=m1.c;
	m=new int *[r];
	for(int i=0;i<r;i++){
		m[i]=new int[c];
		for(int j=0;j<c;j++)
			m[i][j]=m1.m[i][j];
	}
}
matrix::~matrix() 
{ 
	for(int i=0;i<r;i++)
		delete m[i];
	delete m;
} // destructor
matrix& matrix::operator=(const matrix& m1)
{
	for(int i=0;i<r;i++)
		delete m[i];
	r=m1.r;
	c=m1.c;
	m=new int*[r];
	for(i=0;i<r;i++){
		m[i]=new int[c];
		for(int j=0;j<r;j++)
			m[i][j]=m1.m[i][j];
	}
	return *this;	    
}
matrix& matrix::operator+(const matrix& m1)
{
	matrix sum(r,c);
	for(int i=0;i<r;i++)
		for(int j=0;j<r;j++)
			sum.m[i][j]=m1.m[i][j]+m[i][j];
	return sum;
}
matrix& matrix::operator+=(const matrix& m1)
{
	//D(cout << "matrix additive assignment\n";)
	for(int i=0;i<r&&i<m1.r;i++)
		for(int j=0;j<c&&j<m1.c;j++)
			m[i][j]+=(m1.m[i][j]);
	return *this;
}
vec matrix::colslice(int col) 
{
	vec temp(r); 
	for(int i=0;i<r;i++)
		temp.v[i]=m[i][col];
	return temp;
}
vec matrix::rowslice(int row) 
{
	vec temp(c); 
	for(int i=0;i<c;i++)
		temp.v[i]=m[row][i];
	return temp;
}
int matrix::depth(){return r;}
int matrix::width(){return c;}

ostream& operator<<(ostream& s,matrix& m1)
// print a matrix
{
	for(int i=0;i<m1.r;i++){
		for(int j=0;j<m1.c;j++)
			s << m1.m[i][j] << " ";
		s << "\n";
	}
}
//////////////////////////////////////////
// vecpair  member functions 
// constructor
vecpair::vecpair(int n,int p) { }
vecpair::vecpair(const vec& A,const vec& B) {a=A;b=B;}
vecpair::vecpair(const vecpair& AB) {*this=vecpair(AB.a,AB.b);}
vecpair::~vecpair() {} // destructor
vecpair& vecpair::operator=(const vecpair& v1)
{
	a=v1.a;
	b=v1.b;
	return *this;
}
int vecpair::operator==(const vecpair& v1)
{
	return 	(a == v1.a) && (b == v1.b);
}
istream& operator>>(istream& s,vecpair& v1)
// input a vector pair
{
	s>>v1.a>>v1.b;
	return s;
}
ostream& operator<<(ostream& s,vecpair& v1)
// print a vector pair
{
	return s<<v1.a<<v1.b<<"\n";
}
/////////////////////////////////
//bam_matrix  member functions
bam_matrix::bam_matrix(int n,int p):(n,p)
{
	// the maximum number of pattern pairs storable
	// is around min(n,p) where n and p are 
	// the dimensionality of the matrix
	C=new vecpair[min(n,p)*2];
	K=0;
}
bam_matrix::~bam_matrix()
{
} // destructor
void bam_matrix::encode(const vecpair& AB)
// encode a pattern pair
{
	//D(cout << "BAM Matrix encoding: " << AB;)
	matrix T(AB);
	(*this)+=T; // add the matrix transpose to the current matrix
	C[K]=AB;
	K++;
}
void bam_matrix::uncode(const vecpair& AB)
// get rid of a stored pattern (by encoding A-B complement)
{
	//D(cout << "uncode\n";)
	vec v=AB.b*-1;
	matrix T(AB.a,v); // T is A transpose B complement
	*this+=T;// add the matrix transpose to the current matrix
	K--;
}
vecpair bam_matrix::recall(const vec& A)
// BAM Matrix recall algorithm (used by BAM SYSTEM recall)
{
	int givenrow=(A.length()==width());
	D(cout<<"BAM matrix recall of" << A << givenrow?"(row)\n":"(col)\n";)
	vec B(givenrow?depth():width(),1);
	for(;;){ // feed vectors through matrix until "resonant" pattern-pair
		feedthru(A,B);
		if(feedthru(B,A))break; // stop when returned A = input A
	}	
	D(cout<< "resonant pair " << A << "\n and " << B << "\n";)
	if(givenrow)
		return vecpair(B,A);
	else
		return vecpair(A,B);
}
int bam_matrix::feedthru(const vec&A,vec& B)
{
	//D(cout << "Feeding " << A << "\n"; )
	vec temp=B;int n;
	for(int i=0;i<B.length();i++){
		if(A.length()==width())
			n=sigmoid(A*rowslice(i));
		else
			n=sigmoid(A*colslice(i));
		if(n)
			B.v[i]=n;
	}
	return B==temp;
}
int bam_matrix::sigmoid(int n)
// VERY simple (but classic one for BAM) threshold function
//		
//	      1	--------------
//              |
//  - -----------	   +
//	     -1	
{
	if(n<0)return -1;
	if(n>0)return 1;
	return 0;
}
int bam_matrix::check()
// check to see if we have successfully encoded pattern-pair into this matrix
{
	D(cout << "Check BAM matrix for " << K << " pattern pairs\n";)
	vecpair AB;
	for(int i=0;i<K;i++){
		AB=recall(C[i].a);
		if(!(AB==C[i])){
			D(cout <<"failed check\n ";)
			return 0;
		}
	}
	D(cout << "passed check\n ";)
	return 1;    
}
int bam_matrix::check(const vecpair& AB)
{
	// different check routine for orthogonal construction BAM
	//check to see energy of present pattern pair to matrix
	// is equal to orthogonal BAM energy
	matrix T(AB);
	return energy(T)== -depth()*width();
}
int bam_matrix::energy(const matrix& m1)
{
	int sum=0;
	for(int i=0;i<depth();i++)
		for(int j=0;j<width();j++)
			sum+=(m1.m[i][j]*this->m[i][j]);
	D(cout << "Energy of matrix " << -sum << "\n";)
	return -sum;
}

///////////////////////////////////////////
// bam system  functions
// top level of system (for now)

// constructor
bam_system::bam_system(int n)
{
	M=n;
	for(int i=0;i<M;i++)
		W[i]=new bam_matrix;
}
bam_system::~bam_system() // destructor
{
	for(int i=0;i<M;i++)
		delete W[i];
}
void bam_system::encode(const vecpair& AB)
// encode the pattern pair AB into the BAOM system
{
	D(cout << "BAM System encode\n";)
	for(int h=0;h<M;h++){
		W[h]->encode(AB);
		if(!W[h]->check())
			W[h]->uncode(AB);
		else 				
			break;			
	}
	if(h==M){ // all matrices full, add another
		if(h<MAXMATS){
			W[M]=new bam_matrix();
			W[M]->encode(AB);
			M++;
		}
		else{
			cout << "BAM System full\n";
			exit(1);
		}
	}
}
vecpair& bam_system::recall(const vec& A)
// presented with pattern A, recall will return pattern-PAIR 
{
	vecpair XY[MAXMATS];matrix *M1,*M2;
	int E,minimum=0,emin=INT_MAX;
	D(cout << "BAM System recall\n";)
	for(int h=0;h<M;h++){
		XY[h]=W[h]->recall(A);
		D(cout << h <<"-th matrix, returned vecpair "<< XY[h];)
		M1=new matrix(XY[h]);
		E=W[h]->energy(*M1);
		if(A.length()==W[h]->width())
			M2=new matrix(XY[h].a,A);
		else
			M2=new matrix(A,XY[h].b);
		if (  ( E-(W[h]->depth()*W[h]->width()) < emin )
	           && (E==W[h]->energy(*M2))
		)
		{
			emin=E-(W[h]->depth()*W[h]->width());
			minimum=h;						
		}
		delete M1;
		delete M2;
	}			
	return XY[minimum];
}
void bam_system::train(char *patternfile)
// A "multiple-pair" encode - which Simpson calls "encode"
// this could be used for initial BAM Sys training. However an up 
// and running BAM Sys should only need to use "encode".
{
	FILE *f=fopen(patternfile,"r");int n=0;
	filebuf sfile(f);
	istream s(&sfile,0);
	vecpair AB;
	for(;;){
		s >> AB;
		if(s.eof())break;
		D(cout << "Encoding " << n++ << "-th pattern pair:\n" << AB;)
		encode(AB);
	}
	D(cout << "Completed training from " << patternfile;)
}
ostream& operator<<(ostream& s,bam_system& b)
// operator to print out contents of entire BAM system
{
	for(int i=0;i<b.M;i++)
		s<< "BAM Matrix " << i << ": \n" << *(b.W[i]) << "\n";
}


[LISTING THREE]

////////////////////////
// TESTBAM.HPP
// Interactive BAM System Demonstration Program. Used to verify BAM system 
// algorithms and demonstrate them on an abstract (i.e. just 0s and 1s) case.
// Developed with Zortech C++ 2.0 -- Copyright (c) 1989,90 Adam Blum

#include"bam.hpp"

vec v;
vecpair AB;
bam_system B;
char *p;
char patternfile[16]="TEST.FIL"; // file where test data is stored
int trace=0; // SET TRACE=<whatever> at DOS prompt to turn trace on
main()
{
     cout << "Interactive BAM System Demonstration\n";
     trace=(p=getenv("TRACE"))?1:0;
     cout << "Training from " << patternfile << "\n";
     B.train(patternfile);
     D(cout << "Resulting BAM System\n" << B;)
     cout <<"Enter patterns as 0's and 1's terminated by comma.\n"
     <<"Patterns must be length of " << ROWS << " or " << COLS <<".\n"
     << "Null vector (just "","") to end.\n\n" ;
     for(;;){
          cout << "Enter pattern: ";
          cin >> v;
          if(!v.length())break;
          if(v.length()!=ROWS && v.length()!=COLS){
               cout << "Wrong length.\n";
               continue;
          }
          AB=B.recall(v);
          cout << "Recalled pattern pair\n" << AB;
     }
}


[LISTING FOUR]


1100101011010011,11101010,
0110110111110110,11010101,
1101111001010101,11110010,
1010101000010111,11001101,
0011001101011011,11110100,
1100101011010011,11101010,
0110100111110110,11010101,
1101110101010101,11110010,
1011101010010111,11001101,
0001011101011011,11110100,
1100101001010011,11101010,
0110110110110110,11010101,
1100111011010101,11110011,
1010000100010111,11001101,
0001101101011011,11110110,
1100100011010011,11100110,
0110110011110110,11010101,
1101111001010101,11110011,
1010100000011111,11001101,
0001100101111011,11111000,
1100101011010011,11011010,
0010100111110110,11010101,
1101111101010101,11110010,
1010111000010111,11101101,
0001000001011011,11110100,
1100101011010011,11101010,
0110110111110110,11010101,
1101111000010101,11110110,
1010100111010111,11001101,
0001000101011011,11110100,
0110110101110110,11010111,
1101111001010101,11110110,
1010111100110111,11001101,
0001000101011011,11110100,
1100101010010011,11101010,
0110110111110110,11010101,
1101111001010101,11110010,
1010110000010111,11001101,
0011000101011011,11110100,
0011010101111011,10010111,


[LISTING FIVE]

# TESTBAM.MK
# Make file for BAM System implementation tester
# Uses Microsoft Make 
# Compiler: Zortech C++ 2.0
# To make with diagnostics enabled:
# make CFLAGS="-DDEBUG=1" testbam.mk
#

CFLAGS=
.cpp.obj: 
     ztc -c $(CFLAGS) $*.cpp
bam.obj: bam.cpp bam.hpp
testbam.obj: testbam.cpp bam.hpp
testbam.exe: testbam.obj bam.obj
     blink testbam bam;