/*
 *
 *  ART1_1.CN  - version 1 of ART1 on the CNAPS.
 *  July 16, 1996
 *  Clark S. Lindsey
 *
 */

/*
 *
 *  neuronF1 - non-poly structure that implements the F1 style
 *             input layer that uses the 2/3 rule to calculate
 *             the F1 output value
 *  neuronF2 - poly variable to implement the F2 style neurons.
 *             They hold both the bottom-up and top-down weight
 *             arrays.
 *
 */

#include <cnio.h>

/***************************** Type Definitions *************************/
typedef unsigned  scaled 5 11  jammed mval;
typedef unsigned  scaled 8 8   jammed sval;

/***************************** Files  ***********************************/

FILE (OUT_LOG,           out,        "*.log", NULL, NULL);
FILE (OUT_RUN,           out,        "*.log", NULL, NULL);
FILE (IN_TRAIN,           in,        "*.trn", NULL, NULL);
FILE (IN_TEST,            in,        "*.tst", NULL, NULL);
FILE (IN_WTS,         inword,        "*.wts", NULL, NULL);
FILE (OUT_WTS,       outword,        "*.wts", NULL, NULL);

/***************************** Constants *********************************/

#define MAX_NUM_INPUTS 64
#define ABS_MAX_CATEGORIES 128
#define TRAIN_MODE 1
#define TEST_MODE 0
#define CR '\n'
const sval INPUT_THRES=1.5; /* Used for 2/3 rule in F1 output */

/***************************** Entry Constants ***************************/
/* These can be changed from control program */

entry const int  NINP=25;
entry const int  NPATT=20;
entry const int  MAX_CATEGORIES=20;
entry const sval BETA=0.500;
entry const sval rho=0.5;    /* vigilance threshold = RHO/10 */

/* Program control flags */
entry const int  RUN_MODE=TRAIN_MODE; /* = 0 for testing, = 1 for training */
entry const int  LOG_MODE=1; /* Write out step by step results */
entry const int  OUT_MODE=0; /* Write out pattern processing results only */
entry const int  SAVE_WTS=0; /* Save weights to a file */
entry const int  GET_WTS=0;  /* Get weights from a file */

/***************************** Variables *********************************/
int numCategories=0; /* Number classes learned so far */
int classWinner=-1;  /* Current class with best match to input */
sval gainF1=1;       /* Gain control for F1 neurons to do 2/3 rule */
sval vig;            /* Vigilance value */
int  modified=0;     /* Flag changes in number of 1 bits in F2 prototypes */
int outOfOutputs=0;  /* Flag to indicate that not enough outputs available*/
int error=0;         /* Flag to indicate errors */
sval INV_1_N;        /* =1.0/(1+NINP) Initialization for bottom up weights */

/***************************** Structures ********************************/
/*
 * F1 input neurons use the 2/3 rule to determine output
 *     output = ( (input + gain +  top_down) > 1.5) ? 1 : 0;
 *
 * Use non-poly structure array.
 */
mono struct neuronF1 {
  sval input;
  sval output;
} F1[MAX_NUM_INPUTS];
/*
 * F2 neurons are poly types.
 */
domain neuronF2 {
  int category;
  sval sum;
  sval score;
  sval topDown[MAX_NUM_INPUTS];
  sval botUp[MAX_NUM_INPUTS];
} F2[ABS_MAX_CATEGORIES];

/****************************** Error Messages ***************************/
char * errorMsg[2] = {
 "Wts read error: Bad categories number#",
 "Wts read error: Bad inputs number#"
};
/****************************** Functions ********************************/
/* Only need to save the weights for the outputs that have been used. The*
 * rest are set to default values.                                       */
