/*
 * See bottom for address of author.
 *
 * title:       bpsim.c
 * author:      Josiah C. Hoskins
 * date:        June 1987
 *
 * purpose:     backpropagation learning rule neural net simulator
 *              for the tabula rasa Little Red Riding Hood example
 *
 * description: Bpsim provides an implementation of a neural network
 *              containing a single hidden layer which uses the
 *              generalized backpropagation delta rule for learning.
 *              A simple user interface is supplied for experimenting
 *              with a neural network solution to the Little Red Riding
 *              Hood example described in the text.
 *
 *              In addition, bpsim contains some useful building blocks
 *              for further experimentation with single layer neural
 *              networks. The data structure which describes the general
 *              processing unit allows one to easily investigate different
 *              activation (output) and/or error functions. The utility
 *              function create_link can be used to create links between
 *              any two units by supplying your own create_in_out_links
 *              function. The flexibility of creating units and links
 *              to your specifications allows one to modify the code
 *              to tune the network architecture to problems of interest.
 *
 *              There are some parameters that perhaps need some
 *              explanation. You will notice that the target values are
 *              either 0.1 or 0.9 (corresponding to the binary values
 *              0 or 1). With the sigmoidal function used in out_f the
 *              weights become very large if 0 and 1 are used as targets.
 *              The ON_TOLERANCE value is used as a criteria for an output
 *              value to be considered "on", i.e., close enough to the
 *              target of 0.9 to be considered 1. The learning_rate and
 *              momentum variables may be changed to vary the rate of
 *              learning, however, in general they each should be less
 *              than 1.0.
 *
 *              Bpsim has been compiled using CI-C86 version 2.30 on an
 *              IBM-PC and the Sun C compiler on a Sun 3/160.
 *
 *              Note to compile and link on U*IX machines use:
 *                      cc -o bpsim bpsim.c -lm
 *
 *              For other machines remember to link in the math library.
 *
 * status:      This program may be freely used, modified, and distributed
 *              except for commercial purposes.
 *
 * Copyright (c) 1987   Josiah C. Hoskins
 */
 /* Modified to function properly under Turbo C by replacing malloc(...)
    with calloc(...,1). Thanks to Pavel Rozalski who detected the error.
    He assumed that Turbo C's "malloc" doesn't automatically set pointers
    to NULL - and he was right!
    Thomas Muhr, Berlin April, 1988
 */

#include <math.h>
#include <stdio.h>
#include <ctype.h>

#define BUFSIZ          512

#define FALSE           0
#define TRUE            !FALSE
#define NUM_IN          6       /* number of input units */
#define NUM_HID         3       /* number of hidden units */
#define NUM_OUT         7       /* number of output units */
#define TOTAL           (NUM_IN + NUM_HID + NUM_OUT)
#define BIAS_UID        (TOTAL) /* threshold unit */

/* macros to provide indexes for processing units */
#define IN_UID(X)       (X)
#define HID_UID(X)      (NUM_IN + X)
#define OUT_UID(X)      (NUM_IN + NUM_HID + X)
#define TARGET_INDEX(X) (X - (NUM_IN + NUM_HID))

#define WOLF_PATTERN    0
#define GRANDMA_PATTERN 1
#define WOODCUT_PATTERN 2
#define PATTERNS        3       /* number of input patterns */
#define ERROR_TOLERANCE 0.01
#define ON_TOLERANCE    0.8     /* a unit's output is on if > ON_TOLERENCE */
#define NOTIFY          10      /* iterations per dot notification */
#define DEFAULT_ITER    250

struct unit {                   /* general processing unit */
  int    uid;                   /* integer uniquely identifying each unit */
  char   *label;
  double output;                /* activation level */
  double (*unit_out_f)();       /* note output fcn == activation fcn*/
  double delta;                 /* delta for unit */
  double (*unit_delta_f)();     /* ptr to function to calc delta */
  struct link *inlinks;         /* for propagation */
  struct link *outlinks;        /* for back propagation */
} *pu[TOTAL+1];                 /* one extra for the bias unit */

