// $Id: snscode.hoc,v 1.28 1995/11/20 02:24:07 billl Exp $
// handles coding, syn assignment and syn graphics

//* CODE GENERATION AND RECOVERY 

//** Declarations 
chainlen = 1    // allow the user to change this as needed for synfind or connmat
maxflds = 4     // maximum number of codefields
cdflds = 4      // number of 'code fields'
double cdsep[maxflds+1] // code column separators
// default values see mkfields() call below
// cdsep[0] = 1e9  col 9 - 9
// cdsep[1] = 1e8  col 8 - 7
// cdsep[2] = 1e6  col 6 - 4
// cdsep[3] = 1e3  col 3 - 1
// cdsep[4] = 1e0

// labels can be entered for each of 4 fields, up to 8 labels in each
objref cdlbls[cdflds][8]
for ii=0,cdflds-1 for jj=0,7 cdlbls[ii][jj]=new String("")

//** mkfields() creates up to 4 fields (5 args: # fields, f1,f2,f3,f4)
// eg mkfields(4,1,2,3,3) to recreate the defaults
proc mkfields() { local sum,i
  cdflds = $1
  cdsep[4] = 1
  sum = $5
  cdsep[3] = 10 ^ sum
  sum = sum + $4
  cdsep[2] = 10 ^ sum
  sum = sum + $3
  cdsep[1] = 10 ^ sum
  sum = sum + $2
  cdsep[0] = 10 ^ sum
}

mkfields(4,1,2,3,3)

//** mkmkcode(): create the mkcode routine
// no arguments, should follow mkfields()
// not reliable if used under xopen()
proc mkmkcode() { local i
  printf("!{!\n")  
  printf("func mkcode() { \nreturn ")
  for (i=1;i<=cdflds;i=i+1) {
    printf("$%d*cdsep[%d] + ",i,i+maxflds-cdflds)
  }
  printf("0 \n}\n")
  printf("!}!\n")  
}

//** mkcode() creates a code of 9 cols in 4 fields, sized 1,2,3,3
func mkcode() { 
  // standard 9 cols
  // divide into 4 fields of size 1,2,3,3
  //col           8           7,6         5,4,3         2,1,0
  return $1*cdsep[1] + $2*cdsep[2] + $3*cdsep[3] + $4*cdsep[4]
}

//** cd(i,code) returns field i (1-4) from code
// $1 field number (from 1), $2 code
func cd() { local temp
  temp = ($2%cdsep[$1+(maxflds-cdflds)-1]) // remove the left side
  return int(temp/cdsep[$1+(maxflds-cdflds)])  // remove the right side
}

//** setcd(i,code,new) replaces field i (1-4) from code with new
// $1 field number (1 offset), $2 code, $3 new value
func setcd() { local temp, old
  temp = ($2%cdsep[$1+(maxflds-cdflds)-1]) // remove the left side
  old = cdsep[$1+(maxflds-cdflds)]* int(temp/cdsep[$1+(maxflds-cdflds)])
  return $2 - old + $3* cdsep[$1+(maxflds-cdflds)]
}

//** prcode(code) prints a code in readable form
// print a code of 9 cols in 4 fields, sized 1,2,3,3
proc prcode() { local i
  for (i=(maxflds-cdflds+1);i<maxflds;i=i+1) {
    printf("%d,",cd(i,$1))
  }
  printf("%d\n",cd(maxflds,$1))
}

//** prlbls(code) uses cdlbls to print out strings instead of numbers
//   when possible
proc prlbls() { local k,cde
  for (k=(maxflds-cdflds+1);k<=maxflds;k=k+1) {
    cde = cd(k,$1)
    if (k < cdflds && cde < 8) {
      if (strcmp(cdlbls[k][cde].s,"") == 0) {
        printf("%d,",cde)
      } else {
        printf("%s,",cdlbls[k][cde].s)
      }
    } else {
      printf("%d,",cde)
    }
  }
  print ""
}

//** chkpre(OBJ,CODE) searches through a syn OBJECT for this CODE
// check whether a code to be included has already been used
// 2 arguments: syn obj, comparison
// returns 1 if found, 0 if not
func chkpre () { local i
  for(i=0; i<$o1.nsyn; i=i+1) {     
    if ($o1.pre(i) == $2){ 
       return 1 } } 
  return 0 
} 