entry void saveWts(void)
{ int i,j;
  open(OUT_WTS);
  connect(OUT_WTS);

  putword(numCategories);
  putword(NINP);
  for( i=0; i<numCategories; i++){
    for( j=0; j<NINP; j++){
      putword((%int)F2[i].topDown[j]);
    }
  }
  for( i=0; i<numCategories; i++){
    for( j=0; j<NINP; j++){
      putword((%int)F2[i].botUp[j]);
    }
  }
  close(OUT_WTS);
}
/****************************************************************************/
void openOutFiles(void){
  if( LOG_MODE==1){
    open(OUT_LOG);
  }
  if( OUT_MODE==1){
    open(OUT_RUN);
    connect(OUT_RUN); /* RUN is by default connected if opened */
  }
}
/****************************************************************************/
void openInFiles(void){
  /* Open input files */
  if( RUN_MODE == TRAIN_MODE){
    open(IN_TRAIN);
    connect(IN_TRAIN);
  } else {
    open(IN_TEST);
    connect(IN_TEST);
  }
}
/****************************************************************************/
sval divide(sval numerator, sval denominator){
  unsigned int aa, bb, c, abit, arg;
  int flagReady,nShifts;
  sval ans;

  flagReady=0;
  abit = 0x100;
  arg = 0;
  aa = (%unsigned int)numerator;
  bb = (%unsigned int)denominator;
  nShifts=0;

  /* First section shifts denominator up or down until it
   * is between a >= b > a/2
   */
  if (aa >= bb){
    do {                            /* Loop 1 */
      c = bb << 1;  /* while a>b, shift b up */
      if (aa >= c) {
        if (nShifts==7){ /* Don't shift more than 7 places to left */
          arg=abit;
          flagReady=1;
          break;
        }
        abit <<= 1;
        nShifts++;
        bb = c;             /* Only reached if bb still at least 1/2 < a */
      }
    } while (aa >= c);
  }
  else{                             /* Loop 2 */
    /* while b>a, shift b right */
    /* Can't move abit more than 6 bits to right.
       If move 6 bits then arg>>=3 will result in 0 as answer.  */
    do {
      bb >>= 1; abit >>= 1;
      if (abit < 1){  /* Moved to 3rd bit */
        flagReady=1;
        break;
      }
    } while (aa < bb);
  }
/* Now a >= b > a/2 */
  if(!flagReady){                   /* here a >= b > a/2 */
    do{                             /* Loop 3 */
      aa -= bb;                     /* subtract b from a */
      arg += abit;
      if ((bb < 2) || (abit < 2)){
        flagReady=1;
        break;
      }
      bb >>= 1; abit >>= 1;         /* b <- b/2 */
      while (aa < bb) {             /* Loop 4 */
        if (abit < 2){
          flagReady=1;
          break;
        }
        aa <<= 1; abit >>= 1;
      }
      if(flagReady) break;
    } while (abit > 0);
  }
  ans= (%sval)arg;
  return ( ans);
}
/************************************************************************/
void initialize(void){
  mono int i;
  mono sval one=1, norm;
  norm = (sval)(1+NINP);
  INV_1_N = divide(one,norm);
  [domain neuronF2].{
    category=-1;
    score=0;
    /* Initialize categories with all topDown components to 1 and
     * all botUp components to 1/(1+NINP)
     */
    for (i=0; i<NINP; i++){
      topDown[i]=1;
      botUp[i]=INV_1_N;
    }
  }
  for(i=0; i<ABS_MAX_CATEGORIES; i++){
    F2[i].category= i;
    F2[i].sum = (sval)NINP;
  }
}
/****************************************************************************/
void closeInFiles(void){
  if( RUN_MODE == TRAIN_MODE)
    close(IN_TRAIN);
  else
    close(IN_TEST);
}
/****************************************************************************/
void closeOutFiles(void){
  if( LOG_MODE) close(OUT_LOG);
  if( OUT_MODE) close(OUT_RUN);
}
/******************************************************************/
entry void connectLog(void)
{
  if( LOG_MODE) connect(OUT_LOG);
}
/******************************************************************/
entry void connectRun(void)
{
  if( OUT_MODE) connect(OUT_RUN);
}
/*******************************************************************/
entry void closeup(void){
  closeOutFiles();
  closeInFiles();
  if( SAVE_WTS) saveWts();
}
/************************************************************************/
void logMessage(char * msg)
{ mono int i;
  /* Now open the log file  */
  if( LOG_MODE)
    connect(OUT_LOG);
  else
    return;

  /* Write message */
  for(i=0;i<100;i++){
    if(msg[i] != '#')
      putchar(msg[i]);
    else{
      putchar(CR);
      break;
    }
  }
  /* Now put things back as before */
  if( OUT_MODE) connect(OUT_RUN);
}
/************************************************************************/
void processError(int errNum)
{
  logMessage(errorMsg[errNum]);
  closeup();
}
/************************************************************************/
entry void getWts(void)
{ int i,j;
  int numInputs;
  error=0;
/*  char * msg[3]= {
    "Init in getWts#",
    "Read in wts#",
    "getWts finished#"
  };  */

  initialize();
/*  logMessage(msg[0]);   */

  open(IN_WTS);
  connect(IN_WTS);

  numCategories = getword();
  if(numCategories < 1 || numCategories > ABS_MAX_CATEGORIES){
     error=1;
     return ;
  }
  numInputs=getword();
  if(numInputs != NINP){
     error=2;
     return ;
  }

  for( i=0; i<numCategories; i++){
    F2[i].sum = 0;
    for( j=0; j<NINP; j++){
      F2[i].topDown[j]=(%sval)getword();
      F2[i].sum += F2[i].topDown[j];
    }
  }
  for( i=0; i<numCategories; i++){
    for( j=0; j<NINP; j++){
      F2[i].botUp[j]=(%sval)getword();
    }
  }

/*  logMessage(msg[1]);   */

  close(IN_WTS);
}
/****************************************************************************/
int setup(void){
  int status=0;

  openInFiles();
  openOutFiles();
  return status;
}
/****************************************************************************/
void loadInputs(void)
{
  int i,j;
  /* Get inputs to the F1 neurons */
  for(i=0; i<NINP; i++) {
    F1[i].input = (sval)getchar();
    F1[i].output=0;
  }
}
/****************************************************************************/
/*
 * Implement 2/3 rule for determining F1 outputs
 */
