/*----------------------------------------------------------------------------

	Neuron demo file to simulate local field potentials
        ---------------------------------------------------
	
        Model: simple EPSP/IPSP model with two synaptic currents,
	(monopolar current source) => Read data file with source current

        Method: calculate the extracellular field potentials based on
        inhomogeneous conductivity in extracellular space in radial symetry
	(Bedard, Kroger & Destexhe model)

	  - compute the Fourier transform of the source current I
          - compute the impedance Z assuming a non-homogeneous conductivity
            function (radial symetry) - MECHANISM IN MOD FILE
          - compute the extracellular voltage by inverse Fourier transform
            of the product Z(w) * I(w)

	THIS FILE: simulates fig 6 of the paper

        For details, see:

        Bedard C, Kroger H & Destexhe A.  Modeling extracellular field 
        potentials and the frequency-filtering properties of extracellular 
        space.  Biophysical Journal 86: 1829-1842, 2004.


        Written by A. Destexhe, 2002
	http://cns.iaf.cnrs-gif.fr

----------------------------------------------------------------------------*/


/*-------------------------------------------------------------------------

 NOTE ON UNITS: 
 
 Fourier transform of current: 
  I(w) = INT dt exp(-iwt) I(t)
  (A-s) =   (s)  (none)   (A) 
  (nA-s) =  (s)  (none)   (nA)
		=> units of nA-s (otherwise too small numbers)
		=> time in seconds, frequency in Hz
 
 Fourier transform of voltage: 
  V(w) = Z(w) I(w)
  (V-s) = (Ohm) (A-s)
		=> Z(w) must be in units of Ohm (and not Ohm-s)
 
 Fourier transform of impedance:
  Z(w)  = 1 / (4pi sigmaR) INT dr/r^2 (sigR-iw epsR)/(sig(r)-iw eps(r))
  (Ohm) = 1 / (S/m)          (m)/(m^2)   (none)
  (Ohm) = 1 / (S/m)          (1/m)
  (Ohm) = 1 / (S/um)         (1/um)
		=> distances can be specified in microns, if sigmaR in S/um
		=> impedance in Ohm
	
 Inverse Fourier transform of voltage:
  Vext = INT dw exp(iwt) [Z(w) I(w)]
  (V)  =    1/s  (none)  (Ohm A s = V-s)
  (nV) =    1/s  (none)  (Ohm nA-s = nV-s)
 
 => all frequencies in Hz, time in seconds, current in nA
 => extracellular voltage in nV

 (see other notes labeled by "UNITS" below)
    
-------------------------------------------------------------------------*/



//----------------------------------------------------------------------------
//  load and define general graphical procedures
//----------------------------------------------------------------------------

// original code commented for below replacement
// xopen("$(NEURONHOME)/lib/hoc/stdrun.hoc")
load_file("nrngui.hoc")

objectvar g[20]			// max 20 graphs
ngraph = 0

proc addgraph() { local ii	// define subroutine to add a new graph
				// addgraph("variable", minvalue, maxvalue)
	ngraph = ngraph+1
	ii = ngraph-1
	g[ii] = new Graph()
	g[ii].size(0,tstop,$2,$3)
	g[ii].xaxis()
	g[ii].yaxis()
	g[ii].addvar($s1,1,0)
	g[ii].save_name("graphList[0].")
	graphList[0].append(g[ii])
}

proc makegraph() { local ii	// define subroutine to add a new graph
				// makeraph("variable", xmin,xmax,ymin,ymax)
	ngraph = ngraph+1
	ii = ngraph-1
	g[ii] = new Graph()
	g[ii].size($2,$3,$4,$5)
	g[ii].xaxis()
	g[ii].yaxis()
	g[ii].addvar($s1,1,0)
	g[ii].save_name("graphList[0].")
	graphList[0].append(g[ii])
}

nrnmainmenu()			// create main menu
nrncontrolmenu()		// crate control menu



//----------------------------------------------------------------------------
// Read in data points of source current
//----------------------------------------------------------------------------

ropen("curr.dat")	// source current of EPSP/spike model

npoints = fscan()	// nb points should be an exponent of 2
dt=fscan()

objectvar Curr		// create vectors of data points
Curr = new Vector(npoints)

for i=0,npoints-1 { 	// read source currents
 Curr.set(i, fscan() )
}


