/*
 *  functions.cpp
 *  stnetwork
 *
 *  Created by Honi Sanders on 5/25/11.
 *  Copyright 2011 Brandeis University. All rights reserved.
 *
 *
 requires that AMPAscale, NMDAscale, GABAscale, etc. be declared somewhere else
 requires that EIF, HH be declared somewhere else, 
	one as 0, the other as 1 to determine which kind of simulation is being run
 requires that funcoutfiles and funcout be declared somewhere else
 
	all valarray matrices are listed column by column
	time moves forward column by column
	in connectivity valarrays connectivity is from rows to columns
	
	Output files have all data for a given time step on a single line. 
	Time progresses row by row.  The first entry on each row should be the time
	associated with that time step (implemented in main).
 
 
 > only changes necessary for the new (spatial-temporal) implementation are
 > 1. alpha_s = 0.5 in HHstep for 50% activation of NMDARs with single spike
 > 2. change constitutive from 0.25 to 0.05


 */

#include "network.h"
double E_revValues[] = {E_L, E_g_E, E_g_I, 0.0, E_K, E_Na, E_K};
extern double eeAMPAscale, eiAMPAscale, inscale, iniscale, eeNMDAscale, eiNMDAscale;
extern double GABAAscale, GABABscale;
extern double ffAMPAscale, ffNMDAscale;
extern bool EIF, HH;
extern int funcout;
extern ofstream funcoutfile1,funcoutfile2;  
double constitutive=0.25;	// fraction of KIR that is constitutively active





/********** Neuron Functions **********/


Neuron::Neuron() {
	this->initialize("not", 'n', 'n');
}


Neuron::Neuron(const char ty[], const char inputset, const char inhibitionset) {
	this->initialize(ty, inputset, inhibitionset);
}



void Neuron::initialize(const char ty[], const char inputset, const char inhibitionset) {
	strncpy(celltype, ty, 4);
	V_s = V_init + doubrand(-10, 10);
	V_d = V_init + doubrand(-10, 10);
	g_refr = 0;
	AMPA = s_init;
	NMDA = s_init;
	GABAA = s_init;
	GABAB = s_init;
	T = 0;  R = 0;  D = 0;  G = 0;	B = 0;
	inact = 0;
	spike = 0;
	for (int i=0; i<4; i++) {
		gating[i] = 0;
	}
	input = inputset;
	inhibition = inhibitionset;
}



void Neuron::fillwstats(double arr[7]) {
	arr[0] = V_s; arr[1] = V_d; arr[2] = spike; 
	arr[3] = AMPA; arr[4] = NMDA; arr[5] = GABAA; arr[6] = GABAB;
}



void Neuron::step(double g_AMPA, double g_NMDA, double g_GABAA, double g_GABAB, 
						double g_in, double I_ext, double g_PING) {
	if (EIF) 
		EIFstep(g_AMPA, g_NMDA, g_GABAA, g_GABAB, g_in, I_ext, g_PING);
	else if (HH) 
		HHstep(g_AMPA, g_NMDA, g_GABAA, g_GABAB, g_in, I_ext, g_PING);
}






