#include "network.h"
// setgauss needs to be included here because derivs has a #include network.h.
#include "setgauss.c"
#include <cstring>;
using namespace std;
/*
Peter Latham
May, 2002
version 1
---In a departure from the sn series, excitatory parameters
are first. So, for example, num[0] = number of excitatory
neurons and num[1] = number of inhibitory ones.
---public variables
deltav - vthreshold-vrest, in mV.
display - array telling which neurons are displayed.
dt - time step in ms.
dzap - phase gets mulitplied by dezap, which must
be less than 1. this brings phase near zero
and turns neuron off.
eta[i][j] - eta[i] specifies which neurons are involved
in memory i. see set_mem for details.
enorm[i] - normalized reversal potential for cell of type i.
f - fraction of neurons involved in a memory.
j_extra - which extra spike appears.
mu[i] - normalized applied current for each cell.
neurons - number of neurons.
no_display - number of neurons that are displayed
no_mem - number of memories.
num[i] - number of neurons of type i.
p_zap[i] - probability of a zapping neuron firing per time step.
first arg is for on zapping; second is for off.
suffix - suffix that is appended to output files.
tau[i] - time constant of cell i, ms.
tau_s[i] - time constant for synapses of type i, ms.
tmax - maximum time of simulation, in ms.
tmin - starting time of simulation, in ms.
we write to file only for t >= 0, so setting
tmin negative will get rid of transients.
type[i] - type of neuron i. 0=exc, 1=inh.
t_zap_on - time in ms when zapping starts.
t_zap_off - time in ms when zapping starts.
t_dzap_on - time in ms when dezapping (hyperpolarizing) starts.
t_dzap_off - time in ms when dezapping (hyperpolarizing) stops.
t_extra - time at which extra spike appears.
vbar - (vrest+vthreshold)/2, in mV.
w[j][i] - weight, from neuron j to neuron wn[j][i].
wn[j][i] - index of nonzero weights. e.g., if
wn[n][i] = j, then weight from neuron n to
neuron j is w[n][i].
wntot[j] - number of connections neuron j makes.
typical loop. z.x[0][n] is presynaptic,
z.x[2][wn[n][i]] is postsynaptic.
for (n=0; n < neurons; n++)
{
for (i=0; i < wntot[j]; i++)
{
z.x[0][wn[n][i]] = w[n][i] ... z.x[0][n]
}
}
who_zap[i] - list of neurons within a sub-population.
who_zap[i]=1 means neuron is in sub-population,
who_zap[i]=0 means it is not.
zap - add phase zap to any neuron that is zapped.
*/
// ---constructor
network::network(int argc, char** argv, print pr)
{
// ---get raw parameters
params raw(argc, argv, pr.cur, pr.in, pr.s);
// ---miscellaneous
no_display = raw.no_display;
display = (int*) calloc(no_display, sizeof(int));
for (int i=0; i < no_display; i++) display[i] = raw.display[i];
// ---parameters related to numbers of neurons.
neurons = raw.neurons;
num[0] = (int) (raw.frac_inh*neurons + 0.5) > neurons ? 0 :
neurons - (int) (raw.frac_inh*neurons + 0.5);
num[1] = neurons-num[0];
type = set2(num[0], neurons, 0, 1);
// ---firing rate quantization
dnu = raw.dnu;
// ---parameters related to voltage.
vbar = (raw.v_t + raw.v_r)/2.0;
deltav = raw.v_t - raw.v_r;
enorm[0] = (raw.e_exc-vbar)/deltav;
enorm[1] = (raw.e_inh-vbar)/deltav;
// ---time constants/interpoloation
tau = set2(num[0], neurons, raw.tau_m[0], raw.tau_m[1]);
for (int i=0; i < 2; i++) tau_s[i] = raw.tau_s[i];
// ---parameters related to time
dt = raw.dt;
tmax = raw.tmax;
tmin = raw.tmin;
t_average = raw.t_average;
// ---associative memory
zap = raw.zap;
dzap = raw.dzap;
t_zap_on = raw.t_zap_on;
t_zap_off = raw.t_zap_off;
t_dzap_on = raw.t_dzap_on;
t_dzap_off = raw.t_dzap_off;
no_mem = raw.no_mem;
f = raw.f;
p_zap[0] = raw.nu_zap[0]*(dt/1000);
p_zap[1] = raw.nu_zap[1]*(dt/1000);
// ---rezap
rezap = raw.rezap;
dtzap = raw.dtzap;
newzap = raw.newzap;
if (no_mem > 0)
{
deps = (float*) calloc(no_mem, sizeof(float));
for (int i=0; i < no_mem; i++) deps[i]=1;
for (int i=0; i < raw.no_deps/2; i++)
{
int m = (int) (raw.dneps[2*i] + 0.5) - 1;
if (m < 0 || m >= no_mem)
write_warning("n_deps=",m+1,"; out of range.");
else deps[m] = raw.dneps[2*i+1];
}
}
// ---extra spikes
t_extra = raw.t_extra;
j_extra = raw.j_extra;
// ---output files
suffix = (char*) calloc(1024, sizeof(char));
strcpy(suffix, raw.suffix);
// ---start random number generator
srand(raw.seed);
// ---gaussian random variable with 100*neurons possible values
rgauss g(100*neurons);
// ---applied current
set_current(g, raw);
// ---firing rate. quantized at dnu.
nu_in = set_distribution(g, raw.nu_no, raw.nu_p, raw.nu_mean,
raw.nu_sd, raw.nu_min, raw.nu_max, dnu);
// ---weights. don't compute if pr.out=1.
if (!pr.out)
{
float wmin, wmax;
float** w_to_v = set_weights(g, raw, wmin, wmax);
// ---memories
if (no_mem)
{
// ---set memories
set_mem(raw, wmin, wmax);
// ---memories to zap
who_zap = (int*) calloc(num[0], sizeof(int));
if (raw.whozap >= 0) for (int i=0; i < num[0]; i++)
who_zap[i] = eta[raw.whozap][i] > 0 ? 1 : 0;
else for (int i=0; i < num[0]; i++)
who_zap[i] = drand() < raw.f ? 1 : 0;
}
// ---print mean and standard deviation of weights. also
// compute actual max excitatory and inhibitory weights.
float* vmax = get_stats(w_to_v);
// ---diagnostic: print weights.
if (pr.weights) write_weights(pr.weights, vmax, w_to_v);
}
// ---write parameters and exit if pr.out is on.
if (pr.out) write_params(raw);
}
// ---set applied current, ia
void network::set_current(rgauss& g, params& raw)
{
/*
The distribution of currents is the weighted sum of truncated
Gaussian distributions, for mainly historical reasons.
The mean, standard deviation, minimum and maximum values of the
truncated Gaussian are ia_mean, ia_sd, ia_min, ia_max, and ia_sd,
respectively. The number of weighted Gaussians is equal to the
number of variables specified in ia_mean in the namelist.
The variable that determines the weight of each distribution
is ia_p. Unfortunately, this variable is a little bit complicated,
because as an add-on I decided to let the excitatory and
inhibitory neurons have a different distribution. So here's
the convention:
If all the ia_p are non-negative, then the excitatory and
inhibitory neurons have the same distribution.
If any of the ia_p are negative, then the excitatory neurons
go with the distributions with non-negative ia_p and the
inhibitory neurons go with the distributions with negative
ia_p. For both types the total probability is forced to
add to 1, and of course |ia_p| will be used to compute the
probability.
The code will exit if there are no distributions, or only one
distribution with negative probability.
*/
float* ia = set_distribution(g, raw.ia_no, raw.ia_p, raw.ia_mean,
raw.ia_sd, raw.ia_min, raw.ia_max, 0.0);
// ---now compute normalized drive.
mu = (float*) calloc(neurons, sizeof(float));
for (int n=0; n < neurons; n++) mu[n] = ia[n]/deltav - 0.25;
// ---output excitatory and inhibitory current distribution
FILE* ia_e = fopen(cat2("mu_e", raw.suffix), "w");
for (int i=0; i < num[0]; i++)
fprintf(ia_e, "%10.6f %10.6f\n", ia[i], mu[i]);
fclose(ia_e);
FILE* ia_i = fopen(cat2("mu_i", raw.suffix), "w");
for (int i=num[0]; i < neurons; i++)
fprintf(ia_i, "%10.6f %10.6f\n", ia[i], mu[i]);
fclose(ia_i);
free(ia);
}
// ---set distribution
float* network::set_distribution(rgauss& g, int n, float* p,
float* mean, float* sd, float* min, float* max, float dx)
{
/*
Constructs a distribution made up of a weighted sum of
truncated Gaussian distributions, for mainly historical reasons.
The code will exit if there are no distributions, or only one
distribution with negative probability.
INPUT:
g - object containing gaussian random numbers.
n - number of truncated Gaussian distributions. of 0, return
all 0's.
p - probability of each one occurring, with a twist:
If all the p are non-negative, then the excitatory
and inhibitory neurons have the same distribution.
If any of the p are negative, then the excitatory
neurons go with the distributions with non-negative
p and the inhibitory neurons go with the
distributions with negative ia_p. For both types the
total probability is forced to add to 1, and of
course |p| will be used to compute the probability.
For this to work, of course, this routine must have
access to the number of excitatory and inhibitory
neurons, which it does (through num[k]).
mean - mean of each distribution.
sd - standard deviation of each distribution.
min - min of each distribution.
max - max of each distribution.
dx - if > 0, values quantized by rounding to nearest integer*dx.
RETURNS:
x - variable whose distribution is specified, as described
above. note that the excitatory and inhibitory portions
of x (the first num[0] and last num[1] elements) may have
different distributions.
*/
// ---space for output
float* x = (float*) calloc(neurons, sizeof(float));
// ---if no data, return all zeros.
if (n == 0) return x;
// ---reserve (slightly too much) space
float** prob = cfloat(2, n);
float** f = cfloat(n, 5);
// ---first, find probability distributions for each type.
int i;
for (i=0; i < n; i++) if (p[i] < 0) break;
if (i == n)
{
// ---all distributions are non-negative.
for (i=0; i < n; i++) for (int k=0; k < 2; k++)
prob[k][i] = p[i];
}
else
{
// ---non-negative probabilities go to excitatory neurons,
// negative probabilities go to inhibitory neurons,
for (i=0; i < n; i++) for (int k=0; k < 2; k++)
if (p[i] < 0)
{
prob[1][i] = -p[i];
prob[0][i] = 0;
}
else
{
prob[1][i] = 0;
prob[0][i] = +p[i];
}
}
/* ---f defined as follows:
f[i][j] - i labels distribution; it runs from 0 to n-1.
j=0: mean of gaussian.
1: sd of gaussian.
2: min of gaussian.
3: max of gaussian.
4: probability that an element gets assigned to this
group.
*/
// ---set output
float gmin=0;
for (int k=0; k < 2; k++)
{
int cnt=0;
for (int i=0; i < n; i++)
{
if (prob[k][i] > 0)
{
f[cnt][0] = mean[i];
f[cnt][1] = sd[i];
f[cnt][2] = min[i];
f[cnt][3] = max[i];
f[cnt][4] = prob[k][i];
cnt++;
// ---keep track of global min
gmin = min[i] < gmin ? min[i] : gmin;
}
}
float* x_tmp = setgauss(num[k], cnt, f, 0, g);
int imin = k == 0 ? 0 : num[0];
int imax = k == 0 ? num[0] : neurons;
for (int i=imin; i < imax; i++) x[i] = x_tmp[i-imin];
free(x_tmp);
}
// ---cleanup
cfree(2, prob);
cfree(n, f);
// ---quantize if dx > 0
if (dx > 0)
{
int imin = gmin >= 0 ? 0 : (int) (gmin/dx) - 1;
for (int i=0; i < neurons; i++)
x[i] = dx*(imin + (int) (x[i]/dx + 0.5 - imin));
}
// ---return
return x;
}
// ---set weights, w
float** network::set_weights(rgauss& g, params& raw, float& wmin, float& wmax)
{
/*
set weights.
INPUT:
g - Gaussian random variable.
raw - object containing all sorts of parameters.
OUTPUT:
wmin - minimum allowed weight; needed for clipping memories.
wmax - maximum allowed weight; needed for clipping memories.
RETURNS:
w_to_v - factor used to translate from weights to psp size. formula is:
vpsp[type[i][type[j]] =
w_to_v[type[i]][type[wn[i][j]]]*w[i][j]
remember, convention is w[i][j] is weight from neuron i
to neuron wn[i][j]. w_to_v[i][j] is from neuron of type i to
neuron of type j.
COMPUTES:
w[i][j] - weight, from neuron i to neuron wn[i][j].
wn[i][j]- index of postsynaptic neuron.
wntot[i]- number of connections neuron i makes.
*/
// ---probability of connection. pconnect[i][j] is the probability
// that a neuron of type j connects to a neuron of type i.
float** pconnect = cfloat(2, 2);
for (int i=0; i < 2; i++) for (int j=0; j < 2; j++)
pconnect[i][j] = raw.axons[j][i]/neurons;
// ---normalized PSP sizes, from neuron i to neuron j.
float** v_to_w = cfloat(2, 2);
float** w_to_v = cfloat(2, 2);
float tau_m[2] = {raw.tau_m[0], raw.tau_m[1]};
for (int i=0; i < 2; i++) for (int j=0; j < 2; j++)
{
float dum = tau_m[j] == tau_s[i] ? 1 :
log(tau_m[j]/tau_s[i])/(tau_m[j]/tau_s[i]-1);
// ---vpsp[i][j] is from neuron i to neuron j.
v_to_w[i][j] = (raw.vpsp[i][j]/deltav)
* (1/(enorm[i]+0.5))
* (tau_m[j]/tau_s[i])*exp(dum);
w_to_v[i][j] = raw.vpsp[i][j]/v_to_w[i][j];
// ---w_to_v is positive by convention
w_to_v[i][j] *= w_to_v[i][j] > 0 ? 1 : -1;
// ---set wbar, positive by convention
wbar[i][j] = v_to_w[i][j] > 0 ? v_to_w[i][j] : -v_to_w[i][j];
}
// ---minimum and maximum weights between excitatory neurons.
// needed for setting memories.
wmin = v_to_w[0][0]*raw.pspmin/raw.vpsp[0][0];
wmax = v_to_w[0][0]*raw.pspmax/raw.vpsp[0][0];
// ---set weights. w[j][i] is weight from neuron j to neuron wn[j][i]
w = new float*[neurons];
wn = new int*[neurons];
wntot = new int[neurons];
float sqrt3 = sqrt(3);
for (int j=0; j < neurons; j++)
{
ostrstream w_ptr;
ostrstream wn_ptr;
wntot[j]=0;
for (int i=0; i < neurons; i++)
{
if (drand() < pconnect[type[i]][type[j]] && i != j)
{
float ww = v_to_w[type[j]][type[i]]*
(1+raw.dw*drand(-sqrt3, sqrt3));
w_ptr.write((char*) &ww, sizeof(ww));
wn_ptr.write((char*) &i, sizeof(i));
wntot[j]++;
}
}
// freeze ostrstream
w[j] = (float*) w_ptr.str();
wn[j] = (int*) wn_ptr.str();
}
return w_to_v;
}
// ---set memories
void network::set_mem(params& raw, float wmin, float wmax)
{
/*
Set memories. For each memory, the connection strength from
neuron j to neuron i is increased by
eps * (eta[i] + f*norm) * eta[j]
where only excitatory neurons are involved and:
eps = strength of memory normalized to sqrt(p*f*(1-f))
where p is the number of memories and f is
the fraction of neurons involved in a
memory (see eta).
eta = 1-f with probability f and -f with probability 1-f.
norm = 0: both pre and post-synaptic normalization,
1: postsynatpic normalization only.
After modifying the connection strength, the weights are
clipped: they can't fall below wmin or go above wmax.
*/
// ---space for eta.
if (no_mem) eta = cfloat(no_mem, num[0]);
// ---modify memories
for (int m=0; m < no_mem; m++)
{
// ---normalize memory strength.
float wee = deps[m]*raw.eps/sqrt(raw.f*(1-raw.f));
// --create eta.
set_eta(m, raw.f);
// ---modify connection strengths among excitatory neurons,
// from neuron j to neurons wn[j][i].
for (int j=0; j < num[0]; j++)
{
for (int i=0; i < wntot[j]; i++) if (!type[wn[j][i]])
{
w[j][i] += wee*(eta[m][wn[j][i]]+
raw.f*raw.norm)*eta[m][j];
}
}
}
// ---clip memories.
for (int j=0; j < neurons; j++) if (type[j] == 0)
{
for (int i=0; i < wntot[j]; i++)
{
w[j][i] = w[j][i] < wmin ? wmin : w[j][i];
w[j][i] = w[j][i] > wmax ? wmax : w[j][i];
}
}
// ---make list of which memories each neuron participates in.
// wmem stands for "which memory".
int* wtmp = (int*) calloc(no_mem, sizeof(int));
nwmem = (int*) calloc(num[0], sizeof(int));
wmem = (int**) calloc(num[0], sizeof(int*));
for (int i=0; i < num[0]; i++)
{
for (int j=0; j < no_mem; j++) if (eta[j][i] > 0)
wtmp[nwmem[i]++]=j;
wmem[i] = (int*) calloc(nwmem[i], sizeof(int));
for (int j=0; j < nwmem[i]; j++) wmem[i][j] = wtmp[j];
}
free(wtmp);
}
// ---create memory vector, eta.
void network::set_eta(int m, float f)
{
/*
set eta: if neuron is excitatory: 1-f with probability f
-f with probability 1-f
if neuron is inhibitory: no value
*/
for (int i=0; i < num[0]; i++)
eta[m][i] = (drand() < f && !type[i]) ? 1-f : -f;
}
// ---get statistics on memory: mean and variance
float* network::get_stats(float** w_to_v)
{
// ---reserve space
double** mean = init_constant(2, 2, (double) 0);
double** sigma = init_constant(2, 2, (double) 0);
int** cnt = init_constant(2, 2, 0);
// ---means, min, max
float* vmax = (float*) calloc(2, sizeof(float));
for (int i=0; i < neurons; i++) for (int j=0; j < wntot[i]; j++)
{
// ---index of the postsynaptic neuron
int k = wn[i][j];
// ---first and second moments
mean[type[i]][type[k]] += w[i][j];
sigma[type[i]][type[k]] += w[i][j]*w[i][j];
cnt[type[i]][type[k]]++;
// ---min and max
double mv = w_to_v[type[i]][type[wn[i][j]]]*w[i][j];
vmax[type[i]] = mv > vmax[type[i]] ? mv : vmax[type[i]];
}
// ---normalize and compute standard deviation
for (int k=0; k < 2; k++) for (int l=0; l < 2; l++)
{
mean[k][l] /= cnt[k][l] == 0 ? 1 : cnt[k][l];
sigma[k][l] /= cnt[k][l] == 0 ? 1 : cnt[k][l];
sigma[k][l] = sigma[k][l] - mean[k][l]*mean[k][l];
sigma[k][l] = sigma[k][l] < 0 ? 0 : sqrt(sigma[k][l]);
}
// ---print results
cerr << " mean standard deviation" << endl;
fprintf(stderr, "E --> E: %10.6f %10.6f\n", mean[0][0], sigma[0][0]);
fprintf(stderr, "E --> I: %10.6f %10.6f\n", mean[0][1], sigma[0][1]);
fprintf(stderr, "I --> E: %10.6f %10.6f\n", mean[1][0], sigma[1][0]);
fprintf(stderr, "I --> I: %10.6f %10.6f\n", mean[1][1], sigma[1][1]);
return vmax;
}
// ---diagnostic: print weights
void network::write_weights(int nbins, float* vmax, float** w_to_v)
{
// ---output weights for display neurons.
for (int i=0; i < no_display; i++)
{
// ---presynaptic neuron
int n = display[i];
// ---open file
char s[1024];
sprintf(s, "w_%d%s", n, suffix);
FILE* w_out = fopen(s, "w");
// ---write
for (int j=0; j < wntot[n]; j++)
fprintf(w_out, "%d %10.6f\n", wn[n][j],
w_to_v[type[n]][type[wn[n][j]]]*w[n][j]);
// ---close file
fclose(w_out);
}
// ---make histograms. first step is range of weights. weights
// from inhibitory and excitatory cells have different
// signs, so they're binned differently.
// ---reserve space
float** wbins = cfloat(2, nbins);
int** counts = cint(3, nbins);
// ---expand max weights so that we don't have spillover.
for (int i=0; i < 2; i++) vmax[i] *= 1.0001;
// ---delta w
float delta[2] = {vmax[0]/nbins, vmax[1]/nbins};
// ---weights for the bins.
for (int k=0; k < 2; k++)
for (int i=0; i < nbins; i++) wbins[k][i] = (i+0.5)*delta[k];
// ---counts
for (int i=0; i < neurons; i++)
{
// ---determine k:
// 0 for excitatory non-memory,
// 1 for excitatory memory.
// 2 for inhibitory,
int k = type[i] == 1 ? 2 :
(no_mem == 0 ? 0 : (eta[0][i] < 0 ? 0 : 1));
// ---get counts
for (int j=0; j < wntot[i]; j++)
counts[k][(int)
(w_to_v[type[i]][type[wn[i][j]]]*
w[i][j]/delta[type[i]])]++;
}
// ---space for filename
char s[1024];
// ---output excitatory histogram
sprintf(s, "w_e%s", suffix);
FILE* w_exc = fopen(s, "w");
for (int i=0; i < nbins; i++)
fprintf(w_exc, "%10.6f %d %d\n",
wbins[0][i], counts[0][i], counts[1][i]);
fclose(w_exc);
// ---output inhibitory histogram
sprintf(s, "w_i%s", suffix);
FILE* w_inh = fopen(s, "w");
for (int i=0; i < nbins; i++)
fprintf(w_inh, "%10.6f %d\n", wbins[1][i], counts[2][i]);
fclose(w_inh);
// ---free memory
cfree(2, wbins);
cfree(3, counts);
}
// ---set values
int* network::set2(int n1, int n2, int p1, int p2)
{
/*
set first n1 values to p1, next n2-n1 values to p2.
*/
int* x = (int*) calloc(n2, sizeof(int));
for (int i=0 ; i < n1; i++) x[i]=p1;
for (int i=n1; i < n2; i++) x[i]=p2;
return x;
}
// ---set values
float* network::set2(int n1, int n2, float p1, float p2)
{
/*
set first n1 values to p1, next n2-n1 values to p2.
*/
float* x = (float*) calloc(n2, sizeof(float));
for (int i=0 ; i < n1; i++) x[i]=p1;
for (int i=n1; i < n2; i++) x[i]=p2;
return x;
}
// ---write parameters
void network::write_params(params& raw)
{
cout << "neurons: " << neurons << endl;
cout << "exc. neurons: " << num[0] << endl;
cout << "inh. neurons: " << num[1] << endl;
cout << "dt: " << dt << endl;
cout << "tmin: " << tmin << endl;
cout << "tmax: " << tmax << endl;
cout << "exc. tau_s: " << tau_s[0] << endl;
cout << "inh. tau_s: " << tau_s[1] << endl;
cout << "exc. norm v_reverse " << enorm[0] << endl;
cout << "inh. norm v_reverse " << enorm[1] << endl;
// ---PSP sizes
for (int i=0; i < 2; i++) for (int j=0; j < 2; j++) cout
<< "PSP ("
<< (i ? "I" : "E") << " --> "
<< (j ? "I" : "E") << ") "
<< " "
<< raw.vpsp[i][j]
<< endl;
cout << "no_display: " << no_display << endl;
cout << "displayed neurons: ";
for (int i=0; i < no_display; i++) cout << display[i] << " ";
cout << endl;
char s[1024];
if (!strcmp(suffix, "")) strcpy(s, "");
else strcpy(s, &suffix[1]);
cout << "suffix: " << s << endl;
if (no_mem)
{
cout << endl;
cout << "no_mem: " << no_mem << endl;
cout << "zap: " << zap << endl;
cout << "dzap: " << dzap << endl;
cout << "f: " << raw.f << endl;
cout << "norm: " << raw.norm << endl;
cout << "whozap: " << raw.whozap << endl;
cout << "nu_zap: " << raw.nu_zap[0] << " "
<< raw.nu_zap[1] << endl;
cout << "t_zap_on: " << t_zap_on << endl;
cout << "t_zap_off: " << t_zap_off << endl;
cout << "t_dzap_on: " << t_dzap_on << endl;
cout << "t_dzap_off: " << t_dzap_off << endl;
if (raw.no_deps > 0)
{
cout << "modified memories:";
int notfirst=0;
for (int i=0; i < raw.no_deps/2; i++)
{
int m = (int) (raw.dneps[2*i] + 0.5) - 1;
if (m >= 0 && m < no_mem)
{
if (notfirst++)
cout << " ";
fprintf(stdout, "%3d %5.3f\n",
m, raw.dneps[2*i+1]);
}
}
}
}
exit(1);
}