struct link {                   /* link between two processing units */
  char   *label;
  double weight;                /* connection or link weight */
  double data;                  /* used to hold the change in weights */
  int    from_unit;             /* uid of from unit */
  int    to_unit;               /* uid of to unit */
  struct link *next_inlink;
  struct link *next_outlink;
};

int     iterations = DEFAULT_ITER;
double  learning_rate = 0.2;
double  momentum = 0.9;
double  pattern_err[PATTERNS];

/*
 * Input Patterns
 * {Big Ears, Big Eyes, Big Teeth, Kindly, Wrinkled, Handsome}
 *   unit 0    unit 1     unit 2   unit 3   unit 4    unit 5
 */
double  input_pat[PATTERNS+1][NUM_IN] = {
  {1.0, 1.0, 1.0, 0.0, 0.0, 0.0},       /* Wolf */
  {0.0, 1.0, 0.0, 1.0, 1.0, 0.0},       /* Grandma */
  {1.0, 0.0, 0.0, 1.0, 0.0, 1.0},       /* Woodcutter */
  {0.0, 0.0, 0.0, 0.0, 0.0, 0.0},       /* Used for Recognize Mode */
};

/*
 * Target Patterns
 * {Scream, Run Away, Look for Woodcutter, Approach, Kiss on Cheek,
 *      Offer Food, Flirt with}
 */
double  target_pat[PATTERNS][NUM_OUT] = {
  {0.9, 0.9, 0.9, 0.1, 0.1, 0.1, 0.1},  /* response to Wolf */
  {0.1, 0.1, 0.1, 0.9, 0.9, 0.9, 0.1},  /* response to Grandma */
  {0.1, 0.1, 0.1, 0.9, 0.1, 0.9, 0.9},  /* response to Woodcutter */
};

/*
 * function declarations
 */
void    print_header();
char    get_command();
double  out_f(), delta_f_out(), delta_f_hid(), random(), pattern_error();


main()
{
  char   ch;
  extern struct unit *pu[];

  print_header();
  create_processing_units(pu);
  create_in_out_links(pu);
  for (;;) {
    ch = get_command("\nEnter Command (Learn, Recognize, Quit) => ");
    switch (ch) {
    case 'l':
    case 'L':
      printf("\n\tLEARN MODE\n\n");
      learn(pu);
      break;
    case 'r':
    case 'R':
      printf("\n\tRECOGNIZE MODE\n\n");
      recognize(pu);
      break;
    case 'q':
    case 'Q':
      exit(1);
      break;
    default:
      fprintf(stderr, "Invalid Command\n");
      break;
    }
  }
}


void
print_header()
{
  printf("%s%s%s",
         "\n\tBPSIM -- Back Propagation Learning Rule Neural Net Simulator\n",
         "\t\t for the tabula rasa Little Red Riding Hood example.\n\n",
         "\t\t Written by Josiah C. Hoskins\n");
}


/*
 * create input, hidden, output units (and threshold or bias unit)
 */
create_processing_units(pu)
struct  unit *pu[];
{
  int   id;                     /* processing unit index */
  struct unit *create_unit();

  for (id = IN_UID(0); id < IN_UID(NUM_IN); id++)
    pu[id] = create_unit(id, "input", 0.0, NULL, 0.0, NULL);
  for (id = HID_UID(0); id < HID_UID(NUM_HID); id++)
    pu[id] = create_unit(id, "hidden", 0.0, out_f, 0.0, delta_f_hid);
  for (id = OUT_UID(0); id < OUT_UID(NUM_OUT); id++)
    pu[id] = create_unit(id, "output", 0.0, out_f, 0.0, delta_f_out);
  pu[BIAS_UID] = create_unit(BIAS_UID, "bias", 1.0, NULL, 0.0, NULL);
}


/*
 * create links - fully connected for each layer
 *                note: the bias unit has one link to ea hid and out unit
 */
create_in_out_links(pu)
struct  unit *pu[];
{
  int   i, j;           /* i == to and j == from unit id's */
  struct link *create_link();