void Neuron::EIFstep(double g_AMPA, double g_NMDA, double g_GABAA, double g_GABAB, 
							double g_in, double I_ext, double g_PING) {
	valarray<double> E_revs(E_revValues,5), g(E_revs), I(g); g=0; I=0;
	double g_E, g_I, V_d0=V_d, V_s0=V_s, refrfact;
	
	
	AMPA *= exp(-dt/tau_AMPA);		// relax synaptic activations, refractory conductance
	NMDA *= exp(-dt/tau_NMDA);
	GABAA *= exp(-dt/tau_GABAA);
	GABAB *= exp(-dt/tau_GABAB);
	if (strcmp(celltype, "exc") == 0) {refrfact = exp(-dt/tau_refrE);}
	else if (strcmp(celltype, "inh") == 0) {refrfact = exp(-dt/tau_refrI);}
	else {cerr << celltype << " "; refrfact=1;}
	g_refr *= refrfact;
	
	
	g_E = g_AMPA + vdepend(g_NMDA,'N') + w_noise*doubrand(-1, 1)/sqrt(dt);
	g_I = g_GABAA + vdepend(g_GABAB,'G') + w_noise*doubrand(-1, 1)/sqrt(dt);
	
	
	
	g[0]=g_L; g[1]=g_E; g[2]=g_I; g[3]=g_c; g[4] = g_refr;	//leak, exc, inh, refr, coupling conductances
	
	
	V_s = compartmentstep('s', V_s0, g, g_in, V_d0, I_ext, g_PING);	// new somatic voltage
	if (V_s > peak){						// spike?
		Neuron::spiking();
	}
	else if (V_s<thresh && V_s0>thresh) {	// Na+ deinactivation?
		inact = false;
	}
	else {
		spike = 0;
	}
	
	
	if (strcmp(celltype, "exc") == 0) {
		V_d = compartmentstep('d', V_d0, g, g_in, V_s0, 0, g_PING);	// new dendritic voltage
	}
	else {V_d = V_s;}										// no dendritic compartment for inh cells
	
	
	
	V_s = abs(V_s)/V_s * min(100.0,abs(V_s));	//restrict V_s to [-100,100]
	V_d = abs(V_d)/V_d * min(100.0,abs(V_d));	//restrict V_d to [-100,100]
}