//** synfind list1 cd2 fld3 val4 cd5 fld6 val7
// print out codes on all the synapses that criteria
// cd2, cd5 are "pre" or "post" or "code"
// fld3, fld6 is the field
// val4, val7 are the values to look for matching
proc synfind () { local cdx,cdy,i,j,start
  if ((cdx = connmat_findstr($s2)) == -1) { printf("unknown string %s",$s2) return }
  if ((cdy = connmat_findstr($s5)) == -1) { printf("unknown string %s",$s5) return }
  for i=0,$o1.count-1 {
    for (j=0;j<$o1.object(i).nsyn-1;j=j+chainlen) {
      if (synmatch($o1.object(i),j,cdx,$3,$4,cdy,$6,$7)) {
        sprint(temp_string_,"pr = %s.pre(%d)",$o1.object(i),j)
        execute(temp_string_)
        printf("%s syn# %d: pre==",$o1.object(i),j)
        prlbls(pr)
        sprint(temp_string_,"pr = %s.post(%d)",$o1.object(i),j)
        execute(temp_string_)
        printf(" post==")
        prlbls(pr)
        print ""
      }
    }
  }
}

//*** synmatch obj1 ind2 cf3 fld4 val5 cf6 fld7 val8
// use the criterion in s1 to pick out the first syn in list2 and add to list1
func synmatch () { local cdd, cde, i, j
  cdd = connmat_newpt($o1,$3,$4,$2,0)
  cde = connmat_newpt($o1,$6,$7,$2,0)
  if (cdd == $5 && cde == $8) { return 1 } else { return 0 }
}

// parse the strings and return a number telling which it is
// 0:code, 1:pre, 2:post, 3:increment
func connmat_findstr () {
         if (strcmp($s1,"code") == 0) { return 0
  } else if (strcmp($s1,"pre")  == 0) { return 1
  } else if (strcmp($s1,"post") == 0) { return 2
  } else if (strcmp($s1,"inc")  == 0) { return 3
  } else {                              return -1 }
}

// look at return the field needed from the correct word
// word indication as returned by connmat_findstr()
// args: object, word, field, synnum, ind
func connmat_newpt () { local code
         if ($2==0) { code = $o1.code($4)
  } else if ($2==1) { code = $o1.pre($4)
  } else if ($2==2) { code = $o1.post($4)
  } else if ($2==3) { return $5 }
  if (code >= 0) {
    return cd($3,code)
  } else {
    return -1   // error
  }
}

//* LISTS 

//** Declarations

objref prelist  // list of all presynaptic mechanisms
prelist = new List("PRESYN")
objref tmplist
tmplist = new List()
objref sfunc
sfunc = new StringFunctions()
strdef mechnames

//** sublist() places a sublist in LIST0 from LIST1 index BEGIN to END inclusive
proc sublist () { local ii
  $o1.remove_all
  for ii=$3,$4 {
    $o1.append($o2.object(ii))
  }
}

//** catlist() concats LIST1 on end of LIST2
proc catlist () { local ii
  for ii=0,$o2.count-1 {
    $o1.append($o2.object(ii))
  }
}  

//** mechlist() creates a LIST of all this CELL type's TEMPLATE type
// list, cell, template
// make a list of mechanisms belonging to a certain template
proc mechlist () { local num,ii
//  mechnames = ""  // not a good storage since runs out of room
  if (numarg()==0) { print "mechlist(list, cell, template)" return}
  $o1 = new List($s2)
  num = $o1.count
  for ii=0,num-1 {
    sprint(temp_string_,"%s.append(%s.%s)",$o1,$o1.object(ii),$s3)
    execute(temp_string_)
//    sprint(mechnames,"%s/%d/%s.%s",mechnames,ii,$o1.object(ii),$s3)
  }
  for (ii=num-1;ii>=0;ii=ii-1) { $o1.remove(ii) }
}

//** lp() loop through a list running command in object's context
// list($o), obj_elem($s), comm($s)
// with 2 args run $o1.object().obj_elem
// with 3 args run comm($o1.object().obj_elem)
proc lp () {
  for ii=0,$o1.count-1 {
    printf("%s ",$o1.object(ii))
    if (numarg()==3) {
      sprint(temp_string_,"%s(%s.%s)",$s3,$o1.object(ii),$s2)
    } else {
      sprint(temp_string_,"%s.%s",$o1.object(ii),$s2)
    }
    execute(temp_string_)
  }
}

//** prlp() loop through a list printing object name and result of command
proc prlp () {
  for ii=0,$o1.count-1 {
    printf("%s ",$o1.object(ii))
    if (numarg()==2) {
      sprint(temp_string_,"print %s.%s",$o1.object(ii),$s2)
      execute(temp_string_)
    } else { print "" }
  }
}

//* FUNCTIONS OPERATING ACROSS ALL PRESYNS 

//** checksyns() goes through prelist and prints out divergence value
proc checksyns() { local i
  for i=0, prelist.count()-1 {
    printf("%d ",prelist.object(i).check())
  }
}

//** cleansyns() goes through prelist and collapses the synapse list
proc cleansyns() { local i
  for i=0, prelist.count()-1 {
    prelist.object(i).clean()
  }
}

