Source code for aux.aux

########################
# FFT AUTO PHASE METHODS
########################
[docs]def autops(data, fn, p0=0.0, p1=0.0): """ Automated phase correction from NMRglue by https://github.com/jjhelmus These functions provide support for automatic phasing of NMR data. Automatic linear phase correction Parameters data : ndarray Array of NMR data. fn : str or function Algorithm to use for phase scoring. Built in functions can be specified by one of the following strings: "acme", "peak_minima" p0 : float Initial zero order phase in degrees. p1 : float Initial first order phase in degrees. Returns ndata : ndarray Phased NMR data. """ import numpy as np import scipy.optimize from io import StringIO # Python3 use: from io import StringIO from contextlib import redirect_stdout if not callable(fn): fn = { 'peak_minima': _ps_peak_minima_score, 'acme': _ps_acme_score, }[fn] opt = [p0, p1] with StringIO() as buf, redirect_stdout(buf): opt = scipy.optimize.fmin(fn, x0=opt, args=(data, )) mystdout = buf.getvalue() return ps(data, p0=opt[0], p1=opt[1]), opt[0], opt[1], mystdout
[docs]def _ps_acme_score(ph, data): """ Phase correction using ACME algorithm by Chen Li et al. Journal of Magnetic Resonance 158 (2002) 164-168 Parameters * pd : tuple, current p0 and p1 values * data : ndarray, array of NMR data. Returns * score : float, value of the objective function (phase score) """ import numpy as np stepsize = 1 phc0, phc1 = ph s0 = ps(data, p0=phc0, p1=phc1) data = np.real(s0) # Calculation of first derivatives ds1 = np.abs((data[1:]-data[:-1]) / (stepsize*2)) p1 = ds1 / np.sum(ds1) # Calculation of entropy p1[p1 == 0] = 1 h1 = -p1 * np.log(p1) h1s = np.sum(h1) # Calculation of penalty pfun = 0.0 as_ = data - np.abs(data) sumas = np.sum(as_) if sumas < 0: pfun = pfun + np.sum((as_/2) ** 2) p = 1000 * pfun return h1s + p
[docs]def _ps_peak_minima_score(ph, data): """ Phase correction using simple minima-minimisation around highest peak This is a naive approach but is quick and often achieves reasonable results. The optimisation is performed by finding the highest peak in the spectra (e.g. TMSP) and then attempting to reduce minima surrounding it. Parameters * pd : tuple, current p0 and p1 values * data : ndarray, array of NMR data. Returns * score : float, value of the objective function (phase score) """ phc0, phc1 = ph s0 = ps(data, p0=phc0, p1=phc1) data = np.real(s0) i = np.argmax(data) mina = np.min(data[i-100:i]) minb = np.min(data[i:i+100]) return np.abs(mina - minb)
[docs]def ps(data, p0=0.0, p1=0.0, inv=False): """ Linear phase correction Parameters data : ndarray Array of NMR data. p0 : float Zero order phase in degrees. p1 : float First order phase in degrees. inv : bool, optional True for inverse phase correction Returns ndata : ndarray Phased NMR data. """ import numpy as np p0 = p0 * np.pi / 180. # convert to radians p1 = p1 * np.pi / 180. size = data.shape[-1] apod = np.exp(1.0j * (p0 + (p1 * np.arange(size) / size))).astype(data.dtype) if inv: apod = 1 / apod return apod * data
############## # MU FIT AUX ##############
[docs]def TauMu_mus(): ''' muon mean lifetime in microsecond from Particle Data Group 2017 (not present in scipy.constants) ''' return 2.1969811
[docs]def _errors_(component,available_components): ''' inputs: one legal mucomponent name contained in the _available_components_(), which must be the second input output: a list of errors (steps), one for each parameter of this component ''' #print(component,available_components) k = [item['name'] for item in available_components].index(component) return [pardict["error"] for pardict in available_components[k]['pardicts']]
[docs]def _limits_(component,available_components): ''' inputs: one legal mucomponent name contained in the _available_components_(), which must be the second input output: a list of lists of limits (low, high), one for each parameter of this component ''' k = [item['name'] for item in available_components].index(component) return [pardict["limits"] for pardict in available_components[k]['pardicts']]
[docs]def add_step_limits_to_model(dash_in): ''' input: original dashboard dash_in, already checked output: dash_out is a deepcopy including 'error' and 'limits' ''' from copy import deepcopy from mujpy.aux.aux import _available_components_, _errors_, _limits_ available_components = _available_components_() dash_out = deepcopy(dash_in) # these lists contain all parameter values in the dashboard, including their error steps and limits for k, component in enumerate(dash_out['model_guess']): steps = _errors_(component['name'],available_components) limits = _limits_(component['name'],available_components) for j,pardict in enumerate(component['pardicts']): dash_out['model_guess'][k]['pardicts'][j]['error'] = steps[j] dash_out['model_guess'][k]['pardicts'][j]['limits'] = limits[j] return dash_out
[docs]def _available_components_(): ''' returns a list of template dictionaries (one per fit component): Each dictionary contains 'name' and 'pardicts', 'pardicts' = list of parameter dictionaries, keys: 'name','error,'limits' :: ({'name':'bl','pardicts':[{'name':'A','error':0.01,'limits'[0,0]}, {'name':'λ','error':0.01,'limits'[0,0]}}, ...) retreived magically from the mucomponents mumodel class. ''' from mujpy.mucomponents.mucomponents import mumodel from iminuit import describe available_components = [] # generates the 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:] # [12:] because the first two arguments are self, x _pars = [] # print('pars are {}'.format(pars)) for parname in pars: # parname, error, limits # In this template only # {'name':'amplitude','error':0.01,'limits':[0, 0]} # parameter name will get a label later error, limits = 0.01, [None, None] # defaults if parname == 'B' or parname == 'φ' or parname == 'Bd': error = 1.0 if parname == 'β': error,limits = 0.05, [0, None] if parname == 'α': error,limits = 0.01, [0, None] # 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,'pardicts':_pars}) # [available_components[i]['name'] for i in range(len(available_components))] # list of just mucomponents method names return available_components
[docs]def checkvalidmodel(name,component_names): ''' checkvalidmodel(name) checks that name is either :: A1, B1: 2*component string of valid component names, e.g. 'daml' or 'mgmgbl' :: or A2, B2: same, ending with 1 digit, number of groups (max 9 groups), 'daml2' or 'mgmgml2' (2 groups) :: or C1: same, beginning with 1 digit, number of external minuit parameters (max 9) '3mgml' (3 external parameters e.g. A, f, phi) :: or C2: same, both previous options '3mgml2' (3 external parameters, 2 groups) ''' from mujpy.aux.aux import modelstrip try: name, nexternals = modelstrip(name) except: # self.console('name error: '+name+' contains too many externals or groups (max 9 each)') error_msg = 'name error: '+name+' contains too many externals or groups (max 9 each)' return False, error_msg # err code mess # decode model numberofda = 0 components = [name[i:i+2] for i in range(0, len(name), 2)] for component in components: if component == 'da': numberofda += 1 if component == 'al': numberofda += 1 if numberofda > 1: # self.console('name error: '+name+' contains too many da. Not added.') error_msg = 'name error: '+name+' contains too many da/al. Not added.' return False, error_msg # error code, message if component not in component_names: # self.console() error_msg = 'name error: '+component+' is not a known component. Not added.' return False, error_msg # error code, message return True, None
[docs]def _nparam(model): ''' input: dashboard['model_guess'] output: ntot, nmintot, nfree ''' number_components = len(model) # print('_nparam aux debug: model {}'.format(model)) ntot = sum([len(model[k]['pardicts']) for k in range(number_components)]) # total number of component parameters flag = [pardict['flag'] for component in model for pardict in component['pardicts']] nmintot = ntot - sum([1 for k in range(ntot) if flag[k]=='=']) # ntot minus number of functions nfree = nmintot - sum([1 for k in range(ntot) if flag[k]=='!']) # ntot minus number of fixed parameters return ntot, nmintot, nfree
[docs]def int2min(model): ''' input: model either dashboard["model_guess"] (after add_step_limits_to_model) or dashboard["model_guess"] both lists of dicts output: a list of lists: fitvalues: minuit parameter values, either guess of result fiterrors: their steps fitfixed: True/False for each fitlimits: [low, high] limits for each or [None,None] parameter_name: name of parameter 'x_label' for each parameter To be used e.g. as: self.lastfit = Minuit(self._the_model_._chisquare_, name=parameter_name, *fitvalues) self.lastfit.errors = fiterrors self.lastfit.limits = fitlimits self.lastfit.fixed = fitfixed self.lastfit.migrad() ''' from mujpy.aux.aux import _nparam dum, ntot, dum = _nparam(model) ##################################################### # the following variables contain the same as input # # parameters to iMinuit, removing '='s (functions) # ##################################################### fitval, fiterr, fitfix, fitlim = [], [], [], [] parameter_name = [] nint = -1 # initialize for k in range(len(model)): # scan the model components label = model[k]['label'] for j, pardict in enumerate(model[k]['pardicts']): # list of dictionaries nint += 1 # internal parameter incremented always # print(pardict) if pardict['flag'] != '=': # skip functions, only new minuit parameters fitval.append(float(pardict['value'])) parameter_name.append(pardict['name']+'_'+label) fiterr.append(float(pardict['error'])) fitlim.append(pardict['limits']) if pardict['flag'] == '~': fitfix.append(False) elif pardict['flag'] == '!': fitfix.append(True) # self.console('fitval = {}\nfiterr = {}\nfitfix = {}\nfitlim = {}\ncomp name = {},\npar name = {} '.format(fitval,fiterr,fitfix,fitlim,component_name,parameter_name)) return fitval, fiterr, fitfix, fitlim, parameter_name
[docs]def int2min_multigroup(model): ''' input: model either dashboard["model_guess"] (after add_step_limits_to_model) or dashboard["model_guess"] both lists of dicts output: a list of lists: fitvalues: minuit parameter values, either guess of result fiterrors: their steps fitfixed: True/False for each fitlimits: [low, high] limits for each or [None,None] parameter_name: name of parameter 'x_label' for each parameter this works for A2 single fit, multigroup with userpardicts parameters = Minuit parameters ''' pardicts = model["userpardicts_guess"] ntot = len(pardicts) ##################################################### # the following variables contain the same as input # # parameters to iMinuit, removing '='s (functions) # ##################################################### fitval, fiterr, fitfix, fitlim = [], [], [], [] parameter_name = [] for pardict in pardicts: # scan the model components fitval.append(float(pardict['value'])) parameter_name.append(pardict['name']) fiterr.append(float(pardict['error'])) fitlim.append(pardict['limits']) if pardict['flag'] == '!': fitfix.append(True) elif pardict['flag'] == '~': fitfix.append(False) else: return False,_,_,_,_ # self.console('fitval = {}\nfiterr = {}\nfitfix = {}\nfitlim = {}\ncomp name = {},\npar name = {} '.format(fitval,fiterr,fitfix,fitlim,component_name,parameter_name)) return fitval, fiterr, fitfix, fitlim, parameter_name
[docs]def int2_method_key(dashboard,the_model): ''' input: the dashboard dict structure and the fit model instance output: a list of methods, in the order of the model components for the use of mumodel._add_. Invoked by the iMinuit initializing call self._the_model_._load_data_, or self._the_model_._load_calib_data_ just before submitting migrad, self._the_model_ is an instance of mumodel This function applies aux.translate to the parameter numbers in formulas since on the dash each parameter of each component gets an internal number, but shared or formula-determined ('=') ones are not minuit parameters ''' from mujpy.aux.aux import translate model_guess = dashboard['model_guess'] # guess surely exists ntot = sum([len(model_guess[k]['pardicts']) for k in range(len(model_guess))]) lmin = [] # initialize the minuit parameter index of dashboard function indices nint = -1 # initialize the number of internal parameters nmin = -1 # initialize the number of minuit parameters method_key = [] function = [pardict['function'] for component in model_guess for pardict in component['pardicts']] for k in range(len(model_guess)): # scan the model name = model_guess[k]['name'] # print('name = {}, model = {}'.format(name,self._the_model_)) is_al_da = name=='al' or name=='da' bndmthd = [] if is_al_da else the_model.__getattribute__(name) # this is the method to calculate a component, to set alpha, dalpha apart keys = [] # isminuit = [] not used flag = [item['flag'] for item in model_guess[k]['pardicts']] for j,pardict in enumerate(model_guess[k]['pardicts']): nint += 1 # internal parameter incremente always if flag[j] == '=': # function is written in terms of nint # nint must be translated into nmin string = translate(nint,lmin,pardict['function']) # here is where lmin is used # translate substitutes lmin[n] where n is the index read in the function (e.g. p[3]) keys.append(string) # the function will be eval-uated, eval(key) inside mucomponents # isminuit.append(False) lmin.append(0) else:# flag[j] == '~' or flag[j] == '!' nmin += 1 keys.append('p['+str(nmin)+']') # this also needs direct translation lmin.append(nmin) # # isminuit.append(True) # print('int2_method aux debug: bndmthd = {}, keys = {}'.format(bndmthd,keys)) method_key.append([bndmthd,keys]) return method_key
[docs]def int2_multigroup_method_key(dashboard,the_model,guess=True): ''' input: dashboard, the dashboard dict structure fit._the_model_ is an instance of mumodel (the number of groups is obtained from dashboard) output: a list of methods and keys, in the order of the model components for the use of mumodel._add_multigroup_. method is a 2d vector function accepting time and a variable number of lists of (component) parameters e.g if one component is mumodel.bl(x,A,λ) the corresponding component for a two group fit accepts the following argument (t,[A1, A2],[λ1,λ2]) keys is a list of lists of strings they are evaluated to produce the method parameters, there are ngroups strings per parameter (inner list) npar parametes per component (outer list) Invoked by the iMinuit initializing call self._the_model_._load_data_multigroup_ just before submitting migrad, This function does not need userpardicts and aux.translate since the correspondence with Minuit parameters is given directly either by "function" or by "function_multi" ''' from mujpy.aux.aux import multigroup_in_components, fstack model = dashboard['model_guess'] # guess surely exists # these are the only Minuit parameters [p[k] for k in range (nuser)] ntot = sum([len(model[k]['pardicts']) for k in range(len(model))]) method_key = [] pardicts = [pardict for component in model for pardict in component['pardicts']] mask_function_multi = multigroup_in_components(dashboard) # print('int2_multigroup_method_key aux debug: index function_multi {}\npardicts = {}'.format(mask_function_multi,pardicts)) if sum(mask_function_multi): ngroups = len(pardicts[mask_function_multi.index(1)]["function_multi"]) else: return [] nint = -1 # initialize the index of the dashboard component parameters for component in model: # scan the model components name = component['name'] keys = [] # print('name = {}, model = {}'.format(name,self._the_model_)) bndmthd = lambda x,*pars : fstack(the_model.__getattribute__(name),x,*pars) # this is the method to calculate a component, to set alpha, dalpha apart nint0 = nint for l in range(ngroups): key = [] nint = nint0 for j,pardict in enumerate(component['pardicts']): nint += 1 # internal parameter index incremented always if mask_function_multi[nint]>0: key.append(pardict["function_multi"][l]) else: key.append(pardict["function"]) # the function will be eval-uated inside mucomponents keys.append(key) # print('int2_method aux debug: bndmthd = {}, keys = {}'.format(bndmthd,keys)) method_key.append([bndmthd,keys]) # vectorialized method, with keys # keys = [[strp0g0, strp1g0,...],[strp0g1, strp1g1, ..],[strp0g2, strp1g2,...]..] # pars = [[p0g0, p1g0, ...],[p0g1, p1g1, ..],[p0g2, p1g2,...]..] return method_key
[docs]def fstack(npfunc,x,*pars): ''' vectorialize npfunc input: npfunc numpy function with input (x,*argv) x time *argv is variable number of lists of parameters, list len is the output_function_array.shape[0] output: output_function_array stacks vertically n replica of npfunc distributing parameters as in (x, *argv[i]) for each i-th replica ''' from numpy import vstack for k,par in enumerate(pars): if k: f = vstack((f,npfunc(x,*par))) else: f = npfunc(x,*par) return f
[docs]def int2_calib_method_key(dashboard,the_model): ''' input: the dashboard dict structure and the fit model 'alxx..' instance the actual model contains 'al' plus 'xx', .. the present method considers only the latter FOR PLOTTING ONLY (USE int2_method for the actual calib fit) output: a list of methods for calib fits, in the order of the 'xx..' model components (skipping al) for the use of mumodel._add_single_. Invoked by the iMinuit initializing call self._the_model_._load_data_, just before submitting migrad, self._the_model_ is an instance of mumodel This function applies aux.translate to the parameter numbers in formulas since on the dash each parameter of each component gets an internal number, but alpha is popped and shared or formula-determined ('=') ones are not minuit parameters ''' from mujpy.aux.aux import translate model_guess = dashboard['model_guess'] # guess surely exists ntot = sum([len(model_guess[k]['pardicts']) for k in range(len(model_guess))])-1 # minus alpha lmin = [] # initialize the minuit parameter index of dashboard function indices nint = -1 # initialize the number of internal parameters nmin = -1 # initialize the number of minuit parameters method_key = [] function = [pardict['function'] for component in model_guess for pardict in component['pardicts']] for k in range(1,len(model_guess)): # scan the model popping 'al' and its parameter 'alpha' name = model_guess[k]['name'] # print('name = {}, model = {}'.format(name,self._the_model_)) bndmthd = the_model.__getattribute__(name) keys = [] # isminuit = [] not used flag = [item['flag'] for item in model_guess[k]['pardicts']] for j,pardict in enumerate(model_guess[k]['pardicts']): nint += 1 # internal parameter incremente always if flag[j] == '=': # function is written in terms of nint # nint must be translated into nmin string = translate(nint,lmin,pardict['function']) # here is where lmin is used # translate substitutes lmin[n] where n is the index read in the function (e.g. p[3]) keys.append(string) # the function will be eval-uated, eval(key) inside mucomponents # isminuit.append(False) lmin.append(0) else:# flag[j] == '~' or flag[j] == '!' nmin += 1 keys.append('p['+str(nmin)+']') # this also needs direct translation lmin.append(nmin) # # isminuit.append(True) method_key.append([bndmthd,keys]) return method_key
[docs]def min2int(model_guess,values_in,errors_in): ''' input: model_component from dashboard values_in Minuit.values errors_in Minuit.errors output: for all dashbord parameters names list of lists of parameter names values_out list of lists of their values errors_out list of lists of their errors reconstruct dashboard with Minuit best fit values and errors for print_components, compact fit summary ''' # # initialize # from mujpy.aux.aux import translate names, values_out, p, errors_out, e = [], [], [], [], [] nint = -1 # initialize nmin = -1 lmin = [] flag = [pardict['flag'] for component in model_guess for pardict in component['pardicts']] flag = [pardict['flag'] for component in model_guess for pardict in component['pardicts']] for k,component in enumerate(model_guess): # scan the model component_name = component['name'] name, value, error = [], [], [] label = model_guess[k]['label'] for j,pardict in enumerate(model_guess[k]['pardicts']): # list of dictionaries, par is a dictionary nint += 1 # internal parameter incremented always if j==0: name.append('{}: {}_{}'.format(component_name,pardict['name'],label)) else: name.append('{}_{}'.format(pardict['name'],label)) if flag[nint] != '=': # skip functions, they are not new minuit parameter nmin += 1 lmin.append(nmin) p.append(values_in[nmin]) # needed also by functions value.append(values_in[nmin]) e.append(errors_in[nmin]) error.append(errors_in[nmin]) # parvalue item is a string else: # functions, calculate as such # nint must be translated into nmin string = translate(nint,lmin,pardict['function']) p.append(eval(string)) value.append(eval(string)) e.append(eval(string.replace('p','e'))) error.append(eval(string.replace('p','e'))) lmin.append(0) # not needed names.append(name) values_out.append(value) errors_out.append(error) return names, values_out, errors_out # list of parameter values
[docs]def min2int_multigroup(dashboard,p,e): ''' input: userpardicts_guess from dashboard (each dict corresponds to a Minuit parameter) output: for all parameters names list of lists of parameter names used only in summary_global ''' # # initialize # from mujpy.aux.aux import multigroup_in_components # print('min2int_multigroup in aux debug: dash {}'.format(dashboard)) mask_function_multi = multigroup_in_components(dashboard) userpardicts = dashboard['userpardicts_guess'] e = [e[k] if pardict['flag']=='~' else 0 for k,pardict in enumerate(userpardicts)] # names = [pardict['name'] for pardict in userpardicts] model = dashboard['model_guess'] pardicts = [pardict for component in model for pardict in component['pardicts']] ngroups = len(pardicts[mask_function_multi.index(1)]["function_multi"]) nint = -1 # initialize namesg, parsg, eparsg = [], [], [] for l in range(ngroups): nint0 = nint names, pars, epars = [], [], [] for component in model: # scan the model components component_name = component['name'] label = component['label'] nint = nint0 name, par, epar = [], [], [] # inner list, components for j,pardict in enumerate(component['pardicts']): nint += 1 # internal parameter index incremented always if j==0: name.append('{}: {}_{}'.format(component_name,pardict['name'],label)) else: name.append('{}_{}'.format(pardict['name'],label)) if mask_function_multi[nint]>0: par.append(eval(pardict["function_multi"][l])) try: epar.append(eval(pardict["error_propagate_multi"][l])) except: epar.append(eval(pardict["function_multi"][l].replace('p','e'))) else: par.append(eval(pardict["function"])) # the function will be eval-uated inside mucomponents try: epar.append(eval(pardict["error_propagate"])) except: epar.append(eval(pardict["function"].replace('p','e'))) pars.append(par) # middel list, model names.append(name) epars.append(epar) namesg.append(names) parsg.append(pars) eparsg.append(epars) return namesg, parsg, eparsg # list of parameter values
[docs]def model_name(dashboard): ''' input the dashboard dictionary structure output the model name (e.g. 'mgbgbl') ''' return ''.join([item for component in dashboard["model_guess"] for item in component["name"]])
[docs]def userpars(dashboard): ''' checks if there are userpardicts in the fit dashboard used by fit and plt switchyard ''' return "userpardicts_guess" in dashboard
[docs]def multigroup_in_components(dashboard): ''' input full dashboard output mask list, 1 where "model_guess" contains at least one component (dict) whose "pardicts" (list) contains a parameter dict with at least one "function_multi":[string, string ..] key 0 otherwise ''' mask = [] #print('multigroup_in_components aux debug: model_guess len {}'.format(len(dashboard["model_guess"]))) #print('multigroup_in_components aux debug: pardicts len {}'.format(len(dashboard["model_guess"][0]['pardicts']))) #print('multigroup_in_components aux debug: pardict.keys len {}'.format(len(dashboard["model_guess"][0]['pardicts'][0].keys()))) for component in dashboard["model_guess"]: for pardict in component["pardicts"]: for x in pardict.keys(): if x == 'function_multi': k = 1 break else: k = 0 mask.append(k) return mask
# contains 1 for all parameters that have "function_multi", 0 otherwise # return [k for k,component in enumerate(component_function) if component>0]
[docs]def stringify_groups(groups): ''' returns a unique string for many groups to use in json file name ''' strgrp = [] for group in groups: fgroup, bgroup = group['forward'],group['backward'] strgrp.append(fgroup.replace(',','_')+'-'+bgroup.replace(',','_')) return '_'.join(strgrp)
[docs]def tilde_in_components(dashboard): ''' input full dashboard output list of indices where "model_guess" (list) contains at least one component (dict) whose "pardicts" (list) contains a parameter dict with at least one "flag":"~" key Indices refers to a single list of dashboard component parameters The model name (e.g. mgbl) dictates the order ''' component_flags = [pardict["flag"] for component in dashboard["model_guess"] for pardict in component["pardicts"]] indices_tilde = [i for i, x in enumerate(component_flags) if x == "~"] return indices_tilde
[docs]def modelstrip(name): ''' strips numbers of external parameters at beginning of model name ''' import re nexternals, ngroups = 0, 0 # strip the name and extract number of external parameters try: nexternals = int('{}'.format(re.findall('^([0-9]+)',name)[0])) if nexternals>9: return [] name = name[:-1] except: pass # try: # ngroups = int('{}'.format(re.findall('([0-9]+)$',name)[0])) # if ngroups>9: # return [] # name = name[1:] # except: # pass return name, nexternals
############## # MUGUI AUX ? ##############
[docs]def create_model(dashboard): ''' create_model('daml') # adds e.g. the two component 'da' 'ml' model this method does not check syntax (prechecked by checkvalidmodel) separates nexternals number from model name (e.g. '3mgml' -> 'mgml', 3) starts switchyard for A1,A1,B1, B2, C1, C2 fits 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 from mujpy.aux.aux import modelstrip, addcomponent components self.model_guess = [] # start from empty model for k,component in enumerate(components): label = string.ascii_lowercase[k] # was uppercase[k] if not addcomponent(component,dashboard): return False # self.console('create model added {}'.format(component+label)) return True
[docs]def addcomponent(name,label): ''' addcomponent('ml') # adds e.g. a mu precessing, lorentzian decay, component this method adds a component selected from _available_components_(), tuple of directories with zeroed values, error and limits from available_components, flags set to '~' and empty functions [plan also addgroupcomponents and addruncomponents (for A2, B2, C1, C2)] ''' from copy import deepcopy from mujpy.aux.aux import _available_components_ available_components =_available_components_() # creates list automagically from mucomponents component_names = [available_components[i]['name'] for i in range(len(available_components))] if name in component_names: k = component_names.index(name) npar = len(available_components[k]['pardicts']) # number of pars pars = deepcopy(self.available_components[k]['pardicts']) # 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_guess.append() return {'name':name,'pardicts':pars}, None # OK code, no message else: # self.console( error_msg = '\nWarning: '+name+' is not a known component. Not added.' return {}, error_msg # False error code, message
[docs]def chi2std(nu): ''' computes 1 std for least square chi2 ''' import numpy as np from scipy.special import gammainc from scipy.stats import norm mm = round(nu/4) hb = np.linspace(-mm,mm,2*mm+1) cc = gammainc((hb+nu)/2,nu/2) # see mulab: muchi2cdf(x,nu) = gammainc(x/2, nu/2); lc = 1+hb[min(list(np.where((cc<norm.cdf(1))&(cc>norm.cdf(-1))))[0])]/nu hc = 1+hb[max(list(np.where((cc<norm.cdf(1))&(cc>norm.cdf(-1))))[0])]/nu return lc, hc
[docs]def component(model,kin): ''' returns the index of the component to which parameter k belongs in model = self.model_guess, in mugui, a list of complex dictionaries:: [{'name':'da', 'pardicts':{'name':'lpha',...}, {'name':'mg', 'pardicts':{ ... }] kin is the index of a dashboard parameter (kint) ''' from numpy import array, cumsum, argmax ncomp = len(model) # number of components in model npar = array([len(model[k]['pardicts']) for k in range(ncomp)]) # number of parameters of each component npars = cumsum(npar) return argmax(npars>kin)
############# # GENERAL AUX #############
[docs]def calib(dashboard): ''' True if the first component is 'al' ''' return dashboard['model_guess'][0]['name']=='al'
[docs]def derange(string,vmax,int_or_float='int'): ''' derange(string) reads string assuming 2, 3, 4 or 5 csv or space separated values, either int (default) or floats (specify type='float'):: 5: start, stop, packe, last, packl 4: start, stop, last, packl (packe is 1) 3: start, stop, pack 2: start, stop returns 2, 3, 4 or 5 floats or int, or two negative values, if fails either:: validity check (stop>start and bin <stop-start) check for sanity (last value less then a vmax) ''' # print('In derange') try: try: if int_or_float=='float': values = [float(x) for x in string.split(',')] else: values = [int(x) for x in string.split(',')] except: print(string) if len(values)==5: # start, stop, packe, last, packl if values[3]<values[1] or values[4]>values[3]-values[1] or values[2]>values[1]-values[0] or values[1]<values[0] or sum(n<0 for n in values)>0 or values[3]>vmax: return -5,-5 return values[0],values[1],values[2],values[3],values[4] # start, stop, packe, last, packl elif len(values)==4: # start, stop, last, packl if values[2]<values[1] or values[3]>values[2]-values[1] or values[1]<values[0] or sum(n<0 for n in values)>0 or values[2]>vmax: return -4,-4 return values[0],values[1],1,values[2],values[3] # start, stop, packe, last, packl elif len(values)==3: # start, stop, packe if values[2]>values[1] or values[1]<values[0] or sum(n<0 for n in values)>0 or values[1]>vmax: return -3,-3 return values[0],values[1],values[2] # start, stop, pack elif len(values)==2: # start, stop if values[1]<values[0] or sum(n<0 for n in values)>0 or values[1]>vmax: return -2,-2 return values[0],values[1] # start, stop except: # print(string,vmax,int_or_float) return -10,-10
[docs]def derun(string): ''' parses string, producing a list of runs; expects comma separated items looks for 'l','l:m','l+n+m','l:m:-1' where l, m, n are integers also more than one, comma separated rejects all other characters returns a list of lists of integer ''' s = [] try: # systematic str(int(b[])) to check that b[] ARE integers for b in string.split(','): # csv kminus = b.find(':-1') # '-1' means reverse order kcolon = b.find(':') # ':' and '+' are mutually exclusive kplus = b.find('+') #print(kminus,kcolon,kplus) if kminus<0 and kcolon<0 and kplus<0: # single run int(b) # produces an Error if b is not an integer s.append([b]) # append single run string else: if kminus>0 and kminus == kcolon: return [], 'l:-1 is illegal' elif kplus>0: # add files, append a list or run strings ss = [] k0 = 0 while kplus>0: # str(int(b[])) ss.append(int(b[k0:kplus])) k0 = kplus+1 kplus = b.find('+',k0) ss.append(int(b[k0:])) s.append([str(q) for q in ss]) else: # either kminus=-1 (just a range) or kcolon<kminus, (range in reverse order) # in both cases: if kminus<0: #print(int(b[:kcolon]),int(b[kcolon+1:])) if int(b[:kcolon])>int(b[kcolon+1:]): return [], 'l:m must have l<m' for j in range(int(b[:kcolon]),int(b[kcolon+1:])+1): s.append([str(j)]) # append single run strings else: ss = [] # # :-1 reverse order if int(b[:kcolon])>int(b[kcolon+1:kminus]): return ss, 'l:m:-1 must have l<m' for j in range(int(b[:kcolon]),int(b[kcolon+1:kminus])+1): ss.append([str(j)]) # append single run strings ss = ss[::-1] for sss in ss: s.append(sss) return s, None except: return [], 'error to be debugged'
[docs]def findall(p, s): '''Yields all the positions of the pattern p in the string s. Used by translate. ''' i = s.find(p) while i != -1: yield i i = s.find(p, i+1)
[docs]def find_nth(haystack, needle, n): ''' Finds nth needle in haystack Returns its first occurrence (0 if not present) Used by ? ''' start = haystack.rfind(needle) while start >= 0 and n > 1: start = haystack.rfind(needle, 1, start-1) n -= 1 return start
[docs]def get_datafilename(datafile,run): ''' datafilename = template, e.g. '/fullpath/deltat_gps_tdc_0935.bin' run = string of run digits, e.g. '1001' returns '/fullpath/deltat_gps_tdc_1001.bin' ''' import re dot_suffix = datafile[-4:] padded = re.match('.*?([0-9]+)$', datafile[:-4]).group(1) # run string of digits oldrun = str(int(padded)) # strip padding zeros datafileprefix = datafile[:datafile.find(oldrun)] # prefix up to original zero padding if len(run)-len(oldrun)>0: datafilename = datafileprefix[:len(oldrun)-len(run)]+run+dot_suffix elif len(run)-len(oldrun)==-1: datafilename = datafileprefix+'0'+run+dot_suffix elif len(run)-len(oldrun)==-2: datafilename = datafileprefix+'00'+run+dot_suffix elif len(run)-len(oldrun)==-3: datafilename = datafileprefix+'000'+run+dot_suffix else: datafilename = datafileprefix+run+dot_suffix return datafilename
[docs]def get_grouping(groupcsv): """ name = 'forward' or 'backward' * grouping(name) is an np.array wth detector indices * group.value[k] for k=0,1 is a shorthand csv like '1:3,5' or '1,3,5' etc. * index is present mugui.mainwindow.selected_index * out is mugui._output_ for error messages returns * grouping, group, index group and index are changed only in case of errors """ 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 # no more complex nesting (3:5,5,8:10 is not allowed) # get the shorthand from the gui Text groupcsv = groupcsv.replace('.',',') # can only be a mistake: '.' means ',' try: if groupcsv.find(':')==-1: # no colon, it's a pure csv grouping = np.array([int(ss) for ss in groupcsv.split(',')]) # read it else: # colon found if groupcsv.find(',')==-1: # (no commas, only colon, must be n:m) nm = [int(w) for w in groupcsv.split(':')] # read n m grouping = np.array(list(range(nm[0],nm[1]+1))) # single counters else: # general case, mixed csv and colon p = groupcsv.split(':') # '1,2,3,4,6' '7,10,12,14' '16,20,23' ncolon = len(p)-1 grouping = np.array([]) for k in range(ncolon): q = p[k].split(',') # ['1' '2' '3' '4' '6'] if k>0: last = int(q[0]) grouping = np.concatenate((grouping,np.array(list(range(first,last+1))))) first = int(q[-1]) grouping = np.concatenate((grouping,np.array(list(int(w) for w in q[1:-1])))) elif k==0: first = int(q[-1]) grouping = np.concatenate((grouping,np.array(list(int(w) for w in q[:-1])))) q = p[-1].split(',') # '22','25' last = int(q[0]) grouping = np.concatenate((grouping,np.array(list(range(first,last+1))))) grouping = np.concatenate((grouping,np.array(list(int(w) for w in q[1:])))) grouping -=1 # this is counter index, remove 1 for python 0-based indexing except: grouping = np.array([-1]) # error flag return grouping
[docs]def getname(fullname): ''' estracts parameter name from full parameter name (i.e. name + label) for the time being just the first letter ''' return fullname[0]
[docs]def initialize_csv(Bstr, filespec, the_run ): ''' writes beginning of csv row with nrun T [T eT T eT] B for ISIS [PSI] ''' nrun = the_run.get_runNumber_int() if filespec=='bin' or filespec=='mdu': TsTc, eTsTc = the_run.get_temperatures_vector(), the_run.get_devTemperatures_vector() 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)' return form.format(nrun, TsTc[0],eTsTc[0],TsTc[1],eTsTc[1], Bstr[:Bstr.find('G')]) elif filespec=='nxs': Ts = the_run.get_temperatures_vector() n1 = '1' form = '{} {:.' form += '{}'.format(n1) form += 'f} {}' #".format(value) return form.format(nrun, Ts[0], Bstr[:Bstr.find('G')])
[docs]def minparam2_csv(dashboard,values_in,errors_in): ''' input: dashboard dashboard[model_guess"], for single group or None, for multi group multigroup True/False Minuit values Minuit errors output: cvs partial row with parameters and errors ''' from mujpy.aux.aux import min2int, spec_prec # Minuit and user parameters coincide (_, values, errors) = (min2int(dashboard,values_in,errors_in) if dashboard else (None, [values_in], [errors_in])) # from minuit parameters to component parameters # output is lists (components) of lists (parameters) row = '' for parvalues, parerrors in zip(values,errors): for parvalue,parerror in zip(parvalues,parerrors): n1 = spec_prec(parerror) # calculates format specifier precision form = ' {:.' form += '{}'.format(n1) form += 'f} {:.' form += '{}'.format(n1) form += 'f}' row += form.format(parvalue,parerror) return row
[docs]def chi2_csv(chi2,lowchi2,hichi2,groups,offset): ''' input: chi2, chi2-sdt, chi2+sdt, groups, offset (bins) groups is suite.groups and its len, 1 or more, identifies multigroup output: cvs partial row with these values and timestring ''' from time import localtime, strftime 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) for group in groups: row += ' {}'.format(group["alpha"]) row += ' {} {}'.format(offset,strftime("%d.%b.%H:%M:%S", localtime())) return row
[docs]def write_csv(header,row,the_run,file_csv,filespec,scan=None): ''' input : header, the model specific csv header to compare with that of the csv file row, the line to be added to the csv file the_run, run instance (first one for added runs) file_csv, full path/filename to csv file filespec, 'bin', 'mdu' or 'nsx' scan, T, B or None output: two strings to write on console writes onto csv finding the right line writes a new file if csv does not exist or is incompatible (writes ~ version) ''' from mujpy.aux.aux import get_title import os from datetime import datetime nrun = int(row.split(" ")[0]) now = datetime.now() dt_string = now.strftime("%d/%m/%Y %H:%M:%S") if scan==None: # order by nrun, first item in csv csv_index = 0 elif scan=='T': # order by T, 4th or 2nd item in csv csv_index = 3 if filespec == 'bin' or filespec == 'mdu' else 1 else: # order by B, 6th or 4th item in csv csv_index = 5 if filespec == 'bin' or filespec == 'mdu' else 3 rowvalue = float(row.split(" ")[csv_index]) # also nrun is transformed into float if os.path.isfile(file_csv): try: # the file exists lineout = [] # is equivalent to False with open(file_csv,'r') as f_in: notexistent = True # the line dows not exist for nline,line in enumerate(f_in.readlines()): if nline==0: if header!=line: # different headers, substitute present one raise # exits this try else: lineout.append(header) elif float(line.split(" ")[csv_index]) < rowvalue: # append line first lineout.append(line) elif float(line.split(" ")[csv_index]) == rowvalue: # substitute an existing fit notexistent = False else: if notexistent: lineout.append(row) # insert before last existing fit notexistent = False lineout.append(line) # insert all other existing fits if notexistent: lineout.append(row) # append at the end notexistent = False with open(file_csv,'w') as f_out: for line in lineout: f_out.write(line) file_csv = file_csv[file_csv.rfind('/')+1:] return 'Run {}: {} ***'.format(nrun, get_title(the_run)), '. Log added to {}'.format(file_csv) except: # incompatible headers, save backup and write a new file os.rename(file_csv,file_csv+'~') with open(file_csv,'w') as f: f.write(header) f.write(row) file_csv = file_csv[file_csv.rfind('/')+1:] return 'Run {}: {} ***'.format(nrun, get_title(the_run)),'. Log in NEW {} [backup in {}]'.format( file_csv, file_csv+'~') else: # csv does not exist with open(file_csv,'w') as f: f.write(header) f.write(row) file_csv = file_csv[file_csv.rfind('/')+1:] return 'Run {}: {} ***'.format(nrun, get_title(the_run)),'. Log in NEW {}'.format(file_csv)
[docs]def get_title(run,notemp=False,nofield=False): ''' form standard psi title ''' if notemp: return '{} {} {}'.format(run.get_sample(),run.get_orient(),run.get_field()) elif nofield: return '{} {} {}'.format(run.get_sample(),run.get_orient(),run.get_temp()) return '{} {} {} {}'.format(run.get_sample(),run.get_orient(),run.get_field(),run.get_temp())
[docs]def get_run_number_from(path_filename,filespecs): ''' strips number after filespecs[0] and before filespec[1] ''' try: string = path_filename.split(filespecs[0],1)[1] run = string.split('.'+filespecs[1],1)[0] except: run = '-1' return str(int(run)) # to remove leading zeros
[docs]def muvalid(string): ''' parse function CHECK WITH MUCOMPONENT, THAT USES A DIFFERENT SCHEME accepted functions are RHS of agebraic expressions of parameters p[i], i=0...ntot ''' import re error_message = '' if string.strip() !='': # empty and blank strings are validated pattern = re.compile(r"p\[(\d+)\]") # find all patterns p[*] where * is digits test = pattern.sub(r"a",string) # substitute "a" to "p[*]" in s # strindices = pattern.findall(string) # indices = [int(strindices[k]) for k in range(len(strindices))] # in internal parameter list # mindices = ... # produce the equivalent minuit indices try: safetry(test) # should select only safe use (although such a thing does not exist!) except Exception as e: error_message = 'Function: {}. Tested: {}. Wrong or not allowed syntax: {}'.format(string,test,e) return error_message
[docs]def muvaluid(string): ''' Run suite fits: muvaluid returns True/False * checks the syntax for string function corresponding to flag='l'. Meant for pars displaying large changes across the run suite, requiring different migrad start guesses:: # string syntax: e.g. "0.2*3,2.*4,20." # means that for the first 3 runs value = 0.2, # for the next 4 runs value = 2.0 # from the 8th run on value = 20.0 ''' try: value_times_list = string.split(',') last = value_times_list.pop() for value_times in value_times_list: value,times = value_times.split('*') dum, dum = float(value),int(times) dum = float(last) return True except: return False
[docs]def muvalue(lrun,string): ''' Run suite fits: muvalue returns the value for the nint-th parameter of the lrun-th run according to string (corresponding flag='l'). Large parameter change across the run suite requires different migrad start guesses. Probably broken! ''' # string syntax: e.g. "0.2*3,2.*4,20." # means that for the first 3 runs value = 0.2, # for the next 4 runs value = 2.0 # from the 8th run on value = 20.0 value = [] for value_times in string.split(','): try: # if value_times contains a '*' value,times = value_times.split('*') for k in range(int(times)): value.append(float(value)) except: # if value_times is a single value for k in range(len(value),lrun): value.append(float(value_times)) # cannot work! doesn't check for syntax, can be broken; this returns a list that doesn't know about lrun return value[lrun]
[docs]def muzeropad(runs,nzeros=4): ''' runs is a string containing the run number nzeros the number of digit chars in the filename PSI bin: nzeros=4 ISIS nxs nzeros=8 returns the runs string with left zero padding to nzeros digits ''' zeros='0'*nzeros if len(runs)<len(zeros): return zeros[:len(zeros)-len(runs)]+runs elif len(runs)==len(zeros): return runs
[docs]def path_file_dialog(path,spec): import tkinter from tkinter import filedialog import os here = os.getcwd() os.chdir(path) tkinter.Tk().withdraw() # Close the root window spc, spcdef = '.'+spec,'*.'+spec in_path = filedialog.askopenfilename(initialdir = path,filetypes=((spc,spcdef),('all','*.*'))) os.chdir(here) return in_path
[docs]def path_dialog(path,title): import tkinter from tkinter import filedialog import os tkinter.Tk().withdraw() # Close the root window in_path = filedialog.askdirectory(initialdir = path,title = title) return in_path
################ # PLOT METHODS # ################
[docs]def plot_parameters(nsub,labels,fig=None): ''' standard plot of fit parameters vs B,T (or X to be implemente) input nsub<6 is the number of subplots labels is a dict of labels, e.g. {title:self.title, xlabel:'T [K]', ylabels: ['asym',r'$\lambda$',r'$\sigma$,...]} fig is the standard fig e.g self.fig_pars output the ax array on which to plot one dimensional (from top to bottom and again, for two columns) example two asymmetry parameters are both plotfal=1 and are plotted in ax[0] a longitudinal lambda is plotflag=2 and is plotted in ax[1] ... a transverse sigma is plotflag=n and is plotted in ax[n-1] ''' import matplotlib.pyplot as P nsubplots = nsub if nsub!=5 else 6 # nsub = 5 is plotted as 2x3 # select layout, 1 , 2 (1,2) , 3 (1,3) , 4 (2,2) or 6 (3,2) nrc = { '1':(1,[]), '2':(2,1), '3':(3,1), '4':(2,2), '5':(3,2), '6':(3,2) } figsize = { '1':(5,4), '2':(5,6), '3':(5,8), '4':(8,6), '5':(8,8), '6':(8,8) } spaces = { '1':[], '2':{'hspace':0.05,'top':0.90,'bottom':0.09,'left':0.13,'right':0.97,'wspace':0.03}, '3':{'hspace':0.05,'top':0.90,'bottom':0.09,'left':0.08,'right':0.97,'wspace':0.03}, '4':{'hspace':0.,'top':0.90,'bottom':0.09,'left':0.08,'right':0.89,'wspace':0.02}, '5':{'hspace':0.,'top':0.90,'bottom':0.09,'left':0.08,'right':0.89,'wspace':0.02}, '6':{'hspace':0.,'top':0.90,'bottom':0.09,'left':0.08,'right':0.89,'wspace':0.02} } if fig: # has been set to a handle once fig.clf() if nrc[str(nsub)][1]: # not a single subplot fig,ax = P.subplots(nrc[str(nsub)][0],nrc[str(nsub)][1], figsize=figsize[str(nsub)],sharex = 'col', num=fig.number) # existed, keep the same number fig.subplots_adjust(**spaces[str(nsub)]) # fine tune in dictionaries else: # single subplot fig,ax = P.subplots(nrc[str(nsub)][0], figsize=figsize['1'], num=fig.number) # existed, keep the same number else: # handle does not exist, make one if nrc[str(nsub)][1]: # not a single subplot fig,ax = P.subplots(nrc[str(nsub)][0],nrc[str(nsub)][1], figsize=figsize[str(nsub)],sharex = 'col') # first creation fig.subplots_adjust(**spaces[str(nsub)]) # fine tune in dictionaries else: # single subplot fig,ax = P.subplots(nrc[str(nsub)][0], figsize=figsize['1']) # first creation fig.canvas.manager.set_window_title('Fit parameters') # the title on the window bar fig.suptitle(labels['title']) # the sample title axout=[] axright = [] if nsubplots>3: # two columns (nsubplots=6 for nsub=5) ax[-1,0].set_xlabel(labels['xlabel']) # set right xlabel ax[-1,1].set_xlabel(labels['xlabel']) # set left xlabel nrows = int(nsubplots/2) # (nsubplots=6 for nsub=5), 1, 2, 3 # for k in range(0,nrows-1): # ax[k,0].set_xticklabels([]) # no labels on all left xaxes but the last # ax[k,1].set_xticklabels([]) # no labels on all right xaxes but the last for k in range(nrows): axright.append(ax[k,1].twinx()) # creates replica with labels on right axright[k].set_ylabel(labels['ylabels'][nrows+k]) # right ylabels ax[k,0].set_ylabel(labels['ylabels'][k]) # left ylabels axright[k].tick_params(left=True,direction='in') # ticks in for right subplots ax[k,0].tick_params(top=True,right=True,direction='in') # ticks in for x axis, right subplots ax[k,1].tick_params(top=True,left=False,right=False,direction='in') # ticks in for x axis, right subplots ax[k,1].set_yticklabels([]) axout.append(ax[k,0]) # first column for k in range(nrows): axout.append(axright[k]) # second column axout is a one dimensional list of axis else: # one column ax[-1].set_xlabel(labels['xlabel']) # set xlabel for k in range(nsub-12): ax[k].set_xticklabels([]) # no labels on all xaxes but the last for k in range(nsub): ylab = labels['ylabels'][k] if isinstance(ylab,str): # ylab = 1 for empty subplots ax[k].set_ylabel(ylab) # ylabels ax[k].tick_params(top=True,right=True,direction='in') # ticks in for right subplots axout = ax # just one column return fig, axout
[docs]def plotile(x,xdim=0,offset=0): ''' Produces a tiled plot, in the sense of np.tile e.g. :: x.shape = (1,1000) y.shape = (4,1000) xt = plotile(x,4) yt = plotile(y,offset=0.1) ''' # x is an array(x.shape[0],x.shape[1]) # xoffset is a step offset # xdim = x.shape[0] if xdim == 0 else xdim # each row is shifted by xoffset*n, where n is the index of the row # # from copy import deepcopy from numpy import tile, arange xt = deepcopy(x) if xdim != 0: # x is a 1D array, must be tiled to xdim xt = tile(xt,(int(xdim),1)) if offset != 0: xt += tile(offset*arange(xt.shape[0]),(x.shape[1],1)).transpose() return xt
[docs]def set_bar(n,b): ''' service to animate histograms e.g. in the fit tab extracted from matplotlib animate histogram example ''' from numpy import array, zeros, ones import matplotlib.path as path # get the corners of the rectangles for the histogram left = array(b[:-1]) right = array(b[1:]) bottom = zeros(len(left)) top = bottom + n nrects = len(left) # here comes the tricky part -- we have to set up the vertex and path # codes arrays using moveto, lineto and closepoly # for each rect: 1 for the MOVETO, 3 for the LINETO, 1 for the # CLOSEPOLY; the vert for the closepoly is ignored but we still need # it to keep the codes aligned with the vertices nverts = nrects*(1 + 3 + 1) verts = zeros((nverts, 2)) codes = ones(nverts, int) * path.Path.LINETO codes[0::5] = path.Path.MOVETO codes[4::5] = path.Path.CLOSEPOLY verts[0::5, 0] = left verts[0::5, 1] = bottom verts[1::5, 0] = left verts[1::5, 1] = top verts[2::5, 0] = right verts[2::5, 1] = top verts[3::5, 0] = right verts[3::5, 1] = bottom xlim = [left[0], right[-1]] return verts, codes, bottom, xlim
[docs]def set_fig(num,nrow,ncol,title,**kwargs): # unused? perhaps delete? check first ''' num is figure number (static, to keep the same window) nrow, ncol number of subplots rows and columns kwargs is a dict of keys to pass to subplots as is initializes figures when they are first called or after accidental killing ''' import matplotlib.pyplot as P fig,ax = P.subplots(nrow, ncol, num = num, **kwargs) fig.canvas.manager.set_window_title(title) return fig, ax
############### # END OF PLOT # ###############
[docs]def rebin(x,y,strstp,pack,e=None): ''' input: x is 1D intensive (time) y [,e] are 1D, 2D or 3D intensive arrays to be rebinned pack > 1 is the rebinning factor, e.g it returns:: xr = array([x[k*pack:k*(pack+1)].sum()/pack for k in range(int(floor((stop-start)/pack)))]) strstp = [start,stop] is a list of slice indices rebinning of x, y [,e] is done on the slice truncated to the approrpiate pack multiple, stopm x[start:stopm], y[start:stopm], [e[start:stopm]] use either:: xr,yr = rebin(x,y,strstp,pack) or:: xr,yr,eyr = rebin(x,y,strstp,pack,ey) # the 5th is y error ''' from numpy import floor, sqrt, empty start,stop = strstp m = int(floor((stop-start)/pack)) # length of rebinned xb mn = m*pack # length of x slice xx =x[start:start+mn] # slice of the first 1d array xx = xx.reshape(m,pack) # temporaty 2d array xr = xx.sum(1)/pack # rebinned first ndarray if len(y.shape)==1: yb = empty(m) yy = y[start:start+mn] # slice row yy = yy.reshape(m,pack) # temporaty 2d yr = yy.sum(1)/pack # rebinned row if e is not None: ey = e[start:start+mn] # slice row ey = ey.reshape(m,pack) # temporaty 2d er = sqrt((ey**2).sum(1))/pack # rebinned row - only good for ISIS elif len(y.shape)==2: nruns = y.shape[0] # number of runs yr = empty((nruns,m)) if e is not None: er = empty((nruns,m)) for k in range(nruns): # each row is a run yy = y[k][start:start+mn] # slice row yy = yy.reshape(m,pack) # temporaty 2d yr[k] = yy.sum(1)/pack # rebinned row if e is not None: ey = e[k][start:start+mn] # slice row ey = ey.reshape(m,pack) # temporaty 2d er[k] = sqrt((ey**2).sum(1))/pack # rebinned row elif len(y.shape)==3: ngroups,nruns = y.shape[0:2] # number of groups, runs yr = empty((ngroups,nruns,m)) if e is not None: er = empty((ngroups,nruns,m)) for k in range(ngroups): for j in range(nruns): yy = y[k][j][start:start+mn] # slice row yy = yy.reshape(m,pack) # temporaty 2d yr[k][j] = yy.sum(1)/pack # rebinned row if e is not None: ey = e[k][j][start:start+mn] # slice row ey = ey.reshape(m,pack) # temporaty 2d er[k][j] = sqrt((ey**2).sum(1))/pack # rebinned row if e is not None: return xr,yr,er else: return xr,yr
[docs]def rebin_decay(x,yf,yb,bf,bb,ybf,ybm,strstp,pack): ''' input: x is 1D intensive (time) yf, yb 1D, 2D, 3D extensive arrays to be rebinned bf, bb, yfm, yfb are scalars (see musuite.single_for_back_counts) pack > 1 is the rebinning factor, e.g it returns:: xr = array([x[k*pack:k*(pack+1)].sum()/pack for k in range(int(floor((stop-start)/pack)))]) yr = array([y[k*pack:k*(pack+1)].sum() for k in range(int(floor((stop-start)/pack)))]) strstp = [start,stop] is a list of slice indices rebinning of x,y is done on the slice truncated to the approrpiate pack multiple, stopm x[start:stopm], y[start:stopm] use:: xr,yfr, ybr, bfr, bbr, yfmr, ybmr = rebin(x,yf,yb,bf,bb,yfm,ybm,strstp,pack) ''' from numpy import floor, sqrt, exp, empty, mean from mujpy.aux.aux import TauMu_mus start,stop = strstp m = int(floor((stop-start)/pack)) # length of rebinned xb mn = m*pack # length of x slice xx =x[start:start+mn] # slice of the first 2D array xx = xx.reshape(m,pack) # temporaty 2d array xr = xx.sum(1)/pack # rebinned first ndarray if len(yf.shape)==1: yfr = empty(m) ybr = empty(m) yfr = yf[start:start+mn] # slice row ybr = yb[start:start+mn] # slice row yfr = yfr.reshape(m,pack) # temporaty 2d ybr = ybr.reshape(m,pack) # temporaty 2d yfr = yfr.sum(1) # rebinned row extensive ybr = ybr.sum(1) # rebinned row extensive elif len(yf.shape)==2: nruns = yf.shape[0] # number of runs yfr = empty((nruns,m)) for k in range(nruns): # each row is a run yyf = yf[k][start:start+mn] # slice row yyf = yyf.reshape(m,pack) # temporaty 2d yfr[k] = yyf.sum(1) # rebinned row extesive yyb = yb[k][start:start+mn] # slice row yyb = yyb.reshape(m,pack) # temporaty 2d ybr[k] = yyb.sum(1) # rebinned row extesive elif len(yf.shape)==3: ngroups,nruns = yf.shape[0:2] # number of runs yfr = empty((ngroups,nruns,m)) for k in range(ngroups): for j in range(nruns): yyf = yf[k][j][start:start+mn] # slice row yyf = yyf.reshape(m,pack) # temporaty 2d yfr[k][j] = yyf.sum(1) # rebinned row extesive yyb = yb[k][j][start:start+mn] # slice row yyb = yyb.reshape(m,pack) # temporaty 2d ybr[k][j] = yyb.sum(1) # rebinned row extesive bfr, bbr = bf*pack, bb*pack yfmr, ybmr = mean((yfr-bfr)*exp(xr/TauMu_mus())), mean((yfr-bfr)*exp(xr/TauMu_mus())) return xr,yfr,ybr,bfr,bbr,yfmr,ybmr
[docs]def safetry(string): ''' Used by muvalid ''' from math import acos,asin,atan,atan2,ceil,cos,cosh,degrees,e,exp,floor,log,log10,pi,pow,radians,sin,sinh,sqrt,tan,tanh safe_list = ['a','acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'degrees', 'e', 'exp', 'floor', 'log', 'log10', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'] # use the list to filter the local namespace a = 0.3 safe_dict={} for k in safe_list: safe_dict[k]=locals().get(k) # print(safe_dict[k]) return eval(string,{"__builtins__":None},safe_dict)
[docs]def scanms(y,n): # produces guess for hifi t=0 bin, to be fed to a step fit function # check running average of (n bins,n skips,n bins) # with two means m1,m2 and two variances s21,s22, against step pattern # compares m2-m1 with sqrt(s21+s22) from numpy import sqrt istart = [] istop = [] for k in range(y.shape[0]-n): m1,m2 = y[k:k+n].sum()/n, y[k+2*n:k+3*n].sum()/n s = sqrt(((y[k:k+n]-m1)**2).sum()/(n-1)+ ((y[k+2*n:k+3*n]-m2)**2).sum()/(n-1)) if m2-m1>s: if not istart: istart = k+n elif not istop: istop = k+n elif istop == k+n-1: istop = k+n if istop and istart: if istop-istart == n: return istop return -1
[docs]def spec_prec(a): ''' format specifier precision:: 0 for a > 1.0 1 for 1.0 > a > 0.1 2 for 0.1 > a > 0.01 etc. ''' import numpy as np return int(abs(min(0.,np.floor(np.log10(a)))))
[docs]def shorten(path,subpath): ''' shortens path e.g. path, subpath = '/home/myname/myfolder', '/home/myname' shart = './myfolder' ''' short = path.split(subpath) if len(short)==2: short = '.'+short[1] return short
[docs]def exit_safe(): ''' opens an are you sure box? ''' from tkinter.messagebox import askyesno answer = askyesno(title='Exit mujpy', message='Really quit?') return answer
[docs]def step(x,a,n,dn,b): from scipy.stats import norm # error function as step function for t=0 in HIFI return a+b*norm.cdf(x,n,dn)
[docs]def tlog_exists(path,run,ndigits): ''' check if tlog exists under various known filenames types ''' import os filename_psibulk = 'run_'+muzeropad(run,ndigits)+'.mon' # add definitions for e.g. filename_isis ok = os.path.exists(os.path.join(path,filename_psibulk)) # or os.path.exists(os.path.join(paths,filename_isis)) return ok
[docs]def translate(nint,lmin,function_in): ''' input: nint: dashbord index, lmin: list of minuit indices replacement, one for each dashboard index, -1 is blanck function: single function string, of dashboard index nint, to be translated output: function_out: single translated function Used in int2_int and min2int to replace parameter indices contained in function[nint] e.g. :: p[0]*2+p[3] by translate the internal parameter indices 0 and 3 (written according to the dashboard dict order) into the corresponding minuit parameter list indices, skipping shared parameters. e.g. if parameter 1 is shared with parameter 0, the minuit parameter index 3 will be translated to 2 (skipping internal index 1) ''' from copy import deepcopy # print(' nint = {}, lmin = {}\n{}'.format(nint,lmin,function_in)) function_out = deepcopy(function_in) # search for integers between '[' and ']' start = [i+1 for i in findall('[',function_out)] # finds index of number after all occurencies of '[' stop = [i for i in findall(']',function_out)] # same for ']' nints = [function_out[i:j] for (i,j) in zip(start,stop)] # this is a list of strings with the numbers to be replaced nmins = [lmin[int(function_out[i:j])] for (i,j) in zip(start,stop)] # replacements integers for lstr,m in zip(nints,nmins): function_out = function_out.replace(lstr,str(m)) return function_out
[docs]def translate_nint(nint,lmin,function): # NOT USED any more?!! ''' Used in int2_int and min2int to parse parameters contained in function[nint].value e.g. :: p[4]*2+p[7] and translate the internal parameter indices 4 and 7 (written according to the gui parameter list order) into the corresponding minuit parameter list indices, that skips shared and fixed parameters. e.g. if parameter 6 is shared with parameter 4 and parameter 2 is fixed, the minuit parameter indices will be 3 instead of 4 (skipping internal index 2) and 5 instead of 7 (skipping both 2 and 6) Returns lmin[nint] ''' string = function[nint].value # search for integers between '[' and ']' start = [i+1 for i in findall('[',string)] # finds index of number after all occurencies of '[' stop = [i for i in findall(']',string)] # same for ']' nints = [string[i:j] for (i,j) in zip(start,stop)] # this is a list of strings with the numbers nmins = [lmin[int(string[i:j])] for (i,j) in zip(start,stop)] return nmins
[docs]def value_error(value,error): ''' value_error(v,e) returns a string of the format v(e) ''' from numpy import floor, log10, seterr eps = 1e-10 # minimum error if error>eps: # normal error exponent = int(floor(log10(error))) most_significant = int(round(error/10**exponent)) if most_significant>9: exponent += 1 most_significant=1 exponent = -exponent if exponent<0 else 0 form = '"{:.' form += '{}'.format(exponent) form += 'f}({})".format(value,most_significant)' else: if abs(value)<eps: form = 'print("0(0)")' # too small both else: form = 'print("{}(0)".format(value))' # too small error return eval(form)