# -*- coding: utf-8 -*- ########## THIS FITTING PROGRAM IS MEANT TO BE A CLONE OF MUKUND'S AND ADIL'S MATLAB ONE ## USAGE: python2.6 fit_odor_morphs.py ../results/odor_morphs/2011-01-13_odormorph_SINGLES_JOINTS_PGS.pickle [CHISQ_HIST] [SAVEFIG] from scipy import optimize from scipy.special import * # has error function erf() and inverse erfinv() from pylab import * import pickle import sys import math sys.path.extend(["..","../networks","../generators","../simulations"]) from stimuliConstants import * # has SETTLETIME, inputList and pulseList, GLOMS_ODOR, GLOMS_NIL from networkConstants import * # has central_glom from sim_utils import * # has rebin() to alter binsize from analysis_utils import * # has read_morphfile() and NUM_REBINS, etc. ## use error function(x) for x>=0 (zero for x<0), ## OR use sigmoid(x) (non-zero for -ve x) USE_ERF = False#True iterationnum = 1 ## I don't use the NUMBINS in simset_odor.py, rather I rebin() with below NUM_REBINS ## Adil used 17 bins for a 1s rat respiration cycle. ## I'm using 9 bins to get the same binwidth, else there are oscillations ~ 35 Hz gamma? NUM_REBINS = 9#17 NUMMIX = len(inputList) ## remove the two pure odors and one pure air weights NUMWTS = NUMMIX-3 firstrun = False#True ### numbers of mitral to be fitted. fitted_mitral_list = [2*central_glom+0, 2*central_glom+1] ## Fit type: 'lin' : linear or 'arb' : monotonic arbitrary ## if arbitrary fit_type, weights are also free params, ## if linear fit_type, weights are not free params. ## This param is passed to fit_morphs() fit_type = 'arb' log81 = math.log(81) def constrain0to1(x): try: return exp(x)/(1+exp(x)) # use numpy's exp except OverflowError as overflowerr: print overflowerr print x return 1.0 # define sigmoid which runs from (-0.5,0.1) to (+0.5,0.9) # Ideally the fitted sigmoid should be shifted by 0.5 i.e. # exp((x-0.5)*log81)/(1+exp((x-0.5)*log81)) # This will overlap most of the linear part. # But for fitting it doesn't matter, # the fit routine will shift the parameters as required. # But while plotting the internal response parameters, # shift by 0.5 and plot -- see below def outputsigmoid(x): if USE_ERF: if x<0: return 0 else: return erf(x) else: try: return exp(x*log81)/(1+exp(x*log81)) # use numpy's exp except OverflowError as overflowerr: print overflowerr print x return 1.0 def inversesigmoid(x): if USE_ERF: if x<0: return x else: return erfinv(x) else: ## just to set initial values, value doesn't matter too much when x tends to 0 if x>1e-200: return math.log(x/(1-x)) else: return -5e2 def rectifier(x): x[where(x<0)[0]]=0 return x def chisqfunc(params, ydata, errdata, fit_type): RA = params[0:NUM_REBINS] RB = params[NUM_REBINS:2*NUM_REBINS] Rair = params[2*NUM_REBINS:3*NUM_REBINS] if fit_type == 'arb': #### for the weights also, we use exactly what is done by Mukund and Adil in matlab: #### constrain weights to be between 0 and 1 #### sort the weights to ensure monotonicity inputsA = [ constrain0to1(x) for x in params[3*NUM_REBINS:(3*NUM_REBINS+NUMWTS)] ] ## important to put these in else along with sort(), ## weights saturate at 0.9 or so rather than at 1.0 inputsA.extend([0.0,1.0]) # for pure odors inputsA.sort() # in place sort inputsA.append(0.0) # for air - keep this after sort! inputsB = [ constrain0to1(x) for x in params[(3*NUM_REBINS+NUMWTS):(3*NUM_REBINS+2*NUMWTS)] ] ## important to put these in else along with sort(), ## weights saturate at 0.9 or so rather than at 1.0 inputsB.extend([0.0,1.0]) # for pure odors inputsB.sort(reverse=True) # weights of odor B need to be used in reverse inputsB.append(0.0) # for air - keep this after sort! #### Mukund and Adil constrained sigmoidmax > ydatamax (note exp(x)>0.) sigmoidmax = ydata.max() + exp(params[3*NUM_REBINS+2*NUMWTS]) else: ## *-operator unpacks the list which become args of zip() ## zip collects the i-th elements together of all the args. inputsA,inputsB = zip(*inputList) # keep the last (0,0) air input global iterationnum if iterationnum%1000==0: print 'iteration number =',iterationnum #if iterationnum%100==0: print inputsA, inputsB chisqarray = [0.0] for i,(inputA,inputB) in enumerate(inputList): CA = inputsA[i] CB = inputsB[i] if fit_type == 'arb': for bin in range(NUM_REBINS): Rmix = sigmoidmax*outputsigmoid( Rair[bin] + CA*RA[bin] + CB*RB[bin] ) chisqarray.append( (ydata[i][bin] - Rmix)/errdata[i][bin] ) # divide by error to do chi-square fit else: Rmix = rectifier(Rair + CA*RA + CB*RB) chisqarray.extend( (ydata[i] - Rmix)/errdata[i] ) ## not yet squared, so normalize 'chi' to sqrt of number of dof chisqarray = array(chisqarray) / sqrt(ydata.size-params.size) iterationnum += 1 return chisqarray # misnomer -- actually individual chi array def fit_morphs(filename, fitted_mitral, fit_type='arb', refit=True): ## The model predicts the individual response not the mean. ## Hence below fitting uses standard deviation, not standard error of the mean. numavgs,firingbinsmeanList,firingbinserrList = read_morphfile(filename,fitted_mitral,NUM_REBINS) ########################## Initial values for the parameters if fit_type=='arb': params_filename = filename+'_params'+str(fitted_mitral) else: params_filename = filename+'_paramsFULLlin'+str(fitted_mitral) if firstrun or refit: params0 = [] spikesmax = firingbinsmeanList.max() RA = firingbinsmeanList[-2] # odor A is last but one RB = firingbinsmeanList[0] # odor B is first Rair = firingbinsmeanList[-1] # air response is last # The initial parameters are for odor A followed by odor B # extend(): Don't add the list as an element but add the elements of the list if fit_type == 'arb': # the small value 0.001 should be put, else divide by zero errors in chi-sq! params0.extend([ ( inversesigmoid(0.998*RA[i]/spikesmax+0.001) - \ inversesigmoid(0.998*Rair[i]/spikesmax+0.001) )/log81 for i in range(NUM_REBINS) ]) params0.extend([ ( inversesigmoid(0.998*RB[i]/spikesmax+0.001) - \ inversesigmoid(0.998*Rair[i]/spikesmax+0.001) )/log81 for i in range(NUM_REBINS) ]) # initial params for the air vector # air is last params0.extend([ inversesigmoid(0.998*Rair[i]/spikesmax+0.001)/log81 for i in range(NUM_REBINS) ]) params0.extend([0.0]*2*NUMWTS) # weights of mixtures # argument for the exp in sigmoidmax as per Mukund and Adil. # -1 gives match for generated data, -3 went into local minimum. params0.append(-1) else: params0.extend(RA - Rair) params0.extend(RB - Rair) params0.extend(Rair) ##### pure odor concentrations are not parameters. ##### They are set to (CA=1,CB=0) and (CA=0,CB=1) and act as normalization. ## if arbitrary fit_type, weights are also free params, ## if linear fit_type, weights are not free params. if fit_type == 'arb': ## take only the mixture values, not the start and end-1 points which are pure odors, ## nor end point which is pure air for i,(inputA,inputB) in enumerate(inputList[1:-2]): # to constrain weights between 0 and 1, sigmoid is used, # so use inversesigmoid to set initial value for weights params0[3*NUM_REBINS+i] = inversesigmoid(inputA) params0[3*NUM_REBINS+NUMWTS+i] = inversesigmoid(inputB) else: f = open(params_filename,'r') params0,chisq = pickle.load(f) f.close() ###################################### Fitting if not refit: params = array(params0) ## only use params, do not fit again else: ## args is a tuple! if only one element write (elem, ) params = optimize.leastsq( chisqfunc, params0, args=(firingbinsmeanList, firingbinserrList, fit_type), full_output=1, maxfev=50000) params = params[0] # leastsq returns a whole tuple of stuff - errmsg etc. ## Calculate sum of squares of the chisqarray chisqarraysq = [i**2 for i in chisqfunc(params, firingbinsmeanList, firingbinserrList, fit_type)] chisq = reduce(lambda x, y: x+y, chisqarraysq) if refit: paramsfile = open(params_filename,'w') pickle.dump((params,chisq), paramsfile) paramsfile.close() ############################## Calculate fitted responses and return them if fit_type == 'arb': #### for the weights also, we use exactly what is done by Mukund and Adil in matlab: #### constrain weights to be between 0 and 1 #### sort the weights to ensure monotonicity inputsA = [ constrain0to1(x) for x in params[3*NUM_REBINS:(3*NUM_REBINS+NUMWTS)] ] inputsA.extend([0.0,1.0]) inputsA.sort() # in place sort inputsB = [ constrain0to1(x) for x in params[(3*NUM_REBINS+NUMWTS):(3*NUM_REBINS+2*NUMWTS)] ] inputsB.extend([0.0,1.0]) inputsB.sort(reverse=True) # weights of odor B need to be used in reverse #### Mukund and Adil constrained sigmoidmax > ydatamax (note exp(x)>0.) sigmoidmax = firingbinsmeanList.max() + math.exp(params[3*NUM_REBINS+2*NUMWTS]) else: ## *-operator unpacks the list which become args of zip() ## zip collects the i-th elements together of all the args. inputsA,inputsB = zip(*(inputList[:-1])) # leave out the last (0,0) air input fitted_responses = [] Rair = params[2*NUM_REBINS:3*NUM_REBINS] for inpnum,(inputA,inputB) in enumerate(inputList[:-1]): if fit_type == 'arb': fitted_responses.append(\ [ sigmoidmax*outputsigmoid( \ inputsA[inpnum]*params[i] + inputsB[inpnum]*params[NUM_REBINS+i] + Rair[i]\ ) for i in range(NUM_REBINS) ] ) else: fitted_responses.append( rectifier( \ inputsA[inpnum]*params[:NUM_REBINS] + \ inputsB[inpnum]*params[NUM_REBINS:2*NUM_REBINS] + Rair ) ) if fit_type == 'arb': fitted_responses.append([ sigmoidmax*outputsigmoid( Rair[i] ) \ for i in range(NUM_REBINS) ] ) else: fitted_responses.append( rectifier(Rair) ) return (params,chisq,inputsA,inputsB,fitted_responses,numavgs,firingbinsmeanList,firingbinserrList) def plot_example_onemit(ax1,ax2,fitted_mitral,mit_fit_params): bindt = RESPIRATION/float(NUM_REBINS) respiration2time = arange(RESPIRATION,2*RESPIRATION,bindt) + bindt/2.0 params,chisq,inputsA,inputsB,fitted_responses,numavgs,firingbinsmeanList,firingbinserrList =\ mit_fit_params print "Mit",fitted_mitral,"normalized chisq =",chisq brightness = 0.2 num_morphs = len(inputList)-1 for i,(inputA,inputB) in enumerate(inputList): ## The inputA acts to morph odor response from red to blue color ## air response in black ## if not a pure odor/air, bring down its brightness. if i==0: color,alpha = 'b',1.0 elif i==num_morphs-1: color,alpha = 'r',1.0 elif i==num_morphs: color,alpha = 'k',1.0 else: color,alpha = (i/float(num_morphs),0,1.0-i/float(num_morphs)),brightness if i in [0,num_morphs-1,num_morphs]: simresponse = firingbinsmeanList[i] ## For the plots, show std error of the mean simerr = firingbinserrList[i]/sqrt(numavgs) ax1.fill_between(respiration2time,simresponse+simerr,simresponse-simerr, color=color,alpha=alpha*0.4,linewidth=0) ax1.plot(respiration2time,simresponse,\ color=color,alpha=alpha,marker='.',markersize=marker_size,\ linewidth=linewidth,clip_on=False) ##################### Plot fitted responses. ## RA + Rair line, = ax1.plot(respiration2time,fitted_responses[-2],\ color='m',marker='+',markersize=marker_size,\ linestyle='dashed', linewidth=linewidth, label='fit A',clip_on=False) line.set_dashes((3,1)) ## RB + Rair line, = ax1.plot(respiration2time,fitted_responses[0],\ color='c',marker='+',markersize=marker_size,\ linestyle='dashed', linewidth=linewidth, label='fit B',clip_on=False) line.set_dashes((3,1)) ## Rair line, = ax1.plot(respiration2time,fitted_responses[-1],\ color=(0.5,0.5,0.5),marker='+',markersize=marker_size,\ linestyle='dashed', linewidth=linewidth, label='fit air',clip_on=False) line.set_dashes((3,1)) #title('Mitral %d responses & linear-sigmoid fit'%fitted_mitral,fontsize=24 ) #axes_labels(ax,'respiratory phase bin','firing rate (Hz)',adjustpos=True) #ylim(ymin=-6, ymax=4) #legend() ################### Linearity of weights plot print 'weightsA =',inputsA print 'weightsB =',inputsB actualweights = [ wts[0] for wts in inputList[:-1]] ax2.plot(actualweights,arange(0.0,1.01,0.2),color='r',\ marker='.',markersize=marker_size,clip_on=False,\ linestyle='solid',linewidth=linewidth,label='linear A') ax2.plot(actualweights,arange(1.0,-0.01,-0.2),color='b',\ marker='.',markersize=marker_size,clip_on=False,\ linestyle='solid',linewidth=linewidth,label='linear B') line, = ax2.plot(actualweights,inputsA,color='m',clip_on=False,\ marker='+',linestyle='dashed',markersize=marker_size,\ linewidth=linewidth,label='weight odorA') line.set_dashes((3,1)) line, = ax2.plot(actualweights,inputsB,color='c',clip_on=False,\ marker='+',linestyle='dashed',markersize=marker_size,\ linewidth=linewidth,label='weight odorB') line.set_dashes((3,1)) #title( 'chisquare normalized = '+str(chisq) ) maxerror = sqrt(sum(array([0.8,0.6,0.4,0.2])**2)/4.0) # max rms error ## normalized score = 1 - norm-ed rms error scoreA = 1 - sqrt( sum( (inputsA[1:-1]-arange(0.2,0.81,0.2))**2 )/4.0 )/maxerror scoreB = 1 - sqrt( sum( (inputsB[1:-1]-arange(0.8,0.19,-0.2))**2 )/4.0 )/maxerror #title( 'Linearity mitral %d: \nscoreA=%.2f, scoreB=%.2f'%(fitted_mitral,scoreA,scoreB), fontsize=24 ) #axes_labels(ax2,'weight','fitted weight',adjustpos=True) #legend(loc='center right') ## beautify plots for ax in [ax1,ax2]: xmin,xmax,ymin,ymax = \ beautify_plot(ax,x0min=False,y0min=True,xticksposn='bottom',yticksposn='left') def plot_example_chisq(): fig = figure(figsize=(columnwidth,columnwidth/2.0),dpi=300,facecolor='w') # 'none' is transparent if 'CHISQ_HIST' in sys.argv: axgrid = (2,3) ax5 = plt.subplot2grid(axgrid,(0,2),frameon=False) ax6 = plt.subplot2grid(axgrid,(1,2),frameon=False) import average_odor_morphs as chisq_hist ## chi-sq histograms for both non-lin and lin weights chisq_hist.plot_chisq_hist_paperfigure(ax5,ax6,'../results/odor_morphs'+dirextn) else: axgrid = (2,2) for fitted_mitral in fitted_mitral_list: mit_fit_params = fit_morphs(filename, fitted_mitral, fit_type=fit_type) ################# Plot simulated responses ax1 = plt.subplot2grid(axgrid,(fitted_mitral,0),frameon=False) #text(0.1,1.0,['A','C'][fitted_mitral],fontsize=label_fontsize,transform=ax1.transAxes) ax2 = plt.subplot2grid(axgrid,(fitted_mitral,1),frameon=False) #text(0.1,1.0,['B','D'][fitted_mitral],fontsize=label_fontsize,transform=ax2.transAxes) plot_example_onemit(ax1,ax2,fitted_mitral,mit_fit_params) fig.tight_layout() fig_clip_off(fig) subplots_adjust(left=0.1,top=0.92,bottom=0.15,wspace=0.5) fig.text(0.015,0.7,'firing rate (Hz)',fontsize=label_fontsize, rotation='vertical', transform=fig.transFigure) fig.text(0.15,0.025,'time (s)',fontsize=label_fontsize, transform=fig.transFigure) fig.text(0.35,0.7,'fitted weight',fontsize=label_fontsize, rotation='vertical', transform=fig.transFigure) fig.text(0.43,0.025,'ORN weight',fontsize=label_fontsize, transform=fig.transFigure) if 'SAVEFIG' in sys.argv: fig.savefig('../figures/sim_morphs.svg', bbox_inches='tight',dpi=fig.dpi) fig.savefig('../figures/sim_morphs.png', bbox_inches='tight',dpi=fig.dpi) def plot_example(refit=True): fig = figure(figsize=(columnwidth*2./3.,columnwidth/2.0),dpi=300,facecolor='w') # 'none' is transparent axgrid = (2,2) for fitted_mitral in fitted_mitral_list: mit_fit_params = fit_morphs(filename, fitted_mitral, fit_type=fit_type, refit=refit) ################# Plot simulated responses ax1 = plt.subplot2grid(axgrid,(fitted_mitral,0),frameon=False) #text(0.1,1.0,['A','C'][fitted_mitral],fontsize=label_fontsize,transform=ax1.transAxes) ax2 = plt.subplot2grid(axgrid,(fitted_mitral,1),frameon=False) #text(0.1,1.0,['B','D'][fitted_mitral],fontsize=label_fontsize,transform=ax2.transAxes) plot_example_onemit(ax1,ax2,fitted_mitral,mit_fit_params) axes_labels(ax1,['','time (s)'][fitted_mitral],\ ['firing rate (Hz)',''][fitted_mitral],xpad=0,ypad=0) axes_labels(ax2,['','ORN weight'][fitted_mitral],\ ['fitted weight',''][fitted_mitral],xpad=0,ypad=0) ax1.yaxis.set_label_coords(-0.25,-0.3) ax2.yaxis.set_label_coords(-0.16,-0.3) fig.tight_layout() fig.subplots_adjust(hspace=0.2,wspace=0.4) fig_clip_off(fig) if 'SAVEFIG' in sys.argv: fig.savefig('../figures/sim_morphs.svg', bbox_inches='tight',dpi=fig.dpi) fig.savefig('../figures/sim_morphs.png', bbox_inches='tight',dpi=fig.dpi) if __name__ == "__main__": if len(sys.argv) > 1: filename = sys.argv[1] post_pulses = filename.split('odor_morphs')[1] dirextn = post_pulses.split('/')[0] print 'directory extension is',dirextn else: print "Specify data file containing pickled list." sys.exit(1) ### old paper figure that shows example and chisq distribs #plot_example_chisq() ### OBSOLETE: PAPER FIGURE supplementary fig 1 that shows only example ### use average_odor_morphs.py to get the R2N distribution plot ##plot_example() ## For PAPER FIGURE supplementary Fig 5 ## use average_odor_morphs.py to get the sqrt(R2N) distribution plot ## and it also calls plot_example_onemit() to plot one mitral example ## Plot the fitting result of the commandline data file, do not refit plot_example(refit=False) show()