  /* fully connected units */
  for (i = HID_UID(0); i < HID_UID(NUM_HID); i++) { /* links to hidden */
    pu[BIAS_UID]->outlinks =
      pu[i]->inlinks = create_link(pu[i]->inlinks, i,
                                   pu[BIAS_UID]->outlinks, BIAS_UID,
                                   (char *)NULL,
                                   random(), 0.0);
    for (j = IN_UID(0); j < IN_UID(NUM_IN); j++) /* from input units */
      pu[j]->outlinks =
        pu[i]->inlinks = create_link(pu[i]->inlinks, i, pu[j]->outlinks, j,
                                     (char *)NULL, random(), 0.0);
  }
  for (i = OUT_UID(0); i < OUT_UID(NUM_OUT); i++) {     /* links to output */
    pu[BIAS_UID]->outlinks =
            pu[i]->inlinks = create_link(pu[i]->inlinks, i,
                                         pu[BIAS_UID]->outlinks, BIAS_UID,
                                         (char *)NULL, random(), 0.0);
    for (j = HID_UID(0); j < HID_UID(NUM_HID); j++) /* from hidden units */
      pu[j]->outlinks =
        pu[i]->inlinks = create_link(pu[i]->inlinks, i, pu[j]->outlinks, j,
                                     (char *)NULL, random(), 0.0);
  }
}


/*
 * return a random number bet 0.0 and 1.0
 */
double
random()
{
  return((rand() % 32727) / 32737.0);
}


/*
 * the next two functions are general utility functions to create units
 * and create links
 */
struct unit *
create_unit(uid, label, output, out_f, delta, delta_f)
int  uid;
char *label;
double   output, delta;
double   (*out_f)(), (*delta_f)();
{
  struct unit  *unitptr;

/*
  if (!(unitptr = (struct unit *)malloc(sizeof(struct unit)))) {
TURBO C doesnt automatically set pointers to NULL - so use calloc(...,1) */
  if (!(unitptr = (struct unit *)calloc(sizeof(struct unit),1))) {
    fprintf(stderr, "create_unit: not enough memory\n");
    exit(1);
  }
  /* initialize unit data */
  unitptr->uid = uid;
  unitptr->label = label;
  unitptr->output = output;
  unitptr->unit_out_f = out_f;  /* ptr to output fcn */
  unitptr->delta = delta;
  unitptr->unit_delta_f = delta_f;
  return (unitptr);
}


struct link *
create_link(start_inlist, to_uid, start_outlist, from_uid, label, wt, data)
struct  link *start_inlist, *start_outlist;
int     to_uid, from_uid;
char *  label;
double  wt, data;
{
  struct link  *linkptr;

/*  if (!(linkptr = (struct link *)malloc(sizeof(struct link)))) { */
  if (!(linkptr = (struct link *)calloc(sizeof(struct link),1))) {
    fprintf(stderr, "create_link: not enough memory\n");
    exit(1);
  }
  /* initialize link data */
  linkptr->label = label;
  linkptr->from_unit = from_uid;
  linkptr->to_unit = to_uid;
  linkptr->weight = wt;
  linkptr->data = data;
  linkptr->next_inlink = start_inlist;
  linkptr->next_outlink = start_outlist;
  return(linkptr);
}


char
get_command(s)
char    *s;
{
  char  command[BUFSIZ];

  fputs(s, stdout);
  fflush(stdin); fflush(stdout);
  (void)fgets(command, BUFSIZ, stdin);
  return((command[0]));         /* return 1st letter of command */
}


learn(pu)
struct unit *pu[];
{
  register i, temp;
  char   tempstr[BUFSIZ];
  extern int    iterations;
  extern double learning_rate, momentum;
  static char prompt[] = "Enter # iterations (default is 250) => ";
  static char quote1[] = "Perhaps, Little Red Riding Hood ";
  static char quote2[] = "should do more learning.\n";

  printf(prompt);
  fflush(stdin); fflush(stdout);
  gets(tempstr);
  if (temp = atoi(tempstr))
    iterations = temp;