void Neuron::HHstep(double g_AMPA, double g_NMDA, double g_GABAA, double g_GABAB, 
						  double g_in, double I_ext, double g_PING) {
	double alpha_m, beta_m, alpha_h, beta_h, alpha_n, beta_n;
	double  m=gating[0], h=gating[1], n=gating[2], x=gating[3];
	valarray<double> E_revs(E_revValues,7), g(E_revs); g=0;
	double g_E, g_I, V_d0=V_d, V_s0=V_s;
	
	
	g_E = g_AMPA + vdepend(g_NMDA,'N');// + w_noise*doubrand(-1, 1)/sqrt(dt);
	g_I = g_GABAA;// + w_noise*doubrand(-1, 1)/sqrt(dt);

	
	g[0]=g_L; g[1]=g_E; g[2]=g_I; g[3]=g_c;			//leak, exc, inh, coupling conductances
	if (strcmp(celltype, "exc") == 0) {			// include GABAB in excitatory neurons only
		g[6] = vdepend(g_GABAB,'G');	
	}
	
	if (funcout) {			// writes to file
		funcoutfile1 << g_in*(E_g_E-V_d) << " " << vdepend(g_NMDA,'N')*(E_g_E-V_d) << " ";	// currents
		funcoutfile1 << g_GABAA*(E_g_I-V_d) << " " << vdepend(g_GABAB,'G')*(E_K-V_d) << " ";
		if (funcout == 2) 
			funcoutfile2 << g_AMPA << " " << g_NMDA << " " << g_GABAA << " " << g_GABAB << " ";// conductances
	}	

	
	// Hodgkin-Huxley equations
	if (strcmp(celltype, "exc") == 0) {
		alpha_m = -0.1 * (V_s + 32)/(exp(-0.1*(V_s + 32)) - 1);
		beta_m = 4 * exp(-(V_s + 57)/18);
		alpha_h = 0.07 * exp(-(V_s + 48)/20);
		beta_h = 1 / (exp(-0.1*(V_s + 18)) + 1);
		m += (Phi_m * (alpha_m*(1 - m) - beta_m*m)) * dt;
		h += (Phi_h * (alpha_h*(1 - h) - beta_h*h)) * dt;
		g[5] = g_Na*pow(m,3)*h;						// Na conductance
		
		alpha_n = -0.01 * (V_s + 34)/(exp(-0.1*(V_s + 34))-1);
		beta_n = 0.125 * exp(-(V_s + 44)/80);
		n += (Phi_n * (alpha_n*(1 - n) - beta_n*n)) * dt;
		g[4] = g_K*pow(n,4);						// K conductance
	}
	else if (strcmp(celltype, "inh") == 0) {		// see wang & buzsaki, 1996
		alpha_m = -0.1 * (V_s + 35)/(exp(-0.1*(V_s + 35)) - 1);
		beta_m = 4 * exp(-(V_s + 60)/18);
		alpha_h = 0.07 * exp(-(V_s + 58)/20);
		beta_h = 1 / (exp(-0.1*(V_s + 28)) + 1);
		m = alpha_m/(alpha_m + beta_m);
		h += (Phi_n * (alpha_h*(1 - h) - beta_h*h)) * dt;
		g[5] = g_Na*pow(m,3)*h;				// Na conductance
		
		alpha_n = -0.01 * (V_s + 34)/(exp(-0.1*(V_s + 34))-1);
		beta_n = 0.125 * exp(-(V_s + 44)/80);
		n += (Phi_n * (alpha_n*(1 - n) - beta_n*n)) * dt;
		g[4] = g_K*pow(n,4);						// K conductance  (changed from 90 to 180)
	}
	else {
		cerr << celltype << " ";
	}

	
	V_s = compartmentstep('s', V_s0, g, g_in, V_d0, I_ext, g_PING);	// new somatic voltage
	if (strcmp(celltype, "exc") == 0) {
		V_d = compartmentstep('d', V_d0, g, g_in, V_s0, 0, g_PING);	// new dendritic voltage
	}
	else {V_d = V_s;}										// no dendritic compartment for inh cells
	
	V_s = abs(V_s)/V_s * min(100.0,abs(V_s));	//restrict V_s to [-100,100]
	V_d = abs(V_d)/V_d * min(100.0,abs(V_d));	//restrict V_d to [-100,100]

	
	
	
	if (spike == 0 && V_s > 0 && V_s > V_s0) {spike = -1;}		// note the action potential upswing
	else if (spike == -1 && V_s > 20 && V_s < V_s0) {spike = 1;}	// declare spike if V decreases after AP upswing
	else if (spike == 1) {spike = 2;}						// don't count spike again until repolarization
	else if (spike == 2 && V_s < 0) {spike = 0;}			// allow spike after repolarization


	
	// 1/(1 + exp(-V_s/2.0)*dt integrated over one spike ~= 0.3 for i-cells and 0.4 for e-cells												

	if (strcmp(celltype, "exc") == 0) {				// adjust excitatory synaptic gating variables
		AMPA += (kf * 1/(1 + exp(-V_s/2.0)) * (1 - AMPA) - kr * AMPA) * dt;
		
		double alpha_x = 10, beta_x = 0.5, alpha_s = 0.1, beta_s = 0.01;	// in kHz
		// alpha_s was originally 1, adjust to change amount of NMDA activated w/ one spike
		x += (alpha_x*1/(1 + exp(-V_s/2.0)) * (1 - x) - beta_x*x) * dt;
		NMDA += (alpha_s*x*(1 - NMDA) - beta_s*NMDA) * dt;
	}
	else if (strcmp(celltype, "inh") == 0) {		// adjust inhibitory synaptic gating
		GABAA += (kf * 1/(1 + exp(-V_s/2.0)) * (1 - GABAA) - beta * GABAA) * dt;
		
		bool Destexhe1995=0, Destexhe1999=!Destexhe1995;
		
		if (Destexhe1995) {
			double dT, dR, dD, dG, Kd=100, K1=6.6*100, K2=0.02, K3=0.0053, K4=0.017, K5=0.083, K6=0.0079;
			double Vmax=0.0001, Km=0.000004;
			// Comparison to orignal values in Destexhe & Sejnowski, 1995:  Kd is still in uM b/c G is in uM.
			// K1 is in 1/M*1/ms b/c dt is in ms and T is in M, so it is muliplied by 10^-3
			// K2, K3, K4, and K6 are in 1/ms b/c dt is in ms, so they are multiplied by 10^-3
			// K5 is in uM/ms b/c dt is in ms and G is in uM, so it is multiplied by 10^6*10^-3
			// Vmax is in M*1/ms b/c dt is in ms and T is in M, so it is multiplied by 10^-3
			// Km is in M b/c T is in M, so it is multiplied by 10^-6
			dT = - Vmax * T/(T+Km);  dR = K1*T*(1-R-D) - K2*R + K3*D;  dD = K4*R - K3*D;  dG = K5*R - K6*G;
			T += dT*dt;  R += dR*dt;  D += dD*dt;  G += dG*dt; // [GABA], activated, desensitized GABABR, G protein for GABAB current
			if (spike==1)  T += 0.007;    // GABA release w/ spike
			// doesn't much matter how much T changes w/spike unless there is large diffusion and few spikes
			GABAB = pow(G,4)/(pow(G,4) + Kd);
			// cout << T << " " << R << " " << D << " " << G << " ";
		}
		else if (Destexhe1999) {
			
			double dT, dB, dR, dG, kD=0.1, k1=30, kminus1=0.1, k2=0.02, Bm=1, K1=0.18, K2=0.0096, K3=0.19, K4=0.06, Kd=17.83;
			
			dT = -k1*T*(Bm-B) + kminus1*B - kD*T;	dB = k1*T*(Bm-B) - (kminus1+k2)*B;
			dR = K1*T*(1-R) - K2*R;	dG = K3*R - K4*G;
			T += dT*dt;  R += dR*dt;  B += dB*dt;  G += dG*dt;
			if (spike==1)  T += 1;    // GABA release w/ spike
			GABAB = pow(G,4)/(pow(G,4) + Kd);
			// cout << T << " " << B << " " << R << " " << G << " " << dT << " " << dB << " " << dR << " " << dG;
		}
		
      /*
       GABAB *= exp(-dt/tau_GABAB);
       if (spike==1) GABAB += alpha_GABAB * (1-GABAB);
       */
	}
	gating[0]=m; gating[1]=h; gating[2]=n, gating[3]=x;	// store gating variables

	
}