//** clearsyns() goes through postsyn list and reinitializes everything
proc clearsyns() { local i,j
  tmplist.remove_all
  tmplist = new List("POSTSYN")
  for i=0, tmplist.count()-1 {
    for j=0, tmplist.object(i).mech_num - 1 {
      if (tmplist.object(i).mech[j].maxsyn > 0) {
        tmplist.object(i).mech[j].init_arrays(tmplist.object(i).mech[j].maxsyn)
      }
    }
  }
  cleansyns()
}

//* SYNAPSE ASSIGNMENT CODE 
// con=absolute convergence number, div=absolute div number
// con = %con * pre
// div * pre = con * post = S (total synapses)
// %div = div/post = S/(pre*post) = con/pre = %con
// div = %con * post
// maxdiv = int((1 + var) * div)

//** ranverge() produces randomized convergence
// used for convergence, rancon (randomized convergence) is given by 
//     ranverge(%conv,numpre,variance)
// returns a convergence number (number of syns onto a cell)
// that is randomly chosen from (%conv +/- variance)*pre
// if random not wanted just do int(pcon * numpre) instead
func ranverge () { local min, max, ran, pcon, numpre, var
  pcon = $1  numpre = $2  var = $3
  if (pcon == 1) { return numpre } // fully connected
  if (pcon == 0) { return 0 }      // no connection
  if (var != 0) {
    ran = pcon - var + (u_rand() * 2 * var)
  } else { ran = pcon }           // no variability
  return int(ran * numpre + u_rand())  // con * pre or div * post rounded up or down
}

//** pickpre() tries to reduce divergence variance
// pickpre(POSTSYN_MECH,TEMP_PRELIST,MAXDIV)
// like newpre() except that in addition to insuring no repeats
// also tries to even out divergence
// may want to create TEMP_PRELIST from PRELIST using sublist()
// maxdiv == -1 means to ignore divergence
func pickpre () { local ran, maxdiv, maxpre, cnt, ii
  maxdiv = $3
  maxpre = $o2.count-1
  cnt = 0
  while (1) {
    cnt = cnt+1
    ran = fran(0,maxpre) // all possible synapses
    if (chkpre($o1,$o2.object(ran).pre)) { // don't allow 2 connects from same place
      continue 
    }
    // chance of adding another reduces depending on how close to maxdiv
    if ((maxdiv == -1) || ($o2.object(ran).check <= maxdiv && \
	fran(0,maxdiv) < (maxdiv - $o2.object(ran).check))) { break }
    if (cnt > 3*maxpre) { // enough fruitless searching
      for ii=0,$o2.count-1 {
        if ($o2.object(ii).check <= maxdiv) {
          return ii
        }
      }
      print "ERROR in pickpre(): no more room  (maxdiv reached)"
      return -1
      }
  }
  return ran  // return the index into the cell array
}

//* template POSTSYN 

proc callback () {}

begintemplate POSTSYN
  public mech, mech_num, post
  public init_arrays, augment_array, conn
  public up
  external pickpre, callback
  objref mech[1],this,up	// to be resized later 

//** init() args are syn objects
  proc init() { 
    mech_num = numarg()
    objectvar mech[mech_num]
    if (mech_num >= 4) { mech[3] = $o4 }
    if (mech_num >= 3) { mech[2] = $o3 }
    if (mech_num >= 2) { mech[1] = $o2 }
    if (mech_num >= 1) { mech[0] = $o1 }
  }

//** augment_array() add $1 to current number of synapses
  func augment_array() { 
    num = $1
    for k=0,mech_num-1 {
      newmax = mech[k].maxsyn + num
      mech[k].init_arrays(newmax)
    }
    return newmax
  }
    

//** init_arrays() initialize all mechs to maxsyn = $1
  func init_arrays() { 
    num = $1
    for k=0,mech_num-1 {
      mech[k].init_arrays(num)
    }
    return num
  }

//** conn() find some places in a list to connect to
  // (list, conv, maxdiv, [delay, gmax])
  // maxdiv == -1 means to ignore divergence
  proc conn() {
    con = $2 maxdiv = $3
    numpre = $o1.count
    if (numarg()>3) {delay = $4  gmax = $5 setall=1 
    } else { setall = 0 }
    for j=0,con-1 { 
      if (con == numpre) {  // do all of them in order
        rannum = j
      } else {
        rannum = pickpre(mech[0],$o1,maxdiv)
        if (rannum==-1) { return }
      }
      for k=0,mech_num-1 {
        // post, pre, listcnt, syncnt, mechcnt, precnt
        mech[k].setlink($o1.object(rannum).link, $o1.object(rannum).nsyn, $o1.object(rannum).maxsyn)
        if (setall) {
          mech[k].delay(-1,delay) // set most recent (index nsyn-1)
          mech[k].gmax(-1,gmax)
        } else {
          callback(mech[k],k,rannum)
        }
      }
    } 
  }

endtemplate POSTSYN