//----------------------------------------------------------------------------
//  create graphs
//----------------------------------------------------------------------------


tmaxdraw = 10           // set time max to draw

objectvar gI		// create graph for membrane current
gI = new Graph()
gI.xaxis()
gI.yaxis()
gI.label("Im (nA)") 

proc draw_Imem() {     // draw membrane current
  gI.beginline(1,1)
  for i=0, npoints-1 {
    if(i*dt <= tmaxdraw) {
        gI.line( i*dt, Curr.get(i))
    }
  }
  gI.size(0, tmaxdraw, Curr.min(), Curr.max())
}



//----------------------------------------------------------------------------
//  Calculate and store the impedance
//----------------------------------------------------------------------------

// UNITS: compute the impedance in Ohm

npt = npoints/2			// nb of points in frequency 
df = 1000/(npoints*dt)		// freq interval in Hz
fmax = df*npt			// max frequency in Hz

R = 105				// radius of source (UNITS: um)
rext = 500			// distance of the recording electrode (um)
rmax = 200*R			// max distance for integration (um)
dr = rmax/1000                  // integration step (um)

lambda = 500			// space cst. of conductivity decay (UNITS=um)
sigma1 = 1			// high-amplitude of conductivity (normalized)
sigma2 = 0.2			// low-amplitude of conductivity (normalized)
				// (this is the asymptotic value, macroscopic)
sigma2 = 2e-9			// low-amplitude of conductivity (normalized)
				// (this is the minimal value, membranes)
epsilon1 = 6e-11  		// value of permittivity (constant, normalized)

sigmaR = 1.56			// absolute value of conductivity at the source
                                // UNITS: S/m
sigmaR = sigmaR * 1e-6          // converted to UNITS of S/um so that distances
				// can be specified in um

epsilon1 = 0.0001  // heuristic



create soma		// create fake compartment for impedance
access soma
objref C
C = new ImpedanceFM(0.5)	// create object that will contain the C code
soma C.loc(0.5)		// locate the object in the compartment

double Zr[npt+1],Zi[npt+1]
setpointer C.vec1, Zr[0]	// assign pointers to vectors
setpointer C.vec2, Zi[0]


//
// Calculate filter:
//  loop over frequencies -> calculate impedances -> store in vectors
//
proc calc_filter() {
  C.impedance(fmax,df,rext,rmax,dr,R,sigma1,sigma2,lambda,epsilon1,sigmaR)
  draw_filter()
}

objectvar g1		// draw |Z| vs frequency
g1 = new Graph()
g1.xaxis()
g1.yaxis()
g1.label("|Z|") 

fmaxdraw = 2000		// max frequency to draw

proc draw_filter() {
  g1.beginline(1,1)
  i=0
  zmax=0
  for(f=df; f<=fmaxdraw; f=f+df) {			// draw impedance
        ReZ = Zr[i]
        ImZ = Zi[i]
        z = sqrt(ReZ*ReZ+ImZ*ImZ)               // norm of impedance
	if(z>zmax) zmax=z
	g1.line(f, z)
        i=i+1
  }
  g1.size(0,fmaxdraw,0,zmax)
}




//----------------------------------------------------------------------------
//  Procedures to calculate FFT (from Vector objects)
//----------------------------------------------------------------------------

// UNITS: current in nA => FFT (Ir,Ii) will be in nA-s

objref Ir, Ii, Vr, Vi, Vext		// create vectors
Ir = new Vector(npt+1)		// Fourier transform of the current
Ii = new Vector(npt+1)
Vr = new Vector(npt+1)		// Fourier transform of extracellular voltage
Vi = new Vector(npt+1)
Vext = new Vector(npoints)      // Extracellular voltage

objref RFT			// Vector for FFT
RFT = new Vector(npoints)

// fft function from Vector in ivoc:
// The "fft" function is taken from the "realft" procedure from Numerical
// Recipes (Press, W.H., Flannery, B.P., Teukolsky, S.A. and Vetterling, W.T. 
// Numerical Recipes. The Art of Scientific Computing. Cambridge University
// Press, 1986).  It inputs a real data vector of N points (N must be exponent
// of 2) and returns the half of its Fourier transform (the other half being
// symetric), as complex numbers.  The first and second points returned are
// the first and last real-valued points of the FFT.  The other points are
// returned as couples of real/complex values.  The frequency interval is 
// delta-f = 1/(N*dt).  To yield the correct amplitudes of the FT, all real
// (cosine) and complex (sine) transforms must be divided by (N/2), except the 
// first and second points returned which must be divided by N.
// Inverse Fourier transform: use fft with second argument of "-1".