double Neuron::compartmentstep(char compartment, double V, valarray<double> g, 
										 double g_in, double V_c, double I_ext, double g_PING) {
	valarray<double> E_revs(E_revValues, g.size()), I(g);
	// g is [leak,exc,inh,coupling,K,Na,GABAB]
	double Psi=0, dV;
	bool onecomp = false;
	
	if (strcmp(celltype, "inh") == 0) {		// HH interneurons are messed up, so
		onecomp = true;			// note that there is only one compartment
		E_revs[4] = -90;		// change K reversal potential
	}
	
	E_revs[3] = V_c;			// potential of coupled compartment
	
	bool TTX=1;
	if (compartment == 'd' || (I_ext!=0 && TTX)) {	// in dendrite (or with I_ext in TTX), there is
		g[4] = 0;				// no refractory current if EIF and no HH currents if HH
		if (HH) {g[5] = 0;}
	}
	if (compartment == 's' && !onecomp) {	// in soma (except for HH inh cells), there is
		g[1] = 0;				// no excitatory synaptic current
		g[2] += g_PING;		// PING inhibitory conductance
		g[6] = 0;				// no GABA-B current
	}
	if (EIF && (onecomp || compartment == 's') ) {
		Psi = g_L * Delta * exp((V - thresh)/Delta);	// spike-generating current
	}
	if (compartment != inhibition && !onecomp) {
		g[2] = 0;
	}
	if (compartment == input || onecomp) {
		g[1] += g_in;
	}
	g[1] += w_noise*doubrand(-1, 1)/sqrt(dt);
	g[2] += w_noise*doubrand(-1, 1)/sqrt(dt);
	
	I = g*(E_revs - V);	// + w_noise*doubrand(-1,1)/sqrt(dt);			// current
	dV = (I.sum() + I_ext + Psi*!inact)*dt/Cm;
	V = V + dV;					//new compartment voltage	
	return V;
}



double Neuron::vdepend(double g, char type) {
	valarray<double> E_revs(E_revValues,7);
	double g_new, g_max=1;
	bool weak=0;		// use weakly rectifying GIRK?
	if (type == 'N') {
		g_max = 1/(1 + 0.3*Mg*exp(0.08*(0)));		// conductance at reversal potential
		g_new = g/(1 + 0.3*Mg*exp(0.08*(E_revs[1]-V_d))); // /g_max;
	}
	else if (type == 'G') {					// GABAB uses K+ reversal potential
		if (weak==1) {							// weak rectifier
			g_max = 1/(1 + exp(-0.05*(0)));		// conductance at reversal potential of weak rectifier
			g_new = g/(1 + exp(-0.07*(E_revs[6]-V_d))); // /g_max;		
		}
		else {								// strong rectifier
			g_max = 1/(1 + exp(-0.1*(-10)));	// conductance at reversal potential of strong rectifier
			g_new = g/(1 + exp(-0.1*(E_revs[6]-V_d-10))); // /g_max;	
		}
	}
	else {
		cerr << "Did not specify valid type for rectification." << endl;
	}
	return g_new;
}






