# coding=utf-8
# the above line is for python 2 compatibility
# write suite suite [1.0] in progress: inserted suite load, asym as a matrix, fft on matrix CHECK!!!
######################################################
# 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]] (the latter adds data of two runs)
# non global | False | False | >1
# global (TODO) | True | False | >1
#-------------------------------------------------------
# asymmetry loads
# single runs, both for single and for non global fit
# run suites, both for global fit and for multiplot
###############################################################
# another dialog to explore
#import tkinter
#from tkinter import simpledialog
#move = simpledialog.askstring("Pause","hit return when ready")
#simpledialog.mainloop(0)
[docs]class mugui(object):
##########################
# INIT
##########################
def __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
'''
import numpy as np
import os
from scipy.constants import physical_constants as C
from mujpy import __file__ as MuJPyName
from IPython.display import display
# check where constants are needed and how to define them
self.TauMu_mus = 2.1969811 # numbers are from Particle Data Group 2017
self.TauPi_ns = 2.6033 # numbers are from Particle Data Group 2017
self.gamma_Mu_MHzperT = 3.183345142*C['proton gyromag. ratio over 2 pi'][0] # numbers are from Particle Data Group 2017
self.gamma_e_MHzperT = C['electron gyromag. ratio over 2 pi'][0]
# end check constants
# initializations
self.offset0 = 7 # initial value
self.offset = [] # this way the first run load calculates get_totals with self.offset0
self.firstbin = 0
self.second_plateau = 100
self.peakheight = 100000.
self.peakwidth = 1. # broad 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 0?
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 = 'lightgreen'
self.button_color_off = 'gray'
#####################################
# actually produces the gui interface
#####################################
self.gui()
self.output() # this calls output(self) that defines self._output_
self.setup()
self.suite()
self.fit()
self.fft()
self.plots()
#####################################
# static figures
########################## self.fig_fit = [] # initialize to false, it will become a pyplot.subplots instance
###########
self.fig_fit = [] # initialize to false, it will become a pyplot.subplots instance
self.fig_fft = [] # initialize to false, it will become a pyplot.subplots instance
self.fig_multiplot = []
self.fig_counters = []
# self.graph_fit = Toplevel()
# canvas_fit = FigureCanvas(self.fig_fit, master=self.graph_fit)
# canvas_fit.get_tk_widget().pack()
# self.toolbar_fit = Navig(canvas_fit,self.graph_fit)
# self.toolbar_fit.update()
# self.graph_fit.withdraw() # self.graph_fit.deiconify() brings it back up
# self fft
# self.tlog
self.about()
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?
def _eval(string):
'''
yes, I know eval is evil, but
mujpy users with jupyter have already full control
of the machine, hence I do not care!
**** (UNUSED!) *****
BTW exec is used even by python for good: see input!
'''
try:
return eval(string)
except Exception as e:
print(e)
##########################
# 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='100%',height='170px'),
disabled=True)
# now collect the handles of the three horizontal frames to the main fit window (see tabs_contents for index)
self.mainwindow.children[6].children = [_about_area] # add the list of widget handles as the third tab, fit
##########################
# ASYMMETRY
##########################
[docs] def asymmetry(self):
"""
defines self.time
generates asymmetry end error without rebinning
all are 2D numpy arrays,
shape of time is (1,nbins), of asymm, asyme are (nruns, nbins)
* self._the_runs_ is a list of lists of musr2py 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 for ok and -1 for error
"""
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
for k, runs in enumerate(self._the_runs_):
yforw = np.zeros(time.shape[0]) # counts with background substraction
cforw = np.zeros(time.shape[0]) # pure counts for Poisson errors
ybackw = np.zeros(time.shape[0]) # counts with background substraction
cbackw = np.zeros(time.shape[0]) # pure counts for Poisson errors
for j, run in enumerate(runs):
for counter in self.grouping['forward']:
n1, n2 = self.nt0[counter]+self.offset.value, self.nt0[counter]+self.offset.value+self.histoLength
histo = run.get_histo_array_int(counter)
background = np.mean(histo[self.firstbin:self.lastbin])
yforw += histo[n1:n2]-background
cforw += histo[n1:n2]
for counter in self.grouping['backward']:
n1, n2 = self.nt0[counter]+self.offset.value, self.nt0[counter]+self.offset.value+self.histoLength
histo = run.get_histo_array_int(counter)
background = np.mean(histo[self.firstbin:self.lastbin])
ybackw += histo[n1:n2]-background
cbackw += histo[n1:n2]
yplus = yforw + self.alpha.value*ybackw
x = np.exp(-time/self.TauMu_mus)
enn0 = np.polyfit(x,yplus,1)
enn0 = enn0[0] # initial rate per ns
y = (yforw-self.alpha.value*ybackw)/enn0*np.exp(time/self.TauMu_mus) # since self.time is an np.arange, this is a numpy array
ey = np.sqrt(cforw + self.alpha.value**2*cbackw)*np.exp(time/self.TauMu_mus)/enn0 # idem
ey[np.where(ey==0)] = 1 # substitute zero with one 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)
######################################################
##########################
# CREATE_RUNDICT
##########################
[docs] def create_rundict(self,k=0):
'''
creates a dictionary 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
##########################
# GUI
##########################
[docs] def gui(self):
'''
gui layout
Executed only once
It designs an external frame,
the logo and title header
the tab structure.
At the end (Araba.Phoenix) the method redefines self.gui
as a Vbox named 'whole',
that contains the entire gui structure
'''
from ipywidgets import Image, Text, Layout, HBox, Output, VBox, Tab
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)
self.title = Text(description='run title', value='none yet',layout=Layout(width='55%'),disabled=True)
self._the_runs_display = Text(description='run number',value='no run',layout=Layout(width='45%'),disabled=True)
title_content = [self._the_runs_display, self.title]
titlerow = HBox(description='Title')
titlerow.children = title_content
comment_box = HBox(description='comment',layout=Layout(width='100%'))
self.comment_handles = [Text(description='Comment',layout=Layout(width='46%'),disabled=True),
Text(description='Start date',layout=Layout(width='27%'),disabled=True),
Text(description='Stop date',layout=Layout(width='27%'),disabled=True)]
comment_box.children = self.comment_handles
counts = ['Total counts', 'Group counts','ns/bin'] # needs an HBox with three Text blocks
self.totalcounts = Text(value='0',description='Total counts',layout=Layout(width='30%'),disabled=True)
self.groupcounts = Text(value='0',description='Group counts',layout=Layout(width='30%'),disabled=True)
self.nsbin = Text(description='ns/bin',layout=Layout(width='20%'),disabled=True)
self.maxbin = Text(description='Last bin',layout=Layout(width='20%'),disabled=True)
secondrow = HBox(description='counts',layout=Layout(width='100%'))
secondrow.children = [self.totalcounts, self.groupcounts, self.nsbin, self.maxbin]
titlewindow = VBox()
titlewindow_content = [titlerow, comment_box, secondrow] # ,thirdrow (moved to 4th tab)
titlewindow.children = titlewindow_content
titlelogowindow = HBox()
titlelogowindow_content = [logo, titlewindow]
titlelogowindow.children = titlelogowindow_content
# main layout: tabs
tabs_contents = ['setup', 'suite', 'fit', 'output', 'fft', 'plots', 'about']
tabs = [VBox(description=name,layout=Layout(border='solid')) for name in tabs_contents]
self.mainwindow = Tab(children = tabs,layout=Layout(width='99.8%')) # '99.6%' works
self.mainwindow.selected_index = 0 # to stipulate that the first display is on tab 0, 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='100%'))
self.gui.children = [titlelogowindow, self.mainwindow]
##########################
# FFT
##########################
[docs] def fft(self):
'''
fft tab of mugui
'''
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 tun (never checke yet)
insert expected noise level (see bottom comment)
'''
import numpy as np
from mujpy.aux.aux import derange, derange_int, 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],
# with rangetup[1]rangetup = derange(self.fit_range.value),
if not self._the_model_._alpha_:
with self._output_:
self.mainwindow.selected_index = 3
print('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]
rangetup = derange_int(self.fit_range.value)
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) # 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
# with self._output_:
# print("start={},stop={},f={}".format(start, stop, f))
########################
# 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
########################
with self._output_:
fftf_amplitude[0], p0, p1 = autops(fftf_amplitude[0],'acme') # fix phase on theory
fft_amplitude[0] = ps(fft_amplitude[0], p0=p0 , p1=p1).real # apply it to data
for k in range(1,fft_amplitude.shape[0]):
fft_amplitude[k] = ps(fft_amplitude[k], p0=p0 , p1=p1)
fftf_amplitude[k] = ps(fftf_amplitude[k], 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
returnedtup = derange(fft_range.value) # 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, FloatText, 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%'))
fft_button.style.button_color = self.button_color
fft_button.on_click(on_fft_request)
filter0 = 0.3
fft_filter = Text(description='Filter ($\mu s^{-1}$)',
value='{:.4f}'.format(filter0),
layout=Layout(width='20%'),
continuous_update=False) # self.filter.value
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)
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%'))
residues_or_asymmetry = Dropdown(options=['Residues','Asymmetry'],
value='Residues',
layout=Layout(width='13%'))
autophase = Checkbox(description='Autophase',
value=True,
layout=Layout(width='15%'))
autophase.style.description_width='10%'
anim_check = Checkbox(description='Animate',value=True, layout=Layout(width='12%'))
anim_check.style.description_width = '1%'
anim_delay = IntText(description='Delay (ms)',value=1000, layout=Layout(width='20%'))
anim_stop_start = ToggleButton(description='start/stop',value=True)
anim_stop_start.observe(on_start_stop,'value')
anim_stop_start.style.button_color = self.button_color
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])])
# now collect the handles of the three horizontal frames to the main fit window
self.mainwindow.children[4].children = [fft_frame_handle]
# add the list of widget handles as the third tab, fit
##########################
# FIT
##########################
[docs] def fit(self, model_in = 'daml'): # 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 missing)
to select parameters value, fix, function, fft subtract check
'''
# 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, safetry
# 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 mucompon....ents 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.mainwindow.selected_index = 3
with self._output_:
print ('\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:
with self._output_:
print ('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(),self.alpha.value,e=ey)
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_int, 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
###################
# 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
######################################################
# pars is a list of lists of best fit parameter values
# self.time is a 1D array
# self.asymm, self.asyme are 2D arrays
# y, ey, f, fres, ylate, eylate, fl, flres, yfit, eyfit, ffit are 2D arrays
# tf, tfl, tlate, tfit are 1D array
##############################
# plot according to plot_range
##############################
returntup = derange_int(self.plot_range.value)
if sum(n<0 for n in returntup)>0:
tmp = self.plot_range.value
self.plot_range.value = self.plot_range0
self.plot_range.background_color = "mistyrose"
self.mainwindow.selected_index = 3
with self._output_:
print('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_:
# print('global, mumodel load_data')
self._the_model_._load_data_(self.time[0],self.asymm,int2_int(),
self.alpha.value,e=self.asyme,
_nglobal_=self.nglobals,_locals_=self.locals)
else:
# print('no global, mumodel load_data')
self._the_model_._load_data_(self.time[0],self.asymm[0],int2_int(),self.alpha.value,e=self.asyme[0])
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_int(self.fit_range.value) # 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_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)
else: # 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)].set_yticklabels([])
self.ax_fit[(1,1)].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)].set_yticklabels(['']*len(self.ax_fit[(1,-1)].get_yticks()))
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 = '$\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.2,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)
if not plot:
for k in range(len(self.fitargs)):
write_csv(chi2fit[k],lc[k],hc[k],k) # writes csv file
with self._output_:
path = save_fit(k) # saves .fit file
if path.__len__ != 2:
# assume path is a path string, whose length will be definitely > 2
print('chi2r = {:.4f} ({:.4f} - {:.4f}), saved in {}'.format(chi2fit[k],lc[k],hc[k],path))
else:
# assume path is a tuple containing (path, exception)
print('Could not save results in {}, error: {}'.format(path[0],path[1]))
# 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
##############
yoffset = 0.05
ymax = yoffset*fres.max()
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, full suite
tftile, ftile = plotile(tf,y.shape[0],offset=xoffset), plotile(f,offset=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()/20.
t0 = np.array([0,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(ttile[k],
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=0.3,color=color)
if not self._single_:
self.ax_fit[(0,0)].text(t1,y0.max(),str(self.nrun[k]))
self.ax_fit[(1,0)].plot(ttile[k],yres[k],'-',lw=1.0,alpha=0.3,zorder=2,color=color) # residues
self.ax_fit[(0,0)].plot(tftile[k],ftile[k],'-',lw=1.5,alpha=0.5,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
############################
# plot second window, if any
############################
if len(returntup)==5:
tltile, yltile, ylres = plotile(tlate,xdim=ylate.shape[0],offset=xoffset), plotile(ylate,xoffset=xoffset), plotile(ylate-freslate,offset=rmax) # plot arrays, full suite
tfltile, fltile = plotile(tfl,fl,fl,fl,
yoffset=foffset,xoffset=xoffset) # res offset is 0.03 = 0.1-0.07
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(tltile[k],yltile[k],yerr=eylate[k],
fmt='o',elinewidth=1.0,
mec=color,mfc=color,ecolor=color,ms=2.0,alpha=0.5) # data
self.ax_fit[(1,1)].plot(tltile[k],ylres[k],'-',lw=1.0,alpha=0.3,zorder=2,color=color) # residues
self.ax_fit[(0,1)].plot(tfl[0],fl_tile,'-',lw=1.5,alpha=0.5,zorder=2,color=color) # fit
self.ax_fit[(0,1)].set_xlim(tlate[0,0], tlate_tile[-1,-1])
self.ax_fit[(1,1)].plot(tlate,tlate-tlate,'k-',lw=0.5,alpha=0.3,zorder=0) # zero line
###############################
# set title, labels
###############################
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)].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
if not plot:
write_csv(chi2fit,lc,hc,0) # writes csv file
with self._output_:
path = save_fit(0) # saves .fit file
if path.__len__ != 2:
# assume path is a path string, whose length will be definitely > 2
print('chi2r = {:.4f} ({:.4f} - {:.4f}), saved in {}'.format(chi2fit,lc,hc,path))
else:
# assume path is a tuple containing (path, exception)
print('Could not save results in {}, error: {}'.format(path[0],path[1]))
string = '$\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.2,string)
else:
self.ax_fit[(0,0)].set_title(self.title.value)
########################
# chi2 distribution: fit
########################
fittup = derange_int(self.fit_range.value) # 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 = 0.
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
if not plot:
write_csv(chi2fit,lc,hc,k) # writes csv file
with self._output_:
path = save_fit(k) # saves .fit file
if path.__len__ != 2:
# assume path is a path string, whose length will be definitely > 2
print('chi2r = {:.4f} ({:.4f} - {:.4f}), saved in {}'.format(chi2fit,lc,hc,path))
else:
# assume path is a tuple containing (path, exception)
print('Could not save results in {}, error: {}'.format(path[0],path[1]))
pedice = '_{'+str(self.nrun[k])+'}'
string = '$\chi^2'+pedice+'=$ {:.3f}'.format(chi2fit)
self.ax_fit[(0,-1)].text(0.02,ychi,string)
ychi += ymax
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.mainwindow.selected_index = 3 # focus on output tab
self.fig_fit.canvas.manager.window.tkraise()
P.draw()
def int2_int():
'''
From internal parameters to the minimal representation
for the use of mucomponents._add_.
Invoked 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 # initializec\c
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)
# 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)})
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']})
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)})
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']})
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)})
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
path_and_filename = path_file_dialog(self.paths[2].value) # returns the full path and filename
if path_and_filename == '':
return
#with self._output_:
# print('Loaded fit results from: {}'.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
#with self._output_:
# print(fit_dict)
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.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[0].value = fit_dict['self.load_handle[0].value']
except Exception as e:
with self._output_:
print('Problems with reading {} file\n\nException: {}'.format(path_and_filename,e))
self.mainwindow.selected_index = 3
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 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)
except:
change['owner'].value = '{:.4f}'.format(alpha0)
def on_fit_request(b):
'''
retrieve data from the gui dashboard:
parameters values (parvalue[nint].value), flags (flag[nint].value),
errors, limits, functions (function[nint].value), self.alpha.value, range and pack
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_int, rebin, norun_msg, get_title
###################
# error: no run yet
###################
if not self._the_runs_:
norun_msg(self._output_) # writes a message in self._output
self.mainwindow.selected_index = 3
else:
###################
# run loaded
###################
self.asymmetry() # prepare asymmetry
# self.time is 1D asymm, asyme can
pack = 1 # initialize default
returntup = derange_int(eval('self.fit_range.value'))
if len(returntup)==3: #
start, stop, pack = returntup
elif len(returntup)==0:
with self._output_:
print('Empty ranges. Choose fit/plot range')
self.mainwindow.selected_index = 3
else:
start, stop = returntup
time,asymm,asyme = rebin(self.time,self.asymm,[start,stop],pack,e=self.asyme)
level = 1
self.fitargs = []
fitarg = int2min(return_names=True) # from dash
if self._global_:
self._the_model_._load_data_(time[0],asymm,int2_int(),
self.alpha.value,e=asyme,
_nglobal_=self.nglobals,_locals_=self.locals) # pass all data to model
##############################
# actual global migrad call
with self._output_:
lastfit = M(self._the_model_._chisquare_,
pedantic=False,
forced_parameters=self.minuit_parameter_names,
print_level=level,**fitarg[0])
print('{} *****'.format([self.nrun[k] for k in range(len(self.nrun))]))
lastfit.migrad()
self.fitargs.append(lastfit.fitarg)
# lastfit[0].hesse()
##############################
else:
if self._single_:
# print('time.shape = {}, asymm.shape = {}'.format(time.shape,asymm.shape))
self._the_model_._load_data_(time[0],asymm[0],int2_int(),self.alpha.value,e=asyme[0]) # pass data to model, one at a time
##############################
# actual single migrad calls
with self._output_:
lastfit = M(self._the_model_._chisquare_,
pedantic=False,
forced_parameters=self.minuit_parameter_names,
print_level=level,**fitarg[0])
print('{}: {} *******************'.format(self.nrun[0],get_title(self._the_runs_[0][0])))
lastfit.migrad()
self.fitargs.append(lastfit.fitarg)
else:
for k in range(len(self._the_runs_)):
self._the_model_._load_data_(time[0],asymm[k],int2_int(),self.alpha.value,e=asyme[k]) # pass data to model, one at a time
##############################
# actual single migrad calls
with self._output_:
lastfit = M(self._the_model_._chisquare_,
pedantic=False,
forced_parameters=self.minuit_parameter_names,
print_level=level,**fitarg[k])
print('{}: {} *******************'.format(self.nrun[k],get_title(self._the_runs_[k][0])))
lastfit.migrad()
self.fitargs.append(lastfit.fitarg)
# lastfit.hesse()
##############################
fitplot() # plot the best fit results
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'
if not muvalid(change['new'],self._output_,self.mainwindow.selected_index):
self.function[n].value = ''
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
if self.grouping[name][0]==-1:
with self._output_:
print('Wrong group syntax: {}'.format(self.group[groups.index(name)].value))
self.group[groups.index(name)].value = ''
self.grouping[name] = np.array([])
self.mainwindow.selected_index = 3
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
except:
pass
self.fit(change['new']) # restart the gui with a new model
self.mainwindow.selected_index = 2
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_request(b):
'''
plot wrapper
'''
if not guesscheck.value and not self._the_model_._alpha_:
with self._output_:
print('No best fit yet, to plot the guess function tick the checkbox')
self.mainwindow.selected_index = 3
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_int
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_int(change['owner'].value)
# print('sum = {}'.format(sum(returnedtup)))
if sum(returnedtup)<0: # errors return (-1,-1), good values are all positive
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):
if anim_check.value:
if change['new']:
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 path_file_dialog(path):
import tkinter
from tkinter import filedialog
import os
here = os.getcwd()
os.chdir(path)
tkinter.Tk().withdraw() # Close the root window
in_path = filedialog.askopenfilename(filetypes=(('.fit','*.fit'),('all','*.*')))
os.chdir(here)
return in_path
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[0] will reload the data upon load_fit (?)
names = ['self.alpha.value','self.offset.value',
'self.grouping','model.value',
'self.model_components','self.load_handle[0].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)
for j in aux:
s += str(j[0]+1) # convention is from 1 python is from
if len(j)>1:
s += ':'+str(j[-1]+1)
s += ','
s = s[:-1]
self.group[k].value = s
def write_csv(chi2,lowchi2,hichi2,k):
'''
writes a csv 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
'''
import os
import csv
# 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()
row = [self.nrun[k], TsTc[0],eTsTc[0],TsTc[1],eTsTc[1],float(Bstr[:Bstr.find('G')])]
for name in self.minuit_parameter_names:
value, error = self.fitargs[k][name], self.fitargs[k]['error_'+name]
row.append(value)
row.append(error)
row.append(chi2)
row.append(chi2-lowchi2)
row.append(hichi2-chi2)
row.append(self.alpha.value)
row.append(self.offset.value)
for j in range(len(self.nt0)):
row.append(self.nt0[j])
row.append(self.dt0[j])
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')
header.append('nt0')
header.append('dt0')
try: # the file exists
with open(path_csv,'r') as f_in:
reader=csv.reader(f_in,dialect='excel',delimiter=' ',quotechar='"')
headerold = next(reader)
assert header==headerold
with open(path_csv,'w') as f_out:
writer=csv.writer(f_out,dialect='excel',delimiter=' ',quotechar='"')
writer.writerow(header)
for line in reader:
if int(line[0]) < self.nrun[k]: # rewrite previous runs
writer.writerow(line)
elif int(line[0]) == self.nrun[k]: # if it exists, skip it
break
writer.writerow(row) # overwrite or write a new run
writer.writerows(reader) # rewrite the rest
with self._output_:
print('Run {} best fit inserted in existing log {}'.format(self.nrun[k],path_csv))
except: # write a new file
with open(path_csv,'w') as f:
writer=csv.writer(f,dialect='excel',delimiter=' ',quotechar='"')
writer.writerow(header)
writer.writerow(row)
with self._output_:
print('Run {} best fit written in NEW log {}'.format(self.nrun[k],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 FloatText, 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_loadmodel)
try:
alpha0 = self.alpha.value
except:
alpha0 = 1.01 # generic initial value
try:
self.offset0 = self.offset.value
except:
self.offset0 = 7 # generic initial value
loadbutton = Button(description='Load fit',layout=Layout(width='8%'))
loadbutton.style.button_color = self.button_color
loadbutton.on_click(load_fit)
self.alpha = FloatText(description='alpha',value='{:.4f}'.format(alpha0),
layout=Layout(width='12%'),continuous_update=False) # self.alpha.value
self.alpha.observe(on_alpha_changed,'value')
self.offset = IntText(description='offset',value=self.offset0,
layout=Layout(width='11%'),continuous_update=False) # offset, is an integer
# initialized to 7, only input is from an IntText, integer value, or saved and reloaded from mujpy_setup.pkl
self.alpha.style.description_width='32%'
self.offset.style.description_width='38%'
# group and grouping: csv shorthand
self.group = [Text(description='forward',layout=Layout(width='16%'),
continuous_update=False),
Text(description='backward',layout=Layout(width='16%'),
continuous_update=False)]
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='40%'
self.group[1].style.description_width='40%'
self.group[1].observe(on_group_changed,'value')
guesscheck = Checkbox(description='guess',value=False, layout=Layout(width='8%'))
guesscheck.style.description_width='1%'
# end moved
model = Text(description = '', layout=Layout(width='10%'), disabled = True) # this is static, empty description, next to loadmodel
model.value = model_in
version0 = 1
loadmodel = Text(description='loadmodel',layout=Layout(width='20%'),continuous_update=False) # this is where one can input a new model name
loadmodel.observe(on_load_model,'value')
loadmodel.style.description_width='37%'
try:
version0 = self.version.value
except:
version0 = 1
try:
self.plot_range0 = self.plot_range.value
except:
if not self._the_runs_:
self.plot_range0 = ''
try:
fit_range0 = self.fit_range.value
except:
fit_range0 = self.plot_range0
self.version = IntText(description='version',value=version0,layout=Layout(width='11%',indent=False)) # version.value is an int
self.version.style.description_width='48%'
fit_button = Button (description='Fit',layout=Layout(width='6%'))
fit_button.style.button_color = self.button_color
fit_button.on_click(on_fit_request)
self.fit_range = Text(description='fit range\nstart,stop[,pack]',value=fit_range0,layout=Layout(width='22%'),continuous_update=False)
self.fit_range.style.description_width='36%'
self.fit_range.observe(on_range,'value')
plot_button = Button (description='Plot',layout=Layout(width='6%'))
plot_button.style.button_color = self.button_color
plot_button.on_click(on_plot_request)
self.plot_range = Text(description='plot range\nstart,stop\n[,pack]\n[last,pack]',value=self.plot_range0,
layout=Layout(width='22%'),continuous_update=False)
self.plot_range.style.description_width='36%'
self.plot_range.observe(on_range,'value')
update_button = Button (description='Update',layout=Layout(width='8%'))
update_button.style.button_color = self.button_color
update_button.on_click(on_update)
anim_stop_start = ToggleButton(description='stop/start',value=True,layout=Layout(width='10%'))
anim_stop_start.observe(on_start_stop,'value')
label_delay = Label(value='Delay (ms)', layout=Layout(width='8%'))
anim_delay = IntText(value=1000, layout=Layout(width='8%'))
anim_check = Checkbox(description='Animate',value=True, layout=Layout(width='10%'))
anim_check.style.description_width = '1%'
topframe_handle = HBox(description = 'Model', children=[update_button,loadmodel,self.offset,
fit_button,self.group[1],
self.fit_range,anim_check,anim_delay]) #
alphaframe_handle = HBox(description = 'Alpha', children=[loadbutton,guesscheck,self.alpha,self.version,
plot_button,self.group[0],
self.plot_range,anim_stop_start,label_delay]) #
bottomframe_handle = HBox(description = 'Components', layout=Layout(width='100%',border='solid')) #
try:
create_model(model.value) # this may be not a valid model, e.g. after fit('da#ò')
except:
self.fit() # this starts over, producing model = 'daml', which is valid.
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.parvalue, self.flag, self.function = [], [], [] # 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 = []
for k in range(len(self.model_components)): # scan the model
self.fftcheck.append(Checkbox(description='FFT',value=True))
header = HBox([ Text(value=self.model_components[k]['name'],disabled=True,layout=Layout(width='8%')),
self.fftcheck[k]]) # list of HBoxes, the first is the header for the component
# composed of the name (e.g. 'da') and the FFT flag
# fft will be applied to a 'residue' where only checked components
# are subtracted
componentframe_list = [header] # list of HBoxes, header and pars
componentframe_handle = VBox()
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
self.compar.update({nint:[k,j]}) # stores the correspondence between nint and component,parameter
nintlabel_handle = Text(value=str(nint),layout=Layout(width='10%'),disabled=True)
parname_handle = Text(value=self.model_components[k]['pars'][j]['name'],layout=Layout(width='22%'),disabled=True)
# 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='18%'),description='value'+str(nint),continuous_update=False))
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.function.append(Text(value=self.model_components[k]['pars'][j]['function'],
layout=Layout(width='33%'),description='func'+str(nint),continuous_update=False))
self.function[nint].style.description_width='0%'
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='='
self.flag.append(Dropdown(options=['~','!','='],
value=self.model_components[k]['pars'][j]['flag'],
layout=Layout(width='10%'),description='flag'+str(nint)))
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
# now put this set of parameter widgets for the new parameter inside an HBox
par_handle = HBox([nintlabel_handle, parname_handle, self.parvalue[nint], self.flag[nint], self.function[nint]])
# 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
componentframe_list.append(par_handle) # add par widget to the frame list
componentframe_handle.children = componentframe_list # add full component to the frame
if k%2==0: # and ...
leftframe_list.append(componentframe_handle) # append it to the left if k even
else:
rightframe_list.append(componentframe_handle) # or to the right if k odd
# end of model scan, ad two vertical component boxes to the bottom frame
bottomframe_handle.children = [VBox(leftframe_list),VBox(rightframe_list)] # list of handles
# 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[2].children = [alphaframe_handle, topframe_handle ,bottomframe_handle]
# add the list of widget handles as the third tab, fit
##########################
# OUTPUT
##########################
[docs] def output(self):
'''
create an Output widget in fourth tab
select by
self.mainwindow.selected_index = 3
'''
from ipywidgets import Output, HBox, Layout
# Output(layout={'height': '100px', 'overflow_y': 'auto', 'overflow_x': 'auto'})
self._output_ = Output(layout={'height': '300px','width':'100%','overflow_y':'auto','overflow_x':'auto'})
_output_box = HBox([self._output_],layout=Layout(width='100%')) # x works y does scroll
self.mainwindow.children[3].children = [_output_box]
# add the list of widget handles as the fourth tab, output
################
# 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:
with self._output_:
print('Wrong counter syntax or counters out of range: {}'.format(counter_range.value))
self.mainwindow.selected_index =3
counter_range.value = ''
counters = array([])
def on_counterplot(b):
'''
COUNTERPLOT:
produce plot
'''
from numpy import zeros, arange
from mujpy.aux.aux import get_grouping, norun_msg, derange_int
import matplotlib.pyplot as P
font = {'family':'Ubuntu','size':8}
P.rc('font', **font)
dpi = 100.
if not self._the_runs_:
norun_msg(self._output_)
self.mainwindow.selected_index = 3
return
############
# bin range
############
returntup = derange_int(self.counterplot_range.value) #
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 k,nrun in enumerate(self.nrun):
if nrun==int(self.choose_nrun.value):
this_run = k
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')
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')
P.show()
def on_multiplot(b):
'''
MULTIPLOT:
produce plot
'''
import matplotlib.pyplot as P
from numpy import array
from mujpy.aux.aux import derange_int, 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_int(self.multiplot_range.value) #
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.show()
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.derange import derange
# change['owner'].description
returnedtup = derange(change['owner'].value) # 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_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()
from ipywidgets import HBox, VBox, Button, Text, Textarea, Accordion, Layout, Checkbox, IntText, ToggleButton, Label, Dropdown
###########
# multiplot
###########
multiplot_button = Button(description='Multiplot',layout=Layout(width='8%'))
multiplot_button.on_click(on_multiplot)
multiplot_button.style.button_color = self.button_color
anim_check = Checkbox(description='Animate',value=True, layout=Layout(width='9%'))
anim_check.style.description_width = '1%'
anim_delay = IntText(description='Delay (ms)',value=1000, layout=Layout(width='17%'))
anim_delay.style.description_width = '45%'
anim_stop_start = ToggleButton(description='start/stop',value=True,layout={'width':'9%'})
anim_stop_start.observe(on_start_stop,'value')
# anim_stop_start.style.button_color = self.button_color
anim_step = Button(description='step',layout={'width':'8%'})
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='25%'),
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='11%'),
continuous_update=False)
multiplot_offset.style.description_width='35%'
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
# 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%')),self.tlog_accordion],layout=Layout(width='100%',border='solid'))
###################
# counters inspect
###################
counterlabel = Label(value='Inspect',layout=Layout(width='7%'))
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='0', value='0',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='solid'))
vbox = VBox()
vbox.children = [multibox, counterbox]
self.mainwindow.children[5].children = [vbox]
##########################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:
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')
# print('loading {}, presently in {}'.format(path,os.getcwd()))
try:
with open(path,'rb') as f:
mujpy_setup = pickle.load(f)
except:
with self._output_:
print('File {} not found'.format(path))
self.mainwindow.selected_index = 3
# _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:
with self._output_:
print('Error in load_setup: {}'.format(e))
self.mainwindow.selected_index = 3
return -1
def on_introspect(b):
'''
print the unclean list of class attributes
'''
self.introspect()
def on_paths_changed(change):
'''
when user changes this setup tab widget:
check that paths exist, in case creates analysis 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'
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)
with self._output_:
print ('Analysis path {} created'.format(directory))
# self.paths[k].value = dire+os.path.sep # not needed if path is made with os.path.join
self.mainwindow.selected_index = 3
except:
self.paths[k].value = os.path.curdir
with self._output_:
print ('Analysis path {} does not exist and cannot be created'.format(directory))
# self.paths[k].value = dire+os.path.sep # not needed if path is made with os.path.join
self.mainwindow.selected_index = 3
else:
self.paths[k].value = os.path.curdir
with self._output_:
print ('Path {} does not exist, reset to .'.format(directory))
# self.paths[k].value = dire+os.path.sep # not needed if path is made with os.path.join
self.mainwindow.selected_index = 3
def on_prompt_fit_click(b):
'''
when user presses this setup tab widget:
execute 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
from mujpy.aux.aux import norun_msg
font = {'family' : 'Ubuntu','size' : 8}
P.rc('font', **font)
dpi = 100.
if not self._the_runs_:
norun_msg(self._output_)
self.mainwindow.selected_index = 3
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) + self.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 = [ self.peakheight, float(npeaks[counter]), self.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:
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_dt0.children[0].children[0].value = ' '.join(map(str,self.nt0.astype(int)))
nt0_dt0.children[0].children[1].value = ' '.join(map('{:.2f}'.format,self.dt0))
# refresh, they may be slightly adjusted by the fit
# self.t0plot_results.clear_output()
#with self.t0plot_results:
# print('\n\n\n\nRun: {}'.format(self._the_runs_[0][0].get_runNumber_int()))
# print(' Bin nt0')
# for counter in range(self._the_runs_[0][0].get_numberHisto_int()):
# print('#{}: {}'.format(counter,self.nt0[counter]))
# print('\n\n dt0 (bins)')
# for counter in range(self._the_runs_[0][0].get_numberHisto_int()):
# print('#{}: {:.2f}'.format(counter,self.dt0[counter]))
##################################################################################################
def save_log(b):
"""
when user presses this setup tab buttont:
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 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() ))
with self._output_:
print('Saved logbook {}'.format(path_file))
self.mainwindow.selected_index = 3
def save_setup(b):
"""
when user presses this setup tab button:
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.mainwindow.selected_index = 3
with self._output_:
print('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
# first tab: setup for things that have to be set initially (paths, t0, etc.)
# the tab is self.mainwindow.children[0], 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
setup_contents = ['path','promptfit','nt0_dt0','t0plot'] # needs two VBoxes
setup_hbox = [HBox(description=name,layout=Layout(border='solid',)) for name in setup_contents]
self.mainwindow.children[0].children = setup_hbox # first tab (setup)
# first path
paths_content = ['data','tlog','analysis'] # needs a VBox with three Text blocks
paths_box = VBox(description='paths',layout=Layout(width='60%'))
self.paths = [Text(description=paths_content[k],layout=Layout(width='90%'),continuous_update=False) for k in range(len(paths_content))]
# self.paths[k].value='.'+os.path.sep # initial value,
paths_box.children = self.paths
for k in range(len(paths_content)):
self.paths[k].observe(on_paths_changed,'value')
filespecs_content = ['fileprefix','extension']
filespecs_box = VBox(description='filespecs',layout=Layout(width='40%'))
self.filespecs = [Text(description=filespecs_content[k],layout=Layout(width='90%'),continuous_update=False)
for k in range(len(filespecs_content))]
filespecs_box.children = self.filespecs
# for k in range(len(filespecs)): # not needed, only check that data and tlog exixt
# self.filespecs_list[k].observe(on_filespecs_changed,'value')
# paths finished
# now agt0
self.prepostpk = [IntText(description='prepeak',value = 7, layout=Layout(width='20%'),
continuous_update=False),
IntText(description='postpeak',value = 7, layout=Layout(width='20%'),
continuous_update=False)]
self.prepostpk[0].style.description_width='60%'
self.prepostpk[1].style.description_width='60%'
self.plot_check = Checkbox(description='prompt plot',value=True,layout=Layout(width='15%'))
self.plot_check.style.description_width='10%'
fit_button = Button(description='prompt fit',layout=Layout(width='15%'))
fit_button.on_click(on_prompt_fit_click)
fit_button.style.button_color = self.button_color
save_button = Button(description='save setup',layout=Layout(width='15%'))
save_button.style.button_color = self.button_color
load_button = Button(description='load setup',layout=Layout(width='15%'))
load_button.style.button_color = self.button_color
prompt_fit = [self.prepostpk[0], self.prepostpk[1], self.plot_check, fit_button ,save_button, load_button]
# fit bin range is [self.binrange[0].value:self.binrange[1].value]
save_button.on_click(save_setup)
load_button.on_click(load_setup)
nt0_dt0 = Accordion(font_size=10,children=[VBox(children=[Text(description='t0 [bins]',layout={'width':'99%'}), Text(description='dt0 [bins]',layout={'width':'99%'})])],layout={'width':'35%'})#,layout={'height':'22px'})
nt0_dt0.children[0].children[0].style.description_width='20%'
nt0_dt0.children[0].children[1].style.description_width='20%'
nt0_dt0.set_title(0,'t0 bins and remainders')
nt0_dt0.selected_index = None
self.tots_all = Textarea(description='All',layout={'width':'100%','height':'200px',
'overflow_y':'auto','overflow_x':'auto'},disabled=True)
self.tots_all.style.description_width='15%'
self.tots_group = Textarea(description='Group',layout={'width':'100%','height':'200px',
'overflow_y':'auto','overflow_x':'auto'},disabled=True)
self.tots_group.style.description_width='30%'
tots = Accordion(font_size=10,children=[HBox(children=[self.tots_all, self.tots_group])],layout={'width':'35%'})
tots.set_title(0,'Total counts')
tots.selected_index = None
introspect_button = Button(description='Introspect',layout=Layout(width='15%'))
introspect_button.on_click(on_introspect)
introspect_button.style.button_color = self.button_color
log_button = Button(description='Data log',layout=Layout(width='15%'))
log_button.on_click(save_log)
log_button.style.button_color = self.button_color
#self.t0plot_container = Output(layout=Layout(width='85%'))
self.t0plot_results = Output(layout=Layout(width='15%'))
setup_hbox[0].children = [paths_box, filespecs_box]
setup_hbox[1].children = prompt_fit
setup_hbox[2].children = [nt0_dt0,tots,introspect_button,log_button]
#setup_hbox[3].children = [self.t0plot_container,self.t0plot_results]
self.nt0,self.dt0 = array([0.]),array([0.])
load_setup([])
nt0_dt0.children[0].children[0].value = ' '.join(map(str,self.nt0.astype(int)))
nt0_dt0.children[0].children[1].value = ' '.join(map('{:.2f}'.format,self.dt0))
if not self.nt0_run:
self.mainwindow.selected_index=3
with self._output_:
print('WARNING: you must fix t0 = 0, please do a prompt fit from the setup tab')
##########################
# SUITE
##########################
[docs] def suite(self):
'''
suite tab of mugui
used to select: run (single/suite)
load next previous, add next previous
to print: run number, title,
total counts, group counts, ns/bin
comment, start stop date, next run, last add
'''
def get_totals():
'''
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
# self.nt0 roughly set by suite model on_loads_changed
# with self._output_:
# print('offset = {}, nt0 = {}'.format(offset_bin,self.nt0))
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.nsbin.value = '{:.3}'.format(self._the_runs_[0][0].get_binWidth_ns())
self.maxbin.value = str(self.histoLength)
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_handles[0].value = self._the_runs_[0][0].get_comment()
self.comment_handles[1].value = self._the_runs_[0][0].get_timeStart_vector()
self.comment_handles[2].value = self._the_runs_[0][0].get_timeStop_vector()
self._the_runs_display.value = str(self.load_handle[0].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]
with self._output_:
print('WARNING! Run {} mismatch in number of counters, rerun prompt fit'.format(self._the_runs_[0][0].get_runNumber_int()))
self.mainwindow.selected_index = 3
# 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.load_handle[1].value='' # just loaded a single run, incompatible with suite
self.mainwindow.selected_index = 3
with self._output_:
print ('\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)
filename = ''
filename = filename.join([self.filespecs[0].value,
muzeropad(runstr,self._output_),
'.',self.filespecs[1].value]) # needed in muzeropad to write data filename
# 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 (printed in self._output_)
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.mainwindow.selected_index = 3
with self._output_:
print ('\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.mainwindow.selected_index=3
with self._output_:
print('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.
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 = []
for j,run in enumerate(runs): # run is a single run number (string)
filename = ''
filename = filename.join([self.filespecs[0].value,
muzeropad(str(run),self._output_),
'.',self.filespecs[1].value]) # needed in muzeropad to write data filename
path_and_filename = os.path.join(self.paths[0].value,filename)
# data path + filespec + padded run rumber + extension)
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: # error condition, set by musr2py.cpp
self._the_runs_.append(runadd) #
self.nrun.append(runadd[0].get_runNumber_int())
else:
self.mainwindow.selected_index = 3
with self._output_:
print ('\nFile {} not read. Check paths, filespecs and run rumber on setup tab'.
format(path_and_filename))
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[0].value=str(self.nrun[0]+1)
# print('self.nrun[0] = {}'.format(self.nrun[0]))
else:
self.mainwindow.selected_index = 3
with self._output_:
print ('Cannot load next run (no single run loaded)')
return -1 # this leaves the previous loaded runs n the suite
def on_load_prv(b):
'''
load previous run (if it exists)
'''
if self._single_:
self.load_handle[0].value=str(self.nrun[0]-1)
else:
self.mainwindow.selected_index = 3
with self._output_:
print ('Cannot load next run (no single run loaded)')
return -1 # this leaves the previous loaded runs n the suite
def on_add_nxt(b):
'''
add next run (if it exists)
'''
if self._single_:
load_single(self.nrun[0]+1)
get_totals()
else:
self.mainwindow.selected_index = 3
with self._output_:
print ('Cannot load next run (no single run loaded)')
return -1 # this leaves the previous loaded runs n the suite
def on_add_prv(b):
'''
add previous run (if it exists)
'''
if self._single_:
load_single(self.nrun[0]-1)
get_totals()
else:
self.mainwindow.selected_index = 3
with self._output_:
print ('Cannot load next run (no single run loaded)')
return -1 # this leaves the previous loaded runs n the suite
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
# rm: run_or_runs = change['owner'].description # description is either 'Single run' or 'Run suite'
if self.load_handle[0].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[0].value) # runs is a list of lists of run numbers (string)
if errormessage is not None: # derun error
with self._output_:
print('Run syntax error: {}'.format(errormessage))
self.load_handle[0].value=''
self.mainwindow.selected_index=3
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:
self.choose_nrun.options = [str(n) for n in self.nrun]
self.choose_nrun.value = str(self.nrun[0])
get_totals() # sets totalcounts, groupcounts and nsbin
if self._single_:
check_next()
if not self.nt0_run:
self.mainwindow.selected_index=3
with self._output_:
print('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 HBox, Layout, Text, Button
# second tab: select run or suite of runs (for sequential or global fits)
# the tab is self.mainwindow.children[1], a VBox
# containing three HBoxes, loads_box, comment_box, speedloads_box
# path is made of a firstcolumn, paths, and a secondcolumns, filespecs, children of setup_box[0]
# rm: loads = ['Single run','Run suite']
speedloads = ['Next run' 'Load next', 'Load previous', 'Add next', 'Add previous', 'Last added']
load_box = HBox(description='loads',layout=Layout(width='100%'))
speedloads_box = HBox(description='speedloads',layout=Layout(width='100%'))
self.load_handle = [Text(description='Run[run suite]: \nsingle run\ne.g 431\nor run suites\ne.g. 431, 435:439, 443+444',
layout=Layout(width='100%'),continuous_update=False)]
self.load_handle[0].style.description_width='11%'
self.load_handle[0].observe(on_loads_changed,'value')
# the following doesn't work yet
Ln_button = Button(description='Load nxt')
Ln_button.on_click(on_load_nxt)
Ln_button.style.button_color = self.button_color
Lp_button = Button(description='Load prv')
Lp_button.on_click(on_load_prv)
Lp_button.style.button_color = self.button_color
An_button = Button(description='Add nxt')
An_button.on_click(on_add_nxt)
An_button.style.button_color = self.button_color
Ap_button = Button(description='Add prv')
Ap_button.on_click(on_add_prv)
Ap_button.style.button_color = self.button_color
next_label = Text(description='Next run',disabled=True)
self.speedloads_handles = [next_label,
Ln_button, Lp_button, An_button, Ap_button,
Text(description='Last add',disabled=True)]
load_box.children = self.load_handle
speedloads_box.children = self.speedloads_handles
self.mainwindow.children[1].children = [load_box, speedloads_box] # second tab (suite)
[docs] def introspect(self):
'''
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