Source code for mugui

'''A musr analysis class
Notes

    starts from init, the rest is operated by gui ipywidgets
    output for linux; Popen _xterm_ and tail -f /tmp/mujpy_pipe
    then self.console(string) takes care of writing strings to pipe
    Jupyterlab provides a way of writing stderr to console
    description_tooltip in ipywidgets >7.3 should for all items with description
    as of 7.5 buttons still require "tooltip" instead of "description_tooltip"
    
    Gui tabs correspond to distinct gui methods with independes scopes and additional local methods
    gui attributes: 
         entities that must be shared between tabs 
         including variables passed to functions  outside mugui
    
Implementation of multi fit::
    
    #                 logical         logical        list of lists of musr2py instances
    # type of fit   | self._global_ |self._single_ | len(self._the_runs_)
    #-------------------------------------------------------
    # single        | False         | True          | 1 (e.g. [[run234]] or [[run234,run235]] (adds data of two runs)
    # suite         | False         | False         | >1
    #-------------------------------------------------------

asymmetry loads single runs, both for single and for suite fit

        #   python2 would require # utf-8
'''

[docs]class mugui(object): ''' The main method is gui, that designs a tabbed gui. Planned to be run in a Jupyter notebook. Public methods correspond to the main tabs (fft, fit, setup, suite, ...). Other public methods produce the fundamental data structure (asymmetry) or common functions (plot). Their nested methods are not documented by sphynx. One tab handles (badly) the text output, mixing error messages and fit results. function __init__(self) * initiates an instance and a few attributes, * launches the gui. * Use as follows:: from mugui import mugui as MG MuJPy = MG() # instance is MuJPy ''' ########################## # INIT ########################## def __init__(self): ''' * Initiates an instance and a few attributes, * launches the gui. * Use a template notebook, basically as follows:: from mugui import mugui as MG MuJPy = MG() # instance is MuJPy ''' # only constant used here, all others in mujpy.mucomponents self.TauMu_mus = 2.1969811 # number is from Particle Data Group 2017 (not in scipy.constants.physical_constants) # print('__init__ now ...') self.initialize_mujpy()
[docs] def initialize_mujpy(self): from mujpy import __file__ as MuJPyName import numpy as np import os from IPython.display import display # some initializations self.offset0 = 7 # initial value self.offset = [] # this way the first run load calculates get_totals with self.offset0 self.firstbin = 0 # These three RELOCATED TO promptfit local variables # self.second_plateau = 100 # self.peakheight = 100000. # self.peakwidth = 1. # rough guesses for default self.histoLength = 7900 # initialize self.bin_range0 = '0,500' # initialize (for plots, counter inspection) self.nt0_run = [] self._global_ = False self.thermo = 1 # sample thermometer is 1 on gps (check or adapt to other instruments) self.binwidth_ns = [] # this way the first call to asymmetry(_the_runs_) initializes self.time self.grouping = {'forward':np.array([1]),'backward':np.array([0])} # normal dict self._the_runs_ = [] # if self._the_runs_: is False, to check whether the handle is created self.first_t0plot = True self.fitargs = [] # initialize # mujpy paths self.__path__ = os.path.dirname(MuJPyName) self.__logopath__ = os.path.join(self.__path__,"logo") self.__startuppath__ = os.getcwd() # working directory, in which, in case, to find mujpy_setup.pkl # mujpy layout self.button_color = 'lightblue' self.button_color_off = 'gray' self.border_color = 'dodgerblue' self.newtlogdir = True # print('...finshed initializing') ########################################## # This actually produces the gui interface # with tabs: # suite fit fft plots setup [output] about ########################################## # print('Got here: create gui') self.output() # this calls output(self) that defines self._output_ # must be called first! # either it launches a detached os terminal or it adds a tab self.gui() self.setup() ##################################### # static figures ##################################### self.fig_fft = [] # initialize to false, it will become a pyplot.subplots instance self.fig_multiplot = [] self.fig_counters = [] self.fit() self.fft() self.plots() self.about() #self.fig_fit = [] # (initialize to false), now not ititialized try: whereamI = get_ipython().__class__ if not str(whereamI).find('erminal')+1: display(self.gui) # you are in a Jupyter notebook else: print(str(wheremI)) # you are in an ipython terminal except: print('Python test script') # other option?
########################## # ABOUT ##########################
[docs] def about(self): ''' about tab: - a few infos (version and authors) ''' from ipywidgets import Textarea, Layout _version = 'MuJPy version '+'1.0' # increment while progressing _authors = '\n\n Authors: Roberto De Renzi, Pietro BonfĂ  (*)' _blahblah = ('\n\n A Python MuSR data analysis graphical interface.'+ '\n Based on classes, designed for jupyter.'+ '\n Released under the MIT licence') _pronounce = ('\n See docs in ReadTheDocs'+ '\n Pronounce it as mug + pie') _additional_credits_ = ('\n ---------------------\n (*) dynamic Kubo-Toyabe algorithm by G. Allodi\n MuSR_td_PSI by A. Amato and A.-R. Raselli \n acme algorithm code from NMRglue, by Jonathan J. Helmus') _about_text = _version+_blahblah+_pronounce+_authors+_additional_credits_ _about_area = Textarea(value=_about_text, placeholder='Info on MuJPy', layout=Layout(width='98%',height='250px'), disabled=True) # now collect the handles of the three horizontal frames to the main fit window (see tabs_contents for index) if self._output_==self._outputtab_: self.mainwindow.children[4].children = self._park_ # was parked here in self.output self.mainwindow.children[4].layout = Layout(border = '2px solid dodgerblue',width='100%') self.mainwindow.children[5].children = [_about_area] # add the list of widget handles as the third tab, fit self.mainwindow.children[5].layout = Layout(border = '2px solid dodgerblue',width='100%') else: self.mainwindow.children[4].children = [_about_area] # add the list of widget handles as the third tab, fit self.mainwindow.children[4].layout = Layout(border = '2px solid dodgerblue',width='100%')
########################## # ASYMMETRY ##########################
[docs] def asymmetry(self): """ * defines self.time, generates asymmetry end error without rebinning, * all are 2D numpy arrays, shape of self.time is (1,nbins), of self.asymm, self.asyme are (nruns, nbins) * self._the_runs_ is a list of lists of musr2py (psi loading routine) instances * inner list is for adding runs * outer list is suites of runs may be treated equally by a double for loop (single, no addition implies that k,j=0,0) - returns 0 if ok and -1 if an error is encountered """ import numpy as np # no checks, consistency in binWidth and numberHisto etc are done with run loading self.numberHisto = self._the_runs_[0][0].get_numberHisto_int() self.histoLength = self._the_runs_[0][0].get_histoLength_bin() - self.nt0.max() - self.offset.value # max available bins on all histos self.firstrun = True self.binwidth_ns = self._the_runs_[0][0].get_binWidth_ns() time = (np.arange(self.histoLength) + self.offset.value + np.mean(self.dt0 [np.append(self.grouping['forward'],self.grouping['backward'])] ) )*self.binwidth_ns/1000. # in microseconds, 1D np.array self.time = np.array([time]) # in microseconds, 2D np.array ################################################################################################## # Time definition: # 1) Assume the prompt is entirely in bin self.nt0. (python convention, the bin index is 0,...,n,... # The content of bin self.nt0 will be the t=0 value for this case and self.dt0 = 0. # The center of bin self.nt0 will correspond to time t = 0, time = (n-self.nt0 + self.offset.value + self.dt0)*mufit.binWidth_ns/1000. # 2) Assume the prompt is equally distributed between n and n+1. Then self.nt0 = n and self.dt0 = 0.5, the same formula applies # 3) Assume the prompt is 0.45 in n and 0.55 in n+1. Then self.nt0 = n+1 and self.dt0 = -0.45, the same formula applies. ################################################################################################## # calculate asymmetry in y and error in ey M = self.lastbin-self.firstbin+1 # number of pre-prompt bins for background average for k, runs in enumerate(self._the_runs_): # initialize to zero yforw = np.zeros(time.shape[0]) # counts with background substraction cforw = np.zeros(time.shape[0]) # corrected counts for Poisson errors ybackw = np.zeros(time.shape[0]) # counts with background substraction cbackw = np.zeros(time.shape[0]) # corrected counts for Poisson errors for j, run in enumerate(runs): for counter in self.grouping['forward']: # first good bin, last good bin from data attay start n1, n2 = self.nt0[counter] + \ self.offset.value, self.nt0[counter] + \ self.offset.value + \ self.histoLength # set in suite.run_headers as max common good data length histo = run.get_histo_array_int(counter) # counter data array in forw group background = np.mean(histo[self.firstbin:self.lastbin]) # from prepromt estimate yforw += histo[n1:n2]-background # background subtracted add to others cforw += abs(histo[n1:n2]-background*(1-1/M)) # add with correction, see error eval box background_b = 0. for counter in self.grouping['backward']: # first good bin, last good bin from data attay start n1, n2 = self.nt0[counter]+self.offset.value,self.nt0[counter]+self.offset.value+self.histoLength # set in suite.run_headers as max common good data length histo = run.get_histo_array_int(counter) # counter data array in back group background = np.mean(histo[self.firstbin:self.lastbin]) # from prepromt estimate ybackw += histo[n1:n2]-background # background subtracted add to others cbackw += abs(histo[n1:n2]-background*(1-1/M)) # add with correction, see error eval box yplus = yforw + float(self.alpha.value)*ybackw # denominator x = np.exp(-time/self.TauMu_mus) # pure muon decay enn0 = np.polyfit(x,yplus,1) # fit of initial count rate plus average background vs. decay enn0 = enn0[0] # initial rate per ns y = (yforw-float(self.alpha.value)*ybackw)/enn0*np.exp(time/self.TauMu_mus) # since time is an np.arange, this is a numpy array # A(t) = y with corrected counts Nfc(t) = Nf(t) - bf = yforw, Nbc(t) = Nb(t) - bb = ybackw # until 11-2020 error evaluated as Poissonian in Nf, Nb, wrong since Njc and bj j=f,b are independent # from 11-2020 correction thanks to Giuseppe Allodi for pointing this out # and Andreas Suter, inspiring through musrfit bitbucker code # Nf(t) = cforw , Nb(t) = cbackw # whereas yield ######################################################### # error evaluation: # # A = d [Nfc(t) - alpha Nbc(t)] = d a(t) with # # d = 1/[2N0e(-t/tau)], Nfc = Nf -bf, Nbc = Nb - bb # # eNfc = sqrt(|Nf-bf|), eNbc = sqrt(|Nb-bb|) # # ebf = sqrt(bf/M), ebb= sqrt(bb/M) b = M sum_i^M bi # # sum_i^M b_i = Mb e_sum = sqrt(Mb) eb = 1/M e_sum # # error propagation: # # |da/dNfc| = |da/dbf| = 1, |da/dNbc| = |da/dbb|= alpha # # eA/d = sqrt[(|da/dNfc|eNfc)^2 + (|da/dbf|ebf)^2) + # # sqrt[(|da/dNb|eNb)^2 + (|da/dbb|ebb)^2) # # eA = d sqrt[Nf - bf(1-1/M) + alpha^2(Nb - bb(1-1/M))] # ######################################################### # the correction happens in cforw, cbackw above ey = np.sqrt(cforw + float(self.alpha.value)**2*cbackw) \ *np.exp(time/self.TauMu_mus)/enn0 # # May never happen but ey cannot be zero since fcn = sum_bin ((y_bin-y_th)/ey)**2 # The definition contains a difference # of independent terms: histo - background*(1-1/M), in both cforw, cbackw # Set to 1 the minimum error (weights less very few points closer to ~ zero) ey[np.where(ey==0)] = 1 # substitute 0 with 1 in ey if self._single_: # len(self._the_runs_)==1 and k=0 self.asymm = np.array([y]) # 2D np.array self.asyme = np.array([ey]) # 2D np.array self.nrun = [runs[0].get_runNumber_int()] else: # the first call of the suite the master, resets binwidth_ns, hence self.firstrun=True if self.firstrun: self.asymm = y # 1D np.array self.asyme = ey # idem self.firstrun = False self.nrun = [runs[0].get_runNumber_int()] else: self.asymm = np.row_stack((self.asymm, y)) # columns are times, rows are successive runs (for multiplot and global) self.asyme = np.row_stack((self.asyme, ey)) self.nrun.append(runs[0].get_runNumber_int()) # this is a list
###################################################### # self.nrun contains only the first run in case of run addition # used by save_fit (in file name), # write_csv (first item is run number) # animate (multiplot) ###################################################### # self.console('1st, last = {},{} bin for bckg subtraction'.format(self.firstbin,self.lastbin)) #dbg ######### # CONSOLE #########
[docs] def console(self,string,end = '\n'): ''' printed output method:: if self._output_ == self._outputtab_ prints on tab and sets self.mainwindow.selected_index = 5 otherwise writes on terminal ''' if self._output_ == self._outputtab_: with self._output_: print(string) self.mainwindow.selected_index = 5 else: p = open(self._output_,'w') p.write(''.join([string,end])) p.close()
################### # CONSOLE TRACEBACK (to be deleted) #################### # def console_trcbk(self): # ''' # method to print traceback in console # ''' # import sys, traceback # if self._output_ == self._outputtab_: # with self._output_: # raise # self.mainwindow.selected_index = 5 # else: # p = open(self._output_,'w') # formatted_lines = traceback.format_exc().splitlines() # for string in formatted_lines: # p.write(''.join([string,'\n'])) # p.close() ########################## # CREATE_RUNDICT ##########################
[docs] def create_rundict(self,k=0): ''' creates a dictionary rundict to identify and compare runs; refactored for adding runs ''' rundict={} instrument = self.filespecs[0].value.split('_')[2] # valid for psi with standard names 'deltat_tdc_gpd_xxxx.bin' for j,run in enumerate(self._the_runs_[k]): # more than one: add sequence rundict0 = {} rundict0.update({'nhist':run.get_numberHisto_int()}) rundict0.update({'histolen':run.get_histoLength_bin()}) rundict0.update({'binwidth':run.get_binWidth_ns()}) rundict0.update({'instrument':instrument}) if not rundict: # rundict contains only the first run of an add sequence (ok also for no add) rundict = rundict0 elif rundict0!=rundict: # trying to add runs with different nhist, histolen, binwidth, instrument? rundict.update({'error':run.get_runNumber_int()}) break rundict.update({'nrun':self._the_runs_[k][0].get_runNumber_int()}) rundict.update({'date':self._the_runs_[k][0].get_timeStart_vector()}) return rundict
########################## # FIT ##########################
[docs] def fit(self, model_in = ''): # '' is reset to a 'daml' default before create_model(model_in') # self.fit(model_in = 'mgmgbl') produces a different layout ''' fit tab of mugui, used - to set: self.alpha.value, self.offset.value, forw and backw groups fit and plot ranges, model version - to display: model name - to activate: fit, plot and update buttons - to select and load model (load from folder is missing/deprecated in python) - to select parameters value, fix, function, fft subtract :: # the calculation is performed in independent class mucomponents # the methods are "inherited" by mugui # via the reference instance self._the_model_, initialized in steps: # __init__ share initial attributes (constants) # _available_components_ automagical list of mucomponents # clear_asymmetry: includes reset check when suite is implemented # create_model: lay out self._the_model_ # delete_model: for a clean start # functions use eval, evil but needed, checked by muvalid, # iminuit requires them to be formatted as fitarg by int2min # help # load # save_fit/load_ft save results in mujpy format (dill) # write_csv produces a qtiplot/origin loadable summary # # Three fit types: single, suite no global, suite global. # Suite non global iterates a single fit over several runs # Suite global performs a single fit over many runs, # with common (global) and run dependent (local) parameters ''' from mujpy.mucomponents.mucomponents import mumodel import numpy as np def _available_components_(): from iminuit import describe ''' Method, returns a template tuple of dictionaries (one per fit component): Each dictionary contains 'name' and 'pars', the latter in turns is a list of dictionaries, one per parameter, 'name','error,'limits' :: ({'name':'bl','pars':[{'name':'asymmetry','error':0.01,'limits'[0,0]}, {'name':'Lor_rate','error':0.01,'limits'[0,0]}}, ...) retreived magically from the mucomponents class. ''' _available_components = [] # is a list, mutable # generates a template of available components. for name in [module for module in dir(mumodel()) if module[0]!='_']: # magical extraction of component names pars = describe(mumodel.__dict__[name])[2:] # the [2:] is because the first two arguments are self and x _pars = [] # print('pars are {}'.format(pars)) for parname in pars: # The style is like iminuit fitargs, but not exactly, # since the latter is a minuit instance: # it will contain parameter name: parname+str(k)[+'_'+str(nrun)] # error_parname, fix_parname (False/True), limits_parname, e.g. # {'amplitude1_354':0.154,'error_amplitude1_354':0.01,'fix_amplitude1_354':False,'limits_amplitude1_354':[0, 0] # # In this template only # {'name':'amplitude','error':0.01,'limits':[0, 0]} error, limits = 0.01, [0, 0] # defaults if parname == 'field' or parname == 'phase' or parname == 'dipfield': error = 1.0 if parname == 'beta': error,limits = 0.05, [1.e-2, 1.e2] # add here special cases for errors and limits, e.g. positive defined parameters _pars.append({'name':parname,'error':error,'limits':limits}) _available_components.append({'name':name,'pars':_pars}) self.available_components = (_available_components) # these are the mucomponents method directories # transformed in tuple, immutable self.component_names = [self.available_components[i]['name'] for i in range(len(self.available_components))] # list of just mucomponents method names def addcomponent(name,label): ''' myfit = MuFit() addcomponent('ml') # adds e.g. a mu precessing, lorentzian decay, component this method adds a component selected from self.available_component, tuple of directories with zeroed values, stepbounds from available_components, flags set to '~' and empty functions ''' from copy import deepcopy if name in self.component_names: k = self.component_names.index(name) npar = len(self.available_components[k]['pars']) # number of pars pars = deepcopy(self.available_components[k]['pars']) # list of dicts for # parameters, {'name':'asymmetry','error':0.01,'limits':[0, 0]} # now remove parameter name degeneracy for j, par in enumerate(pars): pars[j]['name'] = par['name']+label pars[j].update({'value':0.0}) pars[j].update({'flag':'~'}) pars[j].update({'function':''}) # adds these three keys to each pars dict # they serve to collect values in mugui self.model_components.append({'name':name,'pars':pars}) return True # OK code else: self.console('\nWarning: '+name+' is not a known component. Not added.\n'+ 'With myfit = mufit(), type myfit.help to see the available components') return False # error code def create_model(name): ''' myfit = MuFit() myfit.create_model('daml') # adds e.g. the two component 'da' 'ml' model this method adds a model of components selected from the available_component tuple of directories with zeroed values, stepbounds from available_components, flags set to '~' and empty functions ''' import string # name 2_0_mlml_blbl for 2 global parameters (A0 R), 0 kocal parameters (B end T) and two models # e.g. alpha fit with a WTF and a ZF run, with two muon fractions of amplitude A0*R and A0*(1-R) respectively # find the three underscores in name by # [i for i in range(len(name)) if name.startswith('_', i)] components = checkvalidmodel(name) if components: # exploits the fact that [] is False and ['da'] is true self.model = name self.model_components = [] # start from empty model for k,component in enumerate(components): label = string.ascii_uppercase[k] if not addcomponent(component,label): return False return True else: return False def checkvalidmodel(name): ''' checkvalidmodel(name) checks that name is a :: 2*component string of valid component names, e.g. 'daml' or 'mgmgbl' ''' components = [name[i:i+2] for i in range(0, len(name), 2)] for component in components: if component not in self.component_names: self.console('Warning: '+component+' is not a known component. Not added.\n'+ 'With myfit = mufit(), type myfit.help to see the available components') return [] # error code return components def chi(t,y,ey,pars): ''' stats for the right side of the plot ''' nu = len(t) - self.freepars # degrees of freedom in plot # self.freepars is calculated in int2min self._the_model_._load_data_(t,y,int2_int(), float(self.alpha.value),e=ey) # int2_int() returns a list of methods to calculate the components f = self._the_model_._add_(t,*pars) # f for histogram chi2 = self._the_model_._chisquare_(*pars)/nu # chi2 in plot return nu,f,chi2 def fitplot(guess=False,plot=False): ''' Plots fit results in external Fit window guess=True plot dash guess values guess=False plot best fit results plot=False best fit, invoke write_csv plot=True do not This is a complex routine that allows for :: - single, multiple or global fits - fit range different form plot range - either one plot range, the figure is a subplots((2,2)) plot ax_fit[(0,0), chi2_prints ax_fit[(0,-1)] residues ax_fit[(1,0)], chi2_histograms ax_fit[(1,-1)] two plot ranges, early and late, the figure is a subplots((3,2)) plot_early ax_fit[(0,0)], plot_late ax_fit[(0,1)], chi2_prints ax_fit[(0,-1)] residues_early ax_fit[(1,0)], residues_late ax_fit[(1,1)], chi2_histograms ax_fit[(1,-1)] If multi/globalfit, it also allows for either :: - anim display - offset display ''' import matplotlib.pyplot as P from mujpy.aux.aux import derange, rebin, get_title, plotile, set_bar from scipy.stats import norm from scipy.special import gammainc import matplotlib.path as path import matplotlib.patches as patches import matplotlib.animation as animation from matplotlib import ticker font = {'family':'Ubuntu','size':10} P.rc('font', **font) ################### # PYPLOT ANIMATIONS ################### def animate(i): ''' anim function update errorbar data, fit, residues and their color, chisquares, their histograms ''' # from mujpy.aux.aux import get_title # print('animate') # nufit,ffit,chi2fit = chi(tfit[0],yfit[i],eyfit[i],pars[i]) # nu,dum,chi2plot = chi(t[0],y[i],ey[i],pars[i]) # color = next(self.ax_fit[(0,0)]._get_lines.prop_cycler)['color'] line.set_ydata(y[i]) # begin errorbar line.set_color(color[i]) line.set_markerfacecolor(color[i]) line.set_markeredgecolor(color[i]) segs = [np.array([[q,w-a],[q,w+a]]) for q,w,a in zip(t[0],y[i],ey[i])] ye[0].set_segments(segs) ye[0].set_color(color[i]) # end errorbar fline.set_ydata(f[i]) # fit fline.set_color(color[i]) res.set_ydata(y[i]-fres[i]) # residues res.set_color(color[i]) # self.ax_fit[(0,0)].relim(), self.ax_fit[(0,0)].autoscale_view() if len(returntup)==5: linel.set_ydata(ylate[i]) # begin errorbar linel.set_color(color[i]) linel.set_markerfacecolor(color[i]) linel.set_markeredgecolor(color[i]) segs = [np.array([[q,w-a],[q,w+a]]) for q,w,a in zip(tlate[0],ylate[i],eylate[i])] yel[0].set_segments(segs) yel[0].set_color(color[i]) # end errorbar flinel.set_ydata(fl[i]) # fit flinel.set_color(color[i]) resl.set_ydata(ylate[i]-flres[i]) # residues resl.set_color(color[i]) # self.ax[(0,1)].relim(), self.ax[(0,1)].autoscale_view() self.ax_fit[(0,0)].set_title(get_title(self._the_runs_[i][0])) nhist,dum = np.histogram((yfit[i]-ffit[i])/eyfit[i],xbin) top = bottomf + nhist vertf[1::5, 1] = top vertf[2::5, 1] = top nhist,dum = np.histogram((y[i]-fres[i])/ey[i],xbin,weights=nufit[i]/nu[i]*np.ones(t.shape[1])) top = bottomp + nhist vertp[1::5, 1] = top vertp[2::5, 1] = top patchplot.set_facecolor(color[i]) patchplot.set_edgecolor(color[i]) nufitplot.set_ydata(nufit[i]*yh) string = '$\chi^2_f=$ {:.4f}\n ({:.2f}-{:.2f})\n$\chi^2_c=$ {:.4f}\n{} dof\n'.format(chi2fit[i], lc[i],hc[i],gammainc(chi2fit[i],nufit[i]),nufit[i]) if len(returntup)==5: nulate,dum,chi2late = chi(tlate[0],ylate[i],eylate[i],pars[i]) string += '$\chi^2_e=$ {:.4f}\n$\chi^2_l=$ {:.4f}'.format(chi2plot[i],chi2late) else: string += '$\chi^2_p=$ {:.4f}'.format(chi2plot[i]) text.set_text('{}'.format(string)) if len(returntup)==5: return line, ye[0], fline, res, linel, yel[0], flinel, resl, patchfit, patchplot, nufitplot, text else: return line, ye[0], fline, res, patchfit, patchplot, nufitplot, text def init(): ''' anim init function blitting (see wikipedia) to give a clean slate ''' from mujpy.aux.aux import get_title # nufit,ffit,chi2fit = chi(tfit[0],yfit[0],eyfit[0],pars[0]) # nu,dum,chi2plot = chi(t[0],y[0],ey[0],pars[0]) # color = next(self.ax_fit[(0,0)]._get_lines.prop_cycler)['color'] line.set_ydata(y[0]) # begin errorbar line.set_color(color[0]) segs = [np.array([[q,w-a],[q,w+a]]) for q,w,a in zip(t[0],y[0],ey[0])] ye[0].set_segments(segs) ye[0].set_color(color[0]) # end errorbar fline.set_ydata(f[0]) # fit fline.set_color(color[0]) res.set_ydata(y[0]-fres[0]) # residues res.set_color(color[0]) if len(returntup)==5: linel.set_ydata(ylate[0]) # begin errorbar linel.set_color(color[0]) segs = [np.array([[q,w-a],[q,w+a]]) for q,w,a in zip(tlate[0],ylate[0],eylate[0])] yel[0].set_segments(segs) yel[0].set_color(color[0]) # end errorbar flinel.set_ydata(fl[0]) # fit flinel.set_color(color[0]) resl.set_ydata(ylate[0]-flres[0]) # residues resl.set_color(color[0]) self.ax_fit[(0,0)].set_title(get_title(self._the_runs_[0][0])) nhist,dum = np.histogram((yfit[0]-ffit[0])/eyfit[0],xbin) top = bottomf + nhist vertf[1::5, 1] = top vertf[2::5, 1] = top nhist,dum = np.histogram((y[0]-fres[0])/ey[0],xbin,weights=nufit[0]/nu[0]*np.ones(t.shape[1])) top = bottomp + nhist vertp[1::5, 1] = top vertp[2::5, 1] = top patchplot.set_facecolor(color[0]) patchplot.set_edgecolor(color[0]) nufitplot.set_ydata(nufit[0]*yh) string = '$\chi^2_f=$ {:.4f}\n ({:.2f}-{:.2f})\n$\chi^2_c=$ {:.4f}\n{} dof\n'.format(chi2fit[0], lc[0],hc[0],gammainc(chi2fit[0],nufit[0]),nufit[0]) if len(returntup)==5: nulate,dum,chi2late = chi(tlate[0],ylate[0],eylate[0],pars[0]) string += '$\chi^2_e=$ {:.4f}\n$\chi^2_l=$ {:.4f}'.format(chi2plot[0],chi2late) else: string += '$\chi^2_p=$ {:.4f}'.format(chi2plot[0]) text.set_text('{}'.format(string)) # print('init') if len(returntup)==5: return line, ye[0], fline, res, linel, yel[0], flinel, resl, patchfit, patchplot, nufitplot, text else: return line, ye[0], fline, res, patchfit, patchplot, nufitplot, text # FITPLOT BEGINS HERE ###################################################### # asymm[0] contains data from bin corresponding to # the prompt peak bin (nt0) + self.offset bins (fit_start = 0 to start from there) # pars is a list of lists of best fit parameter values # self.time, self.asymm, self.asyme are 2D arrays (self.times.shape[0] is always 1) # y, ey, f, fres, ylate, eylate, fl, flres, yfit, eyfit, ffit are 2D arrays # tf, tfl, tlate, tfit are 2D array like self.time (e.g. tf.shape[0] is always 1) ############################## # plot according to plot_range ############################## # must provide maximum available bins = self.histoLength returntup = derange(self.plot_range.value,self.histoLength) if sum(n<0 for n in returntup)>0: # illegal values self.plot_range.value # signalled by returntup = (-1,-1); # restore defaults tmp = self.plot_range.value self.plot_range.value = self.plot_range0 self.plot_range.background_color = "mistyrose" self.console('Wrong plot range: {}'.format(tmp)) return self.asymmetry() # prepare asymmetry, ############################################ # choose pars for first/single fit function ############################################ fitarg = int2min(return_names=True) # from dash, fitarg is a list of dictionaries # print('fitarg = {}\nself.minuit_parameter_names = {}'.format(fitarg,self.minuit_parameter_names)) if guess: # from dash, for plot guess pars = [[fitarg[k][name] for name in self.minuit_parameter_names] for k in range(len(fitarg))] ############################################################### # mock data loading to set alpha and global in self._the_model_ # in case no fit was done yet ############################################################### if not self._the_model_._alpha_: # False if no _load_data_ yet if self._global_: self.console('global, mumodel load_data') # self._the_model_ is an instance of mucomponents, and _load_data_ is one of its methods self._the_model_._load_data_(self.time[0],self.asymm,int2_int(), float(self.alpha.value),e=self.asyme, _nglobal_=self.nglobals, _locals_=self.locals) # int2_int() returns a list of methods to calculate the components else: # self.console('no global, mumodel load_data') # debug self._the_model_._load_data_(self.time[0], self.asymm[0], int2_int(), float(self.alpha.value), e=self.asyme[0]) # int2_int() returns a list of methods to calculate the components # string = 'Load data iok = {} [0=fine]. Time last = {:.2e}, A[0] = {:.2f}, alpha = {:.3f}'.format(pok,self._the_model_._x_[-1],self._themodel_._y_[0],self._the_model_._alpha_) # debug # self.console(string) else: # from lastfit, for best fit and plot best fit pars = [[self.fitargs[k][name] for name in self.minuit_parameter_names] for k in range(len(self.fitargs))] ########################################## # now self.time is a 1D array # self.asymm, self.asyme are 1D or 2D arrays # containing asymmetry and its std, # for either single run or suite of runs # pars[k] is the k-th par list for the fit curve of the k-th data row ########################################## ############################################### # rebinnig for plot (different packing from fit) ############################################### # early and late plots ###################### if len(returntup)==5: # start stop pack=packearly last packlate start, stop, pack, last, packlate = returntup tlate,ylate,eylate = rebin(self.time,self.asymm,[stop,last],packlate,e=self.asyme) tfl,dum = rebin(self.time,self.asymm,[stop,last],1) ncols, width_ratios = 3,[2,2,1] ################### # single range plot ################### else: pack = 1 ncols, width_ratios = 2,[4,1] if len(returntup)==3: # plot start stop pack start, stop, pack = returntup elif len(returntup)==2: # plot start stop start, stop = returntup t,y,ey = rebin(self.time,self.asymm,[start,stop],pack,e=self.asyme) tf,dum = rebin(self.time,self.asymm,[start,stop],1) yzero = y[0]-y[0] ############################# # rebinning of data as in fit ############################# fittup = derange(self.fit_range.value,self.histoLength) # range as tuple fit_pack =1 if len(fittup)==3: # plot start stop pack fit_start, fit_stop, fit_pack = fittup[0], fittup[1], fittup[2] elif len(fittup)==2: # plot start stop fit_start, fit_stop = fittup[0], fittup[1] # if not self._single_ each run is a row in 2d ndarrays yfit, eyfit tfit,yfit,eyfit = rebin(self.time,self.asymm,[fit_start,fit_stop],fit_pack,e=self.asyme) # print('pars = {}'.format(pars)) # print('t = {}'.format(t)) f = np.array([self._the_model_._add_(tf[0],*pars[k]) for k in range(len(pars))]) # tf,f for plot curve fres = np.array([self._the_model_._add_(t[0],*pars[k]) for k in range(len(pars))]) # t,fres for residues ffit = np.array([self._the_model_._add_(tfit[0],*pars[k]) for k in range(len(pars))]) # t,fres for residues if len(returntup)==5: ############################################## # prepare fit curves for second window, if any ############################################## fl = np.array([self._the_model_._add_(tfl[0],*pars[k]) for k in range(len(pars))]) # tfl,fl for plot curve flres = np.array([self._the_model_._add_(tlate[0],*pars[k]) for k in range(len(pars))]) # tlate,flate for residues ############################### # set or recover figure, axes ############################### # if self.fig_tlog: # self.fig_tlog.clf() # self.fig_tlog,self.ax_tlog = P.subplots(num=self.fig_tlog.number) # # self.fig_tlog,self.ax_tlog = P.subplots() # self.fig_tlog.canvas.set_window_title('Tlogger') try: if self.fig_fit: # has been set to a handle once self.fig_fit.clf() self.fig_fit,self.ax_fit = P.subplots(2,ncols,sharex = 'col', gridspec_kw = {'height_ratios':[3,1],'width_ratios':width_ratios}, num=self.fig_fit.number) self.fig_fit.subplots_adjust(hspace=0.05,top=0.90,bottom=0.12,right=0.97,wspace=0.03) except: # handle does not exist, make one self.fig_fit,self.ax_fit = P.subplots(2,ncols,figsize=(6,4),sharex = 'col', gridspec_kw = {'height_ratios':[3, 1],'width_ratios':width_ratios}) self.fig_fit.canvas.set_window_title('Fit') self.fig_fit.subplots_adjust(hspace=0.05,top=0.90,bottom=0.12,right=0.97,wspace=0.03) ########################## # plot data and fit curve ########################## ############# # animation ############# if anim_check.value and not self._single_: # a single cannot be animated # THIS BLOCK TAKE CARE OF THE FIRST ROW OF DATA (errobars, fit curve, histograms and all) # pars[k] are the parameters to the run of the FIRST row, both for global and multi fits # in anim therefore FIT CURVES (f, fres, fl, flres) ARE ALWAYS 1D ARRAYS # animate must take care of updating parameters and producing correct fit curves ############## # initial plot ############## nufit,dum,chi2fit = chi(tfit[0],yfit[0],eyfit[0],pars[0]) color = [] for k in range(len(self.fitargs)): color.append(next(self. ax_fit[(0,0)]._get_lines.prop_cycler)['color']) line, xe, ye, = self.ax_fit[(0,0)].errorbar(t[0],y[0],yerr=ey[0], fmt='o',elinewidth=1.0,ms=2.0, mec=color[0],mfc=color[0],ecolor=color[0],alpha=0.5) # data fline, = self.ax_fit[(0,0)].plot(tf[0],f[0],'-',lw=1.0,color=color[0],alpha=0.5) # fit res, = self.ax_fit[(1,0)].plot(t[0],y[0]-fres[0],'-',lw=1.0,color=color[0],alpha=0.5) # residues self.ax_fit[(1,0)].plot(t[0],yzero,'k-',lw=0.5,alpha=0.3) # zero line ym,yM = y.min()*1.02,y.max()*1.02 rm,rM = (y-fres).min()*1.02,(y-fres).max()*1.02 ym,rm = min(ym,0), min(rm,0) ############################ # plot second window, if any ############################ if len(returntup)==5: linel, xel, yel, = self.ax_fit[(0,1)].errorbar(tlate[0],ylate[0],yerr=eylate[0], fmt='o',elinewidth=1.0,ms=2.0,alpha=0.5, mec=color[0],mfc=color[0],ecolor=color[0]) # data flinel, = self.ax_fit[(0,1)].plot(tfl[0],fl[0],'-',lw=1.0,alpha=0.5,color=color[0]) # fit self.ax_fit[(0,1)].set_xlim(tlate[0,0], tlate[0,-1]) # plot residues resl, = self.ax_fit[(1,1)].plot(tlate[0],ylate[0]-flres[0],'-',lw=1.0,alpha=0.5,color=color[0]) # residues self.ax_fit[(1,1)].plot(tlate[0],ylate[0]-ylate[0],'k-',lw=0.5,alpha=0.3) # zero line self.ax_fit[(0,1)].set_xlim(tlate.min(),tlate.max()) # these are the global minima self.ax_fit [(1,1)].set_xlim(tlate.min(),tlate.max()) self.ax_fit [(1,1)].set_xlim(tlate.min(),tlate.max()) self.ax_fit [(1,1)].set_xlim(tlate.min(),tlate.max()) yml,yMl = ylate.min()*1.02,ylate.max()*1.02 rml,rMl = (ylate-flres).min()*1.02,(ylate-flres).max()*1.02 ym,yM,rm,rM = min(ym,yml),max(yM,yMl),min(rm,rml),max(rM,rMl) self.ax_fit[(0,1)].set_ylim(ym,yM) self.ax_fit[(1,1)].set_ylim(rm,rM) self.ax_fit[(0,1)].yaxis.set_major_formatter(ticker.NullFormatter())#set_yticklabels([])# self.ax_fit[(1,1)].yaxis.set_major_formatter(ticker.NullFormatter())#set_yticklabels([])# ############################### # set title, labels ############################### # print('title = {}'.format(get_title(self._the_runs_[0][0]))) self.ax_fit[(0,0)].set_title(get_title(self._the_runs_[0][0])) self.ax_fit[(0,0)].set_xlim(0,t.max()) self.ax_fit[(0,0)].set_ylim(ym,yM) self.ax_fit[(1,0)].set_ylim(rm,rM) self.ax_fit[(1,0)].set_xlim(0,t.max()) self.ax_fit[(0,0)].set_ylabel('Asymmetry') self.ax_fit[(1,0)].set_ylabel('Residues') self.ax_fit[(1,0)].set_xlabel(r'Time [$\mu$s]') self.ax_fit[(1,-1)].set_xlabel("$\sigma$") self.ax_fit[(1,-1)].yaxis.set_major_formatter(ticker.NullFormatter()) #set_yticklabels(['']*len(self.ax_fit[(1,-1)].get_yticks()))#set_yticklabels([])# self.ax_fit[(1,-1)].set_xlim([-5., 5.]) self.ax_fit[(0,-1)].axis('off') ######################## # chi2 distribution: fit ######################## xbin = np.linspace(-5.5,5.5,12) nhist,dum = np.histogram((yfit[0]-ffit[0])/eyfit[0],xbin) # fc, lw, alpha set in patches vertf, codef, bottomf, xlimf = set_bar(nhist,xbin) barpathf = path.Path(vertf, codef) patchfit = patches.PathPatch( barpathf, facecolor='w', edgecolor= 'k', alpha=0.5,lw=0.7) self.ax_fit[(1,-1)].add_patch(patchfit) #hist((yfit-ffit)/eyfit,xbin,rwidth=0.9,fc='w',ec='k',lw=0.7) self.ax_fit[(1,-1)].set_xlim(xlimf[0],xlimf[1]) # self.ax_fit[(1,-1)].set_ylim(0, 1.15*nhist.max()) ######################################### # chi2 distribution: plots, scaled to fit ######################################### nu,dum,chi2plot = chi(t[0],y[0],ey[0],pars[0]) nhist,dum = np.histogram((y[0]-fres[0])/ey[0],xbin,weights=nufit/nu*np.ones(t.shape[1])) vertp, codep, bottomp, xlimp = set_bar(nhist,xbin) # fc, lw, alpha set in patches barpathp = path.Path(vertp, codep) patchplot = patches.PathPatch( barpathp, facecolor=color[0], edgecolor= color[0], alpha=0.5,lw=0.7) self.ax_fit[(1,-1)].add_patch(patchplot) # hist((y[0]-f/ey[0],xbin,weights=nufit/nu*np.ones(t.shape[0]),rwidth=0.9,fc=color,alpha=0.2) ############################### # chi2 dist theo curve & labels ############################### xh = np.linspace(-5.5,5.5,23) # static yh = norm.cdf(xh+1)-norm.cdf(xh) # static nufitplot, = self.ax_fit[(1,-1)].plot(xh+0.5,nufit*yh,'r-') # nufit depends on k mm = round(nufit/4) # nu, mm, hb, cc, lc, hc depend on k hb = np.linspace(-mm,mm,2*mm+1) cc = gammainc((hb+nufit)/2,nufit/2) # muchi2cdf(x,nu) = gammainc(x/2, nu/2); lc = 1+hb[min(list(np.where((cc<norm.cdf(1))&(cc>norm.cdf(-1))))[0])]/nufit hc = 1+hb[max(list(np.where((cc<norm.cdf(1))&(cc>norm.cdf(-1))))[0])]/nufit string = 'F-B: {} - {}\n'.format(self.group[0].value,self.group[1].value) string += r'$\alpha=$ {}'.format(self.alpha.value) string += '\n$\chi^2_f=$ {:.4f}\n ({:.2f}-{:.2f})\n$\chi^2_c=$ {:.4f}\n{} dof\n'.format(chi2fit, lc,hc,gammainc(chi2fit,nufit),nufit) if len(returntup)==5: nulate,dum,chi2late = chi(tlate[0],ylate[0],eylate[0],pars[0]) string += '$\chi^2_e=$ {:.4f}\n$\chi^2_l=$ {:.4f}'.format(chi2plot,chi2late) else: string += '$\chi^2_p=$ {:.4f}'.format(chi2plot) text = self.ax_fit[(0,-1)].text(-4,0.3,string) self.fig_fit.canvas.manager.window.tkraise() # save all chi2 values now nufit,chi2fit,nu,chi2plot,lc,hc = [nufit],[chi2fit],[nu],[chi2plot],[lc],[hc] # initialize lists with k=0 value for k in range(1,len(self.fitargs)): nufitk,dum,chi2fitk = chi(tfit[0],yfit[k],eyfit[k],pars[k]) nufit.append(nufitk) chi2fit.append(chi2fitk) nuk,dum,chi2plotk = chi(t[0],y[k],ey[k],pars[k]) nu.append(nuk) chi2plot.append(chi2plotk) mm = round(nufitk/4) # nu, mm, hb, cc, lc, hc depend on k hb = np.linspace(-mm,mm,2*mm+1) cc = gammainc((hb+nufitk)/2,nufitk/2) # muchi2cdf(x,nu) = gammainc(x/2, nu/2); lc.append(1+hb[min(list(np.where((cc<norm.cdf(1))&(cc>norm.cdf(-1))))[0])]/nufitk) hc.append(1+hb[max(list(np.where((cc<norm.cdf(1))&(cc>norm.cdf(-1))))[0])]/nufitk) # print('len(self.fitargs)) = {}'.format(len(self.fitargs))) ######################################################### # animate (see): TAKES CARE OF i>0 PLOTS IN 2D ARRAYS # DOES ALSO UPDATE pars AND FIT CURVES ######################################################### self.anim_fit = animation.FuncAnimation(self.fig_fit, animate, range(0,len(self.fitargs)),init_func=init, interval=anim_delay.value,repeat=True, blit=False) # ############################### # single and tiles with offset ############################### ######## # tile ######## else: # TILES: creates matrices for offset multiple plots (does nothing on single) ############################## # THIS BLOCK TAKES CARE OF ALL ROWS OF DATA AT ONCE (errobars, fit curve, histograms and all) # pars must refer to the run of the FIRST row, both for global and multi fits # in anim therefore FIT CURVES (f, fres, fl, flres) ARE ALWAYS 1D ARRAYS # animate must take care of updating parameters and producing correct fit curves ############## # initial plot ##############f* yoffset = 0.4 ymax = yoffset*fres.max() # this is an offset in the y direction, to be refined # self.console('ymax = {:.2f}'.format(ymax)) rmax = 0.3*(y-fres).max() xoffset = 0. # print ('fres = {}'.format(fres.shape)) # ttile, ytile, yres = plotile(t,y.shape[0],offset=xoffset), plotile(y,offset=ymax), plotile(y-fres,offset=rmax) # plot arrays, ytile is shifted by ymax # tftile, ftile = plotile(tf,y.shape[0],offset=xoffset), plotile(f,offset=ymax) ytile, yres, ftile = plotile(y,offset=ymax), plotile(y-fres,offset=rmax), plotile(f,offset=ymax) # y plot arrays shifted by ymax # self.console('after plotile ymax = {:.2f}'.format(ymax)) # print('ttile.shape = {}, ytile.shape= {}, yres.shape = {}, tftile.shape = {}, ftile.shape = {}'.format(ttile.shape,ytile.shape,yres.shape,tftile.shape,ftile.shape)) # print('f_tile = {}'.format(f_tile[0,0:50])) ############################# # plot first (or only) window ############################# # print(color) # errorbar does not plot multidim t1 = t.max()#/10. t0 = np.array([0.8*t1,t1]) y0 = np.array([0.,0.]) for k in range(y.shape[0]): color = next(self.ax_fit[0,0]._get_lines.prop_cycler)['color'] self.ax_fit[(0,0)].errorbar(t[0], ytile[k], yerr=ey[k], fmt='o', elinewidth=1.0,ecolor=color,mec=color,mfc=color, ms=2.0,alpha=0.5) # data self.ax_fit[(0,0)].plot(t0,y0,'-',lw=0.5,alpha=1,color=color) self.ax_fit[(1,0)].plot(t[0],yres[k],'-',lw=1.0,alpha=0.3,zorder=2,color=color) # residues self.ax_fit[(0,0)].plot(tf[0],ftile[k],'-',lw=1.5,alpha=1,zorder=2,color=color) # fit y0 = y0 + ymax self.ax_fit[(1,0)].plot(t[0],yzero,'k-',lw=0.5,alpha=0.3,zorder=0) # zero line yzero = yzero + rmax ############################ # plot second window, if any ############################ y0 = np.array([0.,0.]) if len(returntup)==5: # tltile, yltile, ylres = plotile(tlate,xdim=ylate.shape[0], # offset=xoffset),plotile(ylate, # offset=ymax),plotile(ylate-flres,offset=rmax) # plot arrays, full suite # tfltile, fltile = plotile(tfl,offset=xoffset),plotile(fl,offset=ymax) # res offset is 0.03 yltile, ylres, fltile = plotile(ylate,offset=ymax), plotile(ylate-flres, offset=rmax),plotile(fl,offset=ymax) # y arrays, late for k in range(y.shape[0]): color = next(self.ax_fit[0,1]._get_lines.prop_cycler)['color'] self.ax_fit[(0,1)].errorbar(tlate[0], yltile[k], yerr=eylate[k], fmt='o', elinewidth=1.0,ecolor=color,mec=color,mfc=color, ms=2.0,alpha=0.5) # data self.ax_fit[(1,1)].plot(tlate[0],ylres[k],'-',lw=1.0,alpha=0.3,zorder=2,color=color) # residues self.ax_fit[(0,1)].plot(tfl[0],fltile[k],'-',lw=1.5,alpha=1,zorder=2,color=color) # fit # self.ax_fit[(1,1)].plot(tlate,tlate-tlate,'k-',lw=0.5,alpha=0.3,zorder=0) # zero line self.ax_fit[(0,1)].set_xlim(tlate[0,0], tlate[-1,-1]) ############################### # set title, labels ############################### ym,yM,rm,rM = ytile.min()-0.05,ytile.max()+0.01,yres.min()-0.005,yres.max()+0.005 self.ax_fit[(0,0)].set_xlim(0,t.max()) self.ax_fit[(0,0)].set_ylim(ym,yM) self.ax_fit[(1,0)].set_ylim(rm,rM) self.ax_fit[(1,0)].set_xlim(0,t.max()) if len(returntup)==5: self.ax_fit[(0,1)].set_xlim(tlate[0,0], tlate[-1,-1]) self.ax_fit[(0,1)].set_ylim(ym,yM) self.ax_fit[(1,1)].set_xlim(tlate[0,0], tlate[-1,-1]) self.ax_fit[(1,1)].set_ylim(rm,rM) self.ax_fit[(0,1)].yaxis.set_major_formatter(ticker.NullFormatter()) self.ax_fit[(1,1)].yaxis.set_major_formatter(ticker.NullFormatter()) self.ax_fit[(0,0)].set_ylabel('Asymmetry') self.ax_fit[(1,0)].set_ylabel('Residues') self.ax_fit[(1,0)].set_xlabel(r'Time [$\mu$s]') if self._single_: self.ax_fit[(0,0)].set_title(str(self.nrun[0])+': '+self.title.value) ######################## # chi2 distribution: fit ######################## nufit,dum,chi2fit = chi(tfit[0],yfit[0],eyfit[0],pars[0]) nu,f,chi2plot = chi(t[0],y[0],ey[0],pars[0]) self.ax_fit[(0,0)].plot(t[0],f,'g--',lw=1.5 ,alpha=1,zorder=2)#,color=color) # fit xbin = np.linspace(-5.5,5.5,12) self.ax_fit[(1,-1)].hist((yfit[0]-ffit[0])/eyfit[0],xbin,rwidth=0.9,fc='w',ec='k',lw=0.7) # self.ax_fit[(1,-1)].set_ylim(0, 1.15*nhist.max()) ######################################### # chi2 distribution: plots, scaled to fit ######################################### self.ax_fit[(1,-1)].hist((y[0]-fres[0])/ey[0],xbin,weights=nufit/nu*np.ones(t.shape[1]),rwidth=0.9,fc=color,alpha=0.2) ############################### # chi2 dist theo curve & labels ############################### xh = np.linspace(-5.5,5.5,23) yh = norm.cdf(xh+1)-norm.cdf(xh) self.ax_fit[(1,-1)].plot(xh+0.5,nufit*yh,'r-') self.ax_fit[(1,-1)].set_xlabel("$\sigma$") self.ax_fit[(1,-1)].yaxis.set_major_formatter(ticker.NullFormatter())# set_yticklabels(['']*len(self.ax_fit[(1,-1)].get_yticks())) # self.ax_fit[(1,-1)].set_xlim([-5.5, 5.5]) mm = round(nu/4) hb = np.linspace(-mm,mm,2*mm+1) cc = gammainc((hb+nu)/2,nu/2) # muchi2cdf(x,nu) = gammainc(x/2, nu/2); lc = 1+hb[min(list(np.where((cc<norm.cdf(1))&(cc>norm.cdf(-1))))[0])]/nufit hc = 1+hb[max(list(np.where((cc<norm.cdf(1))&(cc>norm.cdf(-1))))[0])]/nufit string = 'F-B: {} - {}\n'.format(self.group[0].value,self.group[1].value) string += r'$\alpha=$ {}'.format(self.alpha.value) string += '\n$\chi^2_f=$ {:.4f}\n ({:.2f}-{:.2f})\n$\chi^2_c=$ {:.4f}\n{} dof\n'.format(chi2fit,lc,hc,gammainc(chi2fit,nufit),nufit) if len(returntup)==5: string += '$\chi^2_e=$ {:.4f}\n$\chi^2_l=$ {:.4f}'.format(chi2plot,chi2late) else: string += '$\chi^2_p=$ {:.4f}'.format(chi2plot) self.ax_fit[(0,-1)].text(-4.,0.3,string) else: self.ax_fit[(0,0)].set_title(str(self.nrun[0])+': '+self.title.value) ######################## # chi2 distribution: fit ######################## fittup = derange(self.fit_range.value,self.histoLength) # range as tuple fit_pack =1 if len(fittup)==3: # plot start stop pack fit_start, fit_stop, fit_pack = fittup[0], fittup[1], fittup[2] elif len(fittup)==2: # plot start stop fit_start, fit_stop = fittup[0], fittup[1] # if not self._single_ each run is a row in 2d ndarrays yfit, eyfit # tfit,yfit,eyfit = rebin(self.time,self.asymm,[fit_start,fit_stop],fit_pack,e=self.asyme) ychi = yM string = 'F-B: {} - {}\n'.format(self.group[0].value,self.group[1].value) string += r'$\alpha=$ {}'.format(self.alpha.value) self.ax_fit[(0,-1)].text(0.03,ychi,string) dychi = (yM-ym)/(len(pars)+2) # trying to separate chi2 ychi -= 2*dychi for k in range(len(pars)): ######################################### # chi2 distribution: plots, scaled to fit ######################################### nufit,ffit,chi2fit = chi(tfit[0],yfit[k],eyfit[k],pars[k]) nu,f,chi2plot = chi(t[0],y[k],ey[k],pars[k]) mm = round(nufit/4) hb = np.linspace(-mm,mm,2*mm+1) cc = gammainc((hb+nu)/2,nu/2) # muchi2cdf(x,nu) = gammainc(x/2, nu/2); lc = 1+hb[min(list(np.where((cc<norm.cdf(1))&(cc>norm.cdf(-1))))[0])]/nufit hc = 1+hb[max(list(np.where((cc<norm.cdf(1))&(cc>norm.cdf(-1))))[0])]/nufit pedice = '_{'+str(self.nrun[k])+'}' string = '$\chi^2'+pedice+'=$ {:.3f}'.format(chi2fit) self.ax_fit[(0,-1)].text(0.03,ychi,string) ychi -= dychi self.ax_fit[(1,-1)].axis('off') self.ax_fit[(0,-1)].set_ylim(self.ax_fit[(0,0)].get_ylim()) self.ax_fit[(0,-1)].axis('off') self.fig_fit.canvas.manager.window.tkraise()# fig.canvas.manager.window.raise_() P.draw() def int2csv(k): ''' translates nint into order numbers in the csv list, plus component number accepts single int, np.array of int, list of int e.g. model daml, da is nint=0, ncomp=0 in daml.1.3-4.csv the columns are Run T1 eT1 T2 eT2 B da eda ... 0 1 2 3 4 5 6 7 int2csv(0) must return [[6,7,0]] ''' from numpy import array ncomp = len(self.model_components) npar = [len(self.model_components[k]['pars']) for k in range(ncomp)] ntot = sum(npar) lmin = [-1]*ntot lcomp = [-1]*ntot nint = -1 # initialize nmin = -1 # initialize for k in range(ncomp): # scan the model for j in range(npar[k]): nint += 1 # internal parameter incremente always if self.flag[nint].value != '=': # free or fixed par nmin += 1 lmin[nint] = nmin # correspondence nint -> nmin lcomp[nint] = k # correspondence nint -> ncomp out = [] lint = array(k) # to grant its ndarray if lint.shape: # to avoid single numbers for k in lint: out.append([5+2*lmin[k]+1,5+2*lmin[k]+2,lcomp[k]]) return out # ls of lists the inner list contains the indices of par and its error in the csv def int2_int(): ''' From internal parameters to the minimal representation for the use of mucomponents._add_. Invoked by the iminuit initializing call, self._the_model_._load_data_, where self._the_model_ is an instance of mucomponents, just before submitting minuit ''' from mujpy.aux.aux import translate #_components_ = [[method,[key,...,key]],...,[method,[key,...,key]]], and eval(key) produces the parmeter value # refactor : this routine has much in common with min2int ntot = sum([len(self.model_components[k]['pars']) for k in range(len(self.model_components))]) lmin = [-1]*ntot nint = -1 # initialize nmin = -1 # initialize _int = [] for k in range(len(self.model_components)): # scan the model name = self.model_components[k]['name'] # print('name = {}, model = {}'.format(name,self._the_model_)) bndmthd = [] if name=='da' else self._the_model_.__getattribute__(name) # this is the method to calculate a component # to set dalpha apart keys = [] isminuit = [] for j in range(len(self.model_components[k]['pars'])): nint += 1 # internal parameter incremente always if self.flag[nint].value == '=': # function is written in terms of nint # nint must be translated into nmin string = translate(nint,lmin,self.function) keys.append(string) # the function will be eval-uated, eval(key) inside mucomponents isminuit.append(False) else: nmin += 1 keys.append('p['+str(nmin)+']') lmin[nmin] = nint # lmin contains the int number of the minuit parameter isminuit.append(True) _int.append([bndmthd,keys]) #,isminuit]) # ([component_dict,keys]) # for k in range(len(_int)): # print(_int[k]) return _int def int2min(return_names=False): ''' From internal parameters to minuit parameters. Invoked just before submitting minuit Internal are numbered progressively according to the display: first global parameters not belonging to components - e.g. A0, R, such as for asymmetry1 = A0*R and asymmetry2= A0*(1.-R) then local parameters not belonging to components - e.g. B and T from the data file headers then the list of components' parameters Minuit parameters are the same, including fixed ones, but the ones defined by functions or sharing Each parameter requires name=value, error_name=value, fix_name=value, limits_name=value,value [plus the local replica of the non global component parameters to be implemented] New version for suite of runs fitarg becomes a list of dictionaries ''' ntot = sum([len(self.model_components[k]['pars']) for k in range(len(self.model_components))]) ntot -= sum([1 for k in range(ntot) if self.flag[k]=='=']) # ntot minus number of functions fitarg = [] # list of dictionaries parameter_names = [] ########################################## # single produces a list of one dictionary # with keys 'par_name':guess_value,'error_par_name':step,... # suite no global produces one dictionary per run # furthermore, if flag == 'l', each run may have a different guess value # suite global produces a single dictionary but has # local, run dependent parameters, that may have flag=='l' ########################################## if not self._global_: for lrun in range(len(self._the_runs_)): lmin = [-1]*ntot nint = -1 # initialize nmin = -1 # initialize free = -1 fitargs= {} for k in range(len(self.model_components)): # scan the model component_name = self.model_components[k]['name'] # name of component keys = [] for j, par in enumerate(self.model_components[k]['pars']): # list of dictionaries, par is a dictionary nint += 1 # internal parameter incremented always if self.flag[nint].value == '~': # skip functions, they are not new minuit parameter keys.append('~') nmin += 1 free += 1 lmin[nmin] = nint # correspondence between nmin and nint, is it useful? fitargs.update({par['name']:float(self.parvalue[nint].value)}) if lrun==0: parameter_names.append(par['name']) fitargs.update({'error_'+par['name']:float(par['error'])}) if not (par['limits'][0] == 0 and par['limits'][1] == 0): fitargs.update({'limit_'+par['name']:par['limits']}) #else: # not needed, defaut is None # fitargs.update({'limit_'+par['name']:None}) elif self.flag[nint].value == 'l': keys.append('~') nmin += 1 free += 1 lmin[nmin] = nint # correspondence between nmin and nint, is it useful? fitargs.update({par['name']:muvalue(lrun,self.function[nint].value)}) if lrun==0: parameter_names.append(par['name']) fitargs.update({'error_'+par['name']:float(par['error'])}) if not (par['limits'][0] == 0 and par['limits'][1] == 0): fitargs.update({'limit_'+par['name']:par['limits']}) #else: # not needed, defaut is None # fitargs.update({'limit_'+par['name']:None}) elif self.flag[nint].value == '!': nmin += 1 lmin[nmin] = nint # correspondence between nmin and nint, is it useful? fitargs.update({par['name']:float(self.parvalue[nint].value)}) if lrun==0: parameter_names.append(par['name']) fitargs.update({'fix_'+par['name']:True}) fitarg.append(fitargs) self.freepars = free else: # global # to be done fitarg.append(fitargs) # print('fitargs= {}'.format(fitargs)) if return_names: self.minuit_parameter_names = tuple(parameter_names) return fitarg def load_fit(b): ''' loads fit values such that the same fit can be reproduced on the same data ''' import dill as pickle import os from mujpy.aux.aux import path_file_dialog path_and_filename = path_file_dialog(self.paths[2].value,'fit') # returns the full path and filename if path_and_filename == '': return # else: # self.console('Trying to read {}'.format(path_and_filename)) try: with open(path_and_filename,'rb') as f: fit_dict = pickle.load(f) try: del self._the_model_ self.fitargs = [] except: pass model.value = fit_dict['model.value'] self.fit(model.value) # re-initialize the tab with a new model self.version.value = fit_dict['version'] self.offset.value = fit_dict['self.offset.value'] self.model_components = fit_dict['self.model_components'] self.grouping = fit_dict['self.grouping'] set_group() self.alpha.value = fit_dict['self.alpha.value'] # self.alphavalue = float(self.alpha.value) self.offset.value = fit_dict['self.offset.value'] nint = fit_dict['nint'] self.fit_range.value = fit_dict['self.fit_range.value'] self.plot_range.value = fit_dict['self.plot_range.value'] # keys for k in range(nint+1): self.parvalue[k].value = fit_dict['_parvalue['+str(k)+']'] self.flag[k].value = fit_dict['_flag['+str(k)+ ']'] self.function[k].value = fit_dict['_function['+str(k)+']'] self.fitargs = fit_dict['self.fitargs'] self.load_handle.value = fit_dict['self.load_handle.value'] except Exception as e: self.console('Problems with reading {} file\n\nException: {}'.format(path_and_filename,e)) def min2int(fitargs): ''' From minuit parameters to internal parameters, see int2min for a description Invoked just after minuit convergence for save_fit, [on_update] ''' # refactor : this routine has much in common with int2_int # initialize from mujpy.aux.aux import translate ntot = sum([len(self.model_components[k]['pars']) for k in range(len(self.model_components))]) parvalue = [] lmin = [-1]*ntot p = [0.0]*ntot nint = -1 nmin = -1 for k in range(len(self.model_components)): # scan the model keys = [] for j, par in enumerate(self.model_components[k]['pars']): # list of dictionaries, par is a dictionary nint += 1 # internal parameter incremented always if self.flag[nint].value != '=': # skip functions, they are not new minuit parameter nmin += 1 p[nmin] = fitargs[par['name']] # needed also by functions parvalue.append('{:4f}'.format(p[nmin])) # parvalue item is a string lmin[nint] = nmin # number of minuit parameter else: # functions, calculate as such # nint must be translated into nmin string = translate(nint,lmin,self.function) # parvalue.append('{:4f}'.format(eval(string))) # parvalue item is a string return parvalue def min2print(fitargs): ''' From minuit parameters to plain print in console, see int2min for a description Invoked just after minuit convergence for output ''' # refactor : this routine has much in common with int2_int # initialize from mujpy.aux.aux import translate, translate_nint, value_error ntot = sum([len(self.model_components[k]['pars']) for k in range(len(self.model_components))]) _parvalue = [] lmin = [-1]*ntot p = [0.0]*ntot e = [0.0]*ntot nint = -1 nmin = -1 self.console('*****************************************************') for k in range(len(self.model_components)): # scan the model keys = [] for j, par in enumerate(self.model_components[k]['pars']): # list of dictionaries, par is a dictionary nint += 1 # internal parameter incremented always if self.flag[nint].value != '=': # skip functions, they are not new minuit parameter nmin += 1 string = ' {}-{} = {}'.format(nmin,par['name'], value_error(fitargs[par['name']],fitargs['error_'+par['name']])) self.console(string,end='') # needed also by functions p[nmin] = fitargs[par['name']] # needed also by functions lmin[nint] = nmin # number of minuit parameter e[nmin] = fitargs['error_'+par['name']] else: # functions, calculate as such # nint must be translated into nmin string = translate(nint,lmin,self.function) # stringe = string.replace('p','e') nfix = translate_nint(nint,lmin,self.function) stringa = '{}-{} = {}'.format(nfix,par['name'],value_error(eval(string),eval(stringe))) self.console(stringa,end='') # needed also by functions self.console('') return def on_alpha_changed(change): ''' observe response of fit tab widgets: validate float ''' string = change['owner'].value # description is three chars ('val','fun','flg') followed by an integer nint # iterable in range(ntot), total number of internal parameters try: float(string) # self.alphavalue = except: change['owner'].value = alpha0 # a string # def on_anim_check(change): # ''' # toggles multiplot and animations for suite fit # no effect on single run fit # ''' # if change['owner'].value: # change['owner'].value=False # else: # change['owner'].value=True def on_fit_request(b): ''' this is the entry to iminuit and fitplot (plot and log method) triggered by Fit Button retrieve data from the gui dashboard: self.alpha.value, range and pack with derange int2min obtains parameters values (parvalue[nint].value), flags (flag[nint].value), errors, limits, functions (function[nint].value) in the dictionary format used by iminuit wrapped inside a list, to allow suite fits of more runs pass _int, generated by int2_int. to mumodel._add_ (distribute minuit parameters) obtain fitargs dictionary, needed by migrad, either from self.fitargs or from min2int pass them to minuit call fit_plot save fit file in save_fit write summary in write_csv ''' from iminuit import Minuit as M from mujpy.aux.aux import derange, rebin, get_title, chi2std # from time import localtime, strftime ################### # error: no run yet ################### if not self._the_runs_: self.console('No run loaded yet! Load one first (select suite tab).') else: ################### # run loaded ################### self.asymmetry() # prepare asymmetry # self.time is 1D asymm, asyme can pack = 1 # initialize default returntup = derange(eval('self.fit_range.value'),self.histoLength) # self.console('values = {}'.format(returntup)) # debug if len(returntup)==3: # start, stop, pack = returntup elif len(returntup)==0: self.console('Empty ranges. Choose fit/plot range') else: start, stop = returntup time,asymm,asyme = rebin(self.time,self.asymm,[start,stop],pack,e=self.asyme) level = 0 # 0 (quiet),1,2 pedantic = True # False, True (warns about parameters value error/stepsize not initialized). self.fitargs = [] fitarg = int2min(return_names=True) # from dash, in v. 1.0 it is a list of dictionaries of (guess) values [{},{}], one per run] # self.console('start parameters:\n{}'.format(fitarg)) if self._global_: # this is not implemented in v. 1.0 self._the_model_._load_data_(time[0],asymm,int2_int(), float(self.alpha.value),e=asyme, _nglobal_=self.nglobals,_locals_=self.locals) # pass all data to model ############################## int2_int() returns a list of methods to calculate the components # actual global migrad call lastfit = M(self._the_model_._chisquare_, pedantic=pedantic, forced_parameters=self.minuit_parameter_names, errordef=M.LEAST_SQUARES, print_level=level,**fitarg[0]) # self.console('{} *****'.format([self.nrun[k] for k in range(len(self.nrun))])) lastfit.migrad() lastfit.hesse() self.fitargs.append(lastfit.fitarg) min2print(lastfit.fitarg) # lastfit[0].hesse() ############################## else: # this is implemented in v. 1.0 # self.console('_single is {}'.format(self._single_)) # debug if self._single_: # works fine self._the_model_._load_data_(time[0],asymm[0],int2_int(), float(self.alpha.value), e=asyme[0]) # pass data to model, one at a time ############################## int2_int() returns a list of methods to calculate the components # actual single migrad calls lastfit = M(self._the_model_._chisquare_, pedantic=pedantic, forced_parameters=self.minuit_parameter_names, errordef=M.LEAST_SQUARES, print_level=level,**fitarg[0]) lastfit.migrad() lastfit.hesse() if lastfit.migrad_ok(): self.fitargs.append(lastfit.fitarg) # for a single fit self.fitargs is a list of dicts nu = len(time[0]) - self.freepars # degrees of freedom in plot # self.freepars is calculated in int2min chi2 = lastfit.fval/nu # self.console('{}: {} ***** chi2 = {:.3f} ***** {}'.format(self.nrun[0],get_title(self._the_runs_[0][0]),chi2,strftime("%d %b %Y %H:%M:%S", localtime()))) min2print(lastfit.fitarg) lc,hc = chi2std(nu) pathfitfile = save_fit(0) # saves .fit file if pathfitfile.__len__ != 2: # assume pathfitfile is a path string, whose length will be definitely > 2 self.console('chi2r = {:.3f} ({:.3f} - {:.3f}) on {:.3f}-{:.3f} mus, saved in {}'.format(chi2,lc,hc,time[0][0],time[0][-1],pathfitfile)) else: # assume pathfitfile is a tuple containing (path, exception) self.console('Could not save results in {}, error: {}'.format(pathfitfile[0],pathfitfile[1])) write_csv(chi2fit,lc,hc,0) # writes csv file self.console('*****************************************************') else: self.console('Fit not converged') else: # suites migrad_ok = True for k in range(len(self._the_runs_)): # loop over runs self._the_model_._load_data_(time[0],asymm[k],int2_int(), float(self.alpha.value), e=asyme[k]) # pass data to model, one run at a time ############################## int2_int() returns a list of methods to calculate the components # actual single migrad calls # self.console('just before calling iminuit') lastfit = M(self._the_model_._chisquare_, pedantic=pedantic, forced_parameters=self.minuit_parameter_names, errordef=M.LEAST_SQUARES, print_level=level,**fitarg[k]) # self.console('start parameters:\n{}'.format(lastfit.values)) # debug # self.console('***********\nfitarg[{}] = {}'.format(k,lastfit.fitarg)) # self.console('values = {}, nmin = {}, free = {}'.format(lastfit.values, # lastfit.narg,lastfit.nfit)) lastfit.migrad() lastfit.hesse() if not lastfit.migrad_ok(): migrad_ok = False self.fitargs.append(lastfit.fitarg) # each run fit added as a list, in a list of lists nu = len(time[0]) - self.freepars # degrees of freedom in plot # self.freepars is calculated in int2min chi2 = lastfit.fval/nu # self.console('{}: {} ***** chi2 = {:.3f} ***** {}'.format(self.nrun[k],get_title(self._the_runs_[k][0]),chi2,strftime("%d %b %Y %H:%M:%S", localtime()))) if k==0: self.console('Fit on time interval ({:.3f},{:.3f}) mus'.format(time[0][0],time[0][-1])) min2print(lastfit.fitarg) lc,hc = chi2std(nu) pathfitfile = save_fit(k) # saves .fit file if pathfitfile.__len__ != 2: # assume pathfitfile is a path string, whose length will be definitely > 2 self.console('chi2r = {:.4f} ({:.4f} - {:.4f}), saved in {}'.format(chi2,lc,hc,pathfitfile)) else: # assume pathfitfile is a tuple containing (path, exception) self.console('Could not save results in {}, error: {}'.format(pathfitfile[0],pathfitfile[1])) write_csv(chi2,lc,hc,k) # writes csv file ############################## self.console('*****************************************************') if not migrad_ok: self.console('* CHECK: some fits did not converge!\n*****************************************************') fitplot() # plot the best fit results # string = 'Time last = {:.2e}, A[0] = {:.2f}, alpha = {:.3f}'.format(self._the_model_._x_[-1],self._themodel_._y_[0],self._the_model_._alpha_) # debug # self.console(string) def on_flag_changed(change): ''' observe response of fit tab widgets: set disabled on corresponding function (True if flag=='!' or '~', False if flag=='=') ''' dscr = change['owner'].description # description is three chars ('val','fun','flg') followed by an integer nint # iterable in range(ntot), total number of internal parameters n = int(dscr[4:]) # description='flag'+str(nint), skip 'flag' self.function[n].disabled=False if change['new']=='=' else True def on_function_changed(change): ''' observe response of fit tab widgets: check for validity of function syntax ''' from mujpy.aux.aux import muvalid dscr = change['owner'].description # description is three chars ('val','fun','flg') followed by an integer nint # iterable in range(ntot), total number of internal parameters n = int(dscr[4:]) # description='func'+str(nint), skip 'func' error_message = muvalid(change['new']) if error_message!='': self.function[n].value = '' self.console(error_message) def on_group_changed(change): ''' observe response of setup tab widgets: ''' from mujpy.aux.aux import get_grouping name = change['owner'].description groups = ['forward','backward'] # now parse groupcsv shorthand self.grouping[name] = get_grouping(self.group[groups.index(name)].value) # stores self.group shorthand in self.grouping dict, grouping is the python based address of the counters # self.console('Group {} -> grouping {}'.format(self.group[groups.index(name)].value, get_grouping(self.group[groups.index(name)].value))) # debug if self.grouping[name][0]==-1: self.console('Wrong group syntax: {}'.format(self.group[groups.index(name)].value)) self.group[groups.index(name)].value = '' self.grouping[name] = np.array([]) else: self.get_totals() def on_integer(change): name = change['owner'].description if name == 'offset': if self.offset.value<0: # must be positive self.offset.value = self.offset0 # standard value def on_load_model(change): ''' observe response of fit tab widgets: check that change['new'] is a valid model relaunch MuJPy.fit(change['new']) ''' if checkvalidmodel(change['new']): # empty list is False, non empty list is True try: del self._the_model_ self.fitargs=[] # so that plot understands that ther is no previous minimization model.value = loadmodel.value except: pass self.fit(change['new']) # restart the gui with a new model self.mainwindow.selected_index = 0 else: loadmodel.value='' def on_parvalue_changed(change): ''' observe response of fit tab widgets: check for validity of function syntax ''' dscr = change['owner'].description # description is three chars ('val','fun','flg') followed by an integer nint # iterable in range(ntot), total number of internal parameters n = int(dscr[5:]) # description='value'+str(nint), skip 'func' try: float(self.parvalue[n].value) self.parvalue[n].background_color = "white" except: self.parvalue[n].value = '0.0' self.parvalue[n].background_color = "mistyrose" def on_plot_par(change): ''' plot par wrapper ''' string = change.description plotpar(x=string[9]) # 'B' or 'T' def on_plot_request(b): ''' plot wrapper ''' if not guesscheck.value and not self._the_model_._alpha_: self.console('No best fit yet, to plot the guess function tick the checkbox') else: fitplot(guess=guesscheck.value,plot=True) # def on_range(change): ''' observe response of FIT, PLOT range widgets: check for validity of function syntax ''' from mujpy.aux.aux import derange fit_or_plot = change['owner'].description[0] # description is a long sentence starting with 'fit range' or 'plot range' if fit_or_plot=='f': name = 'fit' else: name = 'plot' returnedtup = derange(change['owner'].value,self.histoLength) # print('sum = {}'.format(sum(returnedtup))) if sum(returnedtup)<0: # errors return (-1,-1), good values are all positive # go back to defaults if name == 'fit': self.fit_range.value = '0,'+str(self.histoLength) self.fit_range.background_color = "mistyrose" else: self.plot_range.value = self.plot_range0 self.plot_range.background_color = "mistyrose" else: if name == 'fit': self.fit_range.background_color = "white" if len(returnedtup)==5: if returnedtup[4]>self.histoLength: change['owner'].value=str(returnedtup[:-1],self.histoLength) if returnedtup[1]>self.histoLength: change['owner'].value=str(returnedtup[0],self.histoLength) if len(returnedtup)==2 else str(returnedtup[0],self.histoLength,returnedtup[2:]) else: self.plot_range.background_color = "white" if returnedtup[1]>self.histoLength: change['owner'].value=str(returnedtup[0],self.histoLength) if len(returnedtup)==2 else str(returnedtup[0],self.histoLength,returnedtup[2]) def on_start_stop(change): ''' toggle start stop animation of suite fits ''' if anim_check.value and not self._single_: if change['owner'].value: self.anim_fit.event_source.start() else: self.anim_fit.event_source.stop() def on_update(b): ''' update parvalue[k].value with last best fit results ''' if self.fitargs: parvalue = min2int(self.fitargs[0]) # best fit parameters (strings) for k in range(len(_parvalue)): self.parvalue[k].value = parvalue[k] def print_nint(self): ''' debug, delete me ''' print(nint) def plotpar(x='X'): ''' plots parameters that have plotflag set to an integer 0<n<7 x is 'T' or 'B' ''' from mujpy.aux.aux import plot_parameters, component, get_title import os from numpy import array import matplotlib.pyplot as P min2csv = lambda k: 2*k+6 # min2csv( kmin) returns the parameter column in the csv file # matplotlib initialization will provide same color for same component colors = P.rcParams['axes.prop_cycle'].by_key()['color'] markers = ['o','d','^','V'] nsubplots, lmin, lcomp, kminplot = [], [], [], [] # initialize service lists ylabels = [1]*6 # max number of labels kmin = -1 # first loop, the second will be on # parameters with plotflag, find them from dash with kint iterator # kmin is their minuit index # if plotflag <0 or excees 6 warning on console for kint in range(nint+1): # start from the dashboard parameters # discover which component it is (for color coherence) kcomp = component(self.model_components,kint) # aux method returns index of component # that kint belongs to if self.flag[kint]!='=': # this is a minuit parameter kmin += 1 # count again minuit parameters (all but '=') lmin.append(kint) # stored replica for finding kint from kmin if self.plotflag[kint].value>0 and self.plotflag[kint].value<=6: # legal subplot (this is also a syntax checker). # hence this minuit parameter must be plotted kminplot.append(kmin) # store which kmin (for a plotted parameters iteration) nsub = self.plotflag[kint].value - 1 # which axis pythonized nsubplots.append(nsub) # store in which axis, pythonic lcomp.append(kcomp) # to remember which component kmin belongs to if not isinstance(ylabels[nsub],str): # skip if ylabel already allocated ylabels[nsub] = self.parname[kint].value[:-1].capitalize() # name without A,B,C,... # ylabel: minuit pars, parname: dashboard parameters if ylabels[nsub][-4:]=='rate': ylabels[nsub]+=' [mus-1]' if ylabels[nsub][-4:]=='hase': ylabels[nsub]+=' [deg]' if ylabels[nsub][-4:]=='ield': ylabels[nsub]+=' [mT]' elif self.plotflag[k].value<0 or self.plotflag[k].value>6: # excluded, issue Warning self.console('Warning: Parameter {} plot flag, {}, must be 0<=n<=6'.format (self.parname[k].value,self.plotflag[k].value)) if nsubplots: nsubs = int(array(nsubplots).max())+1 # effective subplots (nsubplots is 3 for flag 4) else: self.console('No plots requested: get fit results and select Panels') return nmin = kmin +1 # number of minuit parameters # create dictionary labels, for plot_parameters (organizing subplots layout) # the sample title # the xlabel # the ylabels from self.parname Ăą if x=='T': xlab = x+' [K]' title = get_title(self._the_runs_[0][0],notemp=True) elif x=='B': xlab = x+' [mT]' title = get_title(self._the_runs_[0][0],nofield=True) else: xlab = 'index' title = get_title(self._the_runs_[0][0]) labels ={'xlabel':xlab, 'title':title} labels['ylabels'] = ylabels # list of yaxis labels try: self.fig_pars.clf() self.fig_pars, ax = plot_parameters(nsubs,labels,self.fig_pars) except: self.fig_pars, ax = plot_parameters(nsubs,labels) # default fig=None # # conversion from dashboard index kint (the first box in the dash) to minuit index kmin # # (which governs the csv output, with 6 initial places, run T1 eT1 T2 eT2 B) # # the parameter in the csv is at position kp = 5 + 2*kmin+1 (6, 8, ... pythonic!) # kpec = int2csv(lmin) # list of lists e.g. mgmg with common field and phases # # [[6,7,0],[8,9,0],[10,11,0]],[12,13,0], # # [14,15,1],[16,17,1],[18,19,1],[20,21,1]] # kpar, kepar, kcomp = # retrieve minuit parameters value and error list from .csv # path_csv is as in write_csv version = str(self.version.value) strgrp = self.group[0].value.replace(',','_')+'-'+self.group[1].value.replace(',','_') path_csv = os.path.join(self.paths[2].value,model.value+'.'+version+'.'+strgrp+'.csv') p = np.genfromtxt(path_csv,comments='R') tb = p[:,1] if x=='T' else p[:,5] if x=='B' else np.arange(p.shape[0]) etb = p[:,2] if x=='T' else np.zeros(p.shape[0]) # nsubplots and lint are those to be plotted, but we must translate to minuit parameters by int2csv for kmp in range(len(kminplot)): # do plotted parameters scan kmin = kminplot[kmp] # which minuit parameter is this kcsv = min2csv(kmin) # column of minuit par in p kcomp = lcomp[kmp] # component of kmin parameter l = nsubplots[kmp] # plotflags are not pythonic (start from 1) ax[l].errorbar(tb,p[:,kcsv],yerr=p[:,kcsv+1],xerr=etb,fmt=markers[kcomp],color=colors[kcomp]) if nsubs==5: ax[5].axis('off') # the last of 6 for l in range(nsubs): if ax[l].get_ylabel()=='1': ax[l].axis('off') ym,yM = ax[l].get_ylim() if ym>0: ax[l].set_ylim(0.,yM) if nsubs==2: if ax[0].axison: P.setp(ax[0].get_yticklabels()[0], visible=False) elif nsubs==3: if ax[0].axison: P.setp(ax[0].get_yticklabels()[0], visible=False) if ax[1].axison: P.setp(ax[1].get_yticklabels()[0], visible=False) elif nsubs==4: if ax[0].axison: P.setp(ax[0].get_yticklabels()[0], visible=False) if ax[2].axison: P.setp(ax[2].get_yticklabels()[0], visible=False) else: # 6 if ax[0].axison: P.setp(ax[0].get_yticklabels()[0], visible=False) if ax[1].axison: P.setp(ax[1].get_yticklabels()[0], visible=False) if ax[3].axison: P.setp(ax[3].get_yticklabels()[0], visible=False) if ax[4].axison: P.setp(ax[4].get_yticklabels()[0], visible=False) self.fig_pars.canvas.manager.window.tkraise() P.draw() def save_fit(k): ''' saves fit values such that load_fit can reproduce the same fit includes fit of suite of runs and global fits ''' import dill as pickle import os version = str(self.version.value) fittype = '' # single run fit if self._global_: # global fit of run suite fittype = '.G.' elif not self._single_: # sequential fit of run suite fyttype = '.S.' strgrp = self.group[0].value.replace(',','_')+'-'+self.group[1].value.replace(',','_') path_fit = os.path.join(self.paths[2].value,model.value+'.'+version+fittype+'.'+str(self.nrun[k])+'.'+strgrp+'.fit') # create dictionary setup_dict to be pickled # the inclusion of self.load_handle will reload the data upon load_fit (?) names = ['self.alpha.value','self.offset.value', 'self.grouping','model.value', 'self.model_components','self.load_handle.value', 'version','nint', 'self.fit_range.value','self.plot_range.value', 'self.fitargs','self._global_','self._single_'] # keys fit_dict = {} for k,key in enumerate(names): fit_dict[names[k]] = eval(key) # key:value _parvalue = min2int(self.fitargs[0]) # starting values from first bestfit for k in range(nint+1): fit_dict['_parvalue['+str(k)+']'] = _parvalue[k] # either fit or dashboard fit_dict['_flag['+str(k)+ ']'] = self.flag[k].value # from fit tab fit_dict['_function['+str(k)+']'] = self.function[k].value # from fit tab with open(path_fit,'wb') as f: try: # print ('dictionary to be saved: fit_dict = {}'.format(fit_dict)) pickle.dump(fit_dict, f) except Exception as e: return path_fit, e return path_fit def set_group(): """ return shorthand csv out of grouping name = 'forward' or 'backward' grouping[name] is an np.array wth counter indices group.value[k] for k=0,1 is a shorthand csv like '1:3,5' or '1,3,5' etc. """ import numpy as np # two shorthands: either a list, comma separated, such as 1,3,5,6 # or a pair of integers, separated by a colon, such as 1:3 = 1,2,3 # only one column is allowed, but 1, 3, 5 , 7:9 = 1, 3, 5, 7, 8, 9 # or 1:3,5,7 = 1,2,3,5,7 are also valid # get the shorthand from the gui Text groups = ['forward','backward'] for k, name in enumerate(groups): s = '' aux = np.split(self.grouping[name],np.where(np.diff(self.grouping[name]) != 1)[0]+1) # this finds the gaps in the array, # e.g. 1,2,0,3 yields [array([1,2]),array([0]),array([3])] for j in aux: s += str(j[0]+1) # e.g '2', convention is from 1, python is from 0 if len(j)>1: s += ':'+str(j[-1]+1) # e.g. '2:3' s += ',' s = s[:-1] # remove last ',' self.group[k].value = s def write_csv(chi2,lowchi2,hichi2,k): ''' writes a csv-like file of best fit parameters that can be imported by qtiplot or read by python to produce figures:: refactored for adding runs and for writing one line per run in run suite, both local and global do not use csv, in order to control format (precision) ''' import os from mujpy.aux.aux import get_title, spec_prec from time import localtime, strftime # print('k = {}, self.nrun = {}'.format(k,[j for j in self.nrun])) version = str(self.version.value) strgrp = self.group[0].value.replace(',','_')+'-'+self.group[1].value.replace(',','_') path_csv = os.path.join(self.paths[2].value,model.value+'.'+version+'.'+strgrp+'.csv') TsTc, eTsTc = self._the_runs_[k][0].get_temperatures_vector(), self._the_runs_[k][0].get_devTemperatures_vector() Bstr = self._the_runs_[k][0].get_field() n1,n2 = spec_prec(eTsTc[0]),spec_prec(eTsTc[1]) # calculates format specifier precision form = '{} {:.' form += '{}'.format(n1) form += 'f} {:.' form += '{}'.format(n1) form += 'f} {:.' form += '{}'.format(n2) form += 'f} {:.' form += '{}'.format(n2) form += 'f} {}' #".format(value,most_significant)' # string = "{} {:.1f} {:.1f} {:.1f} {:.1f} {}".format(self.nrun[k], TsTc[0],eTsTc[0],TsTc[1],eTsTc[1], Bstr[:Bstr.find('G')]) #debug # self.console(string) # debug # self.console(form) #debug row = form.format(self.nrun[k], TsTc[0],eTsTc[0],TsTc[1],eTsTc[1], Bstr[:Bstr.find('G')]) for name in self.minuit_parameter_names: value, error = self.fitargs[k][name], self.fitargs[k]['error_'+name] n1 = spec_prec(error) # calculates format specifier precision form = ' {:.' form += '{}'.format(n1) form += 'f} {:.' form += '{}'.format(n1) form += 'f}' row += form.format(value,error) echi = max(chi2-lowchi2,hichi2-chi2) n1 = spec_prec(echi) # calculates format specifier precision form = ' {:.' form += '{}'.format(n1) form += 'f} {:.' form += '{}'.format(n1) form += 'f} {:.' form += '{}'.format(n1) form += 'f} {} {}' row += form.format(chi2,chi2-lowchi2,hichi2-chi2,self.alpha.value,self.offset.value) row += ' {}'.format(strftime("%d %b %Y %H:%M:%S", localtime())) form =' {} {:.2f}' for j in range(len(self.nt0)): row += form.format(self.nt0[j],self.dt0[j]) row += '\n' # row is fromatted with appropriate rounding, write directly # self.console(row) header = ['Run','T_cryo[K]','e_T_cryo[K]','T_sample[K]','e_T_sample[K]','B[G]'] for j,name in enumerate(self.minuit_parameter_names): header.append(name) header.append('e_'+name) header.append('chi2_r') header.append('e_chi2_low') header.append('e_chi2_hi') header.append('alpha') header.append('offset time') for j in range(len(self.nt0)): header.append('nt0{}'.format(j)) header.append('dt0{}'.format(j)) header = ' '.join(header)+'\n' try: # the file exists lineout = [] # is equivalent to False with open(path_csv,'r') as f_in: notexistent = True for nline,line in enumerate(f_in.readlines()): if nline==0: if header!=line: # different headers break lineout = [header] elif int(line.split(" ")[0]) < self.nrun[k]: lineout.append(line) elif int(line.split(" ")[0]) == self.nrun[k]: lineout.append(row) # substitute an existing fit notexistent = False else: if notexistent: lineout.append(row) # insert before last existing fit notexistent = False lineout.append(line) if notexistent: lineout.append(row) # append at the end notexistent = False if not lineout: raise # if headers were different this is an exception with open(path_csv,'w') as f_out: for line in lineout: f_out.write(line) self.console('Run {}: {} *** {}\nbest fit logged in {}'.format(self.nrun[k],get_title(self._the_runs_[k][0]),strftime("%d %b %Y %H:%M:%S", localtime()),path_csv)) except: # write a new file with open(path_csv,'w') as f: f.write(header) f.write(row) self.console('Run {}: {} *** {}\nbest fit logged in NEW {}'.format(self.nrun[k],get_title(self._the_runs_[k][0]),strftime("%d %b %Y %H:%M:%S", localtime()),path_csv)) ######### here starts the fit method of MuGui # no need to observe parvalue, since their value is a perfect storage point for the latest value # validity check before calling fit from ipywidgets import Text, IntText, Layout, Button, HBox, \ Checkbox, VBox, Dropdown, ToggleButton, Label _available_components_() # creates tuple self.available_components automagically from mucomponents self._the_model_ = mumodel() # local instance, need a new one each time a fit tab is reloaded (on_load_model) #------------------------- oneframe fit_button = Button(description='Fit', tooltip='Execute the Minuit fit', layout=Layout(width='15%')) # 15% fit_button.style.button_color = self.button_color fit_button.on_click(on_fit_request) loadmodel = Text(description='model', description_tooltip='Loads the model template\nspecified on the right\n(2 letter-codes,\ne.g. daml, for da + ml, see About)\n on pressing Enter', value='', layout=Layout(width='12%'),continuous_update=False) # this is where one can input a new model name # 27% loadmodel.observe(on_load_model,'value') loadmodel.style.description_width='37%' model = Text(value=model_in) # This is shown in the mainwindow, but is used to store present model # group[0] in oneframe , group[1] really ends up in in twopframe self.group = [Text(description='forward', description_tooltip='list here all detectors\nin the forward group\ne.g. 1, 3, 5 , 7:9\nonly one : allowed', layout=Layout(width='16%'), continuous_update=False), # 43% Text(description='backward', description_tooltip='list here all detectors\nin the backward group\ne.g. 2, 4, 6 , 10:12\nonly one : allowed', layout=Layout(width='16%'), continuous_update=False)] # group and grouping: csv shorthand set_group() # inserts shorthand from self.grouping into seld.group[k].value, k=0,1 self.group[0].observe(on_group_changed,'value') self.group[0].style.description_width='38%' self.group[1].observe(on_group_changed,'value') self.group[1].style.description_width='38%' # self.alpha.value is Text and self.alfavalue is its float try: alpha0 = self.alpha.value except: alpha0 = '1.01' # generic initial value # self.alphavalue=float(alpha0) self.alpha = Text(description='alpha', description_tooltip='N_forward/N_backward\nfor the chosen grouping', value=alpha0, layout=Layout(width='10%'), continuous_update=False) # self.alpha.value # 53% self.alpha.observe(on_alpha_changed,'value') self.alpha.style.description_width='40%' try: fit_range0 = self.fit_range.value except: if not self._the_runs_: fit_range0 = '' # will be fixed at first data load self.fit_range = Text(description='fit range', description_tooltip='start,stop[,pack]\ne.g. 0,20000,10\n(start from first good bin (see offset)\nuse 20000 bins\n pack them 10 by 10', value=fit_range0, layout=Layout(width='22%'), continuous_update=False) # 75% self.fit_range.style.description_width='30%' self.fit_range.observe(on_range,'value') guesscheck = Checkbox(description='guess', description_tooltip='Tick to plot\nstarting guess function', value=False, layout=Layout(width='8%')) # 83% guesscheck.style.description_width='2%' anim_delay = IntText(value=1000, description='Delay (ms)', description_tooltip='between successive runs\n(animation frames)', layout=Layout(width='15%')) # 98% #------------------------------- twoframe loadbutton = Button(description='Load fit', tooltip='Opens GUI to load one of the existing fit templates', layout=Layout(width='7.3%')) # 7.3% loadbutton.style.button_color = self.button_color loadbutton.on_click(load_fit) update_button = Button (description='Update', tooltip='Update parameter starting guess\nfrom latest fit\n(must have fitted this model once).', layout=Layout(width='7.3%')) # 15% update_button.style.button_color = self.button_color update_button.on_click(on_update) try: version0 = self.version.value except: version0 = '1' self.version = Text(description='version', description_tooltip='String to distinguish among model output files', value=version0, layout=Layout(width='12%'))#,indent=False)) # version.value is an int # 27% self.version.style.description_width='40%' # group[1] really ends up here (width=16%) # 43% try: self.offset0 = self.offset.value except: self.offset0 = 7 # generic initial value self.offset = IntText(description='offset', description_tooltip='First good bin\n(number of bins skipped\nafter prompt peak)', value=self.offset0, layout=Layout(width='10%'), continuous_update=False) # offset, is an integer # 53% self.offset.style.description_width='38%' # initialized to 7, only input is from an IntText, integer value, or saved and reloaded from mujpy_setup.pkl try: self.plot_range0 = self.plot_range.value except: if not self._the_runs_: self.plot_range0 = '' self.plot_range = Text(description='plot range', description_tooltp='start,stop[,pack][,last,pack]\n0,20000,10 see fit range\n0,2000,10,20000,100 pack 10 up to bin 2000\npCK 100 from bin 2001 to bin 20000', value=self.plot_range0, layout=Layout(width='22%'), continuous_update=False) # 75% self.plot_range.style.description_width='30%' self.plot_range.observe(on_range,'value') plot_button = Button (description='Plot', tooltip='Generate a plot\n(see guess tooltip)', layout=Layout(width='7.6%')) # 82.6% plot_button.style.button_color = self.button_color plot_button.on_click(on_plot_request) anim_check = Checkbox(description='Anim', description_tooltip='Activate animation with Play button\ntoggle on/off movie with Loop button\nregulate frame Delay.', value=False, layout=Layout(width='7.6%')) # 92.2% # anim_check.observe(on_anim_check) anim_check.style.description_width='2%' anim_start = ToggleButton(description='off/on', tooltip='stop/start movie of fit plots\nfor run suite (see Run:)\nwith frame Delay\nwhen Anim is ticked.', value = False, layout=Layout(width='7.6%')) # 99.8% anim_start.observe(on_start_stop) # anim_start_value = False # anim_start.style.button_color = self.button_color #-------------------- create the model template # arrive here in three ways: model_in='' at start; model typed in loadmodel text box, checked by checkvalidmodel; # load_fit if model_in == '': create_model('daml') model.value = 'daml' # this sets the initial default model else: create_model(model_in) # this should always be a valid model, it is harwdired, model_in = 'daml'; from now on loadmodel is used and checked #-------------------- fill model template into input widgets, two columns # # 12345678901234567890123456789012345678901234567890123456789012345678901234567890 s_n,s_nam,s_val,s_flag,s_func,s_plot ='Par n','Name','Value','Fit flag','Function','Panel' dashhead = HBox([Label(s_n,layout={'width':'8%','height':'16pt'},description_tooltip='Number to be used in Functions.'), Label(s_nam,layout={'width':'18%','height':'16pt'}), Label(s_val,layout={'width':'15%','height':'16pt'},description_tooltip='initial guess.'), Label(s_flag,layout={'width':'11%','height':'16pt'},description_tooltip='~ is free\n! is fixed,= activates Function.'), Label(s_func,layout={'width':'36%','height':'16pt'},description_tooltip='P[0] replaces by par 0 (quote only previous num).\nSimple algebra is allowed, e.g.\n0.5*P[0]+0.5*P[4].'), Label(s_plot,layout={'width':'9%','height':'16pt'},description_tooltip='multipanel plot. \nChoose panel\nUse 1<=n<=6\nPars can share a panel.'), ]) leftframe_list, rightframe_list = [],[] words = ['#','name','value','~!=','function'] nint = -1 # internal parameter count, each widget its unique name ntot = np.array([len(self.model_components[k]['pars']) for k in range(len(self.model_components))]).sum() self.parname, self.parvalue, self.flag, self.function, self.plotflag = [] , [], [], [], [] # lists, index runs according to internal parameter count nint self.compar = {} # dictionary: key nint corresponds to a list of two values, c (int index of component) and p (int index of parameter) # use: self.compar[nint] is a list of two integers, the component index k and its parameter index j self.fftcheck = [] nleft,nright = 0,0 for k in range(len(self.model_components)): # scan the model self.fftcheck.append(Checkbox(description='FFT', description_tooltip='uncheck for showing this component\nin the FFT of residues', value=True, layout=Layout(width='16%'))) self.fftcheck[k].style.description_width='2%' header = HBox([ Text(value=self.model_components[k]['name'], disabled=True, layout=Layout(width='8%')), # 8 % self.fftcheck[k]]) # component header HBox # 24 % # composed of the name (e.g. 'da') and the FFT flag # fft will be applied to a 'residue' where only checked components # are subtracted if k%2==0: # and ... leftframe_list.append(header) # append it to the left if k even if k==0: leftframe_list.append(dashhead) else: rightframe_list.append(header) # or to the right if k odd if k==1: rightframe_list.append(dashhead) # list of HBoxes, headers and pars nleftorright = 0 for j in range(len(self.model_components[k]['pars'])): # make a new par for each parameter # and append it to component_frame_content nint += 1 # all parameters are internal parameters, first is pythonically zero nleftorright += 1 self.compar.update({nint:[k,j]}) # stores the correspondence between nint and component,parameter nintlabel_handle = Text(value=str(nint), layout=Layout(width='7%'), disabled=True) # 7% name = self.model_components[k]['pars'][j]['name'] baloon = '' if 'field' in name: baloon = '[mT]' elif 'rate' in name: baloon = '[mus-1]' elif 'phase' in name: baloon = '[deg]' self.parname.append( Text(value=name, description_tooltip=baloon, layout=Layout(width='16%'), disabled=True)) # 23% # parname can be overwritten, not important to store self.parvalue.append( Text(value='{:.4}'.format(self.model_components[k]['pars'][j]['value']), layout=Layout(width='15%'), description='value'+str(nint), continuous_update=False)) # 38% self.parvalue[nint].style.description_width='0%' try: self.parvalue[nint].value = _parvalue[nint] except: pass # parvalue handle must be unique and stored at position nint, it will provide the initial guess for the fit self.flag.append(Dropdown(options=['~','!','='], value=self.model_components[k]['pars'][j]['flag'], layout=Layout(width='11%'), description='flag'+str(nint))) # 49% self.flag[nint].style.description_width='0%' try: self.flag[nint].value = _flag[nint] except: pass # flag handle must be unique and stored at position nint, it will provide (eventually) the nonlinear relation to be evaluated self.function.append(Text(value=self.model_components[k]['pars'][j]['function'], layout=Layout(width='36%'), description='func'+str(nint), description_tooltip='multipanel plot. \nChoose panel\nUse 1<=n<=6\nPars can share a panel.', continuous_update=False)) # 85% self.function[nint].style.description_width='0%' self.plotflag.append(IntText(value=0, layout=Layout(width='9%'))) # 100% # self.plotflag[k].value is the subplots axis try: self.function[nint].value = _function[nint] except: pass # function handle must be unique and stored at position nint, it will provide (eventually) the nonlinear relation fdis = False if self.model_components[k]['pars'][j]['flag']=='=' else True self.function[nint].disabled = fdis # enabled only if flag='=' # now put this set of parameter widgets for the new parameter inside a parameter HBox par_handle = HBox([nintlabel_handle, self.parname[nint], self.parvalue[nint], self.flag[nint], self.function[nint],self.plotflag[nint]],layout=Layout(width='100%')) # handle to an HBox of a list of handles; notice that parvalue, flag and function are lists of handles # now make value flag and function active self.parvalue[nint].observe(on_parvalue_changed,'value') self.flag[nint].observe(on_flag_changed,'value') # when flag[nint] is modified, function[nint] is z(de)activated self.function[nint].observe(on_function_changed,'value') # when function[nint] is modified, it is validated if k%2==0: # and ... leftframe_list.append(par_handle) # append it to the left if k even nleft += nleftorright else: rightframe_list.append(par_handle) # or to the right if k odd nright += nleftorright if model_in == '': # at startup, model daml, 0 da, 1 A ml, 2 B ml, 3 ph ml, 4 lam ml self.parvalue[1].value = '0.2' # asymmetry self.parvalue[2].value = '3.0' # mT self.parvalue[4].value = '0.2' # mus-1 PT = Button(description='Plot vs. T', tooltip='First perform suite vs. T fits.\nThis creates a csv file in the log folder\nSelect panel for pars you want plotted.', layout=Layout(width='24%')) PT.on_click(on_plot_par) PT.style.button_color = self.button_color PB = Button(description='Plot vs. B', tooltip='First perform suite vs. B fits\nThis creates a csv file in the log folder\nSelect panel for pars you want plotted.', layout=Layout(width='24%')) PB.on_click(on_plot_par) PB.style.button_color = self.button_color PTB = VBox([Label('Show parameter plots',layout={'width':'34%'}), HBox([Label(layout={'width':'24%'}),PT,PB],layout=Layout(width='100%'))], layout={'border':'2px solid dodgerblue','align_items':'stretch'}) if nleft<=nright: leftframe_list.append(PTB) else: rightframe_list.append(PTB) width = '99.8%' widthhalf = '100%' leftframe_handle = VBox(layout=Layout(width=widthhalf), children=leftframe_list)#,layout=Layout(width='100%') rightframe_handle = VBox(layout=Layout(width=widthhalf), children=rightframe_list)# ,layout=Layout(width='100%') #------------------ include frames in boxes oneframe_handle = HBox(layout=Layout(width=width), children=[fit_button,loadmodel,self.group[0], self.alpha,self.fit_range,guesscheck,anim_delay]) twoframe_handle = HBox(layout=Layout(width=width), children=[loadbutton,update_button,self.version, self.group[1],self.offset,self.plot_range,plot_button,anim_check,anim_start]) bottomframe_handle = HBox(layout=Layout(width=width,border='1px solid dodgerblue'), children=[leftframe_handle,rightframe_handle]) # end of model scan, ad two vertical component boxes to the bottom frame # backdoors self._load_fit = load_fit self._fit = fitplot self._int2_int = int2_int # now collect the handles of the three horizontal frames to the main fit window self.mainwindow.children[0].children = [VBox([oneframe_handle, twoframe_handle, bottomframe_handle],layout=Layout(width=width))]# # WHY DOES THE BOX EXTEND TO '100%' ? # add the list of widget handles as the third tab, fit self.mainwindow.children[0].layout = Layout(border = '2px solid dodgerblue',width='100%')
########################## # FFT ##########################
[docs] def fft(self): ''' fft tab of mugui. on_fft_request(b) performs fft and plot (WARNING) * two options: (partial) residues or full asymmetry * two modes: real amplitude or power * vectorized: range(len(self.fitargs)) is (0,1) or (0,n>1) for single or suite ''' def on_fft_request(b): ''' perform fft and plot * two options: (partial) residues or full asymmetry * two modes: real amplitude or power * vectorized: range(len(self.fitargs)) is (0,1) or (0,n>1) for single or suite WARNING: relies on self._the_model_._add_ or self._the_model_._fft_add_ to produce the right function for each run (never checked yet) insert expected noise level (see bottom comment) ''' import numpy as np from mujpy.aux.aux import derange, autops, ps, _ps_acme_score, _ps_peak_minima_score, plotile, get_title from copy import deepcopy import matplotlib.pyplot as P from matplotlib.path import Path import matplotlib.patches as patches import matplotlib.animation as animation ################### # PYPLOT ANIMATIONS ################### def animate(i): ''' anim function update fft data, fit fft and their color ''' # color = next(ax_fft._get_lines.prop_cycler)['color'] self.ax_fft.set_title(str(self._the_runs_[i][0].get_runNumber_int())+': '+get_title(self._the_runs_[i][0])) marks.set_ydata(ap[i]) marks.set_color(color[i]) line.set_ydata(apf[i]) line.set_color(color[i]) top = fft_e[i] errs.set_facecolor(color[i]) return line, marks, errs, def init(): ''' anim init function blitting (see wikipedia) to give a clean slate ''' self.ax_fft.set_title(str(self._the_runs_[0][0].get_runNumber_int())+': '+get_title(self._the_runs_[0][0])) marks.set_ydata(ap[0]) marks.set_color(color[0]) line.set_ydata(apf[0]) line.set_color(color[0]) top = fft_e[0] errs.set_facecolor(color[0]) return line, marks, errs, def fft_std(): ''' Returns fft_e, array, one fft std per bin value per run index k using time std ey[k] and filter filter_apo. The data slice is equivalent (not equal!) to * y[k] = yf[k] + ey[k]*np.random.randn(ey.shape[1]) It is composed of l data plus l zero padding (n=2*l). Here we deal only with the first l data bins (no padding). Assuming that the frequency noise is uniform, the f=0 value of the filtered fft(y) is * ap[k] = (y[k]*filter_apo).sum() and the j-th sample of the corresponding noise is * eapj[k] = ey[k]*np.random.randn(ey.shape[1])*filter_apo).sum() Repeat n times to average the variance, * eapvar[k] = [(eapj[k]**2 for j in range(n)] * fft_e = np.sqrt(eapvar.sum()/n) ''' n = 10 fft_e = np.empty(ey.shape[0]) for k in range(ey.shape[0]): eapvariance = [((ey[k]*np.random.randn(ey.shape[1])*filter_apo).sum())**2 for j in range(n)] fft_e[k] = np.sqrt(sum(eapvariance)/n) return fft_e # ON_FFT_REQUEST STARTS HERE ################################# # retrieve self._the_model_, pars, # fit_start,fit_stop=rangetup[0], rangetup[1] # with rangetup = derange(self.fit_range.value), if not self._the_model_._alpha_: # this is used as a check that an appropriate fit was performed # so we can assume that fit_range.value has already been checked self.console('No fit yet. Please first produce a fit attempt.') return if self._global_: print('not yet!') else: #################### # setup fft #################### dt = self.time[0,1]-self.time[0,0] fmax = 0.5/dt # max frequancy available rangetup = derange(self.fit_range.value,self.histoLength) # no checks, it has already been used in fit fit_start, fit_stop = int(rangetup[0]), int(rangetup[1]) # = self.time[fit_start]/dt, self.time[fit_stop]/dt # print('fit_start, fit_stop = {}, {}'.format(fit_start, fit_stop)) l = fit_stop-fit_start # dimension of data df = 1/(dt*l) n = 2*l # not a power of 2, but surely even filter_apo = np.exp(-(dt*np.linspace(0,l-1,l)*float(fft_filter.value))**3) # hypergaussian filter mask # is applied as if first good bin were t=0 filter_apo = filter_apo/sum(filter_apo)/dt # approx normalization # try hypergauss n=3, varying exponent dfa = 1/n/dt # digital frequency resolution ##################################################################################### # asymm, asyme and the model are a row arrays if _single_ and matrices if not _single_ ##################################################################################### ########################################## # zero padding, apodization [and residues] ########################################## y = np.zeros((self.asymm.shape[0],n)) # for data zero padded to n ey = np.zeros((self.asyme.shape[0],l)) # for errors, l bins, non zero padded yf = np.zeros((self.asymm.shape[0],n)) # for fit function zero padded to n for k in range(len(self.fitargs)): pars = [self.fitargs[k][name] for name in self.minuit_parameter_names] yf[k,0:l] = self._the_model_._add_(self.time[0,fit_start:fit_stop],*pars) # full fit zero padded, if residues_or_asymmetry.value == 'Residues': fft_include_components = [] fft_include_da = False for j,dic in enumerate(self.model_components): if dic['name']=='da' and self.fftcheck[j].value: fft_include_da = True # flag for "da is a component" and "include it" elif dic['name']!='da': # fft_include_components, besides da, True=include, False=do not fft_include_components.append(self.fftcheck[j].value) # from the gui FFT checkboxes self._the_model_._fft_init(fft_include_components,fft_include_da) # sets _the_model_ in fft # t = deepcopy(self.time[fit_start:fit_stop]) # print('self.time.shape = {}, t.shape = {}, range = {}'.format(self.time.shape,t.shape,fit_stop-fit_start)) for k in range(len(self.fitargs)): y[k,0:l] = self.asymm[k,fit_start:fit_stop] # zero padded data ey[k] = self.asyme[k,fit_start:fit_stop] # slice of time stds # print('yf.shape = {}, the_model.shape = {}'.format(yf[k,0:l].shape,t.shape)) ############################################ # if Residues # subtract selected fit components from data ############################################ if residues_or_asymmetry.value == 'Residues': # fft partial subtraction mode: only selected components are subtracted pars = [self.fitargs[k][name] for name in self.minuit_parameter_names] y[k,0:l] -= self._the_model_._add_(self.time[0,fit_start:fit_stop],*pars) y[k,0:l] *= filter_apo # zero padded, filtered data or residues yf[k,0:l] *= filter_apo # zero padded, filtered full fit function ################################################# # noise in the FFT: with scale=1 noise in n data bins, one gets sqrt(n/2) noise per fft bin, real and imag # generalising to scale=sigma noise in n bins -> sqrt(0.5*sum_i=1^n filter_i) ################################################# fft_e = fft_std() # array of fft standard deviations per bin for each run fft_amplitude = np.fft.fft(y) # amplitudes (complex), matrix with rows fft of each run fftf_amplitude = np.fft.fft(yf) # amplitudes (complex), same for fit function ################# # frequency array ################# nf = np.hstack((np.linspace(0,l,l+1,dtype=int), np.linspace(-l+1,-1,l-2,dtype=int))) f = nf*dfa # all frequencies, l+1 >=0 followed by l-1 <0 rangetup = derange(fft_range.value,fmax,int_or_float='float') # translate freq range into bins fstart, fstop = float(rangetup[0]), float(rangetup[1]) start, stop = int(round(fstart/dfa)), int(round(fstop/dfa)) f = deepcopy(f[start:stop]) # selected slice ######################## # build or recall Figure ######################## if self.fig_fft: # has been set to a handle once self.fig_fft.clf() self.fig_fft,self.ax_fft = P.subplots(num=self.fig_fft.number) else: # handle does not exist, make one self.fig_fft,self.ax_fft = P.subplots(figsize=(6,4)) self.fig_fft.canvas.set_window_title('FFT') self.ax_fft.set_xlabel('Frequency [MHz]') self.ax_fft.set_title(get_title(self._the_runs_[0][0])) xm, xM = f.min(),f.max() self.ax_fft.set_xlim(xm,xM) if real_or_power.value=='Real part': ######################## # REAL PART # APPLY PHASE CORRECTION # try acme ######################## fftf_amplitude[0][start:stop], p0, p1, out = autops(fftf_amplitude[0][start:stop],'acme') # fix phase on theory if self.prntps.value: self.console(out) fft_amplitude[0][start:stop] = ps(fft_amplitude[0][start:stop], p0=p0 , p1=p1).real # apply it to data for k in range(1,fft_amplitude.shape[0]): fft_amplitude[k][start:stop] = ps(fft_amplitude[k][start:stop], p0=p0 , p1=p1) fftf_amplitude[k][start:stop] = ps(fftf_amplitude[k][start:stop], p0=p0 , p1=p1) ap = deepcopy(fft_amplitude[:,start:stop].real) apf = deepcopy(fftf_amplitude[:,start:stop].real) label = 'Real part' else: ################## # POWER ################## ap = fft_amplitude.real[:,start:stop]**2+fft_amplitude.imag[:,start:stop]**2 apf = fftf_amplitude.real[:,start:stop]**2+fftf_amplitude.imag[:,start:stop]**2 label = 'Power' ######## # tile ######## if not anim_check.value or self._single_: # TILES: creates matrices for offset multiple plots foffset = 0 # frequency offset yoffset = 0.1*apf.max() # add offset to each row, a fraction of the function maximum f, ap, apf = plotile(f,xdim=ap.shape[0],offset=foffset),\ plotile(ap,offset=yoffset),\ plotile(apf,offset=yoffset) # f, ap, apf are (nrun,nbins) arrays ############# # animation ############# if anim_check.value and not self._single_: # a single cannot be animated ############## # initial plot ############## color = [] for k in range(ap.shape[0]): color.append(next(self.ax_fft._get_lines.prop_cycler)['color']) yM = 1.02*max(ap.max(),apf.max()) ym = min(0,1.02*ap.min(),1.02*apf.min()) line, = self.ax_fft.plot(f,apf[0],'-',lw=1,color=color[0],alpha=0.8) marks, = self.ax_fft.plot(f,ap[0],'o',ms=2,color=color[0],alpha=0.8) self.ax_fft.set_ylim(ym,yM) left, bottom, right, top = f[0],0.,f[-1],fft_e[0] verts = [ (left, bottom), # left, bottom (left, top), # left, top (right, top), # right, top (right, bottom), # right, bottom (0., 0.), # ignored ] codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY, ] path = Path(verts, codes) errs = patches.PathPatch(path, facecolor=color[0], lw=0, alpha=0.3) self.ax_fft.add_patch(errs) ####### # anim ####### self.anim_fft = animation.FuncAnimation(self.fig_fft, animate, np.arange(0,len(self.fitargs)), init_func=init, interval=anim_delay.value, blit=False) ############################### # single and tiles with offset ############################### else: # print('f.shape = {}, ap.shape = {}'.format(f.shape,ap.shape)) color = [] for k in range(ap.shape[0]): color.append(next(self.ax_fft._get_lines.prop_cycler)['color']) self.ax_fft.plot(f[k],ap[k],'o',ms=2,alpha=0.5,color=color[k]) # f, ap, apf are plotiled! self.ax_fft.plot(f[k],apf[k],'-',lw=1,alpha=0.5,color=color[k]) self.ax_fft.fill_between([f[0,0],f[0,-1]],[k*yoffset,k*yoffset],[k*yoffset+fft_e[k],k*yoffset+fft_e[k]],facecolor=color[k],alpha=0.2) ################### # errors, alpha_version for single ################### # if self._single_: self.ax_fft.relim(), self.ax_fft.autoscale_view() ym,yM = self.ax_fft.get_ylim() xm,xM = self.ax_fft.get_xlim() ytext = yM-(ap.shape[0]+1)*yoffset xtext = xM*0.90 for k in range(ap.shape[0]): ytext = ytext+yoffset self.ax_fft.text(xtext,ytext,str(self._the_runs_[k][0].get_runNumber_int()),color=color[k]) if residues_or_asymmetry.value == 'Residues': self.ax_fft.set_ylabel('FFT '+label+' (Residues/Fit)') self._the_model_._include_all_() # usual _the_model_ mode: all components included else: self.ax_fft.set_ylabel('FFT '+label+' (Asymmetry/Fit)') self.fig_fft.canvas.manager.window.tkraise() P.draw() def on_filter_changed(change): ''' observe response of fit tab widgets: validate float ''' string = change['owner'].value # description is three chars ('val','fun','flg') followed by an integer nint # iterable in range(ntot), total number of internal parameters try: float(string) except: change['owner'].value = '{:.4f}'.format(filter0) def on_range(change): ''' observe response of FFT range widgets: check for validity of function syntax ''' from mujpy.aux.aux import derange fmax = 0.5/(self.time[0,1]-self.time[0,0]) returnedtup = derange(fft_range.value,fmax,int_or_float='float') # errors return (-1,-1),(-1,0),(0,-1), good values are all positive if sum(returnedtup)<0: fft_range.background_color = "mistyrose" fft_range.value = fft_range0 else: fft_range.background_color = "white" def on_start_stop(change): if anim_check.value: if change['new']: self.anim_fft.event_source.start() else: self.anim_fft.event_source.stop() # begins fft gui import numpy as np from ipywidgets import HBox, VBox, Layout, Button, Label, Text, IntText, Dropdown, Checkbox, ToggleButton # must inherit/retrieve self._the_model_, pars, fit_range = range(fit_start,fit_stop) # layout a gui to further retrieve # fft_range (MHz), lb (us-1), real_amplitude (True/False) if False then power, autophase (True/False) # Layout gui fft_button = Button(description='Do FFT',layout=Layout(width='12%')) # 12% fft_button.style.button_color = self.button_color fft_button.on_click(on_fft_request) filter0 = 0.3 fft_filter = Text(description='Filter (mus-1)', value='{:.4f}'.format(filter0), layout=Layout(width='20%'), continuous_update=False) # self.filter.value # 32% fft_filter.observe(on_filter_changed,'value') fft_range0 = '0,50' fft_range = Text(description='fit range\nstart,stop\n (MHz)', value=fft_range0, layout=Layout(width='28%'), continuous_update=False) # 60% fft_range.style.description_width='60%' fft_range.observe(on_range,'value') real_or_power = Dropdown(options=['Real part','Power'], value='Real part', layout=Layout(width='12%')) # 72% residues_or_asymmetry = Dropdown(options=['Residues','Asymmetry'], value='Residues', layout=Layout(width='13%')) # 85% autophase = Checkbox(description='Autophase', value=True, layout=Layout(width='15%')) #100% autophase.style.description_width='10%' anim_check = Checkbox(description='Animate', value=False, layout=Layout(width='12%')) # 12% anim_check.style.description_width = '1%' anim_delay = IntText(description='Delay (ms)', value=1000, description_tooltip='between frames', layout=Layout(width='20%')) # 32% anim_stop_start = ToggleButton(description='start/stop', description_tooltip='toggle animation loop', value=True, layout=Layout(width='12%')) # 44% anim_stop_start.observe(on_start_stop,'value') empty = Label(layout=Layout(width='30%')) # 74% prntpslabel = Label('print autophase', description_tooltip='verbose fit', layout=Layout(width='10%')) # 92% self.prntps = Checkbox(description=' ', value=False, layout=Layout(width='12%')) # 100% fft_frame_handle = VBox(description='FFT_bar',children=[HBox(description='first_row',children=[fft_button, fft_filter, fft_range, real_or_power, residues_or_asymmetry, autophase]), HBox(description='second_row',children=[anim_check, anim_delay, anim_stop_start, empty, self.prntps, prntpslabel])]) # now collect the handles of the three horizontal frames to the main fit window self.mainwindow.children[1].children = [fft_frame_handle] # add the list of widget handles as the third tab, fit self.mainwindow.children[1].layout = Layout(border = '2px solid dodgerblue',width='100%')
########################## # GUI ##########################
[docs] def gui(self): ''' Main gui layout. Based on ipywidgets. Executed only once, it designs * an external frame * the logo and title header * the tab structure. Tabs mostly correspond to public mugui methods. Their nested methods cannot be documented by spynx so they are replicated under each public method At the end (Araba.Phoenix) the gui method redefines self.gui as a Vbox named 'whole', containing the entire gui structure Integrated with suite tab of mugui in v. 1.05 used to select: run (single/suite) load next previous, add next previous and to print: run number, title, total counts, group counts, ns/bin comment, start stop date, next run, last add ''' ########################## # SUITE ########################## def run_headers(k): ''' Stores and displays title, comments and histoLength only for master run Saves T, dT and returns 0 ''' import numpy as np from mujpy.aux.aux import get_title, value_error if k==0: try: dummy = self.nt0.sum() # fails if self.nt0 does not exist yet except: # if self.nt0 does not exist, guess from the first in self._the_runs_ self.nt0 = np.zeros(self._the_runs_[0][0].get_numberHisto_int(),dtype=int) self.dt0 = np.zeros(self._the_runs_[0][0].get_numberHisto_int(),dtype=float) for j in range(self._the_runs_[0][0].get_numberHisto_int()): self.nt0[j] = np.where(self._the_runs_[0][0].get_histo_array_int(j)== self._the_runs_[0][0].get_histo_array_int(j).max())[0][0] # self.nt0 exists self.title.value = get_title(self._the_runs_[0][0]) self.comment.value = self._the_runs_[0][0].get_comment() self.start_date.value = self._the_runs_[0][0].get_timeStart_vector() self.stop_date.value = self._the_runs_[0][0].get_timeStop_vector() self._the_runs_display.value = str(self.load_handle.value) # but if it is not compatible with present first run issue warning if len(self.nt0)!=self._the_runs_[0][0].get_numberHisto_int(): # reset nt0,dt0 self.nt0 = np.zeros(self._the_runs_[0][0].get_numberHisto_int(),dtype=int) self.dt0 = np.zeros(self._the_runs_[0][0].get_numberHisto_int(),dtype=float) for j in range(self._the_runs_[0][0].get_numberHisto_int()): self.nt0[j] = np.where(self._the_runs_[0][0].get_histo_array_int(j)== self._the_runs_[0][0].get_histo_array_int(j).max())[0][0] self.console('WARNING! Run {} mismatch in number of counters, rerun prompt fit'.format(self._the_runs_[0][0].get_runNumber_int())) # store max available bins on all histos self.histoLength = self._the_runs_[0][0].get_histoLength_bin() - self.nt0.max() - self.offset.value self.counternumber.value = ' {} counters per run'.format(self._the_runs_[0][0].get_numberHisto_int()) self.plot_range0 = '0,{},100'.format(self.histoLength) self.multiplot_range.value = self.plot_range0 if self.plot_range.value == '': self.plot_range.value = self.plot_range0 if self.fit_range.value == '': self.fit_range.value = self.plot_range0 npk = float(self.nt0.sum())/float(self.nt0.shape[0]) self.bin_range0 = '{},{}'.format(int(0.9*npk),int(1.1*npk)) self.counterplot_range.value = self.bin_range0 else: # k > 0 self._single_ = False ok = [self._the_runs_[k][0].get_numberHisto_int() == self._the_runs_[0][0].get_numberHisto_int(), self._the_runs_[k][0].get_binWidth_ns() == self._the_runs_[0][0].get_binWidth_ns()] if not all(ok): self._the_runs_=[self._the_runs_[0]] # leave just the first one self.console('\nFile {} has wrong histoNumber or binWidth'.format(path_and_filename)) return -1 # this leaves the first run of the suite TdT = value_error(*t_value_error(k)) self.tlog_accordion.children[0].value += '{}: '.format(self._the_runs_[k][0].get_runNumber_int())+TdT+' K\n' # print('3-run_headers') return 0 def check_next(): ''' Checks if next run file exists ''' import os from mujpy.aux.aux import muzeropad runstr = str(self.nrun[0] +1) if len(runstr)>4: self.console('Too long run number!') next_label.value = '' else: filename = '' filename = filename.join([self.filespecs[0].value, muzeropad(runstr), '.',self.filespecs[1].value]) # data path + filespec + padded run rumber + extension) next_label.value = runstr if os.path.exists(os.path.join(self.paths[0].value,filename)) else '' def check_runs(k): ''' Checks nt0, etc. Returns -1 with warnings for severe incompatibility Otherwise calls run_headers to store and display title, comments, T, dT, histoLength [,self._single] ''' from copy import deepcopy from dateutil.parser import parse as dparse import datetime if self.nt0_run: # either freshly produced or loaded from load_setup nt0_experiment = deepcopy(self.nt0_run) # needed to preserve the original from the pops nt0_experiment.pop('nrun') nt0_days = dparse(nt0_experiment.pop('date')) try: this_experiment = self.create_rundict(k) # disposable, no deepcopy, for len(runadd)>1 check they are all compatible # print('check - {}'.format(self._the_runs_[k][0].get_runNumber_int())) rn = this_experiment.pop('nrun') # if there was an error with files to add in create_rundict this will fail except: self.console('\nRun {} not added. Non existent or incompatible'.format(this_experiment('errmsg'))) return -1 # this leaves the previous loaded runs n the suite this_date = this_experiment.pop('date') # no errors with add, pop date then dday = abs((dparse(this_date)-nt0_days).total_seconds()) if nt0_experiment != this_experiment or abs(dday) > datetime.timedelta(7,0).total_seconds(): # runs must have same binwidth etc. and must be within a week self.console('Warning: mismatch in histo length/time bin/instrument/date\nConsider refitting prompt peaks (in setup)') # print('2-check_runs, {} loaded '.format(rn)) return run_headers(k) def add_runs(k,runs): ''' Tries to load one or more runs to be added together by means of murs2py. runs is a list of strings containing integer run numbers provided by aux.derun Returns -1 and quits if musr2py complains If not invokes check_runs an returns its code ''' import os from mujpy.musr2py.musr2py import musr2py as muload from mujpy.aux.aux import muzeropad read_ok = 0 runadd = [] options = self.choose_tlogrun.options.copy() # existing dict (initialized to empty dict on dropdown creation) for j,run in enumerate(runs): # run is a string containing a single run number if len(run)>4: self.console('Too long run number! {}'.format(run)) read_ok = 99 break else: filename = '' filename = filename.join([self.filespecs[0].value, muzeropad(str(run)), '.',self.filespecs[1].value]) path_and_filename = os.path.join(self.paths[0].value,filename) # data path + filespec + padded run rumber + extension) if os.path.exists(os.path.join(self.paths[0].value,filename)): runadd.append(muload()) # this adds to the list in j-th position a new instance of muload() read_ok += runadd[j].read(path_and_filename) # THE RUN DATA FILE IS LOADED HERE if read_ok==0: self.console('Run {} loaded'.format(path_and_filename)) # print('tlog dropdown position {} run {}'.format(str(j),str(run))) options.update({str(run):str(run)}) # adds this run to the tlog display dropdown, # on_loads_changed checks that tlog exists before value selection else: self.console('\nRun: {} does not exist'.format(run)) return -1 if read_ok==0: # no error condition, set by musr2py.cpp or self.choose_tlogrun.options = options # ('self.choose_tlogrun.options = {}'.format(options)) self._the_runs_.append(runadd) # self.nrun.append(runadd[0].get_runNumber_int()) else: try: self.console('\nFile {} not read. Check paths, filespecs and run rumber on setup tab'.format(path_and_filename)) except: pass return -1 # this leaves the previous loaded runs n the suite return check_runs(k) def on_load_nxt(b): ''' load next run (if it exists) ''' if self._single_: # print('self.nrun[0] = {}'.format(self.nrun[0])) self.load_handle.value=str(self.nrun[0]+1) # print('self.nrun[0] = {}'.format(self.nrun[0])) else: self.console('Cannot load next run (multiple runs loaded)') return -1 # this leaves the multiple runs of the suite def on_load_prv(b): ''' load previous run (if it exists) ''' if self._single_: self.load_handle.value=str(self.nrun[0]-1) else: self.console('Cannot load previous run (multiple runs loaded)') return -1 # this leaves the multple runs of the suite def on_add_nxt(b): ''' add next run (if it exists) ''' if self._single_: load_single(self.nrun[0]+1) self.get_totals() else: self.console('Cannot add next run (multiple runs loaded)') return -1 # this leaves the multiple loaded runs of the suite def on_add_prv(b): ''' add previous run (if it exists) ''' if self._single_: load_single(self.nrun[0]-1) self.get_totals() else: self.console('Cannot add previous run (multiple runs loaded)') return -1 # this leaves the multiple loaded runs of the suite def on_load_file(b): ''' loads data from GUI and calls on loads_changed ''' from mujpy.aux.aux import path_file_dialog, get_run_number_from filename = path_file_dialog(self.paths[0].value,self.filespecs[1].value) if filename != '': # if empty, no choice made, nothing should happen run = get_run_number_from(filename,[self.filespecs[0].value,self.filespecs[1].value]) # path_file_dialog returns the full path and filename if run =='-1': self.console('Path = {}, filespecs = {}, {}'.format(p,f[0],f[1])) else: self.load_handle.value = run # self.console('Read {}'.format(self.load_handle.value)) #on_loads_changed('value') def on_loads_changed(change): ''' observe response of suite tab widgets: load a run via musrpy single run and run suite unified in a list clears run suite loads run using derun parsing of a string csv, n:m for range of runs [implement n+m+... for run addition] sets _single_ to True if single plan: derun must recognize '+', e.g. '2277:2280,2281+2282,2283:2284' and produce run = [['2277'],['2278'],['2279'],['2280'],['2281','2282'],['2283'],['2284']] Then the loop must subloop on len(run) to recreate the same list structure in self._the_runs_ and all occurencies of self._the_runs_ must test to add data from len(self._the_runs_[k])>1 check also asymmetry, create_rundict, write_csv, get_totals, promptfit, on_multiplot ''' from mujpy.aux.aux import derun, tlog_exists # rm: run_or_runs = change['owner'].description # description is either 'Single run' or 'Run suite' if self.load_handle.value=='': # either an accitental empty text return, or reset due to derun error return self._single_ = True self._the_runs_ = [] # it will be a list of muload() runs self.nrun = [] # it will contain run numbers (the first in case of run add) self.tlog_accordion.children[0].value='' ####################### # decode the run string ####################### runs, errormessage = derun(self.load_handle.value) # runs is a list of lists of run numbers (string) if errormessage is not None: # derun error self.console('Run syntax error: {}. You typed: {}'.format(errormessage,self.load_handle.value)) self.load_handle.value='' return ################################## # load a single run or a run suite ################################## read_ok = 0 for k,runs_add in enumerate(runs):# rs can be a list of run numbers (string) to add read_ok += add_runs(k,runs_add) # print('on_loads_change, inside loop, runs {}'.format(self._the_runs_)) if read_ok !=0: return # if read_ok == 0: self.choose_nrun.options = [str(n) for n in self.nrun] self.choose_nrun.value = str(self.nrun[0]) options = self.choose_tlogrun.options.copy() runs = list(self.choose_tlogrun.options.keys()) # scheme for popping items from list without altering index count kk = 0 for run in runs: # use original to iterate if not tlog_exists(self.paths[1].value,run): # loading tlogs is optional options.pop(run) # pop runs that do not have a tlog file self.choose_tlogrun.options = options try: self.choose_tlogrun.value = str((sorted(list(options.keys())))[0]) except: if self.newtlogdir: self.console("No tlogs") self.newtlogdir = False self.get_totals() # sets totalcounts, groupcounts and nsbin # self.console('nextnext check next, self_single_ = {}'.format(self._single_)) if self._single_: # self.console('next check next') check_next() if not self.nt0_run: self.console('WARNING: you must fix t0 = 0, please do a prompt fit from the setup tab') def t_value_error(k): ''' calculates T and eT values also for runs to be added silliy, but it works also for single run ''' from numpy import sqrt m = len(self._the_runs_[k]) weight = [float(sum(self._the_runs_[k][j].get_histo_array_int(2))) for j in range(m)] weight = [w/sum(weight) for k,w in enumerate(weight)] t_value = sum([self._the_runs_[k][j].get_temperatures_vector()[self.thermo]*weight[j] for j in range(m)]) t_error = sqrt(sum([(self._the_runs_[k][j].get_devTemperatures_vector()[self.thermo]*weight[j])**2 for j in range(m)])) return t_value, t_error from ipywidgets import Image, Text, Layout, HBox, Output, VBox, Tab from ipywidgets import Button, Label import os file = open(os.path.join(self.__logopath__,"logo.png"), "rb") image = file.read() logo = Image(value=image,format='png',width=132,height=132) ###################### mujpy_width = '980px'# '70%' # ###################### width = '100%' #----------------- firstrow self.maxbin = Text(description='Bins', layout=Layout(width='12%'), disabled=True) # 12% self.maxbin.style.description_width = '30%' self.comment = Text(description='Comment', layout=Layout(width='70%'), disabled=True) # 82% self.comment.style.description_width = '10%' self.nsbin = Text(description='ns/bin', layout=Layout(width='18%'), disabled=True) # 100% self.nsbin.style.description_width = '40%' #----------------- secondtrow self._the_runs_display = Text(description='#', description_tooltip='Run number', value='none', layout=Layout(width='12%'), disabled=True) # 12% self._the_runs_display.style.description_width = '30%' self.title = Text(description='Title', value='none yet', layout=Layout(width='48%'), disabled=True) # 60% self.title.style.description_width = '14.5%' self.totalcounts = Text(value='0', description='Total counts', layout=Layout(width='20%'), disabled=True) # 80% self.groupcounts = Text(value='0', description='Group counts', layout=Layout(width='20%'), disabled=True) # 100% #----------------- thirdrow self.start_date = Text(description='Start date', layout=Layout(width='28%'), disabled=True) # 28% self.start_date.style.description_width = '33%' self.stop_date = Text(description='Stop date', layout=Layout(width='28%'), disabled=True) # 56% self.stop_date.style.description_width = '33%' empty = Label(layout=Layout(width='8.5%')) # 66% Ap_button = Button(description='<Add', tooltip='Add previous run\n(refers Last +\nor to #)', layout=Layout(width='8%')) # 74% Ap_button.on_click(on_add_prv) Ap_button.style.button_color = self.button_color last_add = Text(description='Last +', description_tooltip='Last added run', disabled=True, layout=Layout(width='18%')) # 92% last_add.style.description_width = '27%' An_button = Button(description='Add>', tooltip='Add next run\n(refers Last +\nor to #)', layout=Layout(width='8%')) # 100% An_button.on_click(on_add_nxt) An_button.style.button_color = self.button_color # 100% #------------------- fourthrow small = Label(layout=Layout(width='3.6%')) # 4% Ld_button = Button(description='Load', tooltip='Opens a run file GUI\nin the data path (see setup tab)', layout=Layout(width='8%')) # 12% Ld_button.style.button_color = self.button_color Ld_button.on_click(on_load_file) self.load_handle = Text(description='Run:', description_tooltip='Single run\ne.g. 431\nor run suites\ne.g. 431, 435:439\n or 431, 443+444', layout=Layout(width='54%'), continuous_update=False) # 66% self.load_handle.style.description_width='11%' self.load_handle.observe(on_loads_changed,'value') Lp_button = Button(description='<Load', tooltip='Loads previous run\n(if it exists)', layout=Layout(width='8%')) # 74% Lp_button.on_click(on_load_prv) Lp_button.style.button_color = self.button_color Ln_button = Button(description='Load>', tooltip='Loads Next # run\n(if it exists)', layout=Layout(width='8%')) # 82% Ln_button.on_click(on_load_nxt) Ln_button.style.button_color = self.button_color next_label = Text(description='Next #', description_tooltip='Next run to load', disabled=True, layout=Layout(width='18%')) # 100% next_label.style.description_width = '30%' firstrow = HBox(layout=Layout(width=width)) firstrow.children = [self.maxbin, self.comment, self.nsbin] secondrow = HBox(layout=Layout(width=width)) secondrow.children = [self._the_runs_display, self.title, self.totalcounts, self.groupcounts] thirdrow = HBox(layout=Layout(width=width)) thirdrow.children = [self.start_date, self.stop_date, empty, Ap_button, last_add, An_button, ] fourthrow = HBox(layout=Layout(width=width)) fourthrow.children = [small, Ld_button, self.load_handle, Lp_button, Ln_button, next_label] titlewindow = VBox(layout=Layout(width='100%')) titlewindow.children = [firstrow, secondrow, thirdrow, fourthrow] titlelogowindow = HBox(layout=Layout(width=mujpy_width)) titlelogowindow.children = [logo, titlewindow] # main layout: tabs if self._output_==self._outputtab_: tabs_contents = ['fit', 'fft','setup', 'plots', 'output', 'about']# 'suite', else: tabs_contents = [ 'fit', 'fft','setup', 'plots', 'about']# 'suite', tabs = [VBox(description=name,layout=Layout(border='2px solid dodgerblue')) for name in tabs_contents] self.mainwindow = Tab(children = tabs,layout=Layout(width=mujpy_width,border='2px solid dodgerblue'))# self.mainwindow.selected_index = 2 # to stipulate that the first display is on tab 2, setup for i in range(len(tabs_contents)): self.mainwindow.set_title(i, tabs_contents[i]) # Araba.Phoenix: self.gui = VBox(description='whole',layout=Layout(width='auto')) self.gui.children = [titlelogowindow, self.mainwindow]
# This is the ex-suite tab to select run or suite of runs (for sequential or global fits) # moved to titlewindow thirdrow, an HBox containing various run load methods ['Single run','Run suite'] ########################## # OUTPUT ##########################
[docs] def output(self): ''' create an Output in terminal, or in widget in sixth tab, if needed select by self.mainwindow.selected_index = 5 ''' import os import platform import datetime import locale from shutil import which from subprocess import Popen, PIPE from ipywidgets import Output, HBox, Layout # Output(layout={'height': '100px', 'overflow_y': 'auto', 'overflow_x': 'auto'}) self._output_='' if platform.system()=='Linux': terminal = '' if which('gnome-terminal') is not None: terminal = 'gnome-terminal' # works on Ubuntu # if which('x-terminal-emulator') is not None: # terminal = 'x-terminal-emulator' # works on Debian, Ubuntu if terminal: self._output_ = "/tmp/mujpy_pipe" if not os.path.exists(self._output_): os.mkfifo(self._output_) else: os.unlink(self._output_) os.mkfifo(self._output_) # should write: 'gnome-terminal --title "mujpy console" -- bash -c "tail -f "'+self._output_ # this is to open the xterm mujpy console on linux term_title = 'mujpy console - '+datetime.datetime.today().strftime(locale.nl_langinfo(locale.D_T_FMT)) xterm = Popen([terminal,'--title',term_title,'shell=False','-e','tail -f %s' % self._output_]) # xterm opens a terminal and executes tail -f on the open pipe self._output_ self._outputtab_=[] # for Linux with terminal, if self._output_==self._outputtab_: # Linux no terminal is redirected to tab 5 as other OSs elif platform.system()=='Windows': # put here also a cmd output option # this Windows and the Mac part bust be still checked # xterm = Popen(["cmd.exe","/k"],stdout=PIPE,stderr=PIPE) pass elif platform.system()=='Darwin': # put here also a xterm option # xterm = Popen(['open', '-a', 'Terminal', '-n'],stdout=PIPE,stderr=PIPE) pass if not self._output_: self._outputtab_ = Output(layout={'height': '300px','width':'auto','overflow_y':'auto','overflow_x':'auto'}) _output_box = HBox([self._outputtab_],layout=Layout(width='100%')) # x works y does scroll self._park_ = [_output_box] # add the list of widget handles as the last tab, output self._output_ = self._outputtab_ # all in the tab self.console(''.join(['*****************************************************\n', '* The output of mujpy is displayed here. Watch out! *\n', '* DO NOT CLOSE! *\n', '*****************************************************\n']))
################ # PLOTS ################
[docs] def plots(self): ''' tlog plot multi plot (if not _single_) ''' def on_counter(b): ''' check syntax of counter_range ''' from mujpy.aux.aux import get_grouping from numpy import array # abuse of get_grouping: same syntax here if counter_range.value == '': return counters = get_grouping(counter_range.value) ok = 0 for k in range(counters.shape[0]): if counters[k]<0 or counters[k]>=self._the_runs_[0][0].get_numberHisto_int(): # print('k = {}, counters[k] = {}, numberHisto = {}'.format(k,counters[k],self._the_runs_[0][0].get_numberHisto_int())) ok = -1 if counters[0] == -1 or ok == -1: #print('Wrong counter syntax or counters out of range: {}'.format(counter_range.value)) self.console('Wrong counter syntax or counters out of range: {}'.format(counter_range.value)) counter_range.value = '' counters = array([]) def on_counterplot(b): ''' COUNTERPLOT: produce plot ''' from numpy import zeros, arange from mujpy.aux.aux import get_grouping, derange import matplotlib.pyplot as P font = {'family':'Ubuntu','size':8} P.rc('font', **font) dpi = 100. if not self._the_runs_: self.console('No run loaded yet! Load one first (select suite tab).') ############ # bin range ############ returntup = derange(self.counterplot_range.value,self.histoLength) # start, stop = returntup # abuse of get_grouping: same syntax here counters = get_grouping(counter_range.value) # already tested # now counters is an np.array of counter indices ############# # load histos ############# histo = zeros((self._the_runs_[0][0].get_numberHisto_int(),stop-start),dtype=int) bins = arange(start,stop,dtype=int) # 4x4, 3x3 or 2x3 counters ncounters = counters.shape[0] # self.numberHisto screen_x, screen_y = P.get_current_fig_manager().window.wm_maxsize() # screen size in pixels y_maxinch = float(screen_y)/dpi -0.5 # maximum y size in inches, 1 inch for window decorations fx, f, f1 = 1., 4./5., 16./25. # fraction of screen for if ncounters > 9: nrows,ncols = 4,4 x,y = fx*y_maxinch, y_maxinch elif ncounters > 6: nrows,ncols = 3,3 x,y = fx*y_maxinch*f, y_maxinch*f elif ncounters > 4: nrows,ncols = 2,3 x,y = fx*y_maxinch*f, y_maxinch*f1 elif ncounters > 1: nrows,ncols = 2,2 x,y = fx*y_maxinch*f1, y_maxinch*f1 else: nrows,ncols = 1,1 ############################## # set or recover figure, axes ############################## if self.fig_counters: self.fig_counters.clf() self.fig_counters,self.ax_counters = P.subplots(nrows,ncols,figsize=(x,y),num=self.fig_counters.number) else: # residual problem: when this is the first pyplot window a Figure 1 is opened that nobody ordered self.fig_counters,self.ax_counters = P.subplots(num=10,nrows=nrows,ncols=ncols,figsize=(x,y),dpi=dpi,squeeze=False) self.fig_counters.subplots_adjust(hspace=0.1,top=0.95,bottom=0.11,right=0.98,wspace=0.28) self.fig_counters.canvas.set_window_title('Counters') nplots = nrows*ncols for kk,nrun in enumerate(self.nrun): if nrun==int(self.choose_nrun.value): this_run = kk # if not single selects which run to plot counters for for k in range(nplots): if k < counters.shape[0]: counter = counters[k] # already an index 0:n-1 for run in self._the_runs_[this_run]: # allow for add runs histo[counter] += run.get_histo_array_int(counter)[start:stop] ymax = histo[counter].max() if stop-start<100: self.ax_counters[divmod(counter,ncols)].bar(bins,histo[counter,:],edgecolor='k',color='silver',alpha=0.7,lw=0.7) else: self.ax_counters[divmod(counter,ncols)].plot(bins,histo[counter,:],'k-',lw=0.7) if divmod(counter,ncols)[0]==counters.shape[0]/ncols-1: self.ax_counters[divmod(counter,ncols)].set_xlabel('bins') # does not print if divmod(counter,ncols)[1]==0: self.ax_counters[divmod(counter,ncols)].set_ylabel('counts') self.ax_counters[divmod(counter,ncols)].text(start+(stop-start)*0.9, ymax*0.9,'# '+str(counter+1)) # from index to label else: self.ax_counters[divmod(k,ncols)].cla() self.ax_counters[divmod(k,ncols)].axis('off') self.fig_counters.canvas.manager.window.tkraise() P.draw() def on_multiplot(b): ''' MULTIPLOT: produce plot ''' import matplotlib.pyplot as P from numpy import array from mujpy.aux.aux import derange, rebin, get_title import matplotlib.animation as animation ################### # PYPLOT ANIMATIONS ################### def animate(i): ''' anim function update multiplot data and its color ''' line.set_ydata(asymm[i]) line.set_color(color[i]) self.ax_multiplot.set_title(str(self.nrun[i])+': '+get_title(self._the_runs_[0][0])) return line, def init(): ''' anim init function to give a clean slate ''' line.set_ydata(asymm[0]) line.set_color(color[0]) self.ax_multiplot.set_title(str(self.nrun[0])+': '+get_title(self._the_runs_[0][0])) return line, dpi = 100. ############ # bin range ############ returntup = derange(self.multiplot_range.value,self.histoLength) # pack = 1 if len(returntup)==3: # plot start stop packearly last packlate start, stop, pack = returntup else: start, stop = returntup #################### # load and rebin # time,asymm are 2D arrays, # e.g. time.shape = (1,25000), # asymm.shape = (nruns,25000) ################### self.asymmetry() # prepare asymmetry time,asymm = rebin(self.time,self.asymm,[start,stop],pack) nruns,nbins = asymm.shape #print('start, stop, pack = {},{},{}'.format(start,stop,pack)) #print('shape time {}, asymm {}'.format(time.shape,asymm.shape)) y = 4. # normal y size in inches x = 6. # normal x size in inches my = 12. # try not to go beyond 12 run plots ############################## # set or recover figure, axes ############################## if self.fig_multiplot: self.fig_multiplot.clf() self.fig_multiplot,self.ax_multiplot = P.subplots(figsize=(x,y),num=self.fig_multiplot.number) else: self.fig_multiplot,self.ax_multiplot = P.subplots(figsize=(x,y),dpi=dpi) self.fig_multiplot.canvas.set_window_title('Multiplot') screen_x, screen_y = P.get_current_fig_manager().window.wm_maxsize() # screen size in pixels y_maxinch = float(screen_y)/float(self.fig_multiplot.dpi) # maximum y size in inches ########## note that "inches" are conventional, dince they depend on the display pitch # print('your display is y_maxinch = {:.2f} inches'.format(y_maxinch)) ########## XPS 13 is 10.5 "inches" high @160 ppi (cfr. conventional self.fig_multiplot.dpi = 100) bars = 1. # overhead y size(inches) for three bars (tools, window and icons) dy = 0. if anim_check.value else (y_maxinch-y-1)/my # extra y size per run plot y = y + nruns*dy if nruns < 12 else y + 12*dy # size, does not dilate for anim # self.fig_multiplot.set_size_inches(x,y, forward=True) ########################## # plot data and fit curve ########################## color = [] for run in range(nruns): color.append(next(self.ax_multiplot._get_lines.prop_cycler)['color']) if anim_check.value and not self._single_: ############# # animation ############# ############## # initial plot ############## ylow, yhigh = asymm.min()*1.02, asymm.max()*1.02 line, = self.ax_multiplot.plot(time[0],asymm[0],'o-',ms=2,lw=0.5,color=color[0],alpha=0.5,zorder=1) self.ax_multiplot.set_title(str(self.nrun[0])+': '+get_title(self._the_runs_[0][0])) self.ax_multiplot.plot([time[0,0],time[0,-1]],[0,0],'k-',lw=0.5,alpha=0.3) self.ax_multiplot.set_xlim(time[0,0],time[0,-1]) self.ax_multiplot.set_ylim(ylow,yhigh) self.ax_multiplot.set_ylabel('Asymmetry') self.ax_multiplot.set_xlabel(r'time [$\mu$s]') ####### # anim ####### self.anim_multiplot = animation.FuncAnimation(self.fig_multiplot, animate, nruns, init_func=init, interval=anim_delay.value, blit=False) ############################### # tiles with offset ############################### else: aoffset = asymm.max()*float(multiplot_offset.value)*array([[run] for run in range(nruns)]) asymm = asymm + aoffset # exploits numpy broadcasting ylow,yhigh = min([0,asymm.min()+0.01]),asymm.max()+0.01 for run in range(nruns): self.ax_multiplot.plot(time[0],asymm[run],'o-',lw=0.5,ms=2,alpha=0.5,color=color[run],zorder=1) self.ax_multiplot.plot([time[0,0],time[0,-1]], [aoffset[run],aoffset[run]],'k-',lw=0.5,alpha=0.3,zorder=0) self.ax_multiplot.text(time[0,-1]*1.025,aoffset[run],self._the_runs_[run][0].get_runNumber_int()) self.ax_multiplot.set_title(get_title(self._the_runs_[0][0])) self.ax_multiplot.set_xlim(time[0,0],time[0,-1]*9./8.) self.ax_multiplot.set_ylim(ylow,yhigh) # print('axis = [{},{},{},{}]'.format(time[0,0],time[0,-1]*9./8.,ylow,yhigh)) self.ax_multiplot.set_ylabel('Asymmetry') self.ax_multiplot.set_xlabel(r'time [$\mu$s]') # self.fig_multiplot.tight_layout() self.fig_multiplot.canvas.manager.window.tkraise() P.draw() def on_range(change): ''' observe response of MULTIPLOT range widgets: check for validity of function syntax on_range (PLOTS, FIT, FFT) perhaps made universal and moved to aux ''' from mujpy.aux.aux import derange # change['owner'].description if change['owner'].description[0:2] == 'fft': fmax = 0.5/(self.time[0,1]-self.time[0,0]) returnedtup = derange(change['owner'].value,fmax,int_or_float='float') else: returnedtup = derange(change['owner'].value,self.histoLength) # errors return (-1,-1),(-1,0),(0,-1), good values are all positive if sum(returnedtup)<0: change['owner'].background_color = "mistyrose" if change['owner'].description[0:3] == 'plot': change['owner'].value = self.plot_range0 else: change['owner'].value = self.bin_range0 else: change['owner'].background_color = "white" if returnedtup[1]>self.histoLength: change['owner'].value=str(returnedtup[0],self.histoLength) if len(returnedtup)==2 else str(returnedtup[0],self.histoLength,returnedtup[2]) def on_nexttlog(b): ''' select next run tlog ''' runs = list(self.choose_tlogrun.options.keys()) runs = sorted([int(run) for run in runs]) runindex = self.choose_tlogrun.index if runindex < len(runs): self.choose_tlogrun.value = str(runs[runindex+1]) on_tlogdisplay([]) def on_prevtlog(b): ''' select prev run tlog ''' runs = list(self.choose_tlogrun.options.keys()) runs = sorted([int(run) for run in runs]) runindex = self.choose_tlogrun.index if runindex > 0: self.choose_tlogrun.value = str(runs[runindex-1]) on_tlogdisplay([]) def on_start_stop(change): if anim_check.value: if change['new']: self.anim_multiplot.event_source.start() anim_step.style.button_color = self.button_color_off anim_step.disabled=True else: self.anim_multiplot.event_source.stop() anim_step.style.button_color = self.button_color anim_step.disabled=False def on_step(b): ''' step when stop animate ''' if not anim_step.disabled: self.anim_multiplot.event_source.start() def on_tlogdisplay(b): ''' display a PSI tlog if the files exist ''' import os import matplotlib.pyplot as P from mujpy.aux.aux import muzeropad from numpy import array, mean, std import csv from matplotlib.dates import HourLocator, MinuteLocator, DateFormatter import datetime from datetime import datetime as d ################ # load tlog file ################ if not self._the_runs_: self.console('Cannot plot temperatures: first load the runs!') return pathfile = os.path.join(self.paths[1].value,'run_'+muzeropad(self.choose_tlogrun.value)+'.mon') with open(pathfile,'r') as f: reader=csv.reader(f) header, t, T1,T2,pause,go = [],[],[],[],[],[] for k in range(9): header.append(next(reader)) # print(header[7][0][2:22]) starttime = header[7][0][2:22] start = d.strptime(starttime, "%d-%b-%Y %H:%M:%S") # print(start) for row in reader: # print(row) if row[0][0]!='!': row = row[0].split('\\') stop = d.strptime(row[0], "%H:%M:%S") row = row[2].split() T1.append(float(row[0])) T2.append(float(row[1])) # print('record = {}, stop = {}'.format(row[0][0:8],stop.time())) time = stop.time() t.append(time.hour*60.+time.minute+time.second/60.) # print('{}'.format(t)) else: # print(row) if row[0][24:29]=='Paused': pause.append(d.strptime(row[0][2:22], "%d-%b-%Y %H:%M:%S")) else: go.append(d.strptime(row[0][2:22], "%d-%b-%Y %H:%M:%S")) ############################## # set or recover figure, axes ############################## try: if self.fig_tlog: self.fig_tlog.clf() self.fig_tlog,self.ax_tlog = P.subplots(num=self.fig_tlog.number) except: self.fig_tlog,self.ax_tlog = P.subplots() self.fig_tlog.canvas.set_window_title('Tlogger') T1,T2 = array(T1), array(T2) self.ax_tlog.plot(t,T1,'r-',label=r'$T_{\rm diff}$') self.ax_tlog.plot(t,T2,'b-',label=r'$T_{\rm sample}$') tlim,Tlim = self.ax_tlog.get_xlim(), self.ax_tlog.get_ylim() T1ave, T1std, T2ave, T2std = mean(T1),std(T1),mean(T2),std(T2) self.ax_tlog.plot(tlim,[T1ave, T1ave],'r-',lw=0.5,alpha=0.8,label=r'$\langle T_{\rm diff}\rangle$') self.ax_tlog.fill_between(tlim, [T1ave-T1std,T1ave-T1std ],[T1ave, T1ave],facecolor='r',alpha=0.2) self.ax_tlog.fill_between(tlim, [T1ave+T1std,T1ave+T1std ],[T1ave, T1ave],facecolor='r',alpha=0.2) self.ax_tlog.plot(tlim,[T2ave, T2ave],'b-',lw=0.5,alpha=0.8,label=r'$\langle T_{\rm sample}\rangle$') self.ax_tlog.fill_between(tlim, [T2ave-T2std,T2ave-T2std ],[T2ave, T2ave],facecolor='b',alpha=0.2) self.ax_tlog.fill_between(tlim, [T2ave+T2std,T2ave+T2std ],[T2ave, T2ave],facecolor='b',alpha=0.2) self.ax_tlog.set_title('Run '+self.choose_tlogrun.value+' started at '+starttime) self.ax_tlog.set_xlabel('time [min]') self.ax_tlog.set_ylabel('T [k]') self.ax_tlog.legend() if Tlim[1]-Tlim[0] < 1.: T0 = (Tlim[0]+Tlim[1])/2 self.ax_tlog.set_ylim(T0-1.,T0+0.5) y1,y2 = self.ax_tlog.get_ylim() for x1,x2 in zip(pause,go): self.ax_tlog.fill_between([x1,x2], [y1,y1 ],[y2,y2],facecolor='k',alpha=0.5) self.ax_tlog.text(x1*0.9+x2*0.1,y1*0.9+y2*0.1,'PAUSE',color='w') self.fig_tlog.canvas.manager.window.tkraise() P.draw() from ipywidgets import HBox, VBox, Button, Text, Textarea, Accordion, Layout, Checkbox, IntText, ToggleButton, Label, Dropdown ########### # multiplot ########### multiplot_button = Button(description='Multiplot',layout=Layout(width='10%')) multiplot_button.on_click(on_multiplot) multiplot_button.style.button_color = self.button_color anim_check = Checkbox(description='Animate',value=False, layout=Layout(width='10%')) anim_check.style.description_width = '1%' anim_delay = IntText(description='Delay (ms)',value=1000, layout=Layout(width='20%')) anim_delay.style.description_width = '45%' anim_stop_start = ToggleButton(description='start/stop',value=True,layout={'width':'12%'}) anim_stop_start.observe(on_start_stop,'value') # anim_stop_start.style.button_color = self.button_color anim_step = Button(description='step',layout={'width':'10%'}) anim_step.on_click(on_step) anim_step.style.button_color = self.button_color_off self.multiplot_range = Text(description='plot range\nstart,stop[,pack]', value=self.plot_range0,layout=Layout(width='26%'), continuous_update=False) self.multiplot_range.style.description_width='43%' self.multiplot_range.observe(on_range,'value') multiplot_offset0 = '0.1' multiplot_offset = Text(description='offset', value=multiplot_offset0,layout=Layout(width='12%'), continuous_update=False) multiplot_offset.style.description_width='35%' # self.tlog_accordion.layout.height='10' multibox = HBox([multiplot_button,anim_check,anim_delay,anim_stop_start,self.multiplot_range,multiplot_offset, Label(layout=Layout(width='3%'))],layout=Layout(width='100%',border='2px solid dodgerblue')) ################### # counters inspect ################### counterlabel = Label(value='Inspect',layout=Layout(width='7%'))# count counterplot_button = Button(description='counters',layout=Layout(width='10%')) counterplot_button.on_click(on_counterplot) counterplot_button.style.button_color = self.button_color self.counternumber = Label(value='{} counters per run'.format(' '),layout=Layout(width='15%')) counter_range = Text(description='counters', value='', continuous_update=False,layout=Layout(width='20%')) counter_range.style.description_width='33%' counter_range.observe(on_counter,'value') self.counterplot_range = Text(description='bins: start,stop', value=self.bin_range0,continuous_update=False,layout=Layout(width='25%')) self.counterplot_range.style.description_width='40%' self.counterplot_range.observe(on_range,'value') self.choose_nrun = Dropdown(options=[], description='run', layout=Layout(width='15%')) self.choose_nrun.style.description_width='25%' counterbox = HBox([Label(layout=Layout(width='3%')),counterlabel,counterplot_button,Label(layout=Layout(width='3%')),self.counternumber, counter_range,self.counterplot_range,self.choose_nrun],layout=Layout(width='100%',border='2px solid dodgerblue')) ########## # TLOG PSI ########## spacer = Label(layout=Layout(width='3%')) tloglabel = Label(value='Tlog',layout=Layout(width='7%')) tlog_button = Button(description='display',layout=Layout(width='10%')) tlog_button.on_click(on_tlogdisplay) tlog_button.style.button_color = self.button_color options = {} # empty slot to start with self.choose_tlogrun = Dropdown(options=options,description='Tlog run', layout=Layout(width='15%')) # self.choose_tlogrun.style.description_width='35%' nexttlog_button = Button(description='Next',layout=Layout(width='10%')) nexttlog_button.on_click(on_nexttlog) nexttlog_button.style.button_color = self.button_color prevtlog_button = Button(description='Prev',layout=Layout(width='10%')) prevtlog_button.on_click(on_prevtlog) prevtlog_button.style.button_color = self.button_color self.tlog_accordion = Accordion(children=[Textarea(layout={'width':'100%','height':'200px', 'overflow_y':'auto','overflow_x':'auto'})]) self.tlog_accordion.set_title(0,'run: T(eT)') self.tlog_accordion.selected_index = None tlogbox = HBox([spacer,tloglabel, tlog_button, self.choose_tlogrun, nexttlog_button,prevtlog_button, self.tlog_accordion],layout=Layout(width='100%',border='2px solid dodgerblue')) vbox = VBox(layout=Layout(border='2px solid dodgerblue')) vbox.children = [multibox, counterbox, tlogbox] self.mainwindow.children[3].children = [vbox] self.mainwindow.children[3].layout = Layout(border = '2px solid dodgerblue',width='100%')
##########################i # SETUP ##########################
[docs] def setup(self): ''' setup tab of mugui used to set: paths, fileprefix and extension prepeak, postpeak (for prompt peak fit) prompt plot check, to activate: fit, save and load setup buttons ''' def load_setup(b): """ when user presses this setup tab widget: mugui loads mujpy_setup.pkl with saved attributes and replaces them in setup tab Text widgets """ import dill as pickle import os path = os.path.join(self.__startuppath__,'mujpy_setup.pkl') # self.console('loading {}, presently in {}'.format(path,os.getcwd())) try: with open(path,'rb') as f: mujpy_setup = pickle.load(f) except: self.console('File {} not found'.format(path)) # _paths_content = [ self.paths[k].value for k in range(3) ] # should be 3 ('data','tlag','analysis') # _filespecs_content = [ self.filespecs[k].value for k in range(2) ] # should be 2 ('fileprefix','extension') # _prepostpk = [self.prepostpk[k].value for k in range(2)] # 'pre-prompt bin','post-prompt bin' len(bkg_content) # _nt0 = self.nt0 # numpy array # _dt0 = self.dt0 # numpy array try: for k in range(3): # len(paths_contents) self.paths[k].value = mujpy_setup['_paths_content'][k] # should be 3 ('data','tlag','analysis') for k in range(2): # len(filespecs.content) self.filespecs[k].value = mujpy_setup['_filespecs_content'][k] # should be 2 ('fileprefix','extension') for k in range(2): # len(bkg_content) self.prepostpk[k].value = mujpy_setup['_prepostpk'][k] # 'pre-prompt bin','post-prompt bin' self.nt0 = mujpy_setup['self.nt0'] # bin of peak, nd.array of shape run.get_numberHisto_int() self.dt0 = mujpy_setup['self.dt0'] # fraction of bin, nd.array of shape run.get_numberHisto_int() self.lastbin = mujpy_setup['self.lastbin'] # fraction of bin, nd.array of shape run.get_numberHisto_int() self.nt0_run = mujpy_setup['self.nt0_run'] # dictionary to identify runs belonging to the same setup return 0 except Exception as e: self.console('Error in load_setup: {}'.format(e)) # self.console(mujpy_setup['_paths_content'][2]) # self.console('Status: k={},\nself.paths[k].value={}\nself.filespec[k].value={}\nself.prepostpk[k]={}\nself.nt0={},self.dt0={},self.lastbin={},self.nt0_run={}'.format(k,[self.paths[j].value for j in range(3)],[self.filespecs[j].value for j in range(2)],[self.prepostpk[j].value for j in range(2)],self.nt0,self.dt0,self.nt0_run)) return -1 def on_paths_changed(change): ''' when user changes this setup tab widget mugui checks that paths exist, in case it creates log path ''' import os path = change['owner'].description # description is paths[k] for k in range(len(paths)) () k = paths_content.index(path) # paths_content.index(path) is 0,1,2 for paths_content = 'data','tlog','analysis' if k==1: # tlog self.newtlogdir = True directory = self.paths[k].value # self.paths[k] = handles of the corresponding Text if not os.path.isdir(directory): if k==2: # analysis, if it does not exist mkdir # eventualmente togli ultimo os.path.sep = '/' in directory dire=directory if dire.rindex(os.path.sep)==len(dire): dire=dire[:-1] # splitta all'ultimo os.path.sep = '/' prepath=dire[:dire.rindex(os.path.sep)+1] # controlla che prepath esista # print('prepath for try = {}'.format(prepath)) try: os.stat(prepath) os.mkdir(dire+os.path.sep) self.console('Analysis path {} created\n'.format(directory)) except: self.paths[k].value = os.path.curdir self.console('Analysis path {} does not exist and cannot be created\n'.format(directory)) else: self.paths[k].value = os.path.curdir self.console('Path {} does not exist, reset to .\n'.format(directory)) def on_prompt_fit_click(b): ''' when user presses this setup tab widget mugui executes prompt fits ''' promptfit(mplot=self.plot_check.value) # mprint we leave always False def promptfit(mplot = False, mprint = False): ''' launches t0 prompts fit:: fits peak positions prints migrad results plots prompts and their fit (if plot checked) stores bins for background and t0 refactored for run addition and suite of runs WARNING: this module is for PSI only ''' import numpy as np from iminuit import Minuit, describe import matplotlib.pyplot as P from mujpy.mucomponents.muprompt import muprompt font = {'family' : 'Ubuntu','size' : 8} P.rc('font', **font) dpi = 100. second_plateau = 100 peakheight = 100000. peakwidth = 1. if not self._the_runs_: self.console('No run loaded yet! Load one first (select suite tab).') else: ################################################### # fit a peak with different left and right plateaus ################################################### ############################# # guess prompt peak positions ############################# npeaks = [] for counter in range(self._the_runs_[0][0].get_numberHisto_int()): histo = np.empty(self._the_runs_[0][0].get_histo_array_int(counter).shape) for k in range(len(self._the_runs_[0])): # may add runs histo += self._the_runs_[0][k].get_histo_array_int(counter) npeaks.append(np.where(histo==histo.max())[0][0]) npeaks = np.array(npeaks) ############### # right plateau ############### nbin = max(npeaks) + second_plateau # this sets a counter dependent second plateau bin interval x = np.arange(0,nbin,dtype=int) # nbin bins from 0 to nbin-1 self.lastbin, np3s = npeaks.min() - self.prepostpk[0].value, npeaks.max() + self.prepostpk[1].value # final bin of first and if mplot: ############################## # set or recover figure, axes ############################## if self.fig_counters: self.fig_counters.clf() self.fig_counters,self.ax_counters = P.subplots(2,3,figsize=(7.5,5),num=self.fig_counters.number) else: self.fig_counters,self.ax_counters = P.subplots(2,3,figsize=(7.5,5),dpi=dpi) self.fig_counters.canvas.set_window_title('Prompts fit') screen_x, screen_y = P.get_current_fig_manager().window.wm_maxsize() # screen size in pixels y_maxinch = float(screen_y)/dpi # maximum y size in inches prompt_fit_text = [None]*self._the_runs_[0][0].get_numberHisto_int() for counter in range(self._the_runs_[0][0].get_numberHisto_int(),sum(self.ax_counters.shape)): self.ax_counters[divmod(counter,3)].cla() self.ax_counters[divmod(counter,3)].axis('off') x0 = np.zeros(self._the_runs_[0][0].get_numberHisto_int()) # for center of peaks for counter in range(self._the_runs_[0][0].get_numberHisto_int()): # prepare for muprompt fit histo = np.empty(self._the_runs_[0][0].get_histo_array_int(counter).shape) for k in range(len(self._the_runs_[0])): # may add runs histo += self._the_runs_[0][k].get_histo_array_int(counter) p = [ peakheight, float(npeaks[counter]), peakwidth, np.mean(histo[self.firstbin:self.lastbin]), np.mean(histo[np3s:nbin])] y = histo[:nbin] ############## # guess values ############## pars = dict(a=p[0],error_a=p[0]/100,x0=p[1]+0.1,error_x0=p[1]/100, dx=1.1,error_dx=0.01, ak1=p[3],error_ak1=p[3]/100,ak2=p[4],error_ak2=p[4]/100) level = 1 if mprint else 0 mm = muprompt() mm._init_(x,y) m = Minuit(mm,pedantic=False,print_level=level,**pars) m.migrad() A,X0,Dx,Ak1,Ak2 = m.args x0[counter] = X0 # store float peak bin position (fractional) if mplot: n1 = npeaks[counter]-50 n2 = npeaks[counter]+50 x3 = np.arange(n1,n2,1./10.) # with self.t0plot_container: # if self.first_t0plot: self.ax_counters[divmod(counter,3)].cla() self.ax_counters[divmod(counter,3)].plot(x[n1:n2],y[n1:n2],'.') self.ax_counters[divmod(counter,3)].plot(x3,mm.f(x3,A,X0,Dx,Ak1,Ak2)) x_text,y_text = npeaks[counter]+10,0.8*max(y) prompt_fit_text[counter] = self.ax_counters[divmod(counter,3)].text(x_text,y_text,'Det #{}\nt0={}bin\n$\delta$t0={:.2f}'.format (counter+1,x0.round().astype(int)[counter],x0[counter]-x0.round().astype(int)[counter])) if mplot: self.fig_counters.canvas.manager.window.tkraise() P.draw() ################################################################################################## # Simple cases: # 1) Assume the prompt is entirely in bin nt0. (python convention, the bin index is 0,...,n,... # The content of bin nt0 will be the t=0 value for this case and dt0 = 0. # The center of bin nt0 will correspond to time t = 0, time = (n-nt0 + mufit.offset + mufit.dt0)*mufit.binWidth_ns/1000. # 2) Assume the prompt is equally distributed between n and n+1. Then nt0 = n and dt0 = 0.5, the same formula applies # 3) Assume the prompt is 0.45 in n and 0.55 in n+1. Then nt0 = n+1 and dt0 = -0.45, the same formula applies. ################################################################################################## # these three are the sets of parameters used by other methods self.nt0 = x0.round().astype(int) # bin of peak, nd.array of shape run.get_numberHisto_int() self.dt0 = x0-self.nt0 # fraction of bin, nd.array of shape run.get_numberHisto_int() self.lastbin = self.nt0.min() - self.prepostpk[0].value # nd.array of shape run.get_numberHisto_int() self.nt0_run = self.create_rundict() nt0.children[0].value = ' '.join(map(str,self.nt0.astype(int))) dt0.children[0].value = ' '.join(map('{:.2f}'.format,self.dt0)) # refresh, they may be slightly adjusted by the fit def save_log(b): """ when user presses this setup tab button mugui saves ascii file .log with run list in data directory """ import os from glob import glob from mujpy.musr2py.musr2py import musr2py as muload # from psibin import MuSR_td_PSI_bin as muload from mujpy.aux.aux import value_error run_files = sorted(glob(os.path.join(self.paths[0].value, '*.bin'))) run = muload() run.read(run_files[0]) filename=run.get_sample()+'.log' nastychar=list(' #%&{}\<>*?/$!'+"'"+'"'+'`'+':@') for char in nastychar: filename = "_".join(filename.split(char)) path_file = os.path.join(self.paths[0].value, filename) # set to [2] for analysis with open (path_file,'w') as f: #7082 250.0 250.0(1) 3 4.8 23:40:52 17-DEC-12 PSI8KMnFeF Powder PSI 8 K2.5Mn2.5Fe2.5F15, TF cal 30G, Veto ON, SR ON f.write("Run\tT_nom/T_meas(K)\t\tB(mT)\tMev.\tStart Time & Date\tSample\t\tOrient.\tComments\n\n") for run_file in run_files: run.read(run_file) TdT = value_error(run.get_temperatures_vector()[self.thermo], run.get_devTemperatures_vector()[self.thermo]) tsum = 0 for counter in range(run.get_numberHisto_int()): histo = run.get_histo_array_int(counter).sum() tsum += histo BmT = float(run.get_field().strip()[:-1])/10. # convert to mT, avoid last chars 'G ' Mev = float(tsum)/1.e6 #Run T TdT BmT Mev Date sam or com f.write('{}\t{}/{}\t{:.1f}\t{:.1f}\t{}\t{}\t{}\t{}\n'.format(run.get_runNumber_int(), run.get_temp(), TdT, BmT, Mev, run.get_timeStart_vector(), run.get_sample().strip(), run.get_orient().strip(), run.get_comment().strip() )) self.console('Saved logbook {}'.format(path_file)) def save_setup(b): """ when user presses this setup tab button mugui saves mujpy_setup.pkl with setup tab values """ import dill as pickle import os path = os.path.join(self.__startuppath__, 'mujpy_setup.pkl') # create dictionary setup_dict to be pickled _paths_content = [ self.paths[k].value for k in range(3) ] # should be 3 ('data','tlag','analysis') _filespecs_content = [ self.filespecs[k].value for k in range(2) ] # should be 2 ('fileprefix','extension') _prepostpk = [self.prepostpk[k].value for k in range(2)] # 'pre-prompt bin','post-prompt bin' len(bkg_content) names = ['_paths_content','_filespecs_content', '_prepostpk','self.nt0','self.dt0','self.lastbin','self.nt0_run'] # keys setup_dict = {} for k,key in enumerate(names): setup_dict[names[k]] = eval(key) # key:value with open(path,'wb') as f: pickle.dump(setup_dict, f) # according to __getstate__() self.console('Saved {}'.format(os.path.join(self.__startuppath__,'mujpy_setup.pkl'))) from ipywidgets import HBox, Layout, VBox, Text, Textarea, IntText, Checkbox, Button, Output, Accordion from numpy import array # setup for things that have to be set initially (paths, t0, etc.) # the tab is self.mainwindow.children[2], a VBox # containing a setup_box of three HBoxes: path, and t0plot # path is made of a firstcolumn, paths, and a secondcolumns, filespecs, children of setup_box[0] # agt0 is made of three width = '100%' height = 'auto' # ---------------firstrow datapath = Text(description='data', description_tooltip='data path\n(./ is is the folder\nwhere you launched jupyter)', continuous_update=False, layout=Layout(width='30%')) # 30% datapath.style.description_width='10%' fileprefix = Text(description='fileprefix', description_tooltip='e.g. deltat_tdc_gps_', continuous_update=False, layout=Layout(width='20%')) # 45% fileprefix.style.description_width='30%' extension = Text(description='extension', description_tooltip='e.g. bin', continuous_update=False, layout=Layout(width='15%')) # 65% extension.style.description_width='45%' logpath = Text(description='analysis', description_tooltip='analysis path\n(./ is is the folder\nwhere you launched jupyter)', continuous_update=False, layout=Layout(width='20%')) # 85% logpath.style.description_width='30%' tlogpath = Text(description='tlog', description_tooltip='tlog path\n(./ is is the folder\nwhere you launched jupyter)', continuous_update=False, layout=Layout(width='15%')) # 100% tlogpath.style.description_width='20%' self.paths = [datapath, tlogpath, logpath] for k in range(len(self.paths)): self.paths[k].observe(on_paths_changed,'value') # self.paths[k].value='.'+os.path.sep paths_content = ['data','tlog','analysis'] self.filespecs = [fileprefix, extension] firstrow = HBox([self.paths[0], self.filespecs[0], self.filespecs[1],self.paths[2], self.paths[1]], layout=Layout(width=width)) #----------------- secondrow prepeak = IntText(description='prepeak', description_tooltip='bins before prompt\nfor left plateau', value = 7, layout=Layout(width='14%'), continuous_update=False) # 14% prepeak.style.description_width='50%' postpeak = IntText(description='postpeak', description_tooltip='bins after prompt\nfor right plateau', value = 7, layout=Layout(width='15%'), continuous_update=False) # 29% postpeak.style.description_width='50%' self.prepostpk = [prepeak, postpeak] self.plot_check = Checkbox(description='prompt plot', description_tooltip = 'Show results after fit', value=True, layout=Layout(width='15%')) # 44% self.plot_check.style.description_width='1%' fit_button = Button(description='prompt fit', tooltip = 't0 is fitted as prompt centre\n(peak on step function)', layout=Layout(width='14%')) # 58% fit_button.on_click(on_prompt_fit_click) fit_button.style.button_color = self.button_color save_button = Button(description='save setup', tooltip = 'in file mujpy_setup.pkl\nloaded at startup', layout=Layout(width='14%')) # 72% save_button.style.button_color = self.button_color save_button.on_click(save_setup) load_button = Button(description='load setup', tooltip = 'from file mujpy_setup.pkl', layout=Layout(width='14%')) # 86% load_button.style.button_color = self.button_color load_button.on_click(load_setup) log_button = Button(description='Data log', tooltip = 'Writes titles of runs\nin data path', layout=Layout(width='14%')) #100% log_button.on_click(save_log) log_button.style.button_color = self.button_color secondrow = HBox([prepeak,postpeak, fit_button,self.plot_check, save_button,load_button,log_button], layout=Layout(width=width)) #--------------thirdrow nt0 = Accordion(children=[Text(description='t0 [bins]', layout={'width':'99%'})], font_size=8, layout={'width':'35%','height':height}) nt0.children[0].style.description_width='20%' nt0.set_title(0,'t0 (prompt center bins)') nt0.selected_index = None dt0 = Accordion(children=[Text(description='dt0 [bins]', layout={'width':'99%'})], font_size=8, layout={'width':'35%','height':height}) dt0.children[0].style.description_width='20%' dt0.set_title(0,'t0 remainders (fractions of bin)') dt0.selected_index = None self.nt0,self.dt0 = array([0.]),array([0.]) load_setup([]) # invokes load_setup to load mujpy_setup.pkl nt0.children[0].value = ' '.join(map(str,self.nt0.astype(int))) dt0.children[0].value = ' '.join(map('{:.2f}'.format,self.dt0)) thirdrow = HBox([nt0,dt0],layout=Layout(width=width,height=height)) if not self.nt0_run: self.console('WARNING: you must fix t0 = 0, please do a prompt fit from the setup tab') setup_hbox = [VBox([firstrow,secondrow,thirdrow], layout=Layout(width=width))] self.mainwindow.children[2].children = setup_hbox # first tab (setup) self.mainwindow.children[2].layout=Layout(border='2px solid dodgerblue', width=width,height='auto')
###################### # GET_TOTALS ######################
[docs] def get_totals(self): ''' calculates the grand totals and group totals after a single run or a run suite are read ''' import numpy as np # called only by self.suite after having loaded a run or a run suite ################### # grouping set # initialize totals ################### gr = set(np.concatenate((self.grouping['forward'],self.grouping['backward']))) ts,gs = [],[] if self.offset: # True if self.offset is already created by self.fit() offset_bin = self.offset.value # self.offset.value is int else: # should be False if self.offset = [], as set in self.__init__() offset_bin = self.offset0 # temporary parking for k,runs in enumerate(self._the_runs_): tsum, gsum = 0, 0 for j,run in enumerate(runs): # add values for runs to add for counter in range(run.get_numberHisto_int()): n1 = offset_bin+self.nt0[counter] # if self.nt0 not yet read it is False and totals include prompt histo = run.get_histo_array_int(counter)[n1:].sum() tsum += histo if counter in gr: gsum += histo ts.append(tsum) gs.append(gsum) # print('In get totals inside loop,k {}, runs {}'.format(k,runs)) ####################### # strings containing # individual run totals ####################### # self.tots_all.value = '\n'.join(map(str,np.array(ts))) # self.tots_group.value = ' '.join(map(str,np.array(gs))) # print('In get totals outside loop, ts {},gs {}'.format(ts,gs)) ##################### # display values for self._the_runs_[0][0] self.totalcounts.value = str(ts[0]) self.groupcounts.value = str(gs[0]) # self.console('Updated Group Total for group including counters {}'.format(gr)) # debug self.nsbin.value = '{:.3}'.format(self._the_runs_[0][0].get_binWidth_ns()) self.maxbin.value = str(self.histoLength)
# def introspect(self):# mark for deletion: use MuJPy.__dict__ directly below GUI # ''' # print updated attributes of the class mugui # after each fit in file "mugui.attributes.txt" # in self.__startuppath__ # ''' # import os # import pprint # # from ipywidgets import VBox, HBox, Image, Text, Textarea, Layout, Button, IntText, Checkbox, Output, Accordion, Dropdown, FloatText, Tab # # trick to avoid printing the large mujpy log image binary file # image = self.__dict__['gui'].children[0].children[0].value # self.__dict__['gui'].children[0].children[0].value=b'' # with open(os.path.join(self.__startuppath__,"mugui.attributes.txt"),'w') as f: # pprint.pprint('**************************************************',f) # pprint.pprint('* Mugui attribute list: *',f) # pprint.pprint('**************************************************',f) # pprint.pprint(self.__dict__,f) # self.__dict__['gui'].children[0].children[0].value=image