void Neuron::spiking() {
	V_s = peak;
	if (strcmp(celltype,"exc") == 0) {
		AMPA += alpha * (1 - AMPA);
		NMDA += alpha * (1 - NMDA);
	}
	else if (strcmp(celltype, "inh") == 0) {
		GABAA += alpha * (1 - GABAA);
		GABAB += alpha_GABAB * (1 - GABAB);
	}
	
	g_refr = g_refr_max;
	spike = 1;
	inact = true;
}	















////////////  Network Functions //////////////



Network::Network() {
	N = 400;					// # of neurons in network
	N_E = (ratio*N/(ratio+1));	// # of excitatory neurons
	N_I = (N - N_E);			// # of inhibitory neurons
	N_in = 100;					// # of incoming axons
	allneurons = new Neuron[N];		// initialize an array of N neurons	
	
	// initialize synaptic weights
	w_AMPA.resize(N_E*N,0.0); w_NMDA.resize(N_E*N,0.0); 
	w_GABAA.resize(N_I*N,0.0); w_GABAB.resize(N_I*N,0.0); 
	w_in.resize(N_in*N,0.0); 
}






Network::Network(const size_t N_A, const valarray<double>& w_input, const double connectivity,
					  const char inputset, const char inhibitionset) {	// make new network of size N_A
	this->initialize(N_A, w_input, connectivity, inputset, inhibitionset);
}



Network::Network(const size_t N_A, const size_t N_input, const double connectivity,
					  const char inputset, const char inhibitionset) {	// make new network of size N_A
	this->initialize(N_A, N_input, connectivity, inputset, inhibitionset);
}



void Network::initialize(const size_t N_A, const size_t N_input, const double connectivity,
								 const char inputset, const char inhibitionset) {	// make new network of size N_A
	slice s;
	valarray<double> temp1, w_input;
	
	N = N_A;					// # of neurons in network
	N_E = (ratio*N/(ratio+1));	// # of excitatory neurons
	N_I = (N - N_E);			// # of inhibitory neurons
	N_in = N_input;				// # of incoming axons
	
	w_input.resize(N_in*N,0.0);
	s = slice(0, N_E-1, N_in+1);
	w_input[s] = inscale;								// 1 input cell to each E-cell
	s = slice(N_in*N_E, N_in*N_I, 1);
	temp1.resize(N_in*N_I, 0.0);
	w_input[s] = connect(temp1, 1.0)*iniscale;			// input cells to all I-cells
	
	this->initialize(N_A, w_input, connectivity, inputset, inhibitionset);
}