//
//  Procedure to calculate FFT of source current
//
proc runfft() {
   draw_Imem()				// draw source current
   RFT.fft(Curr)			// calculate FFT of current
   for(i=0; i<npt; i=i+1) {     	// decompose real/complex components
        Ir.set(i, RFT.get(2*i)/npt )	// store into Ir, Ii
        Ii.set(i, RFT.get(2*i+1)/npt )
   }
   Ir.set(npt, RFT.get(1)/npt)		// last real component is 2nd pt of FFT
   Ii.set(0, 0)				// first complex component set to zero
   Ii.set(npt, 0)			// last complex component set to zero
   draw_fft()
}


objectvar g2		// create graph for Power Spectrum
g2 = new Graph()
g2.xaxis()
g2.yaxis()
g2.label("Power") 

proc draw_fft() {       // draw power spectrum of current
  g2.beginline(1,1)
  pmax=0
  for i=0,npt {
    if(i*df<=fmaxdraw) {
        pow = Ir.get(i)^2 + Ii.get(i)^2
	if(pow > pmax) pmax=pow
        g2.line(i*df, pow)
    }
  }
  g2.size(0, fmaxdraw, 0, pmax)
}



//
//  Procedure to calculate extracellular voltage at a given distance
//  (assumes that Ir, Ii contains the Re/Im values of current; UNITS nA-s)
//  (assumes that Zr, Zi contains the Re/Im values of impedance; UNITS Ohm)
//
proc calc_extracellular() {
   for i=0, npt {		// compute the product (Zr,Zi)*(Ir,Ii)
	a = Zr[i]*Ir.get(i) - Zi[i]*Ii.get(i)
        b = Zr[i]*Ii.get(i) + Zi[i]*Ir.get(i)
        Vr.set(i, a)		// store in Vr, Vi (w-freq component of Vext)
        Vi.set(i, b)            // (UNITS: nV-s)
   }
   for i=0, npt-1 {		// store into RFT vector for inverse FFT
        RFT.set(2*i, Vr.get(i) )
        RFT.set(2*i+1, Vi.get(i) )
   }
   RFT.set(1, Vr.get(npt) )     // 2nd point is last real component

   RFT.set(0, 0 )		// remove dc (nonzero dc is due to num error)

   Vext.fft(RFT,-1)             // compute inverse FFT and store into Vext
                                // Vext is the extracellular voltage (UNITS nV)
   draw_extracellular()
}


objectvar g3		// create graph for Vext
g3 = new Graph()
g3.xaxis()
g3.yaxis()
g3.label("Vext (mV)") 

proc draw_extracellular() {     // draw extracellular V (UNITS converted to mV) 
  g3.beginline(1,1)
  for i=0, npoints-1 {
    if(i*dt <= tmaxdraw) {
        g3.line( i*dt, Vext.get(i) * 1e-6 )
    }
  }
  g3.size(0, tmaxdraw, Vext.min()*1e-6, Vext.max()*1e-6)
}



//----------------------------------------------------------------------------
//  Create command panel
//----------------------------------------------------------------------------

proc draw_panel() {
	xpanel("Calculate extracellular field potentials")
	xpvalue("Position of electrode (um)",&rext)
	xpvalue("Max distance for integration",&rmax)
	xpvalue("Integration step",&dr)
	xpvalue("Spatial decay of conductivity",&lambda)
	xpvalue("Minimal conductivity (normalized)",&sigma2)
	xpvalue("Conductivity at the source (absolute)",&sigmaR)
	xpvalue("Value of permittivity (normalized)",&epsilon1)
	xbutton("=> 1. Calculate impedances","calc_filter()")
	xbutton(" => Redraw impedances","draw_filter()")
	xbutton("=> 2. Calculate current FFT","runfft()")
	xbutton(" => Redraw FFT","draw_fft()")
	xbutton("=> 3. Calculate extracellular potential","calc_extracellular()")
        xbutton(" => Redraw extracellular potential","draw_extracellular()")
	xpvalue("Max frequency to draw",&fmaxdraw)
	xpvalue("Max time to draw",&tmaxdraw)
	xpanel()
}

draw_panel()