  printf("\nLearning ");
  for (i = 0; i < iterations; i++) {
    if ((i % NOTIFY) == 0) {
      printf(".");
      fflush(stdout);
    }
    bp_learn(pu, (i == iterations-2 || i == iterations-1 || i == iterations));
  }
  printf(" Done\n\n");
  printf("Error for Wolf pattern = \t%lf\n", pattern_err[0]);
  printf("Error for Grandma pattern = \t%lf\n", pattern_err[1]);
  printf("Error for Woodcutter pattern = \t%lf\n", pattern_err[2]);
  if (pattern_err[WOLF_PATTERN] > ERROR_TOLERANCE) {
    printf("\nI don't know the Wolf very well.\n%s%s", quote1, quote2);
  } else if (pattern_err[GRANDMA_PATTERN] > ERROR_TOLERANCE) {
    printf("\nI don't know Grandma very well.\n%s%s", quote1, quote2);
  } else if (pattern_err[WOODCUT_PATTERN] > ERROR_TOLERANCE) {
    printf("\nI don't know Mr. Woodcutter very well.\n%s%s", quote1, quote2);
  } else {
    printf("\nI feel pretty smart, now.\n");
  }
}


/*
 * back propagation learning
 */
bp_learn(pu, save_error)
struct unit *pu[];
int    save_error;
{
  static int count = 0;
  static int pattern = 0;
  extern double pattern_err[PATTERNS];

  init_input_units(pu, pattern); /* initialize input pattern to learn */
  propagate(pu);                 /* calc outputs to check versus targets */
  if (save_error)
    pattern_err[pattern] = pattern_error(pattern, pu);
  bp_adjust_weights(pattern, pu);
  if (pattern < PATTERNS - 1)
    pattern++;
  else
      pattern = 0;
  count++;
}


/*
 * initialize the input units with a specific input pattern to learn
 */
init_input_units(pu, pattern)
struct unit *pu[];
int    pattern;
{
  int   id;

  for (id = IN_UID(0); id < IN_UID(NUM_IN); id++)
    pu[id]->output = input_pat[pattern][id];
}


/*
 * calculate the activation level of each unit
 */
propagate(pu)
struct unit *pu[];
{
  int   id;

  for (id = HID_UID(0); id < HID_UID(NUM_HID); id++)
    (*(pu[id]->unit_out_f))(pu[id], pu);
  for (id = OUT_UID(0); id < OUT_UID(NUM_OUT); id++)
    (*(pu[id]->unit_out_f))(pu[id], pu);
}


/*
 * function to calculate the activation or output of units
 */
double
out_f(pu_ptr, pu)
struct unit *pu_ptr, *pu[];
{
  double sum = 0.0, exp();
  struct link *tmp_ptr;

  tmp_ptr = pu_ptr->inlinks;
  while (tmp_ptr) {
    /* sum up (outputs from inlinks times weights on the inlinks) */
    sum += pu[tmp_ptr->from_unit]->output * tmp_ptr->weight;
    tmp_ptr = tmp_ptr->next_inlink;
  }
  pu_ptr->output = 1.0/(1.0 + exp(-sum));
}


/*
 * half of the sum of the squares of the errors of the
 * output versus target values
 */
double
pattern_error(pat_num, pu)
int     pat_num;        /* pattern number */
struct  unit *pu[];
{
  int           i;
  double        temp, sum = 0.0;

  for (i = OUT_UID(0); i < OUT_UID(NUM_OUT); i++) {
    temp = target_pat[pat_num][TARGET_INDEX(i)] - pu[i]->output;
    sum += temp * temp;
  }
  return (sum/2.0);
}


bp_adjust_weights(pat_num, pu)
int     pat_num;        /* pattern number */
struct  unit *pu[];
{
  int           i;              /* processing units id */
  double        temp1, temp2, delta, error_sum;
  struct link   *inlink_ptr, *outlink_ptr;