void Network::initialize(const size_t N_A, const valarray<double>& w_input, const double connectivity,
								 const char inputset, const char inhibitionset) {	// make new network of size N_A
	size_t i;
	slice s, EEslice, EIslice, IEslice, IIslice;
	valarray<double> temp1, temp2;
	
	
	N = N_A;					// # of neurons in network
	N_E = (ratio*N/(ratio+1));	// # of excitatory neurons
	N_I = (N - N_E);			// # of inhibitory neurons
	N_in = w_input.size()/N;				// # of incoming axons
	
	allneurons = new Neuron[N];		// initialize an array of N neurons
	for (i=0; i<N_E; i++) {
		allneurons[i].initialize("exc", inputset, inhibitionset);
	}
	for (i=N_E; i<N; i++) {
		allneurons[i].initialize("inh", inputset, inhibitionset);
	}	
	
	
	// initialize synaptic weights
	w_AMPA.resize(N_E*N,0.0); w_NMDA.resize(N_E*N,0.0); 
	w_GABAA.resize(N_I*N,0.0); w_GABAB.resize(N_I*N,0.0); 
	w_in.resize(N_in*N,0.0); 
	w_ffAMPA.resize(N_E*N,0.0); w_ffNMDA.resize(N_E*N,0.0);
	
	temp1.resize(N_E*N_E, 0.0);
	EEslice = slice(0,N_E*N_E,1); EIslice = slice(N_E*N_E,N_E*N_I,1); 
	IEslice = slice(0,N_I*N_E,1), IIslice = slice(N_I*N_E,N_I*N_I,1);
	w_AMPA[EEslice] = connect(temp1, connectivity) * eeAMPAscale;	// E-cells to E-cells
	temp2.resize(N_E*N_I,0.0);
	w_AMPA[EIslice] = connect(temp2, connectivity) * eiAMPAscale;	// E-cells to I-cells
	
	w_NMDA[EEslice] = temp1 * eeNMDAscale;							// E-cells to E-cells
	w_NMDA[EIslice] = temp2 * eiNMDAscale;							// E-cells to I-cells
	
	temp1.resize(N_E*N_I, 0.0);
	w_GABAA[IEslice] = connect(temp1, connectivity) * GABAAscale;					// I-cells to E-cells 
	temp2.resize(N_I*N_I, 0.0);
	//w_GABAA[IIslice] = connect(temp2, connectivity) GABAAscale;			// I-cells to I-cells
	w_GABAB[IEslice] = connect(temp1, connectivity) * GABABscale;			// I-cells to E-cells 
	
	
	w_in = w_input;
    
	w_ffAMPA = w_AMPA/eeAMPAscale*ffAMPAscale;							// last buffer to this buffer
	w_ffNMDA = w_NMDA/eeNMDAscale*ffNMDAscale;							// last buffer to this buffer	
	
	s = slice(0, N_E-1, N+1);
	w_AMPA[s] = 0;									// eliminate autapses
	w_NMDA[s] = 0;
}





void Network::fillwsize(size_t arr[]) {
	arr[0] = N; arr[1] = N_E; arr[2] = N_I; arr[3] = N_in;
}



	
double nullarray1[1]={0.0};
void Network::step(const valarray<double> syn_in, const double I_exts[]=nullarray1) {
	int i;
	double g_AMPA, g_NMDA, g_GABAA, g_GABAB, g_in=0, I_ext=0;
	double stats[7];
	valarray<double> syn_AMPA(N_E), syn_NMDA(N_E), syn_GABAA(N_I), syn_GABAB(N_I);
	valarray<double> syns_AMPA(N_E), syns_NMDA(N_E), syns_GABAA(N_I), syns_GABAB(N_I), syns_in(N_in);
	valarray<double> w_AMPAi(N_E), w_NMDAi(N_E), w_GABAAi(N_I), w_GABABi(N_I), w_ini(N_in);
	slice iEslice, iIslice, iinslice;

	
	for (i=0; i<N_E; i++) {			// gather local synaptic activations
		allneurons[i].fillwstats(stats);
		syn_AMPA[i] = stats[3];
		syn_NMDA[i] = stats[4];
	}
	for (i=0; i<N_I; i++) {
		allneurons[N_E+i].fillwstats(stats);
		syn_GABAA[i] = stats[5];
		syn_GABAB[i] = stats[6]*(1-constitutive) + constitutive;   // set constitutive=1 for fixed KIR
	}
	

	
	for (i=0; i<N; i++) {					// step over all neurons	
		iEslice =  slice(i*N_E, N_E, 1);
		iIslice =  slice(i*N_I, N_I, 1);
		iinslice = slice(i*N_in, N_in, 1);
		w_AMPAi = w_AMPA[iEslice];			// initialize weights
		w_NMDAi = w_NMDA[iEslice];
		w_ini = w_in[iinslice];
		w_GABAAi = w_GABAA[iIslice];
		w_GABABi = w_GABAB[iIslice];
		
		syns_AMPA = syn_AMPA * w_AMPAi;		// calculate synaptic conductances
		syns_NMDA = syn_NMDA * w_NMDAi;
		syns_GABAA = syn_GABAA * w_GABAAi;
		syns_GABAB = syn_GABAB * w_GABABi;
		
		syns_in = syn_in * w_ini;
		g_AMPA = syns_AMPA.sum();
		g_NMDA = syns_NMDA.sum();
		g_GABAA = syns_GABAA.sum();
		g_GABAB = syns_GABAB.sum();
		g_in = syns_in.sum();
		if (I_exts[0]!=0) {I_ext=I_exts[i];}

		
		allneurons[i].step(g_AMPA, g_NMDA, g_GABAA, g_GABAB, g_in, I_ext);	// calculate result of time step for that neuron

	}
	
	
}
	



