//genesis - spectra_funcs2.g /* Functions for calculating/displaying spectra Assumes global definitions for: float tmax // simulation time in sec float out_dt // time step for output from the data source int graphics, debug //flags for using graphics or printing extra info A typical usage is: str timedata ="/time_data" // name of table object for data to analyze str data_source = "/EPSCsummer" // name of object that generates time data include spectra_funcs2.g setclock 1 {out_dt} make_EPSCsummer // assume that make_Vmgraph created a graph to plot sum of EPSCs addmsg /EPSCsummer /data/Iksum PLOT output *EPSC_sum *red make_time_data_table addmsg {data_source} {timedata} INPUT output make_FTgraph {x} {y} */ /* If use_fft_processor = 0, the Fourier Transform will be performed with a GENESIS script function to perform the slow Discrete Fourier Transform. Otherwise, the function plot_FT invokes an external compiled program fft_processor (http://www.arachnoid.com/signal_processing/fft.html). It communicates with the program via files {fft_infile} and {fft_outfile}. */ int use_fft_processor = 0 int debug = 1 setclock 1 {out_dt} function make_FTgraph(xpos, ypos) str form = "/spectra_form" float xpos, ypos float xmin = 0; float xmax = 200; float ymin = 0; float ymax = 1; create xform {form} [{xpos},{ypos},400,380] pushe {form} create xgraph power_spectrum -hgeom 70% -bg white setfield ^ xmin {xmin} xmax {xmax} ymin 0 ymax {ymax} create xbutton do_FT -script calc_spectra \ -label "Calculate frequency spectra of network activity" create xdialog min_time -wgeom 50% -label "Minimum time (sec)" \ -value 0.0 create xdialog max_time -wgeom 50% -ygeom 0:do_FT -xgeom 0:min_time \ -label "Maximum time (sec)" -value {tmax} create xdialog max_freq -label "Maximum frequency (Hz)" \ -value 200 create xbutton DISMISS -script "xhide "{form} pope makegraphscale {form}/power_spectrum xshow /spectra_form end // make table to hold time series data to Fourier analyse function make_time_data_table int xdivs = {round {2*tmax/{getclock 1}}} + 2 // twice as large as needed float xmin = 0; float xmax = {tmax} // this is arbitrary if ({exists {timedata}}) // Get rid of any existing one with old xdivs delete {timedata} // table doesn't seem to have a TABDELETE action end create table {timedata} call {timedata} TABCREATE {xdivs} {xmin} {xmax} setfield {timedata} step_mode 3 useclock {timedata} 1 // One could add a RC element here between {data_source} and {timedata} // as low-pass filter, but aliasing of high frequency spectral components // doesn't seem to be problem end /* Calculate and plot the spectra using a Fast Fourier Transform program fft_processor (http://www.arachnoid.com/signal_processing/fft.html). If this isn't available, set use_fft_processor = 0 to use a GENESIS script function to perform the slow Discrete Fourier Transform. */ function plot_FT(timedata, tmin, tmax, fmax) str timedata float tmin, tmax, fmax, delta_t, delta_f float fmin = 0.0 float h_k, a_n, b_n, p_n, twoPIbyN, scalefactor, pmax int i, n, k, kmin, kmax, Npts, Nfreqs int maxindex = {getfield {timedata} output} // index of last entry // Use the largest even number for Npts, in order to calc Npts/2 freqs int maxpts = 2*{trunc {(maxindex + 1)/2.0}} delta_t = {getclock 1} kmin = {trunc {tmin/delta_t}} kmax = {round {tmax/delta_t}} if ({kmax} > {maxpts}) kmax = maxpts end delta_f = 1.0/((kmax - kmin)*delta_t) Npts = kmax - kmin + 1 // Limit the range of frequencies calculated // fmax must be <= maxpts*delta_f -- this is not checked for! Nfreqs = {round {fmax/delta_f}} + 1 if (debug) echo "Index of last time step: " {kmax} echo {Npts} " time points from " {tmin} " to " {tmax} echo {Nfreqs} " frequency points from 0 to " {(Nfreqs - 1)*delta_f} echo "time interval: " {delta_t} " sec; freq interval: " {delta_f} " Hz." end /* If graphics are used, set up the plots */ if (graphics) str spectraplot = "/spectra_form/power_spectrum/FTpower" float ymin = 0; float ymax = 1.0; setfield /spectra_form/power_spectrum xmin {fmin} xmax {fmax} \ ymin 0 ymax {ymax} if ({exists {spectraplot}}) delete {spectraplot} end create xplot {spectraplot} setfield {spectraplot} fg blue ysquish 0 call /spectra_form/power_spectrum RESET end // if (graphics) if (use_fft_processor) // Use the external program "fft_processor" str fft_infile = "fft_infile.txt" str fft_outfile = "fft_outfile.txt" int array_size // size of array given to fft_processor int sample_rate // Sample rate in Hz (integer for fft_processor) float new_delta_f // Frequency interval in {fft_outfile} int new_Nfreqs // Number of frequencies to plot from {fft_outfile} str ablist // Will hold a line read from {fft_outfile} // Make a new table of the size to hold the data from tmin thru tmax create table /temp_data /* Allocate space with xdivs xmin xmax. Here xmin/xmax are relative to the start and end of the selected time range. i.e. xmin = 0, xmax = tmax - tmin. */ call /temp_data TABCREATE {Npts - 1} 0 {(Npts - 1)*out_dt} i = 0 for (k = {kmin}; k <= {kmax}; k = k +1) // Loop over desired time interval h_k = {getfield {timedata} table->table[{k}]} setfield /temp_data table->table[{i}] {h_k} i = i + 1 end // Number of points in the output file have to be a power of 2 // Horrible curly brackets have to be just right! array_size = {pow 2 { {trunc { {log {Npts} }/{log 2} } } + 1 } } sample_rate = {round {(array_size/out_dt)/Npts} } if (debug) echo "array_size = "{array_size} echo "sample_rate = "{sample_rate} echo "Before expansion /temp_data last entry " {Npts-1} echo {getfield /temp_data table->table[{Npts-1}]} end // Expand the table to array_size call /temp_data TABFILL {array_size - 1} 0 openfile {fft_infile} w writefile {fft_infile} {array_size} writefile {fft_infile} {sample_rate} for (i = 0; i < {array_size}; i = i +1) writefile {fft_infile} {getfield /temp_data table->table[{i}] } 0.0 end closefile {fft_infile} if (debug) echo "After expansion /temp_data last entry " {array_size - 1} echo {getfield /temp_data table->table[{array_size-1}]} end delete /temp_data // delete table, allowing repeated calls to plot_FT // Unix shell command using fft_processor. If this line produces an // error, set use_fft_processor = 0, and see the comments earlier cat fft_infile.txt | fft_processor > {fft_outfile} // now read the resulting fft_outfile openfile {fft_outfile} r array_size = {readfile {fft_outfile}} sample_rate = {readfile {fft_outfile}} new_delta_f = 1.0*sample_rate/array_size // cast to float new_Nfreqs = {trunc {Nfreqs*delta_f/new_delta_f}} if (debug) echo "array_size = "{array_size} echo "sample_rate = "{sample_rate} echo "new_delta_f = "{new_delta_f} echo "new_Nfreqs = "{new_Nfreqs} end // Will store power spectrum in a table create table /temp_spectra call /temp_spectra TABCREATE {new_Nfreqs - 1} 0 \ {(new_Nfreqs - 1)*new_delta_f} pmax = 0.0 ablist = {readfile {fft_outfile} -l} // ignore the zero freq data setfield /temp_spectra table->table[0] 0 // replace it with 0 for (n = 1; n < {new_Nfreqs}; n = n + 1) // This is a trick to separately get the two values on one line // without doing another read ablist = {readfile {fft_outfile} -l} a_n = {getarg {arglist {ablist}} -arg 1} b_n = {getarg {arglist {ablist}} -arg 2} p_n = a_n*a_n + b_n*b_n setfield /temp_spectra table->table[{n}] {p_n} pmax = {max {pmax} {p_n}} // Find spectrum max for normalization end // loop over frequencies scalefactor = 1.0/pmax if (debug) echo "max p_n = " {pmax} end closefile {fft_outfile} if (graphics) for (n = 0; n < {new_Nfreqs}; n = n + 1) p_n = scalefactor * {getfield /temp_spectra table->table[{n}]} call {spectraplot} ADDPTS {n*new_delta_f} {p_n} end end delete /temp_spectra else // Use script function for DFT /* Calculate the spectra with a "Slow Fourier Transform". This is much slower than the Fast Fourier Transform algorithm, which cleverly combines terms in the double sum in the Discrete Fourier Transform. But it is simple to implemement in GENESIS, and doesn't require Npts to be a power of 2. */ // Will store power spectrum in a table create table /temp_spectra call /temp_spectra TABCREATE {Nfreqs - 1} 0 {(Nfreqs - 1)*delta_f} pmax = 0.0 twoPIbyN = 2*3.14159265/Npts if (debug) echo {getdate} end // Now do the slow double summation // First, sum over frequencies, skipping the zero frequency component setfield /temp_spectra table->table[0] 0 // replace it with 0 for (n = 1; n < {Nfreqs}; n = n + 1) a_n = 0.0 b_n = 0.0 for (k = {kmin}; k <= {kmax}; k = k +1) // Sum over time data h_k = {getfield {timedata} table->table[{k}]} a_n = a_n + h_k*{cos {twoPIbyN*n*k}} b_n = b_n + h_k*{sin {twoPIbyN*n*k}} end // time loop p_n = a_n*a_n + b_n*b_n setfield /temp_spectra table->table[{n}] {p_n} pmax = {max {pmax} {p_n}} // Find spectrum max for normalization end // loop over frequencies if (debug) echo {getdate} end scalefactor = 1.0/pmax if (graphics) for (n = 0; n < {Nfreqs}; n = n + 1) p_n = scalefactor * {getfield /temp_spectra table->table[{n}]} call {spectraplot} ADDPTS {n*delta_f} {p_n} end end end // if(use_fft_processor) end function calc_spectra str dialog = "/spectra_form" float tmin = {getfield {dialog}/min_time value} float tmax = {getfield {dialog}/max_time value} float fmax = {getfield {dialog}/max_freq value} plot_FT {timedata} {tmin} {tmax} {fmax} end