void setF1Outputs(){
  mono int i=0;
  sval itot;
  for(i=0;i<NINP;i++){
    F1[i].output=0;
    itot=F1[i].input + gainF1;
    if(classWinner >= 0)itot += F2[classWinner].topDown[i];
    if(itot > INPUT_THRES) F1[i].output=1;
  }
}
/****************************************************************************/
/*
 * Get number of matching 1's between the input and each F2 class
 */
void getScores(){
  mono i;
  [domain neuronF2].{
    score=0;
    for(i=0;i<NINP;i++){
      score+=botUp[i]*F1[i].input;
    }
  }
}
/****************************************************************************/
/*
 *  Get F2 neuron with best match to input
 */
void getWinner(){
  int i=0;
  sval bestScore=0;
  classWinner = -1;
  for(i=0;i<MAX_CATEGORIES;i++){
    if(F2[i].score > bestScore){
      bestScore=F2[i].score;
      classWinner=F2[i].category;
    }
  }
  if(bestScore == 0) classWinner=-1;
}
/****************************************************************************/
sval vigilance(){
  int i;
  sval topDotIn,sum;
  sum = 0;
  topDotIn=0;
  if( classWinner < 0) return (0);
  for(i=0;i<NINP;i++){
    /* Vigilance compares to the complete input vector so use F1.input
     * rather than F1.output
     */
    topDotIn += F2[classWinner].topDown[i] * F1[i].input;
    sum += F1[i].output;
  }
  return (divide(topDotIn,sum));
}
/****************************************************************************/
void adaptLTM(){
  int i;
  sval sum=BETA;

  for(i=0; i<NINP; i++){
    sum += F1[i].output;  /* After 2/3 rule used */
  }
  /* Check if the winner will be altered */
  if( F2[classWinner].sum != sum){
    /* Must be a new category if sum still equal to sum of inputs
     * (assumes no input pattern will have all inputs on)
     */
    if( ((int)(F2[classWinner].sum)) == NINP )++numCategories;
    F2[classWinner].sum=sum;
    modified=1;
  }
  for(i=0; i<NINP; i++){
    F2[classWinner].topDown[i]=F1[i].output; /* After 2/3 rule used */
    F2[classWinner].botUp[i]=divide(F2[classWinner].topDown[i],sum);
  }
}
/****************************************************************************/
void runART1(void)
{ int tested;
  /* Find output */
  gainF1=1;
  setF1Outputs();                   /* Use 2/3 rule to get F1 outputs */
  getScores();
  tested=0;
  do{
    tested++;
    getWinner();
    if(classWinner < 0) break;
    /* Count categories by monitoring when a new classWinner appears*/
    vig=vigilance();
    /* See if best match passes vigilance requirement */
    if( vig > rho){
      if(RUN_MODE==TEST_MODE)break;
      gainF1=0;
      setF1Outputs();               /* Use 2/3 rule to get F1 outputs */
      adaptLTM();
      break;
    } else{
      /* If not, then set this winner off and go to next best */
       F2[classWinner].score=0;
       classWinner=-1;
       /* Turn back on F1 gain to get inputs */
       gainF1=1;
       setF1Outputs();              /* Use 2/3 rule to get F1 outputs */
    }
  } while(tested<MAX_CATEGORIES);
  /* Check if out of F2 outputs  */
  if(tested==MAX_CATEGORIES && vig < rho
     && RUN_MODE==TRAIN_MODE) outOfOutputs=1;
}
/**************************** Entry Points ******************************/
entry void ART_Master(void)
{ int status;
  int i,j,pat,epoch;
  sval x,y;
  char ch='B';
  char * msg[4]= {
    " Setup successful#",
    " Init successful#",
    " Training finished#",
    " Number categories in wt file <= 0#"
  };

  status=setup();
  if( status){
    processError(status);
    return;
  }

  logMessage(msg[0]);

  /* Initialize the neurons unless they were loaded by getWts */
  if(GET_WTS == 0) initialize();
  logMessage(msg[1]);

  outOfOutputs=0;
  epoch=0;
  if( OUT_MODE) {
    i= rho*10;
    putchar(i);
    if( RUN_MODE==TRAIN_MODE)
      putchar(MAX_CATEGORIES);
    else{
      putchar(numCategories);
      if( numCategories <= 0){
        logMessage(msg[3]);
        return;
      }
    }
    putchar(NPATT);
  }
  // Outer loop runs till no more changes in net
  // or net runs out of prototypes.
  do {
    modified = 0;
    epoch++;
    /* run through all train. patterns */
    for (pat=0; pat<NPATT; pat++)
    {
      if( OUT_MODE) putchar(pat);
      classWinner=-1;
      loadInputs();
      runART1();
      if( OUT_MODE) putchar(classWinner);
    }
    if( OUT_MODE) {
      putchar(epoch);
      putchar(modified);
      /* log number of classes created */
      putchar(numCategories);
    }
    rewind(IN_TRAIN);
  } while ((modified) && !outOfOutputs);
  if( OUT_MODE) putchar(outOfOutputs);
  logMessage(msg[2]);
}

Lecture C:
CNAPS cn code
28-2-98