void Network::step(const valarray<double>& syn_in, const valarray<double>& syn_ff, // N_E*2 long
						 const double I_exts[]=nullarray1, const double g_PING) {
	size_t i;
	double g_AMPA, g_NMDA, g_GABAA, g_GABAB, g_in=0, I_ext=0;
	double stats[7];
	valarray<double> syn_AMPA(N_E), syn_NMDA(N_E), syn_GABAA(N_I), syn_GABAB(N_I);
	valarray<double> syns_AMPA(N_E), syns_NMDA(N_E), syns_GABAA(N_I), syns_GABAB(N_I);
	valarray<double> syns_in(N_in), syns_ffAMPA(N_E), syns_ffNMDA(N_E);
	valarray<double> w_AMPAi(N_E), w_NMDAi(N_E), w_GABAAi(N_I), w_GABABi(N_I);
	valarray<double> w_ini(N_in), w_ffAMPAi(N_E), w_ffNMDAi(N_E);
	slice iEslice, iIslice, iinslice, ffAMPAslice(0,N_E,1), ffNMDAslice(N_E,N_E,1);
	
	
	for (i=0; i<N_E; i++) {			// gather local synaptic activations
		allneurons[i].fillwstats(stats);
		syn_AMPA[i] = stats[3];
		syn_NMDA[i] = stats[4];
	}
	for (i=0; i<N_I; i++) {
		allneurons[N_E+i].fillwstats(stats);
		syn_GABAA[i] = stats[5];
		syn_GABAB[i] = stats[6]*(1-constitutive) + constitutive;   // set constitutive=1 for fixed KIR
	}
	
	
	
	for (i=0; i<N; i++) {					// step over all neurons	
		iEslice =  slice(i*N_E, N_E, 1);
		iIslice =  slice(i*N_I, N_I, 1);
		iinslice = slice(i*N_in, N_in, 1);
		w_AMPAi = w_AMPA[iEslice];			// initialize weights
		w_NMDAi = w_NMDA[iEslice];
		w_ini = w_in[iinslice];
		w_ffAMPAi = w_ffAMPA[iEslice];
		w_ffNMDAi = w_ffNMDA[iEslice];
		w_GABAAi = w_GABAA[iIslice];
		w_GABABi = w_GABAB[iIslice];
		
		syns_AMPA = syn_AMPA * w_AMPAi;		// calculate synaptic conductances
		syns_NMDA = syn_NMDA * w_NMDAi;
		syns_GABAA = syn_GABAA * w_GABAAi;
		syns_GABAB = syn_GABAB * w_GABABi;
		syns_in = syn_in * w_ini;
		syns_ffAMPA = syn_ff[ffAMPAslice] * w_ffAMPAi;
		syns_ffNMDA = syn_ff[ffNMDAslice] * w_ffNMDAi;
		
		
		g_AMPA = syns_AMPA.sum() + syns_ffAMPA.sum();
		g_NMDA = syns_NMDA.sum() + syns_ffNMDA.sum();
		g_GABAA = syns_GABAA.sum();
		g_GABAB = syns_GABAB.sum();
		g_in = syns_in.sum();
		
		if (I_exts[0]!=0) {I_ext=I_exts[i];}
		
		// calculate result of time step for that neuron
		allneurons[i].step(g_AMPA, g_NMDA, g_GABAA, g_GABAB, g_in, I_ext, g_PING);	
		
	}
	
}











map<string,bool> arrayfileappend;

void outputvalarray(valarray<int> arr, size_t Nrows, size_t Ncols, string filename) {
	valarray<double> arr_doub(arr.size());
	for (size_t i=0; i<arr.size(); i++) {
		arr_doub[i] = double(arr[i]);
	}
	outputvalarray(arr_doub, Nrows, Ncols, filename);
}

void outputvalarray(valarray<bool> arr, size_t Nrows, size_t Ncols, string filename) {
	valarray<double> arr_doub(arr.size());
	for (size_t i=0; i<arr.size(); i++) {
		arr_doub[i] = double(arr[i]);
	}
	outputvalarray(arr_doub, Nrows, Ncols, filename);
}