  /* calc deltas */
  for (i = OUT_UID(0); i < OUT_UID(NUM_OUT); i++) /* for each output unit */
    (*(pu[i]->unit_delta_f))(pu, i, pat_num); /* calc delta */
  for (i = HID_UID(0); i < HID_UID(NUM_HID); i++) /* for each hidden unit */
    (*(pu[i]->unit_delta_f))(pu, i);      /* calc delta */
  /* calculate weights */
  for (i = OUT_UID(0); i < OUT_UID(NUM_OUT); i++) {     /* for output units */
    inlink_ptr = pu[i]->inlinks;
    while (inlink_ptr) {        /* for each inlink to output unit */
      temp1 = learning_rate * pu[i]->delta *
        pu[inlink_ptr->from_unit]->output;
      temp2 = momentum * inlink_ptr->data;
      inlink_ptr->data = temp1 + temp2; /* new delta weight */
      inlink_ptr->weight += inlink_ptr->data;   /* new weight */
      inlink_ptr = inlink_ptr->next_inlink;
    }
  }
  for (i = HID_UID(0); i < HID_UID(NUM_HID); i++) { /* for ea hid unit */
    inlink_ptr = pu[i]->inlinks;
    while (inlink_ptr) {        /* for each inlink to output unit */
      temp1 = learning_rate * pu[i]->delta *
        pu[inlink_ptr->from_unit]->output;
      temp2 = momentum * inlink_ptr->data;
      inlink_ptr->data = temp1 + temp2; /* new delta weight */
      inlink_ptr->weight += inlink_ptr->data;   /* new weight */
        inlink_ptr = inlink_ptr->next_inlink;
    }
  }
}


/*
 * calculate the delta for an output unit
 */
double
delta_f_out(pu, uid, pat_num)
struct unit *pu[];
int    uid, pat_num;
{
  double        temp1, temp2, delta;

  /* calc deltas */
  temp1 = (target_pat[pat_num][TARGET_INDEX(uid)] - pu[uid]->output);
  temp2 = (1.0 - pu[uid]->output);
  delta = temp1 * pu[uid]->output * temp2; /* calc delta */
  pu[uid]->delta = delta; /* store delta to pass on */
}


/*
 * calculate the delta for a hidden unit
 */
double
delta_f_hid(pu, uid)
struct unit *pu[];
int    uid;
{
  double        temp1, temp2, delta, error_sum;
  struct link   *inlink_ptr, *outlink_ptr;

  outlink_ptr = pu[uid]->outlinks;
  error_sum = 0.0;
  while (outlink_ptr) {
    error_sum += pu[outlink_ptr->to_unit]->delta * outlink_ptr->weight;
    outlink_ptr = outlink_ptr->next_outlink;
  }
  delta = pu[uid]->output * (1.0 - pu[uid]->output) * error_sum;
  pu[uid]->delta = delta;
}


recognize(pu)
struct unit *pu[];
{
  int    i;
  char   tempstr[BUFSIZ];
  static char *p[] = {"Big Ears?", "Big Eyes?", "Big Teeth?",
                      "Kindly?\t", "Wrinkled?", "Handsome?"};

  for (i = 0; i < NUM_IN; i++) {
    printf("%s\t(y/n) ", p[i]);
    fflush(stdin); fflush(stdout);
    fgets(tempstr, BUFSIZ, stdin);
    if (tempstr[0] == 'Y' || tempstr[0] == 'y')
      input_pat[PATTERNS][i] = 1.0;
    else
      input_pat[PATTERNS][i] = 0.0;
  }
  init_input_units(pu, PATTERNS);
  propagate(pu);
  print_behaviour(pu);
}


print_behaviour(pu)
struct unit *pu[];
{
  int   id, count = 0;
  static char *behaviour[] = {
    "Screams", "Runs Away", "Looks for Woodcutter", "Approaches",
    "Kisses on Cheek", "Offers Food", "Flirts with Woodcutter" };

  printf("\nLittle Red Riding Hood: \n");
  for (id = OUT_UID(0); id < OUT_UID(NUM_OUT); id++){ /* links to out units */
    if (pu[id]->output > ON_TOLERANCE)
      printf("\t%s\n", behaviour[count]);
    count++;
  }
  printf("\n");
}

/*
! Thomas Muhr    Knowledge-Based Systems Dept. Technical University of Berlin !
! BITNET/EARN:   muhrth@db0tui11.bitnet                                       !
! UUCP:          morus@netmbx.UUCP (Please don't use from outside Germany)    !
! BTX:           030874162  Tel.: (Germany 0049) (Berlin 030) 87 41 62        !
*/