void outputvalarray(valarray<double> arr, size_t Nrows, size_t Ncols, string filename) {
#ifdef __APPLE__
	// only write valarray to disk on laptop; not on cluster
	if (arr.size()!=Nrows*Ncols) {	// check that array is expected size
		cerr << "Nrows*Ncols = " << Nrows*Ncols << ", but arr.size() = " << arr.size() << endl;
		assert(arr.size()==Nrows*Ncols);
	}
	ofstream arrayfile;
	size_t i,j;
	if (arrayfileappend.count(filename)==0)		// append if file already opened, open if not
		arrayfile.open(filename.c_str());
	else
		arrayfile.open(filename.c_str(),ios_base::app);
	
	for (i = 0; i<Nrows; i++) {
		for (j = 0; j<Ncols; j++) {
			arrayfile << arr[i + j*Nrows] << " ";
		}
		arrayfile << endl;
	}
	arrayfile.close();
	arrayfileappend[filename]=true;		// next time, append instead of overwriting
#endif
}





valarray<double> loadvalarray(string filename, const size_t Nrows, const size_t Ncols) {
   ifstream infile(filename.c_str());
   istringstream *lines = new istringstream[Nrows];
   string line;
   valarray<double> arr(Nrows*Ncols);
   size_t count=0;
	bool err1=0,err2=0,err3=0,err4=0;
   
   if (infile.is_open()) {
		
      while (getline(infile, line)) {
         if (count == Nrows && !err1) {
            cerr << "Valarray file has more rows than expected." << endl;
				err1 = 1;
			}
         lines[count].str(line);
         count++;
      }		// read "filename" into "lines" (arr of istringstreams)
		
      if (count < Nrows && !err2) {
         cerr << "Valarray file has fewer rows than expected." << endl;
			err2 = 1;
		}
		
      infile.close();
		
      for (size_t col=0; col<Ncols; col++) {
         for (size_t row=0; row<Nrows; row++) {
            if (lines[row].good())
               lines[row] >> arr[Nrows*col + row];
            else if (!err3) {
               cerr << "Valarray file has fewer columns than expected." << endl;
					err3 = 1;
				}
				
				
            if (!err4 && col == Ncols-1 && !lines[row].eof()) {
					double entry;
					lines[row] >> entry;
					if (abs(entry)>eps || !lines[row].eof()) {	// taking care of bug in Mac compiler
						cerr << "Valarray file has more columns than expected. ";
						cerr << "Row " <<row << " has leftover " << entry << endl;
						err4=1;
					}
				}
         }
      }		// transfer "lines" to "arr" (valarray)
	}
	else cerr << "Unable to open valarray input file." << endl;

	
	return arr;
}




valarray<double> valarraysort(valarray<double> &arr) {
	size_t N=arr.size();
	if (N<=1)
		return arr;
	
	vector<double> vec(N);
	for (size_t i=0; i<N; i++) {
		vec[i] = arr[i];
	}
	sort(vec.begin(),vec.end());
	for (size_t i=0; i<N; i++) {
		arr[i] = vec[i];
	}
	return arr;
}




	
valarray<double> connect(valarray<double>& w, 
								 double connection) 
								 { // returns a matrix with non-zero values at a frequency of "connection"
	for (size_t i=0; i<w.size(); i++) {
		w[i] = doubrand();
		w[i] = rectify(w[i] - 1 + connection)/connection;	
		// is in [0,1] at a rate of "connection", =0 at a rate of 1-"connection"
		if (w[i]>0) {w[i] = 1.0;}			// =1 at a rate of "connection", =0 at a rate of 1-"connection"
	}
	return w;
}
	


double doubrand(double minimum, double maximum) {		// random double between minimum and maximum (defaults [0,1])
	double a;
	a = minimum + double(rand())/RAND_MAX * (maximum - minimum);
	return a;
}


double normrand(double mean, double std) {		// normally distributed random double with default mean=0, std dev=1
	double U=doubrand(), V=doubrand(),X;
	X = sqrt(-2*log(U))*cos(2*pi*V);
	return mean + X*std;
}



double rectify(double x) {		// =x if x>0, =0 if x<0
	x = (x + abs(x))/2;